diff --git a/src/test/suite/completions.test.ts b/src/test/suite/completions.test.ts index 8bd5de0..42f9b94 100644 --- a/src/test/suite/completions.test.ts +++ b/src/test/suite/completions.test.ts @@ -1,38 +1,64 @@ import * as assert from 'assert'; +import * as vscode from 'vscode'; import { BuiltInCompletion } from '../../providers/completions/BuiltInCompletion'; import { SnippetCompletion } from '../../providers/completions/SnippetCompletion'; +import { LocalContextCompletion } from '../../providers/completions/LocalContextCompletion'; + +// Hilfsfunktion simuliert ein vscode.TextDocument +function createMockDoc(text: string): any { + const lines = text.split('\n'); + return { + getText: () => text, + lineAt: (i: number) => ({ text: lines[i] }) + }; +} suite('Completion Provider Test Suite', () => { - test('BuiltInCompletion: Reagiert auf "log."', () => { - const strategy = new BuiltInCompletion(); - // Wir faken Document und Position, da wir nur linePrefix brauchen - const result = strategy.provideCompletions(null as any, null as any, ' log.'); - - assert.ok(result !== undefined); - assert.strictEqual(result.length, 4); // debug, info, warn, error - assert.strictEqual(result[0].label, 'debug'); - }); - - test('BuiltInCompletion: Ignoriert normalen Text', () => { - const strategy = new BuiltInCompletion(); - const result = strategy.provideCompletions(null as any, null as any, ' my_var'); - - assert.strictEqual(result, undefined); - }); - - test('SnippetCompletion: Liefert Snippets für normalen Text', () => { + test('SnippetCompletion: Kontext-Check (Kein if außerhalb des Bodys)', () => { const strategy = new SnippetCompletion(); - const result = strategy.provideCompletions(null as any, null as any, ' i'); + + // Simuliert, dass wir im "metadata" Block tippen + const docText = `pattern Test 1.0\nmetadata\n `; + const mockDoc = createMockDoc(docText); + const position = new vscode.Position(2, 4); // Zeile 2, Cursor am Ende + + const result = strategy.provideCompletions(mockDoc, position, ' '); assert.ok(result !== undefined); const hasIf = result.some(item => item.label === 'if'); - assert.ok(hasIf, "Muss 'if' Snippet vorschlagen"); + assert.strictEqual(hasIf, false, "Darf kein 'if' im metadata Block vorschlagen"); }); - test('SnippetCompletion: Blockiert Snippets nach Punkt', () => { + test('SnippetCompletion: Vorschläge im Body korrekt', () => { const strategy = new SnippetCompletion(); - const result = strategy.provideCompletions(null as any, null as any, ' discovery.r'); - assert.strictEqual(result, undefined, "Darf keine Block-Snippets in Namespaces anzeigen"); + // Simuliert, dass wir im "body" tippen + const docText = `pattern Test 1.0\nbody\n `; + const mockDoc = createMockDoc(docText); + const position = new vscode.Position(2, 4); + + const result = strategy.provideCompletions(mockDoc, position, ' '); + + assert.ok(result !== undefined); + const hasIf = result.some(item => item.label === 'if'); + assert.strictEqual(hasIf, true, "Muss 'if' im Body vorschlagen"); + }); + + test('LocalContextCompletion: Erkennt lokale defines in Namespaces', async () => { + const strategy = new LocalContextCompletion(); + + const docText = `definitions my_tools 1.0\n define doWork(host)\n end define;\nend definitions;`; + const mockDoc = createMockDoc(docText); + const position = new vscode.Position(4, 0); + + const result = await strategy.provideCompletions(mockDoc, position, 'do'); + + assert.ok(result !== undefined); + const funcItem = result.find(item => item.label === 'my_tools.doWork'); + assert.ok(funcItem !== undefined, "Muss die Methode inkl. Namespace extrahieren"); + + // Prüft, ob das generierte Snippet das Auto-Semikolon und den $0 Cursor hat + const snippet = funcItem?.insertText as vscode.SnippetString; + assert.ok(snippet.value.includes('my_tools.doWork(${1:params});$0')); }); }); \ No newline at end of file diff --git a/src/test/suite/linter.test.ts b/src/test/suite/linter.test.ts index 373c5ab..7c4901e 100644 --- a/src/test/suite/linter.test.ts +++ b/src/test/suite/linter.test.ts @@ -5,65 +5,71 @@ import { SyntaxRule } from '../../core/rules/SyntaxRule'; import { BlockRule } from '../../core/rules/BlockRule'; suite('Linter Test Suite', () => { - test('SyntaxRule: Warnt bei "=" Zuweisung außerhalb von Bedingungen', () => { + test('SyntaxRule: Ignoriert URLs und Sonderzeichen in Strings', () => { const rule = new SyntaxRule(); const ctx = new LinterContext(); - ctx.activePhaseName = 'body'; // Kontext setzen, damit der strict-mode nicht meckert + ctx.activePhaseName = 'body'; // Erlaubte Zone - const rawLine = " my_var = 123;"; - // HIER GEFIXT: Wir übergeben ein Array als 5. Argument + const rawLine = ' log.debug("https://example.com/api?q=1//test");'; rule.checkLine(rawLine.trim(), rawLine, 0, ctx, [rawLine]); - const diag = ctx.diagnostics.find(d => d.message.includes("':=' für Zuweisungen")); - assert.ok(diag !== undefined, "Fehler für falsche Zuweisung erwartet"); - }); - - test('SyntaxRule: Akzeptiert "=" als Vergleich in Bedingungen', () => { - const rule = new SyntaxRule(); - const ctx = new LinterContext(); - ctx.activePhaseName = 'body'; - - const rawLine = " if my_var = 123 then"; - rule.checkLine(rawLine.trim(), rawLine, 0, ctx, [rawLine]); - - // Darf keinen Fehler werfen, da inConditionBlock automatisch auf true geht + // Darf keinen Fehler werfen (weder fehlendes Semikolon noch kaputter Kommentar) assert.strictEqual(ctx.diagnostics.length, 0); }); - test('SyntaxRule: Findet fehlendes Semikolon', () => { + test('SyntaxRule: Erfasst TODOs als Information', () => { + const rule = new SyntaxRule(); + const ctx = new LinterContext(); + + const rawLine = ' // TODO: Diese Funktion refactoren'; + rule.checkLine(rawLine.trim(), rawLine, 0, ctx, [rawLine]); + + assert.strictEqual(ctx.diagnostics.length, 1); + assert.strictEqual(ctx.diagnostics[0].severity, vscode.DiagnosticSeverity.Information); + assert.ok(ctx.diagnostics[0].message.includes("TODO: Diese Funktion refactoren")); + }); + + test('SyntaxRule: Multi-Line if-Bedingung ohne Semikolon-Fehler', () => { const rule = new SyntaxRule(); const ctx = new LinterContext(); ctx.activePhaseName = 'body'; - const rawLine = " my_var := 123"; - rule.checkLine(rawLine.trim(), rawLine, 0, ctx, [rawLine]); + // Zeile 1: or-Umbruch + rule.checkLine('if x = 1 or', 'if x = 1 or', 0, ctx, []); + assert.strictEqual(ctx.diagnostics.length, 0); + assert.strictEqual(ctx.inConditionBlock, true); - const diag = ctx.diagnostics.find(d => d.message.includes("Fehlendes Semikolon")); - assert.ok(diag !== undefined, "Fehler für fehlendes Semikolon erwartet"); + // Zeile 2: then-Abschluss + rule.checkLine('y = 2 then', 'y = 2 then', 1, ctx, []); + assert.strictEqual(ctx.diagnostics.length, 0, "'then' darf kein Semikolon am Ende verlangen!"); + assert.strictEqual(ctx.inConditionBlock, false); }); - test('BlockRule: Erzwingt Module-Header', () => { - const rule = new BlockRule(); + test('SyntaxRule: Akzeptiert Zuweisungen in erlaubten Zonen (z.B. metadata)', () => { + const rule = new SyntaxRule(); const ctx = new LinterContext(); + ctx.inModuleMetadata = true; // Wir sind im Modul-Metadata - const rawLine = "pattern Test 1.0"; + const rawLine = " origin := 'Custom';"; rule.checkLine(rawLine.trim(), rawLine, 0, ctx, [rawLine]); - assert.strictEqual(ctx.diagnostics.length, 1); - assert.ok(ctx.diagnostics[0].message.includes("tpl 1.x module")); + assert.strictEqual(ctx.diagnostics.length, 0, "Zuweisungen müssen in metadata erlaubt sein"); }); - test('BlockRule: Findet fehlendes Pattern-Log im Body', () => { + test('BlockRule: if/for/while Stack-Tracking', () => { const rule = new BlockRule(); const ctx = new LinterContext(); ctx.hasModuleDef = true; ctx.inBlock = 'pattern'; - ctx.currentPatternName = 'TestPattern'; - const lines = ['body', ' x := 1;', 'end body;']; - rule.checkLine('body', 'body', 0, ctx, lines); - - const logDiag = ctx.diagnostics.find(d => d.code === 'missing_log_start'); - assert.ok(logDiag !== undefined, "Erwartet Fehler für fehlendes Log-Statement"); + // Öffnet Block + rule.checkLine('if true then', 'if true then', 0, ctx, []); + assert.strictEqual(ctx.openControlBlocks.length, 1); + assert.strictEqual(ctx.openControlBlocks[0].type, 'if'); + + // Schließt falschen Block (end for statt end if) + rule.checkLine('end for;', 'end for;', 1, ctx, []); + assert.strictEqual(ctx.diagnostics.length, 1); + assert.ok(ctx.diagnostics[0].message.includes("Falsches Ende")); }); }); \ No newline at end of file