Files
numolus/app/frontend/data/__tests__/codeReference.spec.ts
wkhandClaude Opus 5.5 e14bada114 Add Rails backend: accounts via magic link, server documents, sharing
The app becomes a Rails 8 application; the Vue frontend moves to
app/frontend and is served through vite_rails.

- Without an account documents stay in the browser (IndexedDB). Sign-in
  is by magic link or the 6-digit code from the same mail; afterwards
  local documents can be uploaded into the account.
- Server documents (/d/<id>) with versioned saves and a single-editor
  lock per browser tab; others see them read-only and follow by polling.
- Sharing via link (works without an account) and via e-mail invitation,
  roles viewer/editor; the owner manages links and people.
- Store gains a read-only mode that blocks all content changes.
- Solid Queue, Cache and Cable share the one PostgreSQL database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:09:06 +02:00

55 lines
2.3 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { parseFormula, parseFunctionBlock, parseMacroBlock } from '../../engine';
import { CODE_REFERENCE, matchesQuery, referenceFor } from '../codeReference';
describe('CODE_REFERENCE', () => {
it('documents every macro write action', () => {
const names = new Set(referenceFor('macro').map((e) => e.name));
for (const a of [
'SETVALUE', 'INSERTROW', 'DELETEROW', 'INSERTCOL', 'DELETECOL', 'ADDTABLE',
'RENAMETABLE', 'DELETETABLE', 'ADDSHEET', 'RENAMESHEET', 'DELETESHEET',
'GET', 'POST', 'JSONGET',
]) {
expect(names.has(a)).toBe(true);
}
});
it('every example (except "…" fragments) parses in each context it is listed for', () => {
// the parameter names the function examples use; in a function body only
// control-flow examples are whole statement sequences, the rest are
// expressions (usable anywhere inside one)
const params = ['bereich', 'umsatz', 'kosten'];
for (const e of CODE_REFERENCE) {
if (!e.example || e.example.includes('…')) continue;
const ex = e.example;
for (const ctx of e.contexts) {
const parse =
ctx === 'macro'
? () => parseMacroBlock(ex)
: ctx === 'block' && e.category === 'Ablauf'
? () => parseFunctionBlock(ex, params)
: () => parseFormula(ex, params);
expect(parse, `${ctx} · ${e.name}: ${ex}`).not.toThrow();
}
}
});
it('function editors never offer write actions or network calls', () => {
for (const ctx of ['block', 'expression'] as const) {
const names = referenceFor(ctx).map((e) => e.name);
for (const a of ['SETVALUE', 'INSERTROW', 'ADDSHEET', 'GET', 'POST', 'JSONGET']) {
expect(names).not.toContain(a);
}
}
// single formulas have no statements at all
expect(referenceFor('expression').some((e) => e.category === 'Ablauf')).toBe(false);
});
it('search matches synonyms and requires every word', () => {
const hits = (q: string) => referenceFor('macro').filter((e) => matchesQuery(e, q)).map((e) => e.name);
expect(hits('zeile einfügen')).toContain('INSERTROW');
expect(hits('zeile einfügen')).not.toContain('DELETEROW');
expect(hits('schleife')).toContain('FOR … IN … DO … END');
});
});