-
Notifications
You must be signed in to change notification settings - Fork 4
Add unit tests for Languages #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Kr0nox
wants to merge
7
commits into
main
Choose a base branch
from
unit-test
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
387bc5c
test tokenization
Kr0nox 48fede8
simple language test
Kr0nox e514efd
simple test language
Kr0nox 10f944a
mocks for assignment language tests
Kr0nox 96440eb
simple test for assignment language
Kr0nox 0aed040
simple tests for constraint dsl
Kr0nox a16c9e9
fix package.lock
Kr0nox File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
frontend/webEditor/tests/languages/AssignmentLanguage.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import { AssignmentLanguageTreeBuilder } from "../../src/assignment/language"; | ||
| import { DfdNodeImpl } from "../../src/diagram/nodes/common"; | ||
| import { DfdOutputPortImpl } from "../../src/diagram/ports/DfdOutputPort"; | ||
| import { LabelTypeRegistry } from "../../src/labels/LabelTypeRegistry"; | ||
| import { | ||
| ValidExpressionTestData, | ||
| AutoCompleteTestData, | ||
| generateTests, | ||
| InvalidExpressionTestData, | ||
| ReplacementTestData, | ||
| } from "./LanguageTestUtils"; | ||
|
|
||
| const BASE_VALID_EXPRESSIONS: ValidExpressionTestData[] = [ | ||
| ["forward flow"], | ||
| ["forward hello|world"], | ||
| ["set Sensitivity.Public"], | ||
| ["unset Location.nonEU"], | ||
| ["assign Sensitivity.Public if TRUE from flow"], | ||
| ["assign Sensitivity.Public if TRUE"], | ||
| ["assign Sensitivity.Public if flow.Sensitivity.Public"], | ||
| ]; | ||
|
|
||
| const VALID_EXPRESSIONS = [...BASE_VALID_EXPRESSIONS, BASE_VALID_EXPRESSIONS.flat()]; | ||
|
|
||
| const INVALID_EXPRESSIONS: InvalidExpressionTestData[] = [ | ||
| { input: ["forward Sensitivity.Public"] }, | ||
| { input: ["set flow"] }, | ||
| { input: ["forward hello"] }, | ||
| { input: ["set Sensitivity.public"] }, | ||
| { input: ["set sensitivity.Public"] }, | ||
| ]; | ||
|
|
||
| const AUTOCOMPLETE_TEST_DATA: AutoCompleteTestData[] = [ | ||
| { input: [""], completionOptions: ["forward", "set", "unset", "assign"], exactOptionCount: true }, | ||
| { input: ["forward "], completionOptions: ["flow", "hello|world"], exactOptionCount: true }, | ||
| { input: ["set "], completionOptions: ["Sensitivity", "Location"], exactOptionCount: true }, | ||
| { input: ["set Sensitivity."], completionOptions: ["Personal", "Public"], exactOptionCount: true }, | ||
| { input: ["unset "], completionOptions: ["Sensitivity", "Location"], exactOptionCount: true }, | ||
| { input: ["assign "], completionOptions: ["Sensitivity", "Location"] }, | ||
| { input: ["assign Sensitivity.Public "], completionOptions: ["if"], exactOptionCount: true }, | ||
| { input: ["assign Sensitivity.Public if TRUE "], completionOptions: ["from"] }, | ||
| ]; | ||
|
|
||
| const REPLACEMENT_TEST_DATA: ReplacementTestData[] = [ | ||
| { | ||
| input: ["set Sensitivity.Public"], | ||
| replacement: { old: "Sensitivity.Public", replacement: "Sensitivity.New", type: "label" }, | ||
| output: ["set Sensitivity.New"], | ||
| }, | ||
| ]; | ||
|
|
||
| const labelTypeRegistry = new LabelTypeRegistry(); | ||
| const location = labelTypeRegistry.registerLabelType("Location"); | ||
| for (const defaultValue of location.values) { | ||
| labelTypeRegistry.unregisterLabelTypeValue(location.id, defaultValue.id); | ||
| } | ||
| labelTypeRegistry.registerLabelTypeValue(location.id, "EU"); | ||
| labelTypeRegistry.registerLabelTypeValue(location.id, "nonEU"); | ||
| const sensitivity = labelTypeRegistry.registerLabelType("Sensitivity"); | ||
| for (const defaultValue of sensitivity.values) { | ||
| labelTypeRegistry.unregisterLabelTypeValue(sensitivity.id, defaultValue.id); | ||
| } | ||
| labelTypeRegistry.registerLabelTypeValue(sensitivity.id, "Personal"); | ||
| labelTypeRegistry.registerLabelTypeValue(sensitivity.id, "Public"); | ||
|
|
||
| const mockPort = { | ||
| // @ts-expect-error We mock the abstract class into a concrete one for simplicity. TypeScript however still thinks this is an abstract class, so it would throw an error | ||
| parent: new DfdNodeImpl(), | ||
| } as unknown as DfdOutputPortImpl; | ||
|
|
||
| generateTests( | ||
| "Assignment Language", | ||
| AssignmentLanguageTreeBuilder.buildTree(mockPort, labelTypeRegistry), | ||
| VALID_EXPRESSIONS, | ||
| INVALID_EXPRESSIONS, | ||
| AUTOCOMPLETE_TEST_DATA, | ||
| REPLACEMENT_TEST_DATA, | ||
| ); | ||
140 changes: 140 additions & 0 deletions
140
frontend/webEditor/tests/languages/LanguageFunctionality.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| /* | ||
| Tests whether the functions verify, complete and replace to what they are supposed to with a simple custom language: | ||
|
|
||
| root | ||
| ├─> circle1 <─────────┐ | ||
| │ └─> circle2* ──┘ | ||
| │ | ||
| └──> child | ||
| ├──> !child1 | ||
| │ └──> any ──┐ | ||
| │ ├──> child3,... ──> child4 | ||
| └──> child2* ─────┘ | ||
|
Comment on lines
+4
to
+12
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very cool |
||
|
|
||
| * = can be end state | ||
| ! is optional for negation | ||
| ,... is a list with the same word | ||
| */ | ||
| import { ReplacementData } from "../../src/languages/replace"; | ||
| import { LanguageTreeNode } from "../../src/languages/tokenize"; | ||
| import { AnyWord, ConstantWord, ListWord, NegatableWord, Word } from "../../src/languages/words"; | ||
| import { | ||
| AutoCompleteTestData, | ||
| generateTests, | ||
| InvalidExpressionTestData, | ||
| ReplacementTestData, | ||
| } from "./LanguageTestUtils"; | ||
|
|
||
| const VALID_EXPRESSIONS = [ | ||
| ["root circle1 circle2"], | ||
| ["root", "circle1", "circle2"], | ||
| ["root", "circle1 circle2", "circle1 circle2", "circle1 circle2", "circle1 circle2", "circle1 circle2"], | ||
| ["root", "circle1 circle2", " circle1 circle2", "\tcircle1 circle2", "circle1 circle2", "circle1 circle2"], | ||
| ["root child", "child1 bla child3 child4"], | ||
| ["root child", "!child1 blab child3 child4"], | ||
| ["root child child2"], | ||
| ["root child child2 child3 child4"], | ||
| ["root child child2 child3,child3 child4"], | ||
| ["root child child2 child3,child3,child3 child4"], | ||
| ]; | ||
| const INVALID_EXPRESSIONS: InvalidExpressionTestData[] = [ | ||
| { input: ["root"] }, | ||
| { input: ["root circle1 circle2 circle1"] }, | ||
| { input: ["root WRONG child2"], output: [{ line: 1, startColumn: 6, endColumn: 11 }] }, | ||
| { | ||
| input: ["root", "circle1 circle2", "circle1 circle2", "circle1 circle2", "circle1 circle2", "WRONG circle2"], | ||
| output: [{ line: 6, startColumn: 1, endColumn: 6 }], | ||
| }, | ||
| { input: ["root child child2 child3,child3, child4"] }, | ||
| ]; | ||
| const AUTOCOMPLETE_TEST_DATA: AutoCompleteTestData[] = [ | ||
| { input: ["root "], completionOptions: ["circle1", "child"], exactOptionCount: true }, | ||
| { input: ["root c"], completionOptions: ["circle1", "child"], exactOptionCount: true }, | ||
| { | ||
| input: ["root", "circle1 circle2", "circle1 circle2", "circle1 circle2", "circle1 circle2", "circle1 "], | ||
| completionOptions: ["circle2"], | ||
| }, | ||
| { input: ["root child !"], completionOptions: ["child1"] }, | ||
| { input: ["root child child2 child3,child3,child3 child4 "], completionOptions: [], exactOptionCount: true }, | ||
| ]; | ||
| const simpleInput1 = ["root child child1 bla child3 child4"]; | ||
| const simpleInput2 = ["root circle1 circle2 circle1 circle2"]; | ||
| const REPLACEMENT_TEST_DATA: ReplacementTestData[] = [ | ||
| { | ||
| input: ["root child child1 bla child3 child4"], | ||
| output: ["root child child1 blub child3 child4"], | ||
| replacement: { old: "bla", replacement: "blub", type: "replaceAny" }, | ||
| }, | ||
| { | ||
| input: ["root child child1 bla child3 chil"], | ||
| output: ["root child child1 blub child3 chil"], | ||
| replacement: { old: "bla", replacement: "blub", type: "replaceAny" }, | ||
| }, | ||
| // check that no wrong replacements happen | ||
| { input: simpleInput1, output: simpleInput1, replacement: { old: "blub", replacement: "bla", type: "replaceAny" } }, | ||
| { input: simpleInput1, output: simpleInput1, replacement: { old: "bla", replacement: "blub", type: "WRONG" } }, | ||
| { | ||
| input: simpleInput2, | ||
| output: simpleInput2, | ||
| replacement: { old: "circle1", replacement: "circle2", type: "replaceAny" }, | ||
| }, | ||
| ]; | ||
|
|
||
| const circleNode1: LanguageTreeNode<Word> = { | ||
| word: new ConstantWord("circle1"), | ||
| children: [], | ||
| }; | ||
| const circleNode2: LanguageTreeNode<Word> = { | ||
| word: new ConstantWord("circle2"), | ||
| children: [circleNode1], | ||
| canBeFinal: true, | ||
| }; | ||
| circleNode1.children.push(circleNode2); | ||
|
|
||
| const child4: LanguageTreeNode<Word> = { | ||
| word: new ConstantWord("child4"), | ||
| children: [], | ||
| }; | ||
| const child3: LanguageTreeNode<Word> = { | ||
| word: new ListWord(new ConstantWord("child3")), | ||
| children: [child4], | ||
| }; | ||
| const child2: LanguageTreeNode<Word> = { | ||
| word: new ConstantWord("child2"), | ||
| children: [child3], | ||
| canBeFinal: true, | ||
| }; | ||
| class ReplaceAnyWord extends AnyWord { | ||
| replace(text: string, replacement: ReplacementData): string { | ||
| if (replacement.type === "replaceAny" && text === replacement.old) { | ||
| return replacement.replacement; | ||
| } | ||
| return text; | ||
| } | ||
| } | ||
| const anyChild: LanguageTreeNode<Word> = { | ||
| word: new ReplaceAnyWord(), | ||
| children: [child3], | ||
| }; | ||
| const child1: LanguageTreeNode<Word> = { | ||
| word: new NegatableWord(new ConstantWord("child1")), | ||
| children: [anyChild], | ||
| }; | ||
| const child: LanguageTreeNode<Word> = { | ||
| word: new ConstantWord("child"), | ||
| children: [child1, child2], | ||
| }; | ||
|
|
||
| const root: LanguageTreeNode<Word> = { | ||
| word: new ConstantWord("root"), | ||
| children: [circleNode1, child], | ||
| }; | ||
|
|
||
| generateTests( | ||
| "Functionality Test Language", | ||
| [root], | ||
| VALID_EXPRESSIONS, | ||
| INVALID_EXPRESSIONS, | ||
| AUTOCOMPLETE_TEST_DATA, | ||
| REPLACEMENT_TEST_DATA, | ||
| ); | ||
112 changes: 112 additions & 0 deletions
112
frontend/webEditor/tests/languages/LanguageTestUtils.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| import { expect, it } from "vitest"; | ||
| import { verify, VerifyWord } from "../../src/languages/verify"; | ||
| import { complete, CompletionWord } from "../../src/languages/autocomplete"; | ||
| import { replace, ReplaceableWord, ReplacementData } from "../../src/languages/replace"; | ||
| import { LanguageTreeNode, tokenize } from "../../src/languages/tokenize"; | ||
|
|
||
| export function generateTests( | ||
| name: string, | ||
| tree: LanguageTreeNode<VerifyWord & CompletionWord & ReplaceableWord>[], | ||
| validExpressions: ValidExpressionTestData[], | ||
| invalidExpressions: InvalidExpressionTestData[], | ||
| autoCompleteTests: AutoCompleteTestData[], | ||
| replacementTests: ReplacementTestData[], | ||
| ) { | ||
| for (let i = 0; i < validExpressions.length; i++) { | ||
| it(`${name} Valid Expression ${i}`, () => { | ||
| const tokens = tokenize(validExpressions[i]); | ||
| const result = verify(tokens, tree); | ||
| expect( | ||
| result.length, | ||
| `Valid Expression ${joinLines(validExpressions[i])} had Errors: ${result.map((v) => JSON.stringify(v)).join("\n")}`, | ||
| ).toBe(0); | ||
| }); | ||
| } | ||
|
|
||
| for (let i = 0; i < invalidExpressions.length; i++) { | ||
| it(`${name} Invalid Expression ${i}`, () => { | ||
| const tokens = tokenize(invalidExpressions[i].input); | ||
| const result = verify(tokens, tree); | ||
| expect( | ||
| result.length, | ||
| `Invalid Expression ${joinLines(invalidExpressions[i].input)} had no Errors`, | ||
| ).toBeGreaterThan(0); | ||
| if (invalidExpressions[i].output !== undefined) { | ||
| for (const expectedError of invalidExpressions[i].output!) { | ||
| expect | ||
| .soft( | ||
| result.find( | ||
| (e) => | ||
| e.line === expectedError.line && | ||
| e.startColumn === expectedError.startColumn && | ||
| (expectedError.endColumn === undefined || e.endColumn === expectedError.endColumn), | ||
| ), | ||
| `Could not find Error at ${JSON.stringify(expectedError)} in ${joinLines(invalidExpressions[i].input)}`, | ||
| ) | ||
| .toBeDefined(); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| for (let i = 0; i < autoCompleteTests.length; i++) { | ||
| it(`${name} Autocomplete ${i}`, () => { | ||
| const tokens = tokenize(autoCompleteTests[i].input); | ||
| const result = complete(tokens, tree); | ||
| const completionStrings = result.map((r) => r.insertText); | ||
| if (autoCompleteTests[i].exactOptionCount === true) { | ||
| expect.soft(result.length).toBe(autoCompleteTests[i].completionOptions.length); | ||
| } | ||
| for (const option of autoCompleteTests[i].completionOptions) { | ||
| expect | ||
| .soft( | ||
| completionStrings.includes(option), | ||
| `Autocompletion for ${joinLines(autoCompleteTests[i].input)} did not provide option ${option}`, | ||
| ) | ||
| .toBeTruthy(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| for (let i = 0; i < replacementTests.length; i++) { | ||
| it(`${name} Replacement ${i}`, () => { | ||
| const result = replace(replacementTests[i].input, tree, replacementTests[i].replacement); | ||
| expect( | ||
| result.length, | ||
| `Replacement ${replacementTests[i].replacement} resulted in wrong length for ${joinLines(replacementTests[i].input)}`, | ||
| ).toEqual(replacementTests[i].output.length); | ||
| for (let l = 0; l < result.length; l++) { | ||
| expect | ||
| .soft( | ||
| result[l], | ||
| `Line ${l + 1} did not match after replacement ${replacementTests[i].replacement} for in ${joinLines(replacementTests[i].input)}`, | ||
| ) | ||
| .toBe(replacementTests[i].output[l]); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| export type ValidExpressionTestData = string[]; | ||
|
|
||
| export interface InvalidExpressionTestData { | ||
| input: string[]; | ||
| output?: { line: number; startColumn: number; endColumn?: number }[]; | ||
| } | ||
|
|
||
| export interface AutoCompleteTestData { | ||
| input: string[]; | ||
| completionOptions: string[]; | ||
| // if set to true, checks that the number of options returned by the completer matches the length of completionOptions (default: false) | ||
| exactOptionCount?: boolean; | ||
| } | ||
|
|
||
| export interface ReplacementTestData { | ||
| input: string[]; | ||
| replacement: ReplacementData; | ||
| output: string[]; | ||
| } | ||
|
|
||
| export function joinLines(input: string[]) { | ||
| return input.join("\\n"); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is hello world right here, or just leftover?