add initial macOS support #40
This commit is contained in:
@@ -3,11 +3,13 @@ import { expect } from 'chai';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
import { CategoryCollectionParserType, parseApplication } from '@/application/Parser/ApplicationParser';
|
||||
import WindowsData from 'js-yaml-loader!@/application/collections/windows.yaml';
|
||||
import MacOsData from 'js-yaml-loader!@/application/collections/macos.yaml';
|
||||
import { CollectionData } from 'js-yaml-loader!@/*';
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { getEnumValues } from '@/application/Common/Enum';
|
||||
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub';
|
||||
import { getProcessEnvironmentStub } from '../../stubs/ProcessEnvironmentStub';
|
||||
import { CollectionDataStub } from '../../stubs/CollectionDataStub';
|
||||
@@ -24,15 +26,18 @@ describe('ApplicationParser', () => {
|
||||
it('returns result from the parser', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.macOS;
|
||||
const data = new CollectionDataStub();
|
||||
const expected = new CategoryCollectionStub()
|
||||
.withOs(os);
|
||||
const parser = new CategoryCollectionParserSpy()
|
||||
.setResult(expected)
|
||||
.setUpReturnValue(data, expected)
|
||||
.mockParser();
|
||||
const env = getProcessEnvironmentStub();
|
||||
const collections = [ data ];
|
||||
// act
|
||||
const context = parseApplication(parser);
|
||||
const app = parseApplication(parser, env, collections);
|
||||
// assert
|
||||
const actual = context.getCollection(os);
|
||||
const actual = app.getCollection(os);
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
});
|
||||
@@ -44,10 +49,10 @@ describe('ApplicationParser', () => {
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
const context = parseApplication(parserMock, env);
|
||||
const app = parseApplication(parserMock, env);
|
||||
// assert
|
||||
expect(expected).to.deep.equal(context.info);
|
||||
expect(expected).to.deep.equal(parserSpy.lastArguments.info);
|
||||
expect(expected).to.deep.equal(app.info);
|
||||
expect(parserSpy.arguments.map((arg) => arg.info).every((info) => info === expected));
|
||||
});
|
||||
it('defaults to process.env', () => {
|
||||
// arrange
|
||||
@@ -56,54 +61,110 @@ describe('ApplicationParser', () => {
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
const context = parseApplication(parserMock);
|
||||
const app = parseApplication(parserMock);
|
||||
// assert
|
||||
expect(expected).to.deep.equal(context.info);
|
||||
expect(expected).to.deep.equal(parserSpy.lastArguments.info);
|
||||
expect(expected).to.deep.equal(app.info);
|
||||
expect(parserSpy.arguments.map((arg) => arg.info).every((info) => info === expected));
|
||||
});
|
||||
});
|
||||
describe('collectionData', () => {
|
||||
it('parsed with expected data', () => {
|
||||
describe('collectionsData', () => {
|
||||
describe('set as expected', () => {
|
||||
// arrange
|
||||
const expected = new CollectionDataStub();
|
||||
const env = getProcessEnvironmentStub();
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
const testCases = [
|
||||
{
|
||||
name: 'single collection',
|
||||
input: [ new CollectionDataStub() ],
|
||||
output: [ new CategoryCollectionStub().withOs(OperatingSystem.macOS) ],
|
||||
},
|
||||
{
|
||||
name: 'multiple collections',
|
||||
input: [
|
||||
new CollectionDataStub().withOs('windows'),
|
||||
new CollectionDataStub().withOs('macos'),
|
||||
],
|
||||
output: [
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.macOS),
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.Windows),
|
||||
],
|
||||
},
|
||||
];
|
||||
// act
|
||||
parseApplication(parserMock, env, expected);
|
||||
// assert
|
||||
expect(expected).to.equal(parserSpy.lastArguments.file);
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const env = getProcessEnvironmentStub();
|
||||
let parserSpy = new CategoryCollectionParserSpy();
|
||||
for (let i = 0; i < testCase.input.length; i++) {
|
||||
parserSpy = parserSpy.setUpReturnValue(testCase.input[i], testCase.output[i]);
|
||||
}
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
const app = parseApplication(parserMock, env, testCase.input);
|
||||
// assert
|
||||
expect(app.collections).to.deep.equal(testCase.output);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('defaults to windows data', () => {
|
||||
it('defaults to expected data', () => {
|
||||
// arrange
|
||||
const expected = WindowsData;
|
||||
const expected = [ WindowsData, MacOsData ];
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
parseApplication(parserMock);
|
||||
// assert
|
||||
expect(expected).to.equal(parserSpy.lastArguments.file);
|
||||
const actual = parserSpy.arguments.map((args) => args.data);
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
describe('throws when data is invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
expectedError: 'no collection provided',
|
||||
data: [],
|
||||
},
|
||||
{
|
||||
expectedError: 'undefined collection provided',
|
||||
data: [ new CollectionDataStub(), undefined ],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.expectedError, () => {
|
||||
const parserMock = new CategoryCollectionParserSpy().mockParser();
|
||||
const env = getProcessEnvironmentStub();
|
||||
// act
|
||||
const act = () => parseApplication(parserMock, env, testCase.data);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class CategoryCollectionParserSpy {
|
||||
public lastArguments: {
|
||||
file: CollectionData;
|
||||
info: ProjectInformation;
|
||||
} = { file: undefined, info: undefined };
|
||||
private result: ICategoryCollection = new CategoryCollectionStub();
|
||||
public arguments = new Array<{
|
||||
data: CollectionData,
|
||||
info: ProjectInformation,
|
||||
}>();
|
||||
|
||||
public setResult(collection: ICategoryCollection): CategoryCollectionParserSpy {
|
||||
this.result = collection;
|
||||
private returnValues = new Map<CollectionData, ICategoryCollection>();
|
||||
|
||||
public setUpReturnValue(data: CollectionData, collection: ICategoryCollection): CategoryCollectionParserSpy {
|
||||
this.returnValues.set(data, collection);
|
||||
return this;
|
||||
}
|
||||
public mockParser(): CategoryCollectionParserType {
|
||||
return (file: CollectionData, info: IProjectInformation) => {
|
||||
this.lastArguments.file = file;
|
||||
this.lastArguments.info = info;
|
||||
return this.result;
|
||||
return (data: CollectionData, info: IProjectInformation) => {
|
||||
this.arguments.push({ data, info });
|
||||
if (this.returnValues.has(data)) {
|
||||
return this.returnValues.get(data);
|
||||
} else {
|
||||
// Get next OS with a unique OS so mock does not result in invalid app (with duplicate OS collections)
|
||||
const currentRun = this.arguments.length - 1;
|
||||
const nextOs = getEnumValues(OperatingSystem)[currentRun];
|
||||
return new CategoryCollectionStub().withOs(nextOs);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { parseScriptingDefinition } from '@/application/Parser/ScriptingDefinitionParser';
|
||||
import { mockEnumParser } from '../../stubs/EnumParserStub';
|
||||
import { ProjectInformationStub } from '../../stubs/ProjectInformationStub';
|
||||
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
|
||||
import { getCategoryStub, CollectionDataStub } from '../../stubs/CollectionDataStub';
|
||||
import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub';
|
||||
|
||||
describe('CategoryCollectionParser', () => {
|
||||
describe('parseCategoryCollection', () => {
|
||||
@@ -48,8 +48,8 @@ describe('CategoryCollectionParser', () => {
|
||||
it('parses actions', () => {
|
||||
// arrange
|
||||
const actions = [ getCategoryStub('test1'), getCategoryStub('test2') ];
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const expected = [ parseCategory(actions[0], compiler), parseCategory(actions[1], compiler) ];
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
const expected = [ parseCategory(actions[0], context), parseCategory(actions[1], context) ];
|
||||
const collection = new CollectionDataStub()
|
||||
.withActions(actions);
|
||||
const info = new ProjectInformationStub();
|
||||
|
||||
@@ -2,10 +2,12 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { parseCategory } from '@/application/Parser/CategoryParser';
|
||||
import { CategoryData, CategoryOrScriptData } from 'js-yaml-loader!@/*';
|
||||
import { parseScript } from '@/application/Parser/ScriptParser';
|
||||
import { parseScript } from '@/application/Parser/Script/ScriptParser';
|
||||
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
||||
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
|
||||
import { ScriptDataStub } from '../../stubs/ScriptDataStub';
|
||||
import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub';
|
||||
import { LanguageSyntaxStub } from '../../stubs/LanguageSyntaxStub';
|
||||
|
||||
describe('CategoryParser', () => {
|
||||
describe('parseCategory', () => {
|
||||
@@ -14,9 +16,9 @@ describe('CategoryParser', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'category is null or undefined';
|
||||
const category = undefined;
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, compiler);
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
@@ -28,9 +30,9 @@ describe('CategoryParser', () => {
|
||||
category: categoryName,
|
||||
children: [],
|
||||
};
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, compiler);
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
@@ -42,9 +44,9 @@ describe('CategoryParser', () => {
|
||||
category: categoryName,
|
||||
children: undefined,
|
||||
};
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, compiler);
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
@@ -57,21 +59,21 @@ describe('CategoryParser', () => {
|
||||
category: invalidName,
|
||||
children: getTestChildren(),
|
||||
};
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, compiler);
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('throws when compiler is undefined', () => {
|
||||
it('throws when context is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined compiler';
|
||||
const compiler = undefined;
|
||||
const expectedError = 'undefined context';
|
||||
const context = undefined;
|
||||
const category = getValidCategory();
|
||||
// act
|
||||
const act = () => parseCategory(category, compiler);
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
@@ -79,14 +81,14 @@ describe('CategoryParser', () => {
|
||||
// arrange
|
||||
const url = 'https://privacy.sexy';
|
||||
const expected = parseDocUrls({ docs: url });
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const category: CategoryData = {
|
||||
category: 'category name',
|
||||
children: getTestChildren(),
|
||||
docs: url,
|
||||
};
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseCategory(category, compiler).documentationUrls;
|
||||
const actual = parseCategory(category, context).documentationUrls;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
@@ -94,14 +96,14 @@ describe('CategoryParser', () => {
|
||||
it('single script with code', () => {
|
||||
// arrange
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const expected = [ parseScript(script, compiler) ];
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
const expected = [ parseScript(script, context) ];
|
||||
const category: CategoryData = {
|
||||
category: 'category name',
|
||||
children: [ script ],
|
||||
};
|
||||
// act
|
||||
const actual = parseCategory(category, compiler).scripts;
|
||||
const actual = parseCategory(category, context).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
@@ -110,13 +112,15 @@ describe('CategoryParser', () => {
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(script);
|
||||
const expected = [ parseScript(script, compiler) ];
|
||||
const context = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
const expected = [ parseScript(script, context) ];
|
||||
const category: CategoryData = {
|
||||
category: 'category name',
|
||||
children: [ script ],
|
||||
};
|
||||
// act
|
||||
const actual = parseCategory(category, compiler).scripts;
|
||||
const actual = parseCategory(category, context).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
@@ -124,18 +128,44 @@ describe('CategoryParser', () => {
|
||||
// arrange
|
||||
const callableScript = ScriptDataStub.createWithCall();
|
||||
const scripts = [ callableScript, ScriptDataStub.createWithCode() ];
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(callableScript);
|
||||
const expected = scripts.map((script) => parseScript(script, compiler));
|
||||
const category: CategoryData = {
|
||||
category: 'category name',
|
||||
children: scripts,
|
||||
};
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(callableScript);
|
||||
const context = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
const expected = scripts.map((script) => parseScript(script, context));
|
||||
// act
|
||||
const actual = parseCategory(category, compiler).scripts;
|
||||
const actual = parseCategory(category, context).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: CategoryData = {
|
||||
category: 'category name',
|
||||
children: [
|
||||
{
|
||||
category: 'sub-category',
|
||||
children: [
|
||||
ScriptDataStub
|
||||
.createWithoutCallOrCodes()
|
||||
.withCode(duplicatedCode),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
// act
|
||||
const act = () => parseCategory(category, parseContext).scripts;
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
it('returns expected subcategories', () => {
|
||||
// arrange
|
||||
@@ -147,9 +177,9 @@ describe('CategoryParser', () => {
|
||||
category: 'category name',
|
||||
children: expected,
|
||||
};
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseCategory(category, compiler).subCategories;
|
||||
const actual = parseCategory(category, context).subCategories;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(1);
|
||||
expect(actual[0].name).to.equal(expected[0].category);
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ScriptCompiler } from '@/application/Parser/Compiler/ScriptCompiler';
|
||||
import { ScriptDataStub } from '../../../stubs/ScriptDataStub';
|
||||
import { FunctionData, ScriptData, FunctionCallData, ScriptFunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*';
|
||||
import { IScriptCode } from '@/domain/IScriptCode';
|
||||
import { IScriptCompiler } from '@/application/Parser/Compiler/IScriptCompiler';
|
||||
|
||||
describe('ScriptCompiler', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws when functions have same names', () => {
|
||||
// arrange
|
||||
const expectedError = `duplicate function name: "same-func-name"`;
|
||||
const functions: FunctionData[] = [ {
|
||||
name: 'same-func-name',
|
||||
code: 'non-empty-code',
|
||||
}, {
|
||||
name: 'same-func-name',
|
||||
code: 'non-empty-code-2',
|
||||
}];
|
||||
// act
|
||||
const act = () => new ScriptCompiler(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when function parameters have same names', () => {
|
||||
// arrange
|
||||
const func: FunctionData = {
|
||||
name: 'function-name',
|
||||
code: 'non-empty-code',
|
||||
parameters: [ 'duplicate', 'duplicate' ],
|
||||
};
|
||||
const expectedError = `"${func.name}": duplicate parameter name: "duplicate"`;
|
||||
// act
|
||||
const act = () => new ScriptCompiler([func]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws when when function have duplicate code', () => {
|
||||
it('code', () => {
|
||||
// arrange
|
||||
const expectedError = `duplicate "code" in functions: "duplicate-code"`;
|
||||
const functions: FunctionData[] = [ {
|
||||
name: 'func-1',
|
||||
code: 'duplicate-code',
|
||||
}, {
|
||||
name: 'func-2',
|
||||
code: 'duplicate-code',
|
||||
}];
|
||||
// act
|
||||
const act = () => new ScriptCompiler(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('revertCode', () => {
|
||||
// arrange
|
||||
const expectedError = `duplicate "revertCode" in functions: "duplicate-revert-code"`;
|
||||
const functions: FunctionData[] = [ {
|
||||
name: 'func-1',
|
||||
code: 'code-1',
|
||||
revertCode: 'duplicate-revert-code',
|
||||
}, {
|
||||
name: 'func-2',
|
||||
code: 'code-2',
|
||||
revertCode: 'duplicate-revert-code',
|
||||
}];
|
||||
// act
|
||||
const act = () => new ScriptCompiler(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('canCompile', () => {
|
||||
it('returns true if "call" is defined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompiler([]);
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('returns false if "call" is undefined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompiler([]);
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('compile', () => {
|
||||
describe('invalid state', () => {
|
||||
it('throws if functions are empty', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot compile without shared functions';
|
||||
const functions = [];
|
||||
const sut = new ScriptCompiler(functions);
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if call is not an object', () => {
|
||||
// arrange
|
||||
const expectedError = 'called function(s) must be an object';
|
||||
const invalidValues = [undefined, 'string', 33];
|
||||
const sut = new ScriptCompiler(createFunctions());
|
||||
invalidValues.forEach((invalidValue) => {
|
||||
const script = ScriptDataStub.createWithoutCallOrCodes() // because call ctor overwrites "undefined"
|
||||
.withCall(invalidValue as any);
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('invalid function reference', () => {
|
||||
it('throws if function does not exist', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompiler(createFunctions());
|
||||
const nonExistingFunctionName = 'non-existing-func';
|
||||
const expectedError = `called function is not defined "${nonExistingFunctionName}"`;
|
||||
const call: ScriptFunctionCallData = { function: nonExistingFunctionName };
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if function is undefined', () => {
|
||||
// arrange
|
||||
const existingFunctionName = 'existing-func';
|
||||
const sut = new ScriptCompiler(createFunctions(existingFunctionName));
|
||||
const call: ScriptFunctionCallData = [
|
||||
{ function: existingFunctionName },
|
||||
undefined,
|
||||
];
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
const expectedError = `undefined function call in script "${script.name}"`;
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if function name is not given', () => {
|
||||
// arrange
|
||||
const existingFunctionName = 'existing-func';
|
||||
const sut = new ScriptCompiler(createFunctions(existingFunctionName));
|
||||
const call: FunctionCallData[] = [
|
||||
{ function: existingFunctionName },
|
||||
{ function: undefined }];
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
const expectedError = `empty function name called in script "${script.name}"`;
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('builds code as expected', () => {
|
||||
it('builds single call as expected', () => {
|
||||
// arrange
|
||||
const functionName = 'testSharedFunction';
|
||||
const expected: IScriptCode = {
|
||||
execute: 'expected-code',
|
||||
revert: 'expected-revert-code',
|
||||
};
|
||||
const func: FunctionData = {
|
||||
name: functionName,
|
||||
parameters: [],
|
||||
code: expected.execute,
|
||||
revertCode: expected.revert,
|
||||
};
|
||||
const sut = new ScriptCompiler([func]);
|
||||
const call: FunctionCallData = { function: functionName };
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
// act
|
||||
const actual = sut.compile(script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('builds call sequence as expected', () => {
|
||||
// arrange
|
||||
const firstFunction: FunctionData = {
|
||||
name: 'first-function-name',
|
||||
parameters: [],
|
||||
code: 'first-function-code',
|
||||
revertCode: 'first-function-revert-code',
|
||||
};
|
||||
const secondFunction: FunctionData = {
|
||||
name: 'second-function-name',
|
||||
parameters: [],
|
||||
code: 'second-function-code',
|
||||
revertCode: 'second-function-revert-code',
|
||||
};
|
||||
const expected: IScriptCode = {
|
||||
execute: 'first-function-code\nsecond-function-code',
|
||||
revert: 'first-function-revert-code\nsecond-function-revert-code',
|
||||
};
|
||||
const sut = new ScriptCompiler([firstFunction, secondFunction]);
|
||||
const call: FunctionCallData[] = [
|
||||
{ function: firstFunction.name },
|
||||
{ function: secondFunction.name },
|
||||
];
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
// act
|
||||
const actual = sut.compile(script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('parameter substitution', () => {
|
||||
describe('substitutes as expected', () => {
|
||||
it('with different parameters', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
parameters: {
|
||||
firstParameter: 'llo',
|
||||
secondParameter: 'world',
|
||||
},
|
||||
});
|
||||
const expected = env.expect('Hello world!');
|
||||
// act
|
||||
const actual = env.sut.compile(env.script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('with single parameter', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: '{{ $parameter }}!',
|
||||
parameters: {
|
||||
parameter: 'Hodor',
|
||||
},
|
||||
});
|
||||
const expected = env.expect('Hodor!');
|
||||
// act
|
||||
const actual = env.sut.compile(env.script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
it('throws when parameters is undefined', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: '{{ $parameter }} {{ $parameter }}!',
|
||||
});
|
||||
const expectedError = 'no parameters defined, expected: "parameter"';
|
||||
// act
|
||||
const act = () => env.sut.compile(env.script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when parameter value is not provided', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: '{{ $parameter }} {{ $parameter }}!',
|
||||
parameters: {
|
||||
parameter: undefined,
|
||||
},
|
||||
});
|
||||
const expectedError = 'parameter value is not provided for "parameter" in function call';
|
||||
// act
|
||||
const act = () => env.sut.compile(env.script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
interface ITestCase {
|
||||
code: string;
|
||||
parameters?: FunctionCallParametersData;
|
||||
}
|
||||
|
||||
class TestEnvironment {
|
||||
public readonly sut: IScriptCompiler;
|
||||
public readonly script: ScriptData;
|
||||
constructor(testCase: ITestCase) {
|
||||
const functionName = 'testFunction';
|
||||
const func: FunctionData = {
|
||||
name: functionName,
|
||||
parameters: testCase.parameters ? Object.keys(testCase.parameters) : undefined,
|
||||
code: this.getCode(testCase.code, 'execute'),
|
||||
revertCode: this.getCode(testCase.code, 'revert'),
|
||||
};
|
||||
this.sut = new ScriptCompiler([func]);
|
||||
const call: FunctionCallData = {
|
||||
function: functionName,
|
||||
parameters: testCase.parameters,
|
||||
};
|
||||
this.script = ScriptDataStub.createWithCall(call);
|
||||
}
|
||||
public expect(code: string): IScriptCode {
|
||||
return {
|
||||
execute: this.getCode(code, 'execute'),
|
||||
revert: this.getCode(code, 'revert'),
|
||||
};
|
||||
}
|
||||
private getCode(text: string, type: 'execute' | 'revert'): string {
|
||||
return `${text} (${type})`;
|
||||
}
|
||||
}
|
||||
|
||||
function createFunctions(...names: string[]): FunctionData[] {
|
||||
if (!names || names.length === 0) {
|
||||
names = ['test-function'];
|
||||
}
|
||||
return names.map((functionName) => {
|
||||
const func: FunctionData = {
|
||||
name: functionName,
|
||||
parameters: [],
|
||||
code: `REM test-code (${functionName})`,
|
||||
revertCode: `REM test-revert-code (${functionName})`,
|
||||
};
|
||||
return func;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ISyntaxFactory } from '@/application/Parser/Script/Syntax/ISyntaxFactory';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { LanguageSyntaxStub } from '../../../stubs/LanguageSyntaxStub';
|
||||
import { CategoryCollectionParseContext } from '@/application/Parser/Script/CategoryCollectionParseContext';
|
||||
import { ScriptingDefinitionStub } from '../../../stubs/ScriptingDefinitionStub';
|
||||
import { FunctionDataStub } from '../../../stubs/FunctionDataStub';
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
import { ScriptCompiler } from '@/application/Parser/Script/Compiler/ScriptCompiler';
|
||||
import { FunctionData } from 'js-yaml-loader!*';
|
||||
|
||||
describe('CategoryCollectionParseContext', () => {
|
||||
describe('ctor', () => {
|
||||
describe('functionsData', () => {
|
||||
it('can create with empty values', () => {
|
||||
// arrange
|
||||
const testData: FunctionData[][] = [ undefined, [] ];
|
||||
const scripting = new ScriptingDefinitionStub();
|
||||
for (const functionsData of testData) {
|
||||
// act
|
||||
const act = () => new CategoryCollectionParseContext(functionsData, scripting);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
}
|
||||
});
|
||||
});
|
||||
it('scripting', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined scripting';
|
||||
const scripting = undefined;
|
||||
const functionsData = [ new FunctionDataStub() ];
|
||||
// act
|
||||
const act = () => new CategoryCollectionParseContext(functionsData, scripting);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('constructed as expected', () => {
|
||||
// arrange
|
||||
const functionsData = [ new FunctionDataStub() ];
|
||||
const syntax = new LanguageSyntaxStub();
|
||||
const expected = new ScriptCompiler(functionsData, syntax);
|
||||
const language = ScriptingLanguage.shellscript;
|
||||
const factoryMock = mockFactory(language, syntax);
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withLanguage(language);
|
||||
// act
|
||||
const sut = new CategoryCollectionParseContext(functionsData, definition, factoryMock);
|
||||
const actual = sut.compiler;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('syntax', () => {
|
||||
it('set from syntax factory', () => {
|
||||
// arrange
|
||||
const language = ScriptingLanguage.shellscript;
|
||||
const expected = new LanguageSyntaxStub();
|
||||
const factoryMock = mockFactory(language, expected);
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withLanguage(language);
|
||||
// act
|
||||
const sut = new CategoryCollectionParseContext([], definition, factoryMock);
|
||||
const actual = sut.syntax;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockFactory(expectedLanguage: ScriptingLanguage, result: ILanguageSyntax): ISyntaxFactory {
|
||||
return {
|
||||
create: (language: ScriptingLanguage) => {
|
||||
if (language !== expectedLanguage) {
|
||||
throw new Error('unexpected language');
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { generateIlCode } from '@/application/Parser/Compiler/ILCode';
|
||||
import { generateIlCode } from '@/application/Parser/Script/Compiler/ILCode';
|
||||
|
||||
describe('ILCode', () => {
|
||||
describe('getUniqueParameterNames', () => {
|
||||
@@ -0,0 +1,405 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ScriptCompiler } from '@/application/Parser/Script/Compiler/ScriptCompiler';
|
||||
import { FunctionData, ScriptData, FunctionCallData, ScriptFunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*';
|
||||
import { IScriptCode } from '@/domain/IScriptCode';
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
import { IScriptCompiler } from '@/application/Parser/Script/Compiler/IScriptCompiler';
|
||||
import { LanguageSyntaxStub } from '../../../../stubs/LanguageSyntaxStub';
|
||||
import { ScriptDataStub } from '../../../../stubs/ScriptDataStub';
|
||||
import { FunctionDataStub } from '../../../../stubs/FunctionDataStub';
|
||||
|
||||
describe('ScriptCompiler', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws if syntax is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = `undefined syntax`;
|
||||
// act
|
||||
const act = () => new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withSyntax(undefined)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if one of the functions is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = `some functions are undefined`;
|
||||
const functions = [ new FunctionDataStub(), undefined ];
|
||||
// act
|
||||
const act = () => new ScriptCompilerBuilder()
|
||||
.withFunctions(...functions)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when functions have same names', () => {
|
||||
// arrange
|
||||
const name = 'same-func-name';
|
||||
const expectedError = `duplicate function name: "${name}"`;
|
||||
const functions = [
|
||||
new FunctionDataStub().withName(name),
|
||||
new FunctionDataStub().withName(name),
|
||||
];
|
||||
// act
|
||||
const act = () => new ScriptCompilerBuilder()
|
||||
.withFunctions(...functions)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when function parameters have same names', () => {
|
||||
// arrange
|
||||
const parameterName = 'duplicate-parameter';
|
||||
const func = new FunctionDataStub()
|
||||
.withParameters(parameterName, parameterName);
|
||||
const expectedError = `"${func.name}": duplicate parameter name: "${parameterName}"`;
|
||||
// act
|
||||
const act = () => new ScriptCompilerBuilder()
|
||||
.withFunctions(func)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws when when function have duplicate code', () => {
|
||||
it('code', () => {
|
||||
// arrange
|
||||
const code = 'duplicate-code';
|
||||
const expectedError = `duplicate "code" in functions: "${code}"`;
|
||||
const functions = [
|
||||
new FunctionDataStub().withName('func-1').withCode(code),
|
||||
new FunctionDataStub().withName('func-2').withCode(code),
|
||||
];
|
||||
// act
|
||||
const act = () => new ScriptCompilerBuilder()
|
||||
.withFunctions(...functions)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('revertCode', () => {
|
||||
// arrange
|
||||
const revertCode = 'duplicate-revert-code';
|
||||
const expectedError = `duplicate "revertCode" in functions: "${revertCode}"`;
|
||||
const functions = [
|
||||
new FunctionDataStub().withName('func-1').withCode('code-1').withRevertCode(revertCode),
|
||||
new FunctionDataStub().withName('func-2').withCode('code-2').withRevertCode(revertCode),
|
||||
];
|
||||
// act
|
||||
const act = () => new ScriptCompilerBuilder()
|
||||
.withFunctions(...functions)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('can construct with empty functions', () => {
|
||||
// arrange
|
||||
const builder = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions();
|
||||
// act
|
||||
const act = () => builder.build();
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
describe('canCompile', () => {
|
||||
it('returns true if "call" is defined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('returns false if "call" is undefined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('compile', () => {
|
||||
describe('invalid state', () => {
|
||||
it('throws if functions are empty', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot compile without shared functions';
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if call is not an object', () => {
|
||||
// arrange
|
||||
const expectedError = 'called function(s) must be an object';
|
||||
const invalidValues = [undefined, 'string', 33];
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.build();
|
||||
invalidValues.forEach((invalidValue) => {
|
||||
const script = ScriptDataStub.createWithoutCallOrCodes() // because call ctor overwrites "undefined"
|
||||
.withCall(invalidValue as any);
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('invalid function reference', () => {
|
||||
it('throws if function does not exist', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.build();
|
||||
const nonExistingFunctionName = 'non-existing-func';
|
||||
const expectedError = `called function is not defined "${nonExistingFunctionName}"`;
|
||||
const call: ScriptFunctionCallData = { function: nonExistingFunctionName };
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if function is undefined', () => {
|
||||
// arrange
|
||||
const existingFunctionName = 'existing-func';
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withFunctionNames(existingFunctionName)
|
||||
.build();
|
||||
const call: ScriptFunctionCallData = [
|
||||
{ function: existingFunctionName },
|
||||
undefined,
|
||||
];
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
const expectedError = `undefined function call in script "${script.name}"`;
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if function name is not given', () => {
|
||||
// arrange
|
||||
const existingFunctionName = 'existing-func';
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withFunctionNames(existingFunctionName)
|
||||
.build();
|
||||
const call: FunctionCallData[] = [
|
||||
{ function: existingFunctionName },
|
||||
{ function: undefined }];
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
const expectedError = `empty function name called in script "${script.name}"`;
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('builds code as expected', () => {
|
||||
it('creates code with expected syntax', () => { // test through script validation logic
|
||||
// act
|
||||
const commentDelimiter = 'should not throw';
|
||||
const syntax = new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter);
|
||||
const func = new FunctionDataStub()
|
||||
.withCode(`${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withFunctions(func)
|
||||
.withSyntax(syntax)
|
||||
.build();
|
||||
const call: FunctionCallData = { function: func.name };
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
// act
|
||||
const act = () => sut.compile(script);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
it('builds single call as expected', () => {
|
||||
// arrange
|
||||
const functionName = 'testSharedFunction';
|
||||
const expectedExecute = `expected-execute`;
|
||||
const expectedRevert = `expected-revert`;
|
||||
const func = new FunctionDataStub()
|
||||
.withName(functionName)
|
||||
.withCode(expectedExecute)
|
||||
.withRevertCode(expectedRevert);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withFunctions(func)
|
||||
.build();
|
||||
const call: FunctionCallData = { function: functionName };
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
// act
|
||||
const actual = sut.compile(script);
|
||||
// assert
|
||||
expect(actual.execute).to.equal(expectedExecute);
|
||||
expect(actual.revert).to.equal(expectedRevert);
|
||||
});
|
||||
it('builds call sequence as expected', () => {
|
||||
// arrange
|
||||
const firstFunction = new FunctionDataStub()
|
||||
.withName('first-function-name')
|
||||
.withCode('first-function-code')
|
||||
.withRevertCode('first-function-revert-code');
|
||||
const secondFunction = new FunctionDataStub()
|
||||
.withName('second-function-name')
|
||||
.withCode('second-function-code')
|
||||
.withRevertCode('second-function-revert-code');
|
||||
const expectedExecute = `${firstFunction.code}\n${secondFunction.code}`;
|
||||
const expectedRevert = `${firstFunction.revertCode}\n${secondFunction.revertCode}`;
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withFunctions(firstFunction, secondFunction)
|
||||
.build();
|
||||
const call: FunctionCallData[] = [
|
||||
{ function: firstFunction.name },
|
||||
{ function: secondFunction.name },
|
||||
];
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
// act
|
||||
const actual = sut.compile(script);
|
||||
// assert
|
||||
expect(actual.execute).to.equal(expectedExecute);
|
||||
expect(actual.revert).to.equal(expectedRevert);
|
||||
});
|
||||
});
|
||||
describe('parameter substitution', () => {
|
||||
describe('substitutes as expected', () => {
|
||||
it('with different parameters', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
parameters: {
|
||||
firstParameter: 'llo',
|
||||
secondParameter: 'world',
|
||||
},
|
||||
});
|
||||
const expected = env.expect('Hello world!');
|
||||
// act
|
||||
const actual = env.sut.compile(env.script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('with single parameter', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: '{{ $parameter }}!',
|
||||
parameters: {
|
||||
parameter: 'Hodor',
|
||||
},
|
||||
});
|
||||
const expected = env.expect('Hodor!');
|
||||
// act
|
||||
const actual = env.sut.compile(env.script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
it('throws when parameters is undefined', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: '{{ $parameter }} {{ $parameter }}!',
|
||||
});
|
||||
const expectedError = 'no parameters defined, expected: "parameter"';
|
||||
// act
|
||||
const act = () => env.sut.compile(env.script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when parameter value is not provided', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: '{{ $parameter }} {{ $parameter }}!',
|
||||
parameters: {
|
||||
parameter: undefined,
|
||||
},
|
||||
});
|
||||
const expectedError = 'parameter value is not provided for "parameter" in function call';
|
||||
// act
|
||||
const act = () => env.sut.compile(env.script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
interface ITestCase {
|
||||
code: string;
|
||||
parameters?: FunctionCallParametersData;
|
||||
}
|
||||
class TestEnvironment {
|
||||
public readonly sut: IScriptCompiler;
|
||||
public readonly script: ScriptData;
|
||||
constructor(testCase: ITestCase) {
|
||||
const functionName = 'testFunction';
|
||||
const parameters = testCase.parameters ? Object.keys(testCase.parameters) : [];
|
||||
const func = new FunctionDataStub()
|
||||
.withName(functionName)
|
||||
.withParameters(...parameters)
|
||||
.withCode(this.getCode(testCase.code, 'execute'))
|
||||
.withRevertCode(this.getCode(testCase.code, 'revert'));
|
||||
const syntax = new LanguageSyntaxStub();
|
||||
this.sut = new ScriptCompiler([func], syntax);
|
||||
const call: FunctionCallData = {
|
||||
function: functionName,
|
||||
parameters: testCase.parameters,
|
||||
};
|
||||
this.script = ScriptDataStub.createWithCall(call);
|
||||
}
|
||||
public expect(code: string): IScriptCode {
|
||||
return {
|
||||
execute: this.getCode(code, 'execute'),
|
||||
revert: this.getCode(code, 'revert'),
|
||||
};
|
||||
}
|
||||
private getCode(text: string, type: 'execute' | 'revert'): string {
|
||||
return `${text} (${type})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
class ScriptCompilerBuilder {
|
||||
private static createFunctions(...names: string[]): FunctionData[] {
|
||||
return names.map((functionName) => {
|
||||
return new FunctionDataStub().withName(functionName);
|
||||
});
|
||||
}
|
||||
private functions: FunctionData[];
|
||||
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
||||
public withFunctions(...functions: FunctionData[]): ScriptCompilerBuilder {
|
||||
this.functions = functions;
|
||||
return this;
|
||||
}
|
||||
public withSomeFunctions(): ScriptCompilerBuilder {
|
||||
this.functions = ScriptCompilerBuilder.createFunctions('test-function');
|
||||
return this;
|
||||
}
|
||||
public withFunctionNames(...functionNames: string[]): ScriptCompilerBuilder {
|
||||
this.functions = ScriptCompilerBuilder.createFunctions(...functionNames);
|
||||
return this;
|
||||
}
|
||||
public withEmptyFunctions(): ScriptCompilerBuilder {
|
||||
this.functions = [];
|
||||
return this;
|
||||
}
|
||||
public withSyntax(syntax: ILanguageSyntax): ScriptCompilerBuilder {
|
||||
this.syntax = syntax;
|
||||
return this;
|
||||
}
|
||||
public build(): ScriptCompiler {
|
||||
if (!this.functions) {
|
||||
throw new Error('Function behavior not defined');
|
||||
}
|
||||
return new ScriptCompiler(this.functions, this.syntax);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { parseScript } from '@/application/Parser/ScriptParser';
|
||||
import { parseScript } from '@/application/Parser/Script/ScriptParser';
|
||||
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { ScriptCode } from '@/domain/ScriptCode';
|
||||
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
|
||||
import { ScriptDataStub } from '../../stubs/ScriptDataStub';
|
||||
import { mockEnumParser } from '../../stubs/EnumParserStub';
|
||||
import { ICategoryCollectionParseContext } from '@/application/Parser/Script/ICategoryCollectionParseContext';
|
||||
import { ScriptCompilerStub } from '../../../stubs/ScriptCompilerStub';
|
||||
import { ScriptDataStub } from '../../../stubs/ScriptDataStub';
|
||||
import { mockEnumParser } from '../../../stubs/EnumParserStub';
|
||||
import { ScriptCodeStub } from '../../../stubs/ScriptCodeStub';
|
||||
import { CategoryCollectionParseContextStub } from '../../../stubs/CategoryCollectionParseContextStub';
|
||||
import { LanguageSyntaxStub } from '../../../stubs/LanguageSyntaxStub';
|
||||
|
||||
describe('ScriptParser', () => {
|
||||
describe('parseScript', () => {
|
||||
@@ -15,9 +18,9 @@ describe('ScriptParser', () => {
|
||||
const expected = 'test-expected-name';
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withName(expected);
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseScript(script, compiler);
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.name).to.equal(expected);
|
||||
});
|
||||
@@ -26,10 +29,10 @@ describe('ScriptParser', () => {
|
||||
const docs = [ 'https://expected-doc1.com', 'https://expected-doc2.com' ];
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withDocs(docs);
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const expected = parseDocUrls(script);
|
||||
// act
|
||||
const actual = parseScript(script, compiler);
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.documentationUrls).to.deep.equal(expected);
|
||||
});
|
||||
@@ -37,44 +40,44 @@ describe('ScriptParser', () => {
|
||||
it('throws when script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when both function call and code are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot define both "call" and "code"';
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub
|
||||
.createWithCall()
|
||||
.withCode('code');
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when both function call and revertCode are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot define "revertCode" if "call" is defined';
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub
|
||||
.createWithCall()
|
||||
.withRevertCode('revert-code');
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when neither call or revertCode are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'must define either "call" or "code"';
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub.createWithoutCallOrCodes();
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
@@ -84,11 +87,11 @@ describe('ScriptParser', () => {
|
||||
const undefinedLevels: string[] = [ '', undefined ];
|
||||
undefinedLevels.forEach((undefinedLevel) => {
|
||||
// arrange
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withRecommend(undefinedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, compiler);
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.level).to.equal(undefined);
|
||||
});
|
||||
@@ -100,10 +103,10 @@ describe('ScriptParser', () => {
|
||||
const levelText = 'standard';
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withRecommend(levelText);
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const parserMock = mockEnumParser(expectedName, levelText, expectedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, compiler, parserMock);
|
||||
const actual = parseScript(script, parseContext, parserMock);
|
||||
// assert
|
||||
expect(actual.level).to.equal(expectedLevel);
|
||||
});
|
||||
@@ -115,9 +118,9 @@ describe('ScriptParser', () => {
|
||||
const script = ScriptDataStub
|
||||
.createWithCode()
|
||||
.withCode(expected);
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const parsed = parseScript(script, compiler);
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code.execute;
|
||||
expect(actual).to.equal(expected);
|
||||
@@ -128,36 +131,55 @@ describe('ScriptParser', () => {
|
||||
const script = ScriptDataStub
|
||||
.createWithCode()
|
||||
.withRevertCode(expected);
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const parsed = parseScript(script, compiler);
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code.revert;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('throws when compiler is not defined', () => {
|
||||
it('throws when context is not defined', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'undefined context';
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const compiler = undefined;
|
||||
const context: ICategoryCollectionParseContext = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
const act = () => parseScript(script, context);
|
||||
// assert
|
||||
expect(act).to.throw('undefined compiler');
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('gets code from compiler', () => {
|
||||
// arrange
|
||||
const expected = new ScriptCode('test-script', 'code', 'revert-code');
|
||||
const expected = new ScriptCodeStub();
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(script, expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
// act
|
||||
const parsed = parseScript(script, compiler);
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('syntax', () => {
|
||||
it('set from the 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 script = ScriptDataStub
|
||||
.createWithoutCallOrCodes()
|
||||
.withCode(duplicatedCode);
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
import { BatchFileSyntax } from '@/application/Parser/Script/Syntax/BatchFileSyntax';
|
||||
import { ShellScriptSyntax } from '@/application/Parser/Script/Syntax/ShellScriptSyntax';
|
||||
|
||||
|
||||
function getSystemsUnderTest(): ILanguageSyntax[] {
|
||||
return [ new BatchFileSyntax(), new ShellScriptSyntax() ];
|
||||
}
|
||||
|
||||
describe('ConcreteSyntaxes', () => {
|
||||
describe('commentDelimiters', () => {
|
||||
for (const sut of getSystemsUnderTest()) {
|
||||
it(`${sut.constructor.name} returns defined value`, () => {
|
||||
// act
|
||||
const value = sut.commentDelimiters;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('commonCodeParts', () => {
|
||||
for (const sut of getSystemsUnderTest()) {
|
||||
it(`${sut.constructor.name} returns defined value`, () => {
|
||||
// act
|
||||
const value = sut.commonCodeParts;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { SyntaxFactory } from '@/application/Parser/Script/Syntax/SyntaxFactory';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { ShellScriptSyntax } from '@/application/Parser/Script/Syntax/ShellScriptSyntax';
|
||||
import { BatchFileSyntax } from '@/application/Parser/Script/Syntax/BatchFileSyntax';
|
||||
|
||||
describe('SyntaxFactory', () => {
|
||||
describe('getSyntax', () => {
|
||||
describe('creates expected type', () => {
|
||||
it('shellscript returns ShellBuilder', () => {
|
||||
// arrange
|
||||
const testCases: Array< { language: ScriptingLanguage, expected: any} > = [
|
||||
{ language: ScriptingLanguage.shellscript, expected: ShellScriptSyntax},
|
||||
{ language: ScriptingLanguage.batchfile, expected: BatchFileSyntax},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(ScriptingLanguage[testCase.language], () => {
|
||||
// act
|
||||
const sut = new SyntaxFactory();
|
||||
const result = sut.create(testCase.language);
|
||||
// assert
|
||||
expect(result).to.be.instanceOf(testCase.expected,
|
||||
`Actual was: ${result.constructor.name}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
it('throws on unknown scripting language', () => {
|
||||
// arrange
|
||||
const sut = new SyntaxFactory();
|
||||
// act
|
||||
const act = () => sut.create(3131313131);
|
||||
// assert
|
||||
expect(act).to.throw(`unknown language: "${ScriptingLanguage[3131313131]}"`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -34,7 +34,8 @@ describe('ScriptingDefinitionParser', () => {
|
||||
const expectedName = 'language';
|
||||
const info = new ProjectInformationStub();
|
||||
const definition = new ScriptingDefinitionBuilder()
|
||||
.withLanguage(languageText).construct();
|
||||
.withLanguage(languageText)
|
||||
.construct();
|
||||
const parserMock = mockEnumParser(expectedName, languageText, expectedLanguage);
|
||||
// act
|
||||
const actual = parseScriptingDefinition(definition, info, new Date(), parserMock);
|
||||
|
||||
Reference in New Issue
Block a user