refactor application.yaml to become an os definition #40
This commit is contained in:
95
tests/unit/application/Common/Enum.spec.ts
Normal file
95
tests/unit/application/Common/Enum.spec.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { getEnumNames, getEnumValues, createEnumParser } from '@/application/Common/Enum';
|
||||
|
||||
describe('Enum', () => {
|
||||
describe('createEnumParser', () => {
|
||||
enum ParsableEnum { Value1, value2 }
|
||||
describe('parses as expected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'case insensitive',
|
||||
value: 'vALuE1',
|
||||
expected: ParsableEnum.Value1,
|
||||
},
|
||||
{
|
||||
name: 'exact match',
|
||||
value: 'value2',
|
||||
expected: ParsableEnum.value2,
|
||||
},
|
||||
];
|
||||
// act
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const parser = createEnumParser(ParsableEnum);
|
||||
const actual = parser.parseEnum(testCase.value, 'non-important');
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('throws as expected', () => {
|
||||
// arrange
|
||||
const enumName = 'ParsableEnum';
|
||||
const testCases = [
|
||||
{
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
expectedError: `undefined ${enumName}`,
|
||||
},
|
||||
{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
expectedError: `undefined ${enumName}`,
|
||||
},
|
||||
{
|
||||
name: 'out of range',
|
||||
value: 'value3',
|
||||
expectedError: `unknown ${enumName}: "value3"`,
|
||||
},
|
||||
{
|
||||
name: 'out of range',
|
||||
value: 'value3',
|
||||
expectedError: `unknown ${enumName}: "value3"`,
|
||||
},
|
||||
{
|
||||
name: 'unexpected type',
|
||||
value: 55 as any,
|
||||
expectedError: `unexpected type of ${enumName}: "number"`,
|
||||
},
|
||||
];
|
||||
// act
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const parser = createEnumParser(ParsableEnum);
|
||||
const act = () => parser.parseEnum(testCase.value, enumName);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('getEnumNames', () => {
|
||||
it('parses as expected', () => {
|
||||
// arrange
|
||||
enum TestEnum { TestValue1, testValue2, testvalue3, TESTVALUE4 }
|
||||
const expected = [ 'TestValue1', 'testValue2', 'testvalue3', 'TESTVALUE4' ];
|
||||
// act
|
||||
const actual = getEnumNames(TestEnum);
|
||||
// assert
|
||||
expect(expected.sort()).to.deep.equal(actual.sort());
|
||||
});
|
||||
});
|
||||
describe('getEnumValues', () => {
|
||||
it('parses as expected', () => {
|
||||
// arrange
|
||||
enum TestEnum { Red, Green, Blue }
|
||||
const expected = [ TestEnum.Red, TestEnum.Green, TestEnum.Blue ];
|
||||
// act
|
||||
const actual = getEnumValues(TestEnum);
|
||||
// assert
|
||||
expect(expected.sort()).to.deep.equal(actual.sort());
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,16 @@
|
||||
import { IEntity } from '@/infrastructure/Entity/IEntity';
|
||||
import applicationFile, { YamlCategory, YamlScript, ApplicationYaml } from 'js-yaml-loader!@/application/application.yaml';
|
||||
import applicationFile, { YamlCategory, YamlScript, YamlApplication, YamlScriptingDefinition } from 'js-yaml-loader!@/application/application.yaml';
|
||||
import { parseApplication } from '@/application/Parser/ApplicationParser';
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { parseCategory } from '@/application/Parser/CategoryParser';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { parseScriptingDefinition } from '@/application/Parser/ScriptingDefinitionParser';
|
||||
import { mockEnumParser } from '../../stubs/EnumParserStub';
|
||||
|
||||
describe('ApplicationParser', () => {
|
||||
describe('parseApplication', () => {
|
||||
@@ -23,33 +28,50 @@ describe('ApplicationParser', () => {
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when undefined actions', () => {
|
||||
// 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);
|
||||
describe('actions', () => {
|
||||
it('throws when undefined actions', () => {
|
||||
// arrange
|
||||
const app = new YamlApplicationBuilder().withActions(undefined).build();
|
||||
// act
|
||||
const act = () => parseApplication(app);
|
||||
// assert
|
||||
expect(act).to.throw('application does not define any action');
|
||||
});
|
||||
it('throws when has no actions', () => {
|
||||
// arrange
|
||||
const app = new YamlApplicationBuilder().withActions([]).build();
|
||||
// act
|
||||
const act = () => parseApplication(app);
|
||||
// assert
|
||||
expect(act).to.throw('application does not define any action');
|
||||
});
|
||||
it('parses actions', () => {
|
||||
// arrange
|
||||
const actions = [ getTestCategory('test1'), getTestCategory('test2') ];
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const expected = [ parseCategory(actions[0], compiler), parseCategory(actions[1], compiler) ];
|
||||
const app = new YamlApplicationBuilder().withActions(actions).build();
|
||||
// act
|
||||
const actual = parseApplication(app).actions;
|
||||
// assert
|
||||
expect(excludingId(actual)).to.be.deep.equal(excludingId(expected));
|
||||
function excludingId<TId>(array: ReadonlyArray<IEntity<TId>>) {
|
||||
return array.map((obj) => {
|
||||
const { ['id']: omitted, ...rest } = obj;
|
||||
return rest;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
it('throws when has no actions', () => {
|
||||
// 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', () => {
|
||||
describe('info', () => {
|
||||
it('returns expected repository version', () => {
|
||||
// arrange
|
||||
const expected = 'expected-version';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_VERSION = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ], functions: undefined };
|
||||
const app = new YamlApplicationBuilder().build();
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.version;
|
||||
const actual = parseApplication(app, env).info.version;
|
||||
// assert
|
||||
expect(actual).to.be.equal(expected);
|
||||
});
|
||||
@@ -58,9 +80,9 @@ describe('ApplicationParser', () => {
|
||||
const expected = 'https://expected-repository.url';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_REPOSITORY_URL = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ], functions: undefined };
|
||||
const app = new YamlApplicationBuilder().build();
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.repositoryUrl;
|
||||
const actual = parseApplication(app, env).info.repositoryUrl;
|
||||
// assert
|
||||
expect(actual).to.be.equal(expected);
|
||||
});
|
||||
@@ -69,9 +91,9 @@ describe('ApplicationParser', () => {
|
||||
const expected = 'expected-app-name';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_NAME = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ], functions: undefined };
|
||||
const app = new YamlApplicationBuilder().build();
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.name;
|
||||
const actual = parseApplication(app, env).info.name;
|
||||
// assert
|
||||
expect(actual).to.be.equal(expected);
|
||||
});
|
||||
@@ -80,33 +102,79 @@ describe('ApplicationParser', () => {
|
||||
const expected = 'https://expected.sexy';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_HOMEPAGE_URL = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ], functions: undefined };
|
||||
const app = new YamlApplicationBuilder().build();
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.homepage;
|
||||
const actual = parseApplication(app, env).info.homepage;
|
||||
// assert
|
||||
expect(actual).to.be.equal(expected);
|
||||
});
|
||||
});
|
||||
it('parses actions', () => {
|
||||
// arrange
|
||||
const actions = [ getTestCategory('test1'), getTestCategory('test2') ];
|
||||
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
|
||||
expect(excludingId(actual)).to.be.deep.equal(excludingId(expected));
|
||||
function excludingId<TId>(array: ReadonlyArray<IEntity<TId>>) {
|
||||
return array.map((obj) => {
|
||||
const { ['id']: omitted, ...rest } = obj;
|
||||
return rest;
|
||||
});
|
||||
}
|
||||
describe('scripting definition', () => {
|
||||
it('parses scripting definition as expected', () => {
|
||||
// arrange
|
||||
const app = new YamlApplicationBuilder().build();
|
||||
const information = parseProjectInformation(process.env);
|
||||
const expected = parseScriptingDefinition(app.scripting, information);
|
||||
// act
|
||||
const actual = parseApplication(app).scripting;
|
||||
// assert
|
||||
expect(expected).to.deep.equal(actual);
|
||||
});
|
||||
});
|
||||
describe('os', () => {
|
||||
it('parses as expected', () => {
|
||||
// arrange
|
||||
const expectedOs = OperatingSystem.macOS;
|
||||
const osText = 'macos';
|
||||
const expectedName = 'os';
|
||||
const app = new YamlApplicationBuilder()
|
||||
.withOs(osText)
|
||||
.build();
|
||||
const parserMock = mockEnumParser(expectedName, osText, expectedOs);
|
||||
const env = getProcessEnvironmentStub();
|
||||
// act
|
||||
const actual = parseApplication(app, env, parserMock);
|
||||
// assert
|
||||
expect(actual.os).to.equal(expectedOs);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class YamlApplicationBuilder {
|
||||
private os = 'windows';
|
||||
private actions: readonly YamlCategory[] = [ getTestCategory() ];
|
||||
private scripting: YamlScriptingDefinition = getTestDefinition();
|
||||
|
||||
public withActions(actions: readonly YamlCategory[]): YamlApplicationBuilder {
|
||||
this.actions = actions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withOs(os: string): YamlApplicationBuilder {
|
||||
this.os = os;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withScripting(scripting: YamlScriptingDefinition): YamlApplicationBuilder {
|
||||
this.scripting = scripting;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build(): YamlApplication {
|
||||
return { os: this.os, scripting: this.scripting, actions: this.actions };
|
||||
}
|
||||
}
|
||||
|
||||
function getTestDefinition(): YamlScriptingDefinition {
|
||||
return {
|
||||
fileExtension: '.bat',
|
||||
language: ScriptingLanguage[ScriptingLanguage.batchfile],
|
||||
startCode: 'start',
|
||||
endCode: 'end',
|
||||
};
|
||||
}
|
||||
|
||||
function getTestCategory(scriptPrefix = 'testScript'): YamlCategory {
|
||||
return {
|
||||
category: 'category name',
|
||||
|
||||
141
tests/unit/application/Parser/Compiler/ILCode.spec.ts
Normal file
141
tests/unit/application/Parser/Compiler/ILCode.spec.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { generateIlCode } from '@/application/Parser/Compiler/ILCode';
|
||||
|
||||
describe('ILCode', () => {
|
||||
describe('getUniqueParameterNames', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'empty parameters: returns an empty array',
|
||||
code: 'no expressions',
|
||||
expected: [ ],
|
||||
},
|
||||
{
|
||||
name: 'single parameter: returns expected for single usage',
|
||||
code: '{{ $single }}',
|
||||
expected: [ 'single' ],
|
||||
},
|
||||
{
|
||||
name: 'single parameter: returns distinct values for repeating parameters',
|
||||
code: '{{ $singleRepeating }}, {{ $singleRepeating }}',
|
||||
expected: [ 'singleRepeating' ],
|
||||
},
|
||||
{
|
||||
name: 'multiple parameters: returns expected for single usage of each',
|
||||
code: '{{ $firstParameter }}, {{ $secondParameter }}',
|
||||
expected: [ 'firstParameter', 'secondParameter' ],
|
||||
},
|
||||
{
|
||||
name: 'multiple parameters: returns distinct values for repeating parameters',
|
||||
code: '{{ $firstParameter }}, {{ $firstParameter }}, {{ $firstParameter }} {{ $secondParameter }}, {{ $secondParameter }}',
|
||||
expected: [ 'firstParameter', 'secondParameter' ],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const sut = generateIlCode(testCase.code);
|
||||
const actual = sut.getUniqueParameterNames();
|
||||
// assert
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('substituteParameter', () => {
|
||||
describe('substitutes by ignoring white spaces inside mustaches', () => {
|
||||
// arrange
|
||||
const mustacheVariations = [
|
||||
'Hello {{ $test }}!',
|
||||
'Hello {{$test }}!',
|
||||
'Hello {{ $test}}!',
|
||||
'Hello {{$test}}!'];
|
||||
mustacheVariations.forEach((variation) => {
|
||||
it(variation, () => {
|
||||
// arrange
|
||||
const ilCode = generateIlCode(variation);
|
||||
const expected = 'Hello world!';
|
||||
// act
|
||||
const actual = ilCode
|
||||
.substituteParameter('test', 'world')
|
||||
.compile();
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('substitutes as expected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'single parameter',
|
||||
code: 'Hello {{ $firstParameter }}!',
|
||||
expected: 'Hello world!',
|
||||
parameters: {
|
||||
firstParameter: 'world',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'single parameter repeated',
|
||||
code: '{{ $firstParameter }} {{ $firstParameter }}!',
|
||||
expected: 'hello hello!',
|
||||
parameters: {
|
||||
firstParameter: 'hello',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'multiple parameters',
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
expected: 'Hello world!',
|
||||
parameters: {
|
||||
firstParameter: 'llo',
|
||||
secondParameter: 'world',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'multiple parameters repeated',
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }} and He{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
expected: 'Hello world and Hello world!',
|
||||
parameters: {
|
||||
firstParameter: 'llo',
|
||||
secondParameter: 'world',
|
||||
},
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
let ilCode = generateIlCode(testCase.code);
|
||||
for (const parameterName of Object.keys(testCase.parameters)) {
|
||||
const value = testCase.parameters[parameterName];
|
||||
ilCode = ilCode.substituteParameter(parameterName, value);
|
||||
}
|
||||
const actual = ilCode.compile();
|
||||
// assert
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('compile', () => {
|
||||
it('throws if there are expressions left', () => {
|
||||
// arrange
|
||||
const expectedError = 'unknown expression: "each"';
|
||||
const code = '{{ each }}';
|
||||
// act
|
||||
const ilCode = generateIlCode(code);
|
||||
const act = () => ilCode.compile();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns code as it is if there are no expressions', () => {
|
||||
// arrange
|
||||
const expected = 'I should be the same!';
|
||||
const ilCode = generateIlCode(expected);
|
||||
// act
|
||||
const actual = ilCode.compile();
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -215,30 +215,6 @@ describe('ScriptCompiler', () => {
|
||||
});
|
||||
});
|
||||
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
|
||||
@@ -255,15 +231,15 @@ describe('ScriptCompiler', () => {
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('with same parameter repeated', () => {
|
||||
it('with single parameter', () => {
|
||||
// arrange
|
||||
const env = new TestEnvironment({
|
||||
code: '{{ $parameter }} {{ $parameter }}!',
|
||||
code: '{{ $parameter }}!',
|
||||
parameters: {
|
||||
parameter: 'Hodor',
|
||||
},
|
||||
});
|
||||
const expected = env.expect('Hodor Hodor!');
|
||||
const expected = env.expect('Hodor!');
|
||||
// act
|
||||
const actual = env.sut.compile(env.script);
|
||||
// assert
|
||||
@@ -295,20 +271,6 @@ describe('ScriptCompiler', () => {
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
|
||||
describe('ProjectInformationParser', () => {
|
||||
describe('parseProjectInformation', () => {
|
||||
it('parses expected repository version', () => {
|
||||
// arrange
|
||||
const expected = 'expected-version';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_VERSION = expected;
|
||||
// act
|
||||
const info = parseProjectInformation(env);
|
||||
// assert
|
||||
expect(info.version).to.be.equal(expected);
|
||||
});
|
||||
it('parses expected repository url', () => {
|
||||
// arrange
|
||||
const expected = 'https://expected-repository.url';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_REPOSITORY_URL = expected;
|
||||
// act
|
||||
const info = parseProjectInformation(env);
|
||||
// assert
|
||||
expect(info.repositoryUrl).to.be.equal(expected);
|
||||
});
|
||||
it('parses expected name', () => {
|
||||
// arrange
|
||||
const expected = 'expected-app-name';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_NAME = expected;
|
||||
// act
|
||||
const info = parseProjectInformation(env);
|
||||
// assert
|
||||
expect(info.name).to.be.equal(expected);
|
||||
});
|
||||
it('parses expected homepage url', () => {
|
||||
// arrange
|
||||
const expected = 'https://expected.sexy';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_HOMEPAGE_URL = expected;
|
||||
// act
|
||||
const info = parseProjectInformation(env);
|
||||
// assert
|
||||
expect(info.homepage).to.be.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function getProcessEnvironmentStub(): NodeJS.ProcessEnv {
|
||||
return {
|
||||
VUE_APP_VERSION: 'stub-version',
|
||||
VUE_APP_NAME: 'stub-name',
|
||||
VUE_APP_REPOSITORY_URL: 'stub-repository-url',
|
||||
VUE_APP_HOMEPAGE_URL: 'stub-homepage-url',
|
||||
};
|
||||
}
|
||||
@@ -2,10 +2,11 @@ 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 { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { ScriptCode } from '@/domain/ScriptCode';
|
||||
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
|
||||
import { YamlScriptStub } from '../../stubs/YamlScriptStub';
|
||||
import { mockEnumParser } from '../../stubs/EnumParserStub';
|
||||
|
||||
describe('ScriptParser', () => {
|
||||
describe('parseScript', () => {
|
||||
@@ -84,63 +85,27 @@ describe('ScriptParser', () => {
|
||||
undefinedLevels.forEach((undefinedLevel) => {
|
||||
// arrange
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
script.recommend = undefinedLevel;
|
||||
const script = YamlScriptStub.createWithCode()
|
||||
.withRecommend(undefinedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, compiler);
|
||||
// assert
|
||||
expect(actual.level).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
it('throws on unknown level', () => {
|
||||
// arrange
|
||||
const unknownLevel = 'boi';
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
script.recommend = unknownLevel;
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
// assert
|
||||
expect(act).to.throw(`unknown level: "${unknownLevel}"`);
|
||||
});
|
||||
it('throws on non-string type', () => {
|
||||
const nonStringTypes: any[] = [ 5, true ];
|
||||
nonStringTypes.forEach((nonStringType) => {
|
||||
// arrange
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub();
|
||||
script.recommend = nonStringType;
|
||||
// act
|
||||
const act = () => parseScript(script, compiler);
|
||||
// assert
|
||||
expect(act).to.throw(`level must be a string but it was ${typeof nonStringType}`);
|
||||
});
|
||||
});
|
||||
describe('parses level as expected', () => {
|
||||
for (const levelText of RecommendationLevelNames) {
|
||||
it(levelText, () => {
|
||||
// arrange
|
||||
const expectedLevel = RecommendationLevel[levelText];
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub();
|
||||
script.recommend = levelText;
|
||||
// act
|
||||
const actual = parseScript(script, compiler);
|
||||
// assert
|
||||
expect(actual.level).to.equal(expectedLevel);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('parses level case insensitive', () => {
|
||||
// arrange
|
||||
const script = YamlScriptStub.createWithCode();
|
||||
const expectedLevel = RecommendationLevel.Standard;
|
||||
const expectedName = 'level';
|
||||
const levelText = 'standard';
|
||||
const script = YamlScriptStub.createWithCode()
|
||||
.withRecommend(levelText);
|
||||
const compiler = new ScriptCompilerStub();
|
||||
const expected = RecommendationLevel.Standard;
|
||||
script.recommend = RecommendationLevel[expected].toUpperCase();
|
||||
const parserMock = mockEnumParser(expectedName, levelText, expectedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, compiler);
|
||||
const actual = parseScript(script, compiler, parserMock);
|
||||
// assert
|
||||
expect(actual.level).to.equal(expected);
|
||||
expect(actual.level).to.equal(expectedLevel);
|
||||
});
|
||||
});
|
||||
describe('code', () => {
|
||||
@@ -196,3 +161,4 @@ describe('ScriptParser', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
150
tests/unit/application/Parser/ScriptingDefinitionParser.spec.ts
Normal file
150
tests/unit/application/Parser/ScriptingDefinitionParser.spec.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { YamlScriptingDefinition } from 'js-yaml-loader!./application.yaml';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { parseScriptingDefinition } from '@/application/Parser/ScriptingDefinitionParser';
|
||||
import { ProjectInformationStub } from './../../stubs/ProjectInformationStub';
|
||||
import { mockEnumParser } from '../../stubs/EnumParserStub';
|
||||
|
||||
describe('ScriptingDefinitionParser', () => {
|
||||
describe('parseScriptingDefinition', () => {
|
||||
it('throws when info is undefined', () => {
|
||||
// arrange
|
||||
const info = undefined;
|
||||
const definition = new ScriptingDefinitionBuilder().construct();
|
||||
// act
|
||||
const act = () => parseScriptingDefinition(definition, info);
|
||||
// assert
|
||||
expect(act).to.throw('undefined info');
|
||||
});
|
||||
it('throws when definition is undefined', () => {
|
||||
// arrange
|
||||
const info = new ProjectInformationStub();
|
||||
const definition = undefined;
|
||||
// act
|
||||
const act = () => parseScriptingDefinition(definition, info);
|
||||
// assert
|
||||
expect(act).to.throw('undefined definition');
|
||||
});
|
||||
describe('language', () => {
|
||||
it('parses as expected', () => {
|
||||
// arrange
|
||||
const expectedLanguage = ScriptingLanguage.batchfile;
|
||||
const languageText = 'batchfile';
|
||||
const expectedName = 'language';
|
||||
const info = new ProjectInformationStub();
|
||||
const definition = new ScriptingDefinitionBuilder()
|
||||
.withLanguage(languageText).construct();
|
||||
const parserMock = mockEnumParser(expectedName, languageText, expectedLanguage);
|
||||
// act
|
||||
const actual = parseScriptingDefinition(definition, info, new Date(), parserMock);
|
||||
// assert
|
||||
expect(actual.language).to.equal(expectedLanguage);
|
||||
});
|
||||
});
|
||||
describe('fileExtension', () => {
|
||||
it('parses as expected', () => {
|
||||
// arrange
|
||||
const expected = 'bat';
|
||||
const info = new ProjectInformationStub();
|
||||
const file = new ScriptingDefinitionBuilder()
|
||||
.withExtension(expected).construct();
|
||||
// act
|
||||
const definition = parseScriptingDefinition(file, info);
|
||||
// assert
|
||||
const actual = definition.fileExtension;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('startCode', () => {
|
||||
it('sets as it is', () => {
|
||||
// arrange
|
||||
const expected = 'expected-start-code';
|
||||
const info = new ProjectInformationStub();
|
||||
const file = new ScriptingDefinitionBuilder().withStartCode(expected).construct();
|
||||
// act
|
||||
const definition = parseScriptingDefinition(file, info);
|
||||
// assert
|
||||
expect(definition.startCode).to.equal(expected);
|
||||
});
|
||||
it('substitutes as expected', () => {
|
||||
// arrange
|
||||
const code = 'homepage: {{ $homepage }}, version: {{ $version }}, date: {{ $date }}';
|
||||
const homepage = 'https://cloudarchitecture.io';
|
||||
const version = '1.0.2';
|
||||
const date = new Date();
|
||||
const expected = `homepage: ${homepage}, version: ${version}, date: ${date.toUTCString()}`;
|
||||
const info = new ProjectInformationStub().withHomepageUrl(homepage).withVersion(version);
|
||||
const file = new ScriptingDefinitionBuilder().withStartCode(code).construct();
|
||||
// act
|
||||
const definition = parseScriptingDefinition(file, info, date);
|
||||
// assert
|
||||
const actual = definition.startCode;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('endCode', () => {
|
||||
it('sets as it is', () => {
|
||||
// arrange
|
||||
const expected = 'expected-end-code';
|
||||
const info = new ProjectInformationStub();
|
||||
const file = new ScriptingDefinitionBuilder().withEndCode(expected).construct();
|
||||
// act
|
||||
const definition = parseScriptingDefinition(file, info);
|
||||
// assert
|
||||
expect(definition.endCode).to.equal(expected);
|
||||
});
|
||||
it('substitutes as expected', () => {
|
||||
// arrange
|
||||
const code = 'homepage: {{ $homepage }}, version: {{ $version }}, date: {{ $date }}';
|
||||
const homepage = 'https://cloudarchitecture.io';
|
||||
const version = '1.0.2';
|
||||
const date = new Date();
|
||||
const expected = `homepage: ${homepage}, version: ${version}, date: ${date.toUTCString()}`;
|
||||
const info = new ProjectInformationStub().withHomepageUrl(homepage).withVersion(version);
|
||||
const file = new ScriptingDefinitionBuilder().withEndCode(code).construct();
|
||||
// act
|
||||
const definition = parseScriptingDefinition(file, info, date);
|
||||
// assert
|
||||
const actual = definition.endCode;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ScriptingDefinitionBuilder {
|
||||
private language = ScriptingLanguage[ScriptingLanguage.batchfile];
|
||||
private fileExtension = 'bat';
|
||||
private startCode = 'startCode';
|
||||
private endCode = 'endCode';
|
||||
|
||||
public withLanguage(language: string): ScriptingDefinitionBuilder {
|
||||
this.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withStartCode(startCode: string): ScriptingDefinitionBuilder {
|
||||
this.startCode = startCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withEndCode(endCode: string): ScriptingDefinitionBuilder {
|
||||
this.endCode = endCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withExtension(extension: string): ScriptingDefinitionBuilder {
|
||||
this.fileExtension = extension;
|
||||
return this;
|
||||
}
|
||||
|
||||
public construct(): YamlScriptingDefinition {
|
||||
return {
|
||||
language: this.language,
|
||||
fileExtension: this.fileExtension,
|
||||
startCode: this.startCode,
|
||||
endCode: this.endCode,
|
||||
};
|
||||
}
|
||||
}
|
||||
84
tests/unit/application/State/ApplicationState.spec.ts
Normal file
84
tests/unit/application/State/ApplicationState.spec.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { UserSelection } from '@/application/State/Selection/UserSelection';
|
||||
import { ApplicationCode } from '@/application/State/Code/ApplicationCode';
|
||||
import { ScriptStub } from './../../stubs/ScriptStub';
|
||||
import { CategoryStub } from './../../stubs/CategoryStub';
|
||||
import { ApplicationStub } from './../../stubs/ApplicationStub';
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ApplicationState } from '@/application/State/ApplicationState';
|
||||
import { IScript } from '@/domain/IScript';
|
||||
|
||||
describe('ApplicationState', () => {
|
||||
describe('code', () => {
|
||||
it('initialized with empty code', () => {
|
||||
// arrange
|
||||
const app = new ApplicationStub();
|
||||
const sut = new ApplicationState(app);
|
||||
// act
|
||||
const code = sut.code.current;
|
||||
// assert
|
||||
expect(!code);
|
||||
});
|
||||
it('reacts to selection changes as expected', () => {
|
||||
// arrange
|
||||
const app = new ApplicationStub().withAction(new CategoryStub(0).withScriptIds('scriptId'));
|
||||
const selectionStub = new UserSelection(app, []);
|
||||
const expectedCodeGenerator = new ApplicationCode(selectionStub, app.scripting);
|
||||
selectionStub.selectAll();
|
||||
const expectedCode = expectedCodeGenerator.current;
|
||||
// act
|
||||
const sut = new ApplicationState(app);
|
||||
sut.selection.selectAll();
|
||||
const actualCode = sut.code.current;
|
||||
// assert
|
||||
expect(actualCode).to.equal(expectedCode);
|
||||
});
|
||||
});
|
||||
describe('selection', () => {
|
||||
it('initialized with no selection', () => {
|
||||
// arrange
|
||||
const app = new ApplicationStub();
|
||||
const sut = new ApplicationState(app);
|
||||
// act
|
||||
const actual = sut.selection.totalSelected;
|
||||
// assert
|
||||
expect(actual).to.equal(0);
|
||||
});
|
||||
it('can select a script from current application', () => {
|
||||
// arrange
|
||||
const expectedScript = new ScriptStub('scriptId');
|
||||
const app = new ApplicationStub().withAction(new CategoryStub(0).withScript(expectedScript));
|
||||
const sut = new ApplicationState(app);
|
||||
// act
|
||||
sut.selection.selectAll();
|
||||
// assert
|
||||
expect(sut.selection.totalSelected).to.equal(1);
|
||||
expect(sut.selection.isSelected(expectedScript.id)).to.equal(true);
|
||||
});
|
||||
});
|
||||
describe('filter', () => {
|
||||
it('initialized with an empty filter', () => {
|
||||
// arrange
|
||||
const app = new ApplicationStub();
|
||||
const sut = new ApplicationState(app);
|
||||
// act
|
||||
const actual = sut.filter.currentFilter;
|
||||
// assert
|
||||
expect(actual).to.equal(undefined);
|
||||
});
|
||||
it('can match a script from current application', () => {
|
||||
// arrange
|
||||
const scriptNameFilter = 'scriptName';
|
||||
const expectedScript = new ScriptStub('scriptId').withName(scriptNameFilter);
|
||||
const app = new ApplicationStub()
|
||||
.withAction(new CategoryStub(0).withScript(expectedScript));
|
||||
const sut = new ApplicationState(app);
|
||||
// act
|
||||
let actualScript: IScript;
|
||||
sut.filter.filtered.on((result) => actualScript = result.scriptMatches[0]);
|
||||
sut.filter.setFilter(scriptNameFilter);
|
||||
// assert
|
||||
expect(expectedScript).to.equal(actualScript);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,29 +10,41 @@ import { ICodeChangedEvent } from '@/application/State/Code/Event/ICodeChangedEv
|
||||
import { IUserScriptGenerator } from '@/application/State/Code/Generation/IUserScriptGenerator';
|
||||
import { CodePosition } from '@/application/State/Code/Position/CodePosition';
|
||||
import { ICodePosition } from '@/application/State/Code/Position/ICodePosition';
|
||||
import { ScriptingDefinitionStub } from './../../../stubs/ScriptingDefinitionStub';
|
||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
import { IUserScript } from '@/application/State/Code/Generation/IUserScript';
|
||||
|
||||
// TODO: Test scriptingDefinition: IScriptingDefinition logic
|
||||
|
||||
describe('ApplicationCode', () => {
|
||||
describe('ctor', () => {
|
||||
it('empty when selection is empty', () => {
|
||||
// arrange
|
||||
const selection = new UserSelection(new ApplicationStub(), []);
|
||||
const sut = new ApplicationCode(selection, 'version');
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
const sut = new ApplicationCode(selection, definition);
|
||||
// act
|
||||
const actual = sut.current;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(0);
|
||||
});
|
||||
it('has code when selection is not empty', () => {
|
||||
it('generates code from script generator when selection is not empty', () => {
|
||||
// arrange
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const selection = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
|
||||
const version = 'version-string';
|
||||
const sut = new ApplicationCode(selection, version);
|
||||
const selection = new UserSelection(app, scripts.map((script) => script.toSelectedScript()));
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
const expected: IUserScript = {
|
||||
code: 'expected-code',
|
||||
scriptPositions: new Map(),
|
||||
};
|
||||
const generator = new UserScriptGeneratorMock()
|
||||
.plan({ scripts: selection.selectedScripts, definition }, expected);
|
||||
const sut = new ApplicationCode(selection, definition, generator);
|
||||
// act
|
||||
const actual = sut.current;
|
||||
// assert
|
||||
expect(actual).to.have.length.greaterThan(0).and.include(version);
|
||||
expect(actual).to.equal(expected.code);
|
||||
});
|
||||
});
|
||||
describe('changed event', () => {
|
||||
@@ -43,7 +55,8 @@ describe('ApplicationCode', () => {
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const selection = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
|
||||
const sut = new ApplicationCode(selection, 'version');
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
const sut = new ApplicationCode(selection, definition);
|
||||
sut.changed.on((code) => signaled = code);
|
||||
// act
|
||||
selection.changed.notify([]);
|
||||
@@ -57,54 +70,123 @@ describe('ApplicationCode', () => {
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const selection = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
|
||||
const version = 'version-string';
|
||||
const sut = new ApplicationCode(selection, version);
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
const sut = new ApplicationCode(selection, definition);
|
||||
sut.changed.on((code) => signaled = code);
|
||||
// act
|
||||
selection.changed.notify(scripts.map((s) => new SelectedScript(s, false)));
|
||||
// assert
|
||||
expect(signaled.code).to.have.length.greaterThan(0).and.include(version);
|
||||
expect(signaled.code).to.have.length.greaterThan(0);
|
||||
expect(signaled.code).to.equal(sut.current);
|
||||
});
|
||||
});
|
||||
it('sets positions from the generator', () => {
|
||||
// arrange
|
||||
let signaled: ICodeChangedEvent;
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const selection = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
|
||||
const expectedVersion = 'version-string';
|
||||
const scriptsToSelect = scripts.map((s) => new SelectedScript(s, false));
|
||||
const totalLines = 20;
|
||||
const expected = new Map<SelectedScript, ICodePosition>(
|
||||
[
|
||||
[ scriptsToSelect[0], new CodePosition(0, totalLines / 2)],
|
||||
[ scriptsToSelect[1], new CodePosition(totalLines / 2, totalLines)],
|
||||
],
|
||||
);
|
||||
const generatorMock: IUserScriptGenerator = {
|
||||
buildCode: (selectedScripts, version) => {
|
||||
if (version !== expectedVersion) {
|
||||
throw new Error('Unexpected version');
|
||||
}
|
||||
if (JSON.stringify(selectedScripts) !== JSON.stringify(scriptsToSelect)) {
|
||||
throw new Error('Unexpected scripts');
|
||||
}
|
||||
return {
|
||||
code: '\nREM LINE'.repeat(totalLines),
|
||||
scriptPositions: expected,
|
||||
};
|
||||
},
|
||||
};
|
||||
const sut = new ApplicationCode(selection, expectedVersion, generatorMock);
|
||||
sut.changed.on((code) => signaled = code);
|
||||
// act
|
||||
selection.changed.notify(scriptsToSelect);
|
||||
// assert
|
||||
expect(signaled.getScriptPositionInCode(scripts[0]))
|
||||
.to.deep.equal(expected.get(scriptsToSelect[0]));
|
||||
expect(signaled.getScriptPositionInCode(scripts[1]))
|
||||
.to.deep.equal(expected.get(scriptsToSelect[1]));
|
||||
describe('calls UserScriptGenerator', () => {
|
||||
it('sends scripting definition to generator', () => {
|
||||
// arrange
|
||||
const expectedDefinition = new ScriptingDefinitionStub();
|
||||
const app = new ApplicationStub();
|
||||
const selection = new UserSelection(app, []);
|
||||
const generatorMock: IUserScriptGenerator = {
|
||||
buildCode: (selectedScripts, definition) => {
|
||||
if (definition !== expectedDefinition) {
|
||||
throw new Error('Unexpected scripting definition');
|
||||
}
|
||||
return {
|
||||
code: '',
|
||||
scriptPositions: new Map<SelectedScript, ICodePosition>(),
|
||||
};
|
||||
},
|
||||
};
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
new ApplicationCode(selection, expectedDefinition, generatorMock);
|
||||
// act
|
||||
const act = () => selection.changed.notify([]);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
it('sends selected scripts to generator', () => {
|
||||
// arrange
|
||||
const expectedDefinition = new ScriptingDefinitionStub();
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const selection = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
|
||||
const scriptsToSelect = scripts.map((s) => new SelectedScript(s, false));
|
||||
const generatorMock: IUserScriptGenerator = {
|
||||
buildCode: (selectedScripts) => {
|
||||
if (JSON.stringify(selectedScripts) !== JSON.stringify(scriptsToSelect)) {
|
||||
throw new Error('Unexpected scripts');
|
||||
}
|
||||
return {
|
||||
code: '',
|
||||
scriptPositions: new Map<SelectedScript, ICodePosition>(),
|
||||
};
|
||||
},
|
||||
};
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
new ApplicationCode(selection, expectedDefinition, generatorMock);
|
||||
// act
|
||||
const act = () => selection.changed.notify(scriptsToSelect);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
it('sets positions from the generator', () => {
|
||||
// arrange
|
||||
let signaled: ICodeChangedEvent;
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const selection = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
|
||||
const scriptingDefinition = new ScriptingDefinitionStub();
|
||||
const scriptsToSelect = scripts.map((s) => new SelectedScript(s, false));
|
||||
const totalLines = 20;
|
||||
const expected = new Map<SelectedScript, ICodePosition>(
|
||||
[
|
||||
[scriptsToSelect[0], new CodePosition(0, totalLines / 2)],
|
||||
[scriptsToSelect[1], new CodePosition(totalLines / 2, totalLines)],
|
||||
],
|
||||
);
|
||||
const generatorMock: IUserScriptGenerator = {
|
||||
buildCode: () => {
|
||||
return {
|
||||
code: '\nREM LINE'.repeat(totalLines),
|
||||
scriptPositions: expected,
|
||||
};
|
||||
},
|
||||
};
|
||||
const sut = new ApplicationCode(selection, scriptingDefinition, generatorMock);
|
||||
sut.changed.on((code) => signaled = code);
|
||||
// act
|
||||
selection.changed.notify(scriptsToSelect);
|
||||
// assert
|
||||
expect(signaled.getScriptPositionInCode(scripts[0]))
|
||||
.to.deep.equal(expected.get(scriptsToSelect[0]));
|
||||
expect(signaled.getScriptPositionInCode(scripts[1]))
|
||||
.to.deep.equal(expected.get(scriptsToSelect[1]));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
interface IScriptGenerationParameters {
|
||||
scripts: readonly SelectedScript[];
|
||||
definition: IScriptingDefinition;
|
||||
}
|
||||
class UserScriptGeneratorMock implements IUserScriptGenerator {
|
||||
private prePlanned = new Map<IScriptGenerationParameters, IUserScript>();
|
||||
public plan(
|
||||
parameters: IScriptGenerationParameters,
|
||||
result: IUserScript): UserScriptGeneratorMock {
|
||||
this.prePlanned.set(parameters, result);
|
||||
return this;
|
||||
}
|
||||
public buildCode(
|
||||
selectedScripts: readonly SelectedScript[],
|
||||
scriptingDefinition: IScriptingDefinition): IUserScript {
|
||||
for (const [parameters, result] of Array.from(this.prePlanned)) {
|
||||
if (selectedScripts === parameters.scripts
|
||||
&& scriptingDefinition === parameters.definition) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw new Error('Unexpected parameters');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,98 @@
|
||||
import { ScriptStub } from '../../../../stubs/ScriptStub';
|
||||
import { UserScriptGenerator, adminRightsScript } from '@/application/State/Code/Generation/UserScriptGenerator';
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { UserScriptGenerator } from '@/application/State/Code/Generation/UserScriptGenerator';
|
||||
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
|
||||
import { SelectedScriptStub } from '../../../../stubs/SelectedScriptStub';
|
||||
import { CodeBuilder } from '@/application/State/Code/Generation/CodeBuilder';
|
||||
import { ScriptStub } from '../../../../stubs/ScriptStub';
|
||||
import { ScriptingDefinitionStub } from '../../../../stubs/ScriptingDefinitionStub';
|
||||
|
||||
describe('UserScriptGenerator', () => {
|
||||
it('adds version', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const version = '1.5.0';
|
||||
const selectedScripts = [ new SelectedScript(new ScriptStub('id'), false)];
|
||||
// act
|
||||
const actual = sut.buildCode(selectedScripts, version);
|
||||
// assert
|
||||
expect(actual.code).to.include(version);
|
||||
});
|
||||
it('adds admin rights function', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const selectedScripts = [ new SelectedScript(new ScriptStub('id'), false)];
|
||||
// act
|
||||
const actual = sut.buildCode(selectedScripts, 'non-important-version');
|
||||
// assert
|
||||
expect(actual.code).to.include(adminRightsScript.code);
|
||||
expect(actual.code).to.include(adminRightsScript.name);
|
||||
describe('scriptingDefinition', () => {
|
||||
describe('startCode', () => {
|
||||
it('is prepended if not empty', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const startCode = 'Start\nCode';
|
||||
const script = new ScriptStub('id')
|
||||
.withCode('code\nmulti-lined')
|
||||
.toSelectedScript();
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withStartCode(startCode)
|
||||
.withEndCode(undefined);
|
||||
const expectedStart = `${startCode}\n`;
|
||||
// act
|
||||
const code = sut.buildCode([script], definition);
|
||||
// assert
|
||||
const actual = code.code;
|
||||
expect(actual.startsWith(expectedStart));
|
||||
});
|
||||
it('is not prepended if empty', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const script = new ScriptStub('id')
|
||||
.withCode('code\nmulti-lined')
|
||||
.toSelectedScript();
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withStartCode(undefined)
|
||||
.withEndCode(undefined);
|
||||
const expectedStart = new CodeBuilder()
|
||||
.appendFunction(script.script.name, script.script.code.execute)
|
||||
.toString();
|
||||
// act
|
||||
const code = sut.buildCode([script], definition);
|
||||
// assert
|
||||
const actual = code.code;
|
||||
expect(actual.startsWith(expectedStart));
|
||||
});
|
||||
});
|
||||
describe('endCode', () => {
|
||||
it('is appended if not empty', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const endCode = 'End\nCode';
|
||||
const script = new ScriptStub('id')
|
||||
.withCode('code\nmulti-lined')
|
||||
.toSelectedScript();
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withEndCode(endCode);
|
||||
const expectedEnd = `${endCode}\n`;
|
||||
// act
|
||||
const code = sut.buildCode([script], definition);
|
||||
// assert
|
||||
const actual = code.code;
|
||||
expect(actual.endsWith(expectedEnd));
|
||||
});
|
||||
it('is not appended if empty', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const script = new ScriptStub('id')
|
||||
.withCode('code\nmulti-lined')
|
||||
.toSelectedScript();
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withEndCode(undefined);
|
||||
const expectedEnd = new CodeBuilder()
|
||||
.appendFunction(script.script.name, script.script.code.execute)
|
||||
.toString();
|
||||
// act
|
||||
const code = sut.buildCode([script], definition);
|
||||
// assert
|
||||
const actual = code.code;
|
||||
expect(actual.endsWith(expectedEnd));
|
||||
});
|
||||
});
|
||||
});
|
||||
it('appends revert script', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const scriptName = 'test non-revert script';
|
||||
const scriptCode = 'REM nop';
|
||||
const script = new ScriptStub('id').withName(scriptName).withRevertCode(scriptCode);
|
||||
const selectedScripts = [ new SelectedScript(script, true)];
|
||||
const script = new ScriptStub('id')
|
||||
.withName(scriptName)
|
||||
.withRevertCode(scriptCode)
|
||||
.toSelectedScript(true);
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
// act
|
||||
const actual = sut.buildCode(selectedScripts, 'non-important-version');
|
||||
const actual = sut.buildCode([ script ], definition);
|
||||
// assert
|
||||
expect(actual.code).to.include(`${scriptName} (revert)`);
|
||||
expect(actual.code).to.include(scriptCode);
|
||||
@@ -46,49 +104,98 @@ describe('UserScriptGenerator', () => {
|
||||
const scriptCode = 'REM nop';
|
||||
const script = new ScriptStub('id').withName(scriptName).withCode(scriptCode);
|
||||
const selectedScripts = [ new SelectedScript(script, false)];
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
// act
|
||||
const actual = sut.buildCode(selectedScripts, 'non-important-version');
|
||||
const actual = sut.buildCode(selectedScripts, definition);
|
||||
// assert
|
||||
expect(actual.code).to.include(scriptName);
|
||||
expect(actual.code).to.not.include(`${scriptName} (revert)`);
|
||||
expect(actual.code).to.include(scriptCode);
|
||||
});
|
||||
describe('scriptPositions', () => {
|
||||
it('single script', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const scriptName = 'test non-revert script';
|
||||
const scriptCode = 'REM nop\nREM nop2';
|
||||
const script = new ScriptStub('id').withName(scriptName).withCode(scriptCode);
|
||||
const selectedScripts = [ new SelectedScript(script, false)];
|
||||
// act
|
||||
const actual = sut.buildCode(selectedScripts, 'non-important-version');
|
||||
// assert
|
||||
expect(actual.scriptPositions.size).to.equal(1);
|
||||
const position = actual.scriptPositions.get(selectedScripts[0]);
|
||||
expect(position.endLine).to.be.greaterThan(position.startLine + 2);
|
||||
});
|
||||
it('multiple scripts', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const selectedScripts = [ new SelectedScriptStub('1'), new SelectedScriptStub('2') ];
|
||||
// act
|
||||
const actual = sut.buildCode(selectedScripts, 'non-important-version');
|
||||
// assert
|
||||
const firstPosition = actual.scriptPositions.get(selectedScripts[0]);
|
||||
const secondPosition = actual.scriptPositions.get(selectedScripts[1]);
|
||||
expect(actual.scriptPositions.size).to.equal(2);
|
||||
expect(firstPosition.endLine).to.be.greaterThan(firstPosition.startLine + 1);
|
||||
expect(secondPosition.startLine).to.be.greaterThan(firstPosition.endLine);
|
||||
expect(secondPosition.endLine).to.be.greaterThan(secondPosition.startLine + 1);
|
||||
});
|
||||
it('no script', () => {
|
||||
it('without script; returns empty', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const selectedScripts = [ ];
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
// act
|
||||
const actual = sut.buildCode(selectedScripts, 'non-important-version');
|
||||
const actual = sut.buildCode(selectedScripts, definition);
|
||||
// assert
|
||||
expect(actual.scriptPositions.size).to.equal(0);
|
||||
});
|
||||
describe('with scripts', () => {
|
||||
// arrange
|
||||
const totalStartCodeLines = 2;
|
||||
const totalFunctionNameLines = 4;
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withStartCode('First line\nSecond line');
|
||||
describe('single script', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'single-lined',
|
||||
scriptCode: 'only line',
|
||||
codeLines: 1,
|
||||
},
|
||||
{
|
||||
name: 'multi-lined',
|
||||
scriptCode: 'first line\nsecond line',
|
||||
codeLines: 2,
|
||||
},
|
||||
];
|
||||
const sut = new UserScriptGenerator();
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const expectedStartLine = totalStartCodeLines
|
||||
+ 1 // empty line code begin
|
||||
+ 1; // code begin
|
||||
const expectedEndLine = expectedStartLine
|
||||
+ totalFunctionNameLines
|
||||
+ testCase.codeLines;
|
||||
const selectedScript = new ScriptStub(`script-id`)
|
||||
.withName(`script`)
|
||||
.withCode(testCase.scriptCode)
|
||||
.toSelectedScript(false);
|
||||
// act
|
||||
const actual = sut.buildCode([ selectedScript ], definition);
|
||||
// expect
|
||||
expect(1).to.equal(actual.scriptPositions.size);
|
||||
const position = actual.scriptPositions.get(selectedScript);
|
||||
expect(expectedStartLine).to.equal(position.startLine, 'Unexpected start line position');
|
||||
expect(expectedEndLine).to.equal(position.endLine, 'Unexpected end line position');
|
||||
});
|
||||
}
|
||||
});
|
||||
it('multiple scripts', () => {
|
||||
const sut = new UserScriptGenerator();
|
||||
const selectedScripts = [
|
||||
new ScriptStub('1').withCode('only line'),
|
||||
new ScriptStub('2').withCode('first line\nsecond line'),
|
||||
].map((s) => s.toSelectedScript());
|
||||
const expectedFirstScriptStart = totalStartCodeLines
|
||||
+ 1 // empty line code begin
|
||||
+ 1; // code begin
|
||||
const expectedFirstScriptEnd = expectedFirstScriptStart
|
||||
+ totalFunctionNameLines
|
||||
+ 1; // total code lines
|
||||
const expectedSecondScriptStart = expectedFirstScriptEnd
|
||||
+ 1 // code end hyphens
|
||||
+ 1 // new line
|
||||
+ 1; // code begin
|
||||
const expectedSecondScriptEnd =
|
||||
expectedSecondScriptStart
|
||||
+ totalFunctionNameLines
|
||||
+ 2; // total lines of second script
|
||||
// act
|
||||
const actual = sut.buildCode(selectedScripts, definition);
|
||||
// assert
|
||||
const firstPosition = actual.scriptPositions.get(selectedScripts[0]);
|
||||
const secondPosition = actual.scriptPositions.get(selectedScripts[1]);
|
||||
expect(actual.scriptPositions.size).to.equal(2);
|
||||
expect(expectedFirstScriptStart).to.equal(firstPosition.startLine, 'Unexpected start line position (first script)');
|
||||
expect(expectedFirstScriptEnd).to.equal(firstPosition.endLine, 'Unexpected end line position (first script)');
|
||||
expect(expectedSecondScriptStart).to.equal(secondPosition.startLine, 'Unexpected start line position (second script)');
|
||||
expect(expectedSecondScriptEnd).to.equal(secondPosition.endLine, 'Unexpected end line position (second script)');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,21 +5,27 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { RecommendationLevel, RecommendationLevels } from '@/domain/RecommendationLevel';
|
||||
import { ICategory } from '@/domain/IApplication';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { getEnumValues } from '@/application/Common/Enum';
|
||||
|
||||
describe('Application', () => {
|
||||
describe('getScriptsByLevel', () => {
|
||||
it('filters out scripts without levels', () => {
|
||||
// arrange
|
||||
const scriptsWithLevels = RecommendationLevels.map((level, index) =>
|
||||
const recommendationLevels = getEnumValues(RecommendationLevel);
|
||||
const scriptsWithLevels = recommendationLevels.map((level, index) =>
|
||||
new ScriptStub(`Script${index}`).withLevel(level),
|
||||
);
|
||||
const toIgnore = new ScriptStub('script-to-ignore').withLevel(undefined);
|
||||
for (const currentLevel of RecommendationLevels) {
|
||||
for (const currentLevel of recommendationLevels) {
|
||||
const category = new CategoryStub(0)
|
||||
.withScripts(...scriptsWithLevels)
|
||||
.withScript(toIgnore);
|
||||
const sut = new Application(createInformation(), [category]);
|
||||
const sut = new ApplicationBuilder().withActions([category]).construct();
|
||||
// act
|
||||
const actual = sut.getScriptsByLevel(currentLevel);
|
||||
// assert
|
||||
@@ -33,10 +39,11 @@ describe('Application', () => {
|
||||
new ScriptStub('S1').withLevel(level),
|
||||
new ScriptStub('S2').withLevel(level),
|
||||
];
|
||||
const sut = new Application(createInformation(), [
|
||||
const actions = [
|
||||
new CategoryStub(3).withScripts(...expected,
|
||||
new ScriptStub('S3').withLevel(RecommendationLevel.Strict)),
|
||||
]);
|
||||
];
|
||||
const sut = new ApplicationBuilder().withActions(actions).construct();
|
||||
// act
|
||||
const actual = sut.getScriptsByLevel(level);
|
||||
// assert
|
||||
@@ -49,9 +56,10 @@ describe('Application', () => {
|
||||
new ScriptStub('S1').withLevel(RecommendationLevel.Standard),
|
||||
new ScriptStub('S2').withLevel(RecommendationLevel.Strict),
|
||||
];
|
||||
const sut = new Application(createInformation(), [
|
||||
const actions = [
|
||||
new CategoryStub(3).withScripts(...expected),
|
||||
]);
|
||||
];
|
||||
const sut = new ApplicationBuilder().withActions(actions).construct();
|
||||
// act
|
||||
const actual = sut.getScriptsByLevel(level);
|
||||
// assert
|
||||
@@ -59,7 +67,7 @@ describe('Application', () => {
|
||||
});
|
||||
it('throws when level is undefined', () => {
|
||||
// arrange
|
||||
const sut = new Application(createInformation(), [ getCategoryForValidApplication() ]);
|
||||
const sut = new ApplicationBuilder().construct();
|
||||
// act
|
||||
const act = () => sut.getScriptsByLevel(undefined);
|
||||
// assert
|
||||
@@ -68,21 +76,21 @@ describe('Application', () => {
|
||||
it('throws when level is out of range', () => {
|
||||
// arrange
|
||||
const invalidValue = 66;
|
||||
const sut = new Application(createInformation(), [
|
||||
getCategoryForValidApplication(),
|
||||
]);
|
||||
const sut = new ApplicationBuilder().construct();
|
||||
// act
|
||||
const act = () => sut.getScriptsByLevel(invalidValue);
|
||||
// assert
|
||||
expect(act).to.throw(`invalid level: ${invalidValue}`);
|
||||
});
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('cannot construct without categories', () => {
|
||||
describe('actions', () => {
|
||||
it('cannot construct without actions', () => {
|
||||
// arrange
|
||||
const categories = [];
|
||||
// act
|
||||
function construct() { return new Application(createInformation(), categories); }
|
||||
function construct() {
|
||||
new ApplicationBuilder().withActions(categories).construct();
|
||||
}
|
||||
// assert
|
||||
expect(construct).to.throw('Application must consist of at least one category');
|
||||
});
|
||||
@@ -93,33 +101,31 @@ describe('Application', () => {
|
||||
new CategoryStub(2),
|
||||
];
|
||||
// act
|
||||
function construct() { return new Application(createInformation(), categories); }
|
||||
function construct() {
|
||||
new ApplicationBuilder().withActions(categories).construct();
|
||||
}
|
||||
// assert
|
||||
expect(construct).to.throw('Application must consist of at least one script');
|
||||
});
|
||||
describe('cannot construct without any recommended scripts', () => {
|
||||
for (const missingLevel of RecommendationLevels) {
|
||||
// arrange
|
||||
const expectedError = `none of the scripts are recommended as ${RecommendationLevel[missingLevel]}`;
|
||||
const otherLevels = RecommendationLevels.filter((level) => level !== missingLevel);
|
||||
const categories = otherLevels.map((level, index) =>
|
||||
new CategoryStub(index).withScript(new ScriptStub(`Script${index}`).withLevel(level)),
|
||||
);
|
||||
// act
|
||||
const construct = () => new Application(createInformation(), categories);
|
||||
// assert
|
||||
expect(construct).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
it('cannot construct without information', () => {
|
||||
// arrange
|
||||
const categories = [ new CategoryStub(1).withScripts(
|
||||
new ScriptStub('S1').withLevel(RecommendationLevel.Standard))];
|
||||
const information = undefined;
|
||||
// act
|
||||
function construct() { return new Application(information, categories); }
|
||||
// assert
|
||||
expect(construct).to.throw('info is undefined');
|
||||
const recommendationLevels = getEnumValues(RecommendationLevel);
|
||||
for (const missingLevel of recommendationLevels) {
|
||||
it(`when "${RecommendationLevel[missingLevel]}" is missing`, () => {
|
||||
const expectedError = `none of the scripts are recommended as ${RecommendationLevel[missingLevel]}`;
|
||||
const otherLevels = recommendationLevels.filter((level) => level !== missingLevel);
|
||||
const categories = otherLevels.map((level, index) =>
|
||||
new CategoryStub(index).withScript(
|
||||
new ScriptStub(`Script${index}`).withLevel(level),
|
||||
));
|
||||
// act
|
||||
const construct = () => new ApplicationBuilder()
|
||||
.withActions(categories)
|
||||
.construct();
|
||||
// assert
|
||||
expect(construct).to.throw(expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('totalScripts', () => {
|
||||
@@ -135,7 +141,7 @@ describe('Application', () => {
|
||||
new CategoryStub(4).withScripts(new ScriptStub('S4'))),
|
||||
];
|
||||
// act
|
||||
const sut = new Application(createInformation(), categories);
|
||||
const sut = new ApplicationBuilder().withActions(categories).construct();
|
||||
// assert
|
||||
expect(sut.totalScripts).to.equal(4);
|
||||
});
|
||||
@@ -143,36 +149,132 @@ describe('Application', () => {
|
||||
describe('totalCategories', () => {
|
||||
it('returns total of initial categories', () => {
|
||||
// arrange
|
||||
const expected = 4;
|
||||
const categories = [
|
||||
new CategoryStub(1).withScripts(new ScriptStub('S1').withLevel(RecommendationLevel.Strict)),
|
||||
new CategoryStub(2).withScripts(new ScriptStub('S2'), new ScriptStub('S3')),
|
||||
new CategoryStub(3).withCategories(new CategoryStub(4).withScripts(new ScriptStub('S4'))),
|
||||
];
|
||||
// act
|
||||
const sut = new Application(createInformation(), categories);
|
||||
const sut = new ApplicationBuilder()
|
||||
.withActions(categories)
|
||||
.construct();
|
||||
// assert
|
||||
expect(sut.totalCategories).to.equal(4);
|
||||
expect(sut.totalCategories).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('info', () => {
|
||||
it('returns initial information', () => {
|
||||
describe('information', () => {
|
||||
it('sets information as expected', () => {
|
||||
// arrange
|
||||
const expected = createInformation();
|
||||
const expected = new ProjectInformation(
|
||||
'expected-name', 'expected-repo', '0.31.0', 'expected-homepage');
|
||||
// act
|
||||
const sut = new Application(
|
||||
expected, [ getCategoryForValidApplication() ]);
|
||||
const sut = new ApplicationBuilder().withInfo(expected).construct();
|
||||
// assert
|
||||
expect(sut.info).to.deep.equal(expected);
|
||||
});
|
||||
it('cannot construct without information', () => {
|
||||
// arrange
|
||||
const information = undefined;
|
||||
// act
|
||||
function construct() {
|
||||
return new ApplicationBuilder().withInfo(information).construct();
|
||||
}
|
||||
// assert
|
||||
expect(construct).to.throw('undefined info');
|
||||
});
|
||||
});
|
||||
describe('os', () => {
|
||||
it('sets os as expected', () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.macOS;
|
||||
// act
|
||||
const sut = new ApplicationBuilder().withOs(expected).construct();
|
||||
// assert
|
||||
expect(sut.os).to.deep.equal(expected);
|
||||
});
|
||||
it('cannot construct with unknown os', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Unknown;
|
||||
// act
|
||||
const construct = () => new ApplicationBuilder().withOs(os).construct();
|
||||
// assert
|
||||
expect(construct).to.throw('unknown os');
|
||||
});
|
||||
it('cannot construct with undefined os', () => {
|
||||
// arrange
|
||||
const os = undefined;
|
||||
// act
|
||||
const construct = () => new ApplicationBuilder().withOs(os).construct();
|
||||
// assert
|
||||
expect(construct).to.throw('undefined os');
|
||||
});
|
||||
it('cannot construct with OS not in range', () => {
|
||||
// arrange
|
||||
const os: OperatingSystem = 666;
|
||||
// act
|
||||
const construct = () => new ApplicationBuilder().withOs(os).construct();
|
||||
// assert
|
||||
expect(construct).to.throw(`os "${os}" is out of range`);
|
||||
});
|
||||
});
|
||||
describe('scriptingDefinition', () => {
|
||||
it('sets scriptingDefinition as expected', () => {
|
||||
// arrange
|
||||
const expected = getValidScriptingDefinition();
|
||||
// act
|
||||
const sut = new ApplicationBuilder().withScripting(expected).construct();
|
||||
// assert
|
||||
expect(sut.scripting).to.deep.equal(expected);
|
||||
});
|
||||
it('cannot construct without initial script', () => {
|
||||
// arrange
|
||||
const scriptingDefinition = undefined;
|
||||
// act
|
||||
function construct() {
|
||||
return new ApplicationBuilder().withScripting(scriptingDefinition).construct();
|
||||
}
|
||||
// assert
|
||||
expect(construct).to.throw('undefined scripting definition');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getCategoryForValidApplication() {
|
||||
return new CategoryStub(1).withScripts(
|
||||
new ScriptStub('S1').withLevel(RecommendationLevel.Standard),
|
||||
new ScriptStub('S2').withLevel(RecommendationLevel.Strict));
|
||||
function getValidScriptingDefinition(): IScriptingDefinition {
|
||||
return {
|
||||
fileExtension: '.bat',
|
||||
language: ScriptingLanguage.batchfile,
|
||||
startCode: 'start',
|
||||
endCode: 'end',
|
||||
};
|
||||
}
|
||||
|
||||
function createInformation(): IProjectInformation {
|
||||
return new ProjectInformation('name', 'repo', '0.1.0', 'homepage');
|
||||
class ApplicationBuilder {
|
||||
private os = OperatingSystem.Windows;
|
||||
private info = new ProjectInformation('name', 'repo', '0.1.0', 'homepage');
|
||||
private actions: readonly ICategory[] = [
|
||||
new CategoryStub(1).withScripts(
|
||||
new ScriptStub('S1').withLevel(RecommendationLevel.Standard),
|
||||
new ScriptStub('S2').withLevel(RecommendationLevel.Strict)),
|
||||
];
|
||||
private script: IScriptingDefinition = getValidScriptingDefinition();
|
||||
public withOs(os: OperatingSystem): ApplicationBuilder {
|
||||
this.os = os;
|
||||
return this;
|
||||
}
|
||||
public withInfo(info: IProjectInformation) {
|
||||
this.info = info;
|
||||
return this;
|
||||
}
|
||||
public withActions(actions: readonly ICategory[]) {
|
||||
this.actions = actions;
|
||||
return this;
|
||||
}
|
||||
public withScripting(script: IScriptingDefinition) {
|
||||
this.script = script;
|
||||
return this;
|
||||
}
|
||||
public construct(): Application {
|
||||
return new Application(this.os, this.info, this.actions, this.script);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { RecommendationLevelNames, RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
|
||||
describe('RecommendationLevel', () => {
|
||||
describe('RecommendationLevelNames', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
RecommendationLevel[RecommendationLevel.Strict],
|
||||
RecommendationLevel[RecommendationLevel.Standard],
|
||||
];
|
||||
// act
|
||||
const actual = RecommendationLevelNames;
|
||||
// assert
|
||||
expect(actual).to.have.deep.members(expected);
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
import { getEnumValues } from '@/application/Common/Enum';
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { Script } from '@/domain/Script';
|
||||
import { RecommendationLevel, RecommendationLevels } from '@/domain/RecommendationLevel';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { ScriptCode } from '@/domain/ScriptCode';
|
||||
import { IScriptCode } from '@/domain/IScriptCode';
|
||||
|
||||
@@ -80,7 +81,7 @@ describe('Script', () => {
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
for (const expected of RecommendationLevels) {
|
||||
for (const expected of getEnumValues(RecommendationLevel)) {
|
||||
// act
|
||||
const sut = new ScriptBuilder()
|
||||
.withRecommendationLevel(expected)
|
||||
|
||||
134
tests/unit/domain/ScriptingDefinition.spec.ts
Normal file
134
tests/unit/domain/ScriptingDefinition.spec.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ScriptingDefinition } from '@/domain/ScriptingDefinition';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { getEnumValues } from '@/application/Common/Enum';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
describe('ScriptingDefinition', () => {
|
||||
describe('language', () => {
|
||||
describe('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedValues = getEnumValues(ScriptingLanguage);
|
||||
expectedValues.forEach((expected) => {
|
||||
it(ScriptingLanguage[expected], () => {
|
||||
// act
|
||||
const sut = new ScriptingDefinitionBuilder()
|
||||
.withLanguage(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.language).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('throws if unknown', () => {
|
||||
// arrange
|
||||
const unknownValue: ScriptingLanguage = 666;
|
||||
const errorMessage = `unsupported language: ${unknownValue}`;
|
||||
// act
|
||||
const act = () => new ScriptingDefinitionBuilder()
|
||||
.withLanguage(unknownValue)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(errorMessage);
|
||||
});
|
||||
});
|
||||
describe('fileExtension', () => {
|
||||
describe('returns expected for each language', () => {
|
||||
// arrange
|
||||
const testCases = new Map<ScriptingLanguage, string>([
|
||||
[ScriptingLanguage.batchfile, 'bat'],
|
||||
[ScriptingLanguage.bash, 'sh'],
|
||||
]);
|
||||
Array.from(testCases.entries()).forEach((test) => {
|
||||
const language = test[0];
|
||||
const expectedExtension = test[1];
|
||||
it(`${ScriptingLanguage[language]} has ${expectedExtension}`, () => {
|
||||
// act
|
||||
const sut = new ScriptingDefinitionBuilder()
|
||||
.withLanguage(language)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.fileExtension, expectedExtension);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('startCode', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = 'REM start-code';
|
||||
// act
|
||||
const sut = new ScriptingDefinitionBuilder()
|
||||
.withStartCode(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.startCode).to.equal(expected);
|
||||
});
|
||||
it('throws when undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined start code';
|
||||
const undefinedValues = [ '', undefined ];
|
||||
for (const undefinedValue of undefinedValues) {
|
||||
// act
|
||||
const act = () => new ScriptingDefinitionBuilder()
|
||||
.withStartCode(undefinedValue)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('endCode', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = 'REM end-code';
|
||||
// act
|
||||
const sut = new ScriptingDefinitionBuilder()
|
||||
.withEndCode(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.endCode).to.equal(expected);
|
||||
|
||||
});
|
||||
it('throws when undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined end code';
|
||||
const undefinedValues = [ '', undefined ];
|
||||
for (const undefinedValue of undefinedValues) {
|
||||
// act
|
||||
const act = () => new ScriptingDefinitionBuilder()
|
||||
.withEndCode(undefinedValue)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ScriptingDefinitionBuilder {
|
||||
private language = ScriptingLanguage.bash;
|
||||
private startCode = 'REM start-code';
|
||||
private endCode = 'REM end-code';
|
||||
|
||||
public withLanguage(language: ScriptingLanguage): ScriptingDefinitionBuilder {
|
||||
this.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withStartCode(startCode: string): ScriptingDefinitionBuilder {
|
||||
this.startCode = startCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withEndCode(endCode: string): ScriptingDefinitionBuilder {
|
||||
this.endCode = endCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build(): ScriptingDefinition {
|
||||
return new ScriptingDefinition(
|
||||
this.language, this.startCode, this.endCode);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
import { ScriptingDefinitionStub } from './ScriptingDefinitionStub';
|
||||
import { IApplication, ICategory, IScript } from '@/domain/IApplication';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ScriptStub } from './ScriptStub';
|
||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
|
||||
export class ApplicationStub implements IApplication {
|
||||
public scripting: IScriptingDefinition = new ScriptingDefinitionStub();
|
||||
public os = OperatingSystem.Linux;
|
||||
public initialScript: IScript = new ScriptStub('55');
|
||||
public totalScripts = 0;
|
||||
public totalCategories = 0;
|
||||
public readonly info = new ProjectInformation('StubApplication', '0.1.0', 'https://github.com/undergroundwires/privacy.sexy', 'https://privacy.sexy');
|
||||
@@ -11,6 +18,18 @@ export class ApplicationStub implements IApplication {
|
||||
this.actions.push(category);
|
||||
return this;
|
||||
}
|
||||
public withOs(os: OperatingSystem): ApplicationStub {
|
||||
this.os = os;
|
||||
return this;
|
||||
}
|
||||
public withScripting(scripting: IScriptingDefinition): ApplicationStub {
|
||||
this.scripting = scripting;
|
||||
return this;
|
||||
}
|
||||
public withInitialScript(script: IScript): ApplicationStub {
|
||||
this.initialScript = script;
|
||||
return this;
|
||||
}
|
||||
public findCategory(categoryId: number): ICategory {
|
||||
return this.getAllCategories().find(
|
||||
(category) => category.id === categoryId);
|
||||
|
||||
15
tests/unit/stubs/EnumParserStub.ts
Normal file
15
tests/unit/stubs/EnumParserStub.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { IEnumParser } from '@/application/Common/Enum';
|
||||
|
||||
export function mockEnumParser<T>(inputName: string, inputValue: string, outputValue: T): IEnumParser<T> {
|
||||
return {
|
||||
parseEnum: (value, name) => {
|
||||
if (name !== inputName) {
|
||||
throw new Error(`Unexpected name: "${name}"`);
|
||||
}
|
||||
if (value !== inputValue) {
|
||||
throw new Error(`Unexpected value: "${value}"`);
|
||||
}
|
||||
return outputValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
43
tests/unit/stubs/ProjectInformationStub.ts
Normal file
43
tests/unit/stubs/ProjectInformationStub.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
export class ProjectInformationStub implements IProjectInformation {
|
||||
public name: string;
|
||||
public version: string;
|
||||
public repositoryUrl: string;
|
||||
public homepage: string;
|
||||
public feedbackUrl: string;
|
||||
public releaseUrl: string;
|
||||
public repositoryWebUrl: string;
|
||||
public withName(name: string): ProjectInformationStub {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
public withVersion(version: string): ProjectInformationStub {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
public withRepositoryUrl(repositoryUrl: string): ProjectInformationStub {
|
||||
this.repositoryUrl = repositoryUrl;
|
||||
return this;
|
||||
}
|
||||
public withHomepageUrl(homepageUrl: string): ProjectInformationStub {
|
||||
this.homepage = homepageUrl;
|
||||
return this;
|
||||
}
|
||||
public withFeedbackUrl(feedbackUrl: string): ProjectInformationStub {
|
||||
this.feedbackUrl = feedbackUrl;
|
||||
return this;
|
||||
}
|
||||
public withReleaseUrl(releaseUrl: string): ProjectInformationStub {
|
||||
this.releaseUrl = releaseUrl;
|
||||
return this;
|
||||
}
|
||||
public withRepositoryWebUrl(repositoryWebUrl: string): ProjectInformationStub {
|
||||
this.repositoryWebUrl = repositoryWebUrl;
|
||||
return this;
|
||||
}
|
||||
public getDownloadUrl(os: OperatingSystem): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
|
||||
import { IScript } from '@/domain/IScript';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
|
||||
|
||||
export class ScriptStub extends BaseEntity<string> implements IScript {
|
||||
public name = `name${this.id}`;
|
||||
@@ -38,4 +39,8 @@ export class ScriptStub extends BaseEntity<string> implements IScript {
|
||||
this.code.revert = revertCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public toSelectedScript(isReverted = false): SelectedScript {
|
||||
return new SelectedScript(this, isReverted);
|
||||
}
|
||||
}
|
||||
|
||||
18
tests/unit/stubs/ScriptingDefinitionStub.ts
Normal file
18
tests/unit/stubs/ScriptingDefinitionStub.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
|
||||
export class ScriptingDefinitionStub implements IScriptingDefinition {
|
||||
public fileExtension: string = '.bat';
|
||||
public language = ScriptingLanguage.batchfile;
|
||||
public startCode = 'REM start code';
|
||||
public endCode = 'REM end code';
|
||||
|
||||
public withStartCode(startCode: string): ScriptingDefinitionStub {
|
||||
this.startCode = startCode;
|
||||
return this;
|
||||
}
|
||||
public withEndCode(endCode: string): ScriptingDefinitionStub {
|
||||
this.endCode = endCode;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -58,4 +58,10 @@ export class YamlScriptStub implements YamlScript {
|
||||
this.call = call;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public withRecommend(recommend: string): YamlScriptStub {
|
||||
this.recommend = recommend;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user