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.
300 lines
11 KiB
TypeScript
300 lines
11 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import type { CategoryData, CategoryOrScriptData } from '@/application/collections/';
|
|
import { CategoryFactoryType, parseCategory } from '@/application/Parser/CategoryParser';
|
|
import { parseScript } from '@/application/Parser/Script/ScriptParser';
|
|
import { parseDocs } from '@/application/Parser/DocumentationParser';
|
|
import { ScriptCompilerStub } from '@tests/unit/shared/Stubs/ScriptCompilerStub';
|
|
import { ScriptDataStub } from '@tests/unit/shared/Stubs/ScriptDataStub';
|
|
import { CategoryCollectionParseContextStub } from '@tests/unit/shared/Stubs/CategoryCollectionParseContextStub';
|
|
import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
|
|
import { CategoryDataStub } from '@tests/unit/shared/Stubs/CategoryDataStub';
|
|
import { itEachAbsentCollectionValue, itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
|
import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
|
|
import { expectThrowsNodeError, ITestScenario, NodeValidationTestRunner } from '@tests/unit/application/Parser/NodeValidation/NodeValidatorTestRunner';
|
|
import { ICategoryCollectionParseContext } from '@/application/Parser/Script/ICategoryCollectionParseContext';
|
|
import { Category } from '@/domain/Category';
|
|
|
|
describe('CategoryParser', () => {
|
|
describe('parseCategory', () => {
|
|
describe('invalid category data', () => {
|
|
describe('validates script data', () => {
|
|
describe('satisfies shared node tests', () => {
|
|
new NodeValidationTestRunner()
|
|
.testInvalidNodeName((invalidName) => {
|
|
return createTest(
|
|
new CategoryDataStub().withName(invalidName),
|
|
);
|
|
})
|
|
.testMissingNodeData((node) => {
|
|
return createTest(node as CategoryData);
|
|
});
|
|
});
|
|
describe('throws when category children is absent', () => {
|
|
itEachAbsentCollectionValue((absentValue) => {
|
|
// arrange
|
|
const categoryName = 'test';
|
|
const expectedMessage = `"${categoryName}" has no children.`;
|
|
const category = new CategoryDataStub()
|
|
.withName(categoryName)
|
|
.withChildren(absentValue);
|
|
// act
|
|
const test = createTest(category);
|
|
// assert
|
|
expectThrowsNodeError(test, expectedMessage);
|
|
});
|
|
});
|
|
describe('throws when category child is missing', () => {
|
|
new NodeValidationTestRunner()
|
|
.testMissingNodeData((missingNode) => {
|
|
// arrange
|
|
const invalidChildNode = missingNode;
|
|
const parent = new CategoryDataStub()
|
|
.withName('parent')
|
|
.withChildren([new CategoryDataStub().withName('valid child'), invalidChildNode]);
|
|
return ({
|
|
// act
|
|
act: () => new TestBuilder()
|
|
.withData(parent)
|
|
.parseCategory(),
|
|
// assert
|
|
expectedContext: {
|
|
selfNode: invalidChildNode,
|
|
parentNode: parent,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
it('throws when node is neither a category or a script', () => {
|
|
// arrange
|
|
const expectedError = 'Node is neither a category or a script.';
|
|
const invalidChildNode = { property: 'non-empty-value' } as never as CategoryOrScriptData;
|
|
const parent = new CategoryDataStub()
|
|
.withName('parent')
|
|
.withChildren([new CategoryDataStub().withName('valid child'), invalidChildNode]);
|
|
// act
|
|
const test: ITestScenario = {
|
|
// act
|
|
act: () => new TestBuilder()
|
|
.withData(parent)
|
|
.parseCategory(),
|
|
// assert
|
|
expectedContext: {
|
|
selfNode: invalidChildNode,
|
|
parentNode: parent,
|
|
},
|
|
};
|
|
// assert
|
|
expectThrowsNodeError(test, expectedError);
|
|
});
|
|
describe('throws when category child is invalid category', () => {
|
|
new NodeValidationTestRunner().testInvalidNodeName((invalidName) => {
|
|
// arrange
|
|
const invalidChildNode = new CategoryDataStub()
|
|
.withName(invalidName);
|
|
const parent = new CategoryDataStub()
|
|
.withName('parent')
|
|
.withChildren([new CategoryDataStub().withName('valid child'), invalidChildNode]);
|
|
return ({
|
|
// act
|
|
act: () => new TestBuilder()
|
|
.withData(parent)
|
|
.parseCategory(),
|
|
// assert
|
|
expectedContext: {
|
|
type: NodeType.Category,
|
|
selfNode: invalidChildNode,
|
|
parentNode: parent,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
function createTest(category: CategoryData): ITestScenario {
|
|
return {
|
|
act: () => new TestBuilder()
|
|
.withData(category)
|
|
.parseCategory(),
|
|
expectedContext: {
|
|
type: NodeType.Category,
|
|
selfNode: category,
|
|
},
|
|
};
|
|
}
|
|
});
|
|
it(`rethrows exception if ${Category.name} cannot be constructed`, () => {
|
|
// arrange
|
|
const expectedError = 'category creation failed';
|
|
const factoryMock: CategoryFactoryType = () => { throw new Error(expectedError); };
|
|
const data = new CategoryDataStub();
|
|
// act
|
|
const act = () => new TestBuilder()
|
|
.withData(data)
|
|
.withFactory(factoryMock)
|
|
.parseCategory();
|
|
// expect
|
|
expectThrowsNodeError({
|
|
act,
|
|
expectedContext: {
|
|
type: NodeType.Category,
|
|
selfNode: data,
|
|
},
|
|
}, expectedError);
|
|
});
|
|
});
|
|
describe('throws when context is absent', () => {
|
|
itEachAbsentObjectValue((absentValue) => {
|
|
// arrange
|
|
const expectedError = 'missing context';
|
|
const context = absentValue;
|
|
// act
|
|
const act = () => new TestBuilder()
|
|
.withContext(context)
|
|
.parseCategory();
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
});
|
|
});
|
|
it('returns expected docs', () => {
|
|
// arrange
|
|
const url = 'https://privacy.sexy';
|
|
const expected = parseDocs({ docs: url });
|
|
const category = new CategoryDataStub()
|
|
.withDocs(url);
|
|
// act
|
|
const actual = new TestBuilder()
|
|
.withData(category)
|
|
.parseCategory()
|
|
.docs;
|
|
// assert
|
|
expect(actual).to.deep.equal(expected);
|
|
});
|
|
describe('parses expected subscript', () => {
|
|
it('single script with code', () => {
|
|
// arrange
|
|
const script = ScriptDataStub.createWithCode();
|
|
const context = new CategoryCollectionParseContextStub();
|
|
const expected = [parseScript(script, context)];
|
|
const category = new CategoryDataStub()
|
|
.withChildren([script]);
|
|
// act
|
|
const actual = new TestBuilder()
|
|
.withData(category)
|
|
.withContext(context)
|
|
.parseCategory()
|
|
.scripts;
|
|
// assert
|
|
expect(actual).to.deep.equal(expected);
|
|
});
|
|
it('single script with function call', () => {
|
|
// arrange
|
|
const script = ScriptDataStub.createWithCall();
|
|
const compiler = new ScriptCompilerStub()
|
|
.withCompileAbility(script);
|
|
const context = new CategoryCollectionParseContextStub()
|
|
.withCompiler(compiler);
|
|
const expected = [parseScript(script, context)];
|
|
const category = new CategoryDataStub()
|
|
.withChildren([script]);
|
|
// act
|
|
const actual = new TestBuilder()
|
|
.withData(category)
|
|
.withContext(context)
|
|
.parseCategory()
|
|
.scripts;
|
|
// assert
|
|
expect(actual).to.deep.equal(expected);
|
|
});
|
|
it('multiple scripts with function call and code', () => {
|
|
// arrange
|
|
const callableScript = ScriptDataStub.createWithCall();
|
|
const scripts = [callableScript, ScriptDataStub.createWithCode()];
|
|
const category = new CategoryDataStub()
|
|
.withChildren(scripts);
|
|
const compiler = new ScriptCompilerStub()
|
|
.withCompileAbility(callableScript);
|
|
const context = new CategoryCollectionParseContextStub()
|
|
.withCompiler(compiler);
|
|
const expected = scripts.map((script) => parseScript(script, context));
|
|
// act
|
|
const actual = new TestBuilder()
|
|
.withData(category)
|
|
.withContext(context)
|
|
.parseCategory()
|
|
.scripts;
|
|
// assert
|
|
expect(actual).to.deep.equal(expected);
|
|
});
|
|
it('script is created with right context', () => { // test 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 category = new CategoryDataStub()
|
|
.withChildren([
|
|
new CategoryDataStub()
|
|
.withName('sub-category')
|
|
.withChildren([
|
|
ScriptDataStub
|
|
.createWithoutCallOrCodes()
|
|
.withCode(duplicatedCode),
|
|
]),
|
|
]);
|
|
// act
|
|
const act = () => new TestBuilder()
|
|
.withData(category)
|
|
.withContext(parseContext)
|
|
.parseCategory()
|
|
.scripts;
|
|
// assert
|
|
expect(act).to.not.throw();
|
|
});
|
|
});
|
|
it('returns expected subcategories', () => {
|
|
// arrange
|
|
const expected = [new CategoryDataStub()
|
|
.withName('test category')
|
|
.withChildren([ScriptDataStub.createWithCode()]),
|
|
];
|
|
const category = new CategoryDataStub()
|
|
.withName('category name')
|
|
.withChildren(expected);
|
|
// act
|
|
const actual = new TestBuilder()
|
|
.withData(category)
|
|
.parseCategory()
|
|
.subCategories;
|
|
// assert
|
|
expect(actual).to.have.lengthOf(1);
|
|
expect(actual[0].name).to.equal(expected[0].category);
|
|
expect(actual[0].scripts.length).to.equal(expected[0].children.length);
|
|
});
|
|
});
|
|
});
|
|
|
|
class TestBuilder {
|
|
private data: CategoryData = new CategoryDataStub();
|
|
|
|
private context: ICategoryCollectionParseContext = new CategoryCollectionParseContextStub();
|
|
|
|
private factory: CategoryFactoryType = undefined;
|
|
|
|
public withData(data: CategoryData) {
|
|
this.data = data;
|
|
return this;
|
|
}
|
|
|
|
public withContext(context: ICategoryCollectionParseContext) {
|
|
this.context = context;
|
|
return this;
|
|
}
|
|
|
|
public withFactory(factory: CategoryFactoryType) {
|
|
this.factory = factory;
|
|
return this;
|
|
}
|
|
|
|
public parseCategory() {
|
|
return parseCategory(this.data, this.context, this.factory);
|
|
}
|
|
}
|