Files
privacy.sexy/tests/unit/application/Parser/Script/ScriptParser.spec.ts
undergroundwires 5f11c8d98f Migrate unit/integration tests to Vitest with Vite
As part of transition to Vue 3.0 and Vite (#230), this commit
facilitates the shift towards building rest of the application using
Vite. By doing so, it eliminates reliance on outdated Electron building
system that offered limited control, blocking desktop builds (#233).

Changes include:

- Introduce Vite with Vue 2.0 plugin for test execution.
- Remove `mocha`, `chai` and other related dependencies.
- Adjust test to Vitest syntax.
- Revise and update `tests.md` to document the changes.
- Add `@modyfi/vite-plugin-yaml` plugin to be able to use yaml file
  depended logic on test files, replacing previous webpack behavior.
- Fix failing tests that are revealed by Vitest due to unhandled errors
  and lack of assertments.
- Remove the test that depends on Vue CLI populating `process.env`.
- Use `jsdom` for unit test environment, adding it to dependency to
  `package.json` as project now depends on it and it was not specified
  even though `package-lock.json` included it.
2023-08-22 14:02:35 +02:00

317 lines
12 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import type { ScriptData } from '@/application/collections/';
import { parseScript, ScriptFactoryType } from '@/application/Parser/Script/ScriptParser';
import { parseDocs } from '@/application/Parser/DocumentationParser';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { ICategoryCollectionParseContext } from '@/application/Parser/Script/ICategoryCollectionParseContext';
import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
import { ScriptCompilerStub } from '@tests/unit/shared/Stubs/ScriptCompilerStub';
import { ScriptDataStub } from '@tests/unit/shared/Stubs/ScriptDataStub';
import { EnumParserStub } from '@tests/unit/shared/Stubs/EnumParserStub';
import { ScriptCodeStub } from '@tests/unit/shared/Stubs/ScriptCodeStub';
import { CategoryCollectionParseContextStub } from '@tests/unit/shared/Stubs/CategoryCollectionParseContextStub';
import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
import { expectThrowsNodeError, ITestScenario, NodeValidationTestRunner } from '@tests/unit/application/Parser/NodeValidation/NodeValidatorTestRunner';
import { Script } from '@/domain/Script';
import { IEnumParser } from '@/application/Common/Enum';
import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmptyLines';
import { NoDuplicatedLines } from '@/application/Parser/Script/Validation/Rules/NoDuplicatedLines';
import { CodeValidatorStub } from '@tests/unit/shared/Stubs/CodeValidatorStub';
import { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
describe('ScriptParser', () => {
describe('parseScript', () => {
it('parses name as expected', () => {
// arrange
const expected = 'test-expected-name';
const script = ScriptDataStub.createWithCode()
.withName(expected);
// act
const actual = new TestBuilder()
.withData(script)
.parseScript();
// assert
expect(actual.name).to.equal(expected);
});
it('parses docs as expected', () => {
// arrange
const docs = ['https://expected-doc1.com', 'https://expected-doc2.com'];
const script = ScriptDataStub.createWithCode()
.withDocs(docs);
const expected = parseDocs(script);
// act
const actual = new TestBuilder()
.withData(script)
.parseScript();
// assert
expect(actual.docs).to.deep.equal(expected);
});
describe('level', () => {
describe('accepts absent level', () => {
itEachAbsentStringValue((absentValue) => {
// arrange
const script = ScriptDataStub.createWithCode()
.withRecommend(absentValue);
// act
const actual = new TestBuilder()
.withData(script)
.parseScript();
// assert
expect(actual.level).to.equal(undefined);
});
});
it('parses level as expected', () => {
// arrange
const expectedLevel = RecommendationLevel.Standard;
const expectedName = 'level';
const levelText = 'standard';
const script = ScriptDataStub.createWithCode()
.withRecommend(levelText);
const parserMock = new EnumParserStub<RecommendationLevel>()
.setup(expectedName, levelText, expectedLevel);
// act
const actual = new TestBuilder()
.withData(script)
.withParser(parserMock)
.parseScript();
// assert
expect(actual.level).to.equal(expectedLevel);
});
});
describe('code', () => {
it('parses "execute" as expected', () => {
// arrange
const expected = 'expected-code';
const script = ScriptDataStub
.createWithCode()
.withCode(expected);
// act
const parsed = new TestBuilder()
.withData(script)
.parseScript();
// assert
const actual = parsed.code.execute;
expect(actual).to.equal(expected);
});
it('parses "revert" as expected', () => {
// arrange
const expected = 'expected-revert-code';
const script = ScriptDataStub
.createWithCode()
.withRevertCode(expected);
// act
const parsed = new TestBuilder()
.withData(script)
.parseScript();
// assert
const actual = parsed.code.revert;
expect(actual).to.equal(expected);
});
describe('compiler', () => {
describe('throws when context is not defined', () => {
itEachAbsentObjectValue((absentValue) => {
// arrange
const expectedMessage = 'missing context';
const context: ICategoryCollectionParseContext = absentValue;
// act
const act = () => new TestBuilder()
.withContext(context)
.parseScript();
// assert
expect(act).to.throw(expectedMessage);
});
});
it('gets code from compiler', () => {
// arrange
const expected = new ScriptCodeStub();
const script = ScriptDataStub.createWithCode();
const compiler = new ScriptCompilerStub()
.withCompileAbility(script, expected);
const parseContext = new CategoryCollectionParseContextStub()
.withCompiler(compiler);
// act
const parsed = new TestBuilder()
.withData(script)
.withContext(parseContext)
.parseScript();
// assert
const actual = parsed.code;
expect(actual).to.equal(expected);
});
});
describe('syntax', () => {
it('set from the context', () => { // tests through script validation logic
// arrange
const commentDelimiter = 'should not throw';
const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
const parseContext = new CategoryCollectionParseContextStub()
.withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
const script = ScriptDataStub
.createWithoutCallOrCodes()
.withCode(duplicatedCode);
// act
const act = () => new TestBuilder()
.withData(script)
.withContext(parseContext);
// assert
expect(act).to.not.throw();
});
});
describe('validates a expected', () => {
it('validates script with inline code (that is not compiled)', () => {
// arrange
const expectedRules = [
NoEmptyLines,
NoDuplicatedLines,
];
const validator = new CodeValidatorStub();
const script = ScriptDataStub
.createWithCode()
.withCode('expected code to be validated')
.withRevertCode('expected revert code to be validated');
// act
new TestBuilder()
.withData(script)
.withCodeValidator(validator)
.parseScript();
// assert
validator.assertHistory({
validatedCodes: [script.code, script.revertCode],
rules: expectedRules,
});
});
it('does not validate compiled code', () => {
// arrange
const expectedRules = [];
const expectedCodeCalls = [];
const validator = new CodeValidatorStub();
const script = ScriptDataStub
.createWithCall();
const compiler = new ScriptCompilerStub()
.withCompileAbility(script, new ScriptCodeStub());
const parseContext = new CategoryCollectionParseContextStub()
.withCompiler(compiler);
// act
new TestBuilder()
.withData(script)
.withCodeValidator(validator)
.withContext(parseContext)
.parseScript();
// assert
validator.assertHistory({
validatedCodes: expectedCodeCalls,
rules: expectedRules,
});
});
});
});
describe('invalid script data', () => {
describe('validates script data', () => {
// arrange
const createTest = (script: ScriptData): ITestScenario => ({
act: () => new TestBuilder()
.withData(script)
.parseScript(),
expectedContext: {
type: NodeType.Script,
selfNode: script,
},
});
// act and assert
new NodeValidationTestRunner()
.testInvalidNodeName((invalidName) => {
return createTest(
ScriptDataStub.createWithCall().withName(invalidName),
);
})
.testMissingNodeData((node) => {
return createTest(node as ScriptData);
})
.runThrowingCase({
name: 'throws when both function call and code are defined',
scenario: createTest(
ScriptDataStub.createWithCall().withCode('code'),
),
expectedMessage: 'Cannot define both "call" and "code".',
})
.runThrowingCase({
name: 'throws when both function call and revertCode are defined',
scenario: createTest(
ScriptDataStub.createWithCall().withRevertCode('revert-code'),
),
expectedMessage: 'Cannot define "revertCode" if "call" is defined.',
})
.runThrowingCase({
name: 'throws when neither call or revertCode are defined',
scenario: createTest(
ScriptDataStub.createWithoutCallOrCodes(),
),
expectedMessage: 'Must define either "call" or "code".',
});
});
it(`rethrows exception if ${Script.name} cannot be constructed`, () => {
// arrange
const expectedError = 'script creation failed';
const factoryMock: ScriptFactoryType = () => { throw new Error(expectedError); };
const data = ScriptDataStub.createWithCode();
// act
const act = () => new TestBuilder()
.withData(data)
.withFactory(factoryMock)
.parseScript();
// expect
expectThrowsNodeError({
act,
expectedContext: {
type: NodeType.Script,
selfNode: data,
},
}, expectedError);
});
});
});
});
class TestBuilder {
private data: ScriptData = ScriptDataStub.createWithCode();
private context: ICategoryCollectionParseContext = new CategoryCollectionParseContextStub();
private parser: IEnumParser<RecommendationLevel> = new EnumParserStub<RecommendationLevel>()
.setupDefaultValue(RecommendationLevel.Standard);
private factory: ScriptFactoryType = undefined;
private codeValidator: ICodeValidator = new CodeValidatorStub();
public withCodeValidator(codeValidator: ICodeValidator) {
this.codeValidator = codeValidator;
return this;
}
public withData(data: ScriptData) {
this.data = data;
return this;
}
public withContext(context: ICategoryCollectionParseContext) {
this.context = context;
return this;
}
public withParser(parser: IEnumParser<RecommendationLevel>) {
this.parser = parser;
return this;
}
public withFactory(factory: ScriptFactoryType) {
this.factory = factory;
return this;
}
public parseScript(): Script {
return parseScript(this.data, this.context, this.parser, this.factory, this.codeValidator);
}
}