add support for shared functions #41
This commit is contained in:
@@ -5,22 +5,41 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { parseCategory } from '@/application/Parser/CategoryParser';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
|
||||
|
||||
describe('ApplicationParser', () => {
|
||||
describe('parseApplication', () => {
|
||||
it('can parse current application file', () => {
|
||||
expect(() => parseApplication(applicationFile)).to.not.throw();
|
||||
// act
|
||||
const act = () => parseApplication(applicationFile);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
it('throws when undefined', () => {
|
||||
expect(() => parseApplication(undefined)).to.throw('application is null or undefined');
|
||||
// arrange
|
||||
const expectedError = 'application is null or undefined';
|
||||
// act
|
||||
const act = () => parseApplication(undefined);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when undefined actions', () => {
|
||||
const sut: ApplicationYaml = { actions: undefined };
|
||||
expect(() => parseApplication(sut)).to.throw('application does not define any action');
|
||||
// arrange
|
||||
const sut: ApplicationYaml = { actions: undefined, functions: undefined };
|
||||
const expectedError = 'application does not define any action';
|
||||
// act
|
||||
const act = () => parseApplication(sut);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when has no actions', () => {
|
||||
const sut: ApplicationYaml = { actions: [] };
|
||||
expect(() => parseApplication(sut)).to.throw('application does not define any action');
|
||||
// arrange
|
||||
const sut: ApplicationYaml = { actions: [], functions: undefined };
|
||||
const expectedError = 'application does not define any action';
|
||||
// act
|
||||
const act = () => parseApplication(sut);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('information', () => {
|
||||
it('returns expected repository version', () => {
|
||||
@@ -28,7 +47,7 @@ describe('ApplicationParser', () => {
|
||||
const expected = 'expected-version';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_VERSION = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ] };
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ], functions: undefined };
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.version;
|
||||
// assert
|
||||
@@ -39,7 +58,7 @@ describe('ApplicationParser', () => {
|
||||
const expected = 'https://expected-repository.url';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_REPOSITORY_URL = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ] };
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ], functions: undefined };
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.repositoryUrl;
|
||||
// assert
|
||||
@@ -50,7 +69,7 @@ describe('ApplicationParser', () => {
|
||||
const expected = 'expected-app-name';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_NAME = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ] };
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ], functions: undefined };
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.name;
|
||||
// assert
|
||||
@@ -61,7 +80,7 @@ describe('ApplicationParser', () => {
|
||||
const expected = 'https://expected.sexy';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_HOMEPAGE_URL = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ] };
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ], functions: undefined };
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.homepage;
|
||||
// assert
|
||||
@@ -71,8 +90,9 @@ describe('ApplicationParser', () => {
|
||||
it('parses actions', () => {
|
||||
// arrange
|
||||
const actions = [ getTestCategory('test1'), getTestCategory('test2') ];
|
||||
const expected = [ parseCategory(actions[0]), parseCategory(actions[1]) ];
|
||||
const sut: ApplicationYaml = { actions };
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const expected = [ parseCategory(actions[0], compiler), parseCategory(actions[1], compiler) ];
|
||||
const sut: ApplicationYaml = { actions, functions: undefined };
|
||||
// act
|
||||
const actual = parseApplication(sut).actions;
|
||||
// assert
|
||||
@@ -103,6 +123,7 @@ function getTestScript(scriptName: string, level: RecommendationLevel = Recommen
|
||||
code: 'script code',
|
||||
revertCode: 'revert code',
|
||||
recommend: RecommendationLevel[level].toLowerCase(),
|
||||
call: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,88 +4,150 @@ import { parseCategory } from '@/application/Parser/CategoryParser';
|
||||
import { YamlCategory, CategoryOrScript, YamlScript } from 'js-yaml-loader!./application.yaml';
|
||||
import { parseScript } from '@/application/Parser/ScriptParser';
|
||||
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
|
||||
import { YamlScriptStub } from '../../stubs/YamlScriptStub';
|
||||
|
||||
describe('CategoryParser', () => {
|
||||
describe('parseCategory', () => {
|
||||
|
||||
it('throws when undefined', () => {
|
||||
expect(() => parseCategory(undefined)).to.throw('category is null or undefined');
|
||||
describe('invalid category', () => {
|
||||
it('throws when undefined', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'category is null or undefined';
|
||||
const category = undefined;
|
||||
const compiler = new ScriptCompilerStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, compiler);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('throws when children are empty', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'category has no children';
|
||||
const category: YamlCategory = {
|
||||
category: 'test',
|
||||
children: [],
|
||||
};
|
||||
const compiler = new ScriptCompilerStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, compiler);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('throws when children are undefined', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'category has no children';
|
||||
const category: YamlCategory = {
|
||||
category: 'test',
|
||||
children: undefined,
|
||||
};
|
||||
const compiler = new ScriptCompilerStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, compiler);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('throws when name is empty or undefined', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'category has no name';
|
||||
const invalidNames = ['', undefined];
|
||||
invalidNames.forEach((invalidName) => {
|
||||
const category: YamlCategory = {
|
||||
category: invalidName,
|
||||
children: getTestChildren(),
|
||||
};
|
||||
const compiler = new ScriptCompilerStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, compiler);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when children is empty', () => {
|
||||
const category: YamlCategory = {
|
||||
category: 'test',
|
||||
children: [],
|
||||
};
|
||||
expect(() => parseCategory(category)).to.throw('category has no children');
|
||||
it('throws when compiler is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined compiler';
|
||||
const compiler = undefined;
|
||||
const category = getValidCategory();
|
||||
// act
|
||||
const act = () => parseCategory(category, compiler);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
|
||||
it('throws when children is undefined', () => {
|
||||
const category: YamlCategory = {
|
||||
category: 'test',
|
||||
children: undefined,
|
||||
};
|
||||
expect(() => parseCategory(category)).to.throw('category has no children');
|
||||
});
|
||||
|
||||
it('throws when name is empty', () => {
|
||||
const category: YamlCategory = {
|
||||
category: '',
|
||||
children: getTestChildren(),
|
||||
};
|
||||
expect(() => parseCategory(category)).to.throw('category has no name');
|
||||
});
|
||||
|
||||
it('throws when name is undefined', () => {
|
||||
const category: YamlCategory = {
|
||||
category: undefined,
|
||||
children: getTestChildren(),
|
||||
};
|
||||
expect(() => parseCategory(category)).to.throw('category has no name');
|
||||
});
|
||||
|
||||
it('returns expected docs', () => {
|
||||
// arrange
|
||||
const url = 'https://privacy.sexy';
|
||||
const expected = parseDocUrls({ docs: url });
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const category: YamlCategory = {
|
||||
category: 'category name',
|
||||
children: getTestChildren(),
|
||||
docs: url,
|
||||
};
|
||||
// act
|
||||
const actual = parseCategory(category).documentationUrls;
|
||||
const actual = parseCategory(category, compiler).documentationUrls;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('returns expected scripts', () => {
|
||||
// arrange
|
||||
const script = getTestScript();
|
||||
const expected = [ parseScript(script) ];
|
||||
const category: YamlCategory = {
|
||||
category: 'category name',
|
||||
children: [ script ],
|
||||
};
|
||||
// act
|
||||
const actual = parseCategory(category).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
describe('parses expected subscript', () => {
|
||||
it('single script with code', () => {
|
||||
// arrange
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const expected = [ parseScript(script, compiler) ];
|
||||
const category: YamlCategory = {
|
||||
category: 'category name',
|
||||
children: [ script ],
|
||||
};
|
||||
// act
|
||||
const actual = parseCategory(category, compiler).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('single script with function call', () => {
|
||||
// arrange
|
||||
const script = YamlScriptStub.createWithCall();
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(script);
|
||||
const expected = [ parseScript(script, compiler) ];
|
||||
const category: YamlCategory = {
|
||||
category: 'category name',
|
||||
children: [ script ],
|
||||
};
|
||||
// act
|
||||
const actual = parseCategory(category, compiler).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('multiple scripts with function call and code', () => {
|
||||
// arrange
|
||||
const callableScript = YamlScriptStub.createWithCall();
|
||||
const scripts = [ callableScript, YamlScriptStub.createWithCode() ];
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(callableScript);
|
||||
const expected = scripts.map((script) => parseScript(script, compiler));
|
||||
const category: YamlCategory = {
|
||||
category: 'category name',
|
||||
children: scripts,
|
||||
};
|
||||
// act
|
||||
const actual = parseCategory(category, compiler).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns expected subcategories', () => {
|
||||
// arrange
|
||||
const expected: YamlCategory[] = [ {
|
||||
category: 'test category',
|
||||
children: [ getTestScript() ],
|
||||
children: [ YamlScriptStub.createWithCode() ],
|
||||
}];
|
||||
const category: YamlCategory = {
|
||||
category: 'category name',
|
||||
children: expected,
|
||||
};
|
||||
const compiler = new ScriptCompilerStub();
|
||||
// act
|
||||
const actual = parseCategory(category).subCategories;
|
||||
const actual = parseCategory(category, compiler).subCategories;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(1);
|
||||
expect(actual[0].name).to.equal(expected[0].category);
|
||||
@@ -94,17 +156,16 @@ describe('CategoryParser', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function getTestChildren(): ReadonlyArray<CategoryOrScript> {
|
||||
return [
|
||||
getTestScript(),
|
||||
];
|
||||
}
|
||||
|
||||
function getTestScript(): YamlScript {
|
||||
function getValidCategory(): YamlCategory {
|
||||
return {
|
||||
name: 'script name',
|
||||
code: 'script code',
|
||||
revertCode: 'revert code',
|
||||
recommend: RecommendationLevel[RecommendationLevel.Standard],
|
||||
category: 'category name',
|
||||
children: getTestChildren(),
|
||||
docs: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function getTestChildren(): ReadonlyArray<CategoryOrScript> {
|
||||
return [
|
||||
YamlScriptStub.createWithCode(),
|
||||
];
|
||||
}
|
||||
|
||||
363
tests/unit/application/Parser/Compiler/ScriptCompiler.spec.ts
Normal file
363
tests/unit/application/Parser/Compiler/ScriptCompiler.spec.ts
Normal file
@@ -0,0 +1,363 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ScriptCompiler } from '@/application/Parser/Compiler/ScriptCompiler';
|
||||
import { YamlScriptStub } from '../../../stubs/YamlScriptStub';
|
||||
import { YamlFunction, YamlScript, FunctionCall, ScriptFunctionCall, FunctionCallParameters } from 'js-yaml-loader!./application.yaml';
|
||||
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: YamlFunction[] = [ {
|
||||
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: YamlFunction = {
|
||||
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: YamlFunction[] = [ {
|
||||
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: YamlFunction[] = [ {
|
||||
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 = YamlScriptStub.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 = YamlScriptStub.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 = YamlScriptStub.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 = YamlScriptStub.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: ScriptFunctionCall = { function: nonExistingFunctionName };
|
||||
const script = YamlScriptStub.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: ScriptFunctionCall = [
|
||||
{ function: existingFunctionName },
|
||||
undefined,
|
||||
];
|
||||
const script = YamlScriptStub.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: FunctionCall[] = [
|
||||
{ function: existingFunctionName },
|
||||
{ function: undefined }];
|
||||
const script = YamlScriptStub.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: YamlFunction = {
|
||||
name: functionName,
|
||||
parameters: [],
|
||||
code: expected.execute,
|
||||
revertCode: expected.revert,
|
||||
};
|
||||
const sut = new ScriptCompiler([func]);
|
||||
const call: FunctionCall = { function: functionName };
|
||||
const script = YamlScriptStub.createWithCall(call);
|
||||
// act
|
||||
const actual = sut.compile(script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('builds call sequence as expected', () => {
|
||||
// arrange
|
||||
const firstFunction: YamlFunction = {
|
||||
name: 'first-function-name',
|
||||
parameters: [],
|
||||
code: 'first-function-code',
|
||||
revertCode: 'first-function-revert-code',
|
||||
};
|
||||
const secondFunction: YamlFunction = {
|
||||
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: FunctionCall[] = [
|
||||
{ function: firstFunction.name },
|
||||
{ function: secondFunction.name },
|
||||
];
|
||||
const script = YamlScriptStub.createWithCall(call);
|
||||
// act
|
||||
const actual = sut.compile(script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('parameter substitution', () => {
|
||||
describe('substitutes by ignoring whitespaces inside mustaches', () => {
|
||||
// arrange
|
||||
const mustacheVariations = [
|
||||
'Hello {{ $test }}!',
|
||||
'Hello {{$test }}!',
|
||||
'Hello {{ $test}}!',
|
||||
'Hello {{$test}}!'];
|
||||
mustacheVariations.forEach((variation) => {
|
||||
it(variation, () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: variation,
|
||||
parameters: {
|
||||
test: 'world',
|
||||
},
|
||||
});
|
||||
const expected = env.expect('Hello world!');
|
||||
// act
|
||||
const actual = env.sut.compile(env.script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
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 same parameter repeated', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: '{{ $parameter }} {{ $parameter }}!',
|
||||
parameters: {
|
||||
parameter: 'Hodor',
|
||||
},
|
||||
});
|
||||
const expected = env.expect('Hodor 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);
|
||||
});
|
||||
it('throws on unknown expressions', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: '{{ each }}',
|
||||
parameters: {
|
||||
parameter: undefined,
|
||||
},
|
||||
});
|
||||
const expectedError = 'unknown expression: "each"';
|
||||
// act
|
||||
const act = () => env.sut.compile(env.script);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
interface ITestCase {
|
||||
code: string;
|
||||
parameters?: FunctionCallParameters;
|
||||
}
|
||||
|
||||
class TestEnvironment {
|
||||
public readonly sut: IScriptCompiler;
|
||||
public readonly script: YamlScript;
|
||||
constructor(testCase: ITestCase) {
|
||||
const functionName = 'testFunction';
|
||||
const func: YamlFunction = {
|
||||
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: FunctionCall = {
|
||||
function: functionName,
|
||||
parameters: testCase.parameters,
|
||||
};
|
||||
this.script = YamlScriptStub.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[]): YamlFunction[] {
|
||||
if (!names || names.length === 0) {
|
||||
names = ['test-function'];
|
||||
}
|
||||
return names.map((functionName) => {
|
||||
const func: YamlFunction = {
|
||||
name: functionName,
|
||||
parameters: [],
|
||||
code: `REM test-code (${functionName})`,
|
||||
revertCode: `REM test-revert-code (${functionName})`,
|
||||
};
|
||||
return func;
|
||||
});
|
||||
}
|
||||
@@ -1,58 +1,93 @@
|
||||
import { YamlScript } from 'js-yaml-loader!./application.yaml';
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { parseScript } from '@/application/Parser/ScriptParser';
|
||||
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
||||
import { RecommendationLevelNames, RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { ScriptCode } from '@/domain/ScriptCode';
|
||||
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
|
||||
import { YamlScriptStub } from '../../stubs/YamlScriptStub';
|
||||
|
||||
describe('ScriptParser', () => {
|
||||
describe('parseScript', () => {
|
||||
it('parses name as expected', () => {
|
||||
// arrange
|
||||
const script = getValidScript();
|
||||
script.name = 'expected-name';
|
||||
const expected = 'test-expected-name';
|
||||
const script = YamlScriptStub.createWithCode()
|
||||
.withName(expected);
|
||||
const compiler = new ScriptCompilerStub();
|
||||
// act
|
||||
const actual = parseScript(script);
|
||||
const actual = parseScript(script, compiler);
|
||||
// assert
|
||||
expect(actual.name).to.equal(script.name);
|
||||
});
|
||||
it('parses code as expected', () => {
|
||||
// arrange
|
||||
const script = getValidScript();
|
||||
script.code = 'expected-code';
|
||||
// act
|
||||
const actual = parseScript(script);
|
||||
// assert
|
||||
expect(actual.code).to.equal(script.code);
|
||||
});
|
||||
it('parses revertCode as expected', () => {
|
||||
// arrange
|
||||
const script = getValidScript();
|
||||
script.code = 'expected-code';
|
||||
// act
|
||||
const actual = parseScript(script);
|
||||
// assert
|
||||
expect(actual.revertCode).to.equal(script.revertCode);
|
||||
expect(actual.name).to.equal(expected);
|
||||
});
|
||||
it('parses docs as expected', () => {
|
||||
// arrange
|
||||
const script = getValidScript();
|
||||
script.docs = [ 'https://expected-doc1.com', 'https://expected-doc2.com' ];
|
||||
const docs = [ 'https://expected-doc1.com', 'https://expected-doc2.com' ];
|
||||
const script = YamlScriptStub.createWithCode()
|
||||
.withDocs(docs);
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const expected = parseDocUrls(script);
|
||||
// act
|
||||
const actual = parseScript(script);
|
||||
const actual = parseScript(script, compiler);
|
||||
// assert
|
||||
expect(actual.documentationUrls).to.deep.equal(expected);
|
||||
});
|
||||
describe('invalid script', () => {
|
||||
it('throws when script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const script = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
// 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 script = YamlScriptStub
|
||||
.createWithCall()
|
||||
.withCode('code');
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
// 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 script = YamlScriptStub
|
||||
.createWithCall()
|
||||
.withRevertCode('revert-code');
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
// 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 script = YamlScriptStub.createWithoutCallOrCodes();
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('level', () => {
|
||||
it('accepts undefined level', () => {
|
||||
const undefinedLevels: string[] = [ '', undefined ];
|
||||
undefinedLevels.forEach((undefinedLevel) => {
|
||||
// arrange
|
||||
const script = getValidScript();
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
script.recommend = undefinedLevel;
|
||||
// act
|
||||
const actual = parseScript(script);
|
||||
const actual = parseScript(script, compiler);
|
||||
// assert
|
||||
expect(actual.level).to.equal(undefined);
|
||||
});
|
||||
@@ -60,10 +95,11 @@ describe('ScriptParser', () => {
|
||||
it('throws on unknown level', () => {
|
||||
// arrange
|
||||
const unknownLevel = 'boi';
|
||||
const script = getValidScript();
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
script.recommend = unknownLevel;
|
||||
// act
|
||||
const act = () => parseScript(script);
|
||||
const act = () => parseScript(script, compiler);
|
||||
// assert
|
||||
expect(act).to.throw(`unknown level: "${unknownLevel}"`);
|
||||
});
|
||||
@@ -71,10 +107,11 @@ describe('ScriptParser', () => {
|
||||
const nonStringTypes: any[] = [ 5, true ];
|
||||
nonStringTypes.forEach((nonStringType) => {
|
||||
// arrange
|
||||
const script = getValidScript();
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub();
|
||||
script.recommend = nonStringType;
|
||||
// act
|
||||
const act = () => parseScript(script);
|
||||
const act = () => parseScript(script, compiler);
|
||||
// assert
|
||||
expect(act).to.throw(`level must be a string but it was ${typeof nonStringType}`);
|
||||
});
|
||||
@@ -84,10 +121,11 @@ describe('ScriptParser', () => {
|
||||
it(levelText, () => {
|
||||
// arrange
|
||||
const expectedLevel = RecommendationLevel[levelText];
|
||||
const script = getValidScript();
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub();
|
||||
script.recommend = levelText;
|
||||
// act
|
||||
const actual = parseScript(script);
|
||||
const actual = parseScript(script, compiler);
|
||||
// assert
|
||||
expect(actual.level).to.equal(expectedLevel);
|
||||
});
|
||||
@@ -95,24 +133,66 @@ describe('ScriptParser', () => {
|
||||
});
|
||||
it('parses level case insensitive', () => {
|
||||
// arrange
|
||||
const script = getValidScript();
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const expected = RecommendationLevel.Standard;
|
||||
script.recommend = RecommendationLevel[expected].toUpperCase();
|
||||
// act
|
||||
const actual = parseScript(script);
|
||||
const actual = parseScript(script, compiler);
|
||||
// assert
|
||||
expect(actual.level).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('code', () => {
|
||||
it('parses code as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const script = YamlScriptStub
|
||||
.createWithCode()
|
||||
.withCode(expected);
|
||||
const compiler = new ScriptCompilerStub();
|
||||
// act
|
||||
const parsed = parseScript(script, compiler);
|
||||
// assert
|
||||
const actual = parsed.code.execute;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('parses revertCode as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-revert-code';
|
||||
const script = YamlScriptStub
|
||||
.createWithCode()
|
||||
.withRevertCode(expected);
|
||||
const compiler = new ScriptCompilerStub();
|
||||
// act
|
||||
const parsed = parseScript(script, compiler);
|
||||
// assert
|
||||
const actual = parsed.code.revert;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('throws when compiler is not defined', () => {
|
||||
// arrange
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
const compiler = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
// assert
|
||||
expect(act).to.throw('undefined compiler');
|
||||
});
|
||||
it('gets code from compiler', () => {
|
||||
// arrange
|
||||
const expected = new ScriptCode('test-script', 'code', 'revert-code');
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(script, expected);
|
||||
// act
|
||||
const parsed = parseScript(script, compiler);
|
||||
// assert
|
||||
const actual = parsed.code;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getValidScript(): YamlScript {
|
||||
return {
|
||||
name: 'valid-name',
|
||||
code: 'valid-code',
|
||||
revertCode: 'expected revert code',
|
||||
docs: ['hello.com'],
|
||||
recommend: RecommendationLevel[RecommendationLevel.Standard].toLowerCase(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,10 +18,11 @@ describe('UserFilter', () => {
|
||||
// assert
|
||||
expect(isCalled).to.be.equal(true);
|
||||
});
|
||||
it('currentFilter is undefined', () => {
|
||||
it('sets currentFilter to undefined', () => {
|
||||
// arrange
|
||||
const sut = new UserFilter(new ApplicationStub());
|
||||
// act
|
||||
sut.setFilter('non-important');
|
||||
sut.removeFilter();
|
||||
// assert
|
||||
expect(sut.currentFilter).to.be.equal(undefined);
|
||||
@@ -51,68 +52,69 @@ describe('UserFilter', () => {
|
||||
expect(actual.hasAnyMatches()).be.equal(false);
|
||||
expect(actual.query).to.equal(nonMatchingFilter);
|
||||
});
|
||||
describe('signals when script matches', () => {
|
||||
it('code matches', () => {
|
||||
// arrange
|
||||
const code = 'HELLO world';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
const script = new ScriptStub('id').withCode(code);
|
||||
const category = new CategoryStub(33).withScript(script);
|
||||
const sut = new UserFilter(new ApplicationStub()
|
||||
.withAction(category));
|
||||
sut.filtered.on((filterResult) => actual = filterResult);
|
||||
// act
|
||||
sut.setFilter(filter);
|
||||
// assert
|
||||
expect(actual.hasAnyMatches()).be.equal(true);
|
||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
||||
expect(actual.query).to.equal(filter);
|
||||
expect(sut.currentFilter).to.deep.equal(actual);
|
||||
describe('signals when matches', () => {
|
||||
describe('signals when script matches', () => {
|
||||
it('code matches', () => {
|
||||
// arrange
|
||||
const code = 'HELLO world';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
const script = new ScriptStub('id').withCode(code);
|
||||
const category = new CategoryStub(33).withScript(script);
|
||||
const sut = new UserFilter(new ApplicationStub()
|
||||
.withAction(category));
|
||||
sut.filtered.on((filterResult) => actual = filterResult);
|
||||
// act
|
||||
sut.setFilter(filter);
|
||||
// assert
|
||||
expect(actual.hasAnyMatches()).be.equal(true);
|
||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
||||
expect(actual.query).to.equal(filter);
|
||||
expect(sut.currentFilter).to.deep.equal(actual);
|
||||
});
|
||||
it('revertCode matches', () => {
|
||||
// arrange
|
||||
const revertCode = 'HELLO world';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
const script = new ScriptStub('id').withRevertCode(revertCode);
|
||||
const category = new CategoryStub(33).withScript(script);
|
||||
const sut = new UserFilter(new ApplicationStub()
|
||||
.withAction(category));
|
||||
sut.filtered.on((filterResult) => actual = filterResult);
|
||||
// act
|
||||
sut.setFilter(filter);
|
||||
// assert
|
||||
expect(actual.hasAnyMatches()).be.equal(true);
|
||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
||||
expect(actual.query).to.equal(filter);
|
||||
expect(sut.currentFilter).to.deep.equal(actual);
|
||||
});
|
||||
it('name matches', () => {
|
||||
// arrange
|
||||
const name = 'HELLO world';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
const script = new ScriptStub('id').withName(name);
|
||||
const category = new CategoryStub(33).withScript(script);
|
||||
const sut = new UserFilter(new ApplicationStub()
|
||||
.withAction(category));
|
||||
sut.filtered.on((filterResult) => actual = filterResult);
|
||||
// act
|
||||
sut.setFilter(filter);
|
||||
// assert
|
||||
expect(actual.hasAnyMatches()).be.equal(true);
|
||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
||||
expect(actual.query).to.equal(filter);
|
||||
expect(sut.currentFilter).to.deep.equal(actual);
|
||||
});
|
||||
});
|
||||
it('revertCode matches', () => {
|
||||
// arrange
|
||||
const revertCode = 'HELLO world';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
const script = new ScriptStub('id').withRevertCode(revertCode);
|
||||
const category = new CategoryStub(33).withScript(script);
|
||||
const sut = new UserFilter(new ApplicationStub()
|
||||
.withAction(category));
|
||||
sut.filtered.on((filterResult) => actual = filterResult);
|
||||
// act
|
||||
sut.setFilter(filter);
|
||||
// assert
|
||||
expect(actual.hasAnyMatches()).be.equal(true);
|
||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
||||
expect(actual.query).to.equal(filter);
|
||||
expect(sut.currentFilter).to.deep.equal(actual);
|
||||
});
|
||||
it('name matches', () => {
|
||||
// arrange
|
||||
const name = 'HELLO world';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
const script = new ScriptStub('id').withName(name);
|
||||
const category = new CategoryStub(33).withScript(script);
|
||||
const sut = new UserFilter(new ApplicationStub()
|
||||
.withAction(category));
|
||||
sut.filtered.on((filterResult) => actual = filterResult);
|
||||
// act
|
||||
sut.setFilter(filter);
|
||||
// assert
|
||||
expect(actual.hasAnyMatches()).be.equal(true);
|
||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
||||
expect(actual.query).to.equal(filter);
|
||||
expect(sut.currentFilter).to.deep.equal(actual);
|
||||
});
|
||||
|
||||
it('signals when category matches', () => {
|
||||
// arrange
|
||||
const categoryName = 'HELLO world';
|
||||
|
||||
Reference in New Issue
Block a user