Refactor code to comply with ESLint rules
Major refactoring using ESLint with rules from AirBnb and Vue. Enable most of the ESLint rules and do necessary linting in the code. Also add more information for rules that are disabled to describe what they are and why they are disabled. Allow logging (`console.log`) in test files, and in development mode (e.g. when working with `npm run serve`), but disable it when environment is production (as pre-configured by Vue). Also add flag (`--mode production`) in `lint:eslint` command so production linting is executed earlier in lifecycle. Disable rules that requires a separate work. Such as ESLint rules that are broken in TypeScript: no-useless-constructor (eslint/eslint#14118) and no-shadow (eslint/eslint#13014).
This commit is contained in:
@@ -4,57 +4,57 @@ import { ApplicationFactory, ApplicationGetter } from '@/application/Application
|
||||
import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub';
|
||||
|
||||
describe('ApplicationFactory', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws if getter is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined getter';
|
||||
const getter = undefined;
|
||||
// act
|
||||
const act = () => new SystemUnderTest(getter);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('throws if getter is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined getter';
|
||||
const getter = undefined;
|
||||
// act
|
||||
const act = () => new SystemUnderTest(getter);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('getApp', () => {
|
||||
it('returns result from the getter', async () => {
|
||||
// arrange
|
||||
const expected = new ApplicationStub();
|
||||
const getter: ApplicationGetter = () => expected;
|
||||
const sut = new SystemUnderTest(getter);
|
||||
// act
|
||||
const actual = await Promise.all( [
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
]);
|
||||
// assert
|
||||
expect(actual.every((value) => value === expected));
|
||||
});
|
||||
it('only executes getter once', async () => {
|
||||
// arrange
|
||||
let totalExecution = 0;
|
||||
const expected = new ApplicationStub();
|
||||
const getter: ApplicationGetter = () => {
|
||||
totalExecution++;
|
||||
return expected;
|
||||
};
|
||||
const sut = new SystemUnderTest(getter);
|
||||
// act
|
||||
await Promise.all( [
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
]);
|
||||
// assert
|
||||
expect(totalExecution).to.equal(1);
|
||||
});
|
||||
});
|
||||
describe('getApp', () => {
|
||||
it('returns result from the getter', async () => {
|
||||
// arrange
|
||||
const expected = new ApplicationStub();
|
||||
const getter: ApplicationGetter = () => expected;
|
||||
const sut = new SystemUnderTest(getter);
|
||||
// act
|
||||
const actual = await Promise.all([
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
]);
|
||||
// assert
|
||||
expect(actual.every((value) => value === expected));
|
||||
});
|
||||
it('only executes getter once', async () => {
|
||||
// arrange
|
||||
let totalExecution = 0;
|
||||
const expected = new ApplicationStub();
|
||||
const getter: ApplicationGetter = () => {
|
||||
totalExecution++;
|
||||
return expected;
|
||||
};
|
||||
const sut = new SystemUnderTest(getter);
|
||||
// act
|
||||
await Promise.all([
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
sut.getApp(),
|
||||
]);
|
||||
// assert
|
||||
expect(totalExecution).to.equal(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class SystemUnderTest extends ApplicationFactory {
|
||||
public constructor(costlyGetter: ApplicationGetter) {
|
||||
super(costlyGetter);
|
||||
}
|
||||
public constructor(costlyGetter: ApplicationGetter) {
|
||||
super(costlyGetter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,74 @@
|
||||
interface IComparerTestCase<T> {
|
||||
readonly name: string;
|
||||
readonly first: readonly T[];
|
||||
readonly second: readonly T[];
|
||||
readonly expected: boolean;
|
||||
readonly name: string;
|
||||
readonly first: readonly T[];
|
||||
readonly second: readonly T[];
|
||||
readonly expected: boolean;
|
||||
}
|
||||
|
||||
export class ComparerTestScenario {
|
||||
private readonly testCases: Array<IComparerTestCase<number>> = [];
|
||||
private readonly testCases: Array<IComparerTestCase<number>> = [];
|
||||
|
||||
public addEmptyArrays(expectedResult: boolean) {
|
||||
return this.addTestCase({
|
||||
name: 'empty array',
|
||||
first: [ ],
|
||||
second: [ ],
|
||||
expected: expectedResult,
|
||||
}, true);
|
||||
}
|
||||
public addSameItemsWithSameOrder(expectedResult: boolean) {
|
||||
return this.addTestCase({
|
||||
name: 'same items with same order',
|
||||
first: [ 1, 2, 3 ],
|
||||
second: [ 1, 2, 3 ],
|
||||
expected: expectedResult,
|
||||
}, true);
|
||||
}
|
||||
public addSameItemsWithDifferentOrder(expectedResult: boolean) {
|
||||
return this.addTestCase({
|
||||
name: 'same items with different order',
|
||||
first: [ 1, 2, 3 ],
|
||||
second: [ 2, 3, 1 ],
|
||||
expected: expectedResult,
|
||||
}, true);
|
||||
}
|
||||
public addDifferentItemsWithSameLength(expectedResult: boolean) {
|
||||
return this.addTestCase({
|
||||
name: 'different items with same length',
|
||||
first: [ 1, 2, 3 ],
|
||||
second: [ 4, 5, 6 ],
|
||||
expected: expectedResult,
|
||||
}, true);
|
||||
}
|
||||
public addDifferentItemsWithDifferentLength(expectedResult: boolean) {
|
||||
return this.addTestCase({
|
||||
name: 'different items with different length',
|
||||
first: [ 1, 2 ],
|
||||
second: [ 3, 4, 5 ],
|
||||
expected: expectedResult,
|
||||
}, true);
|
||||
}
|
||||
public forEachCase(handler: (testCase: IComparerTestCase<number>) => void) {
|
||||
for (const testCase of this.testCases) {
|
||||
handler(testCase);
|
||||
}
|
||||
}
|
||||
public addEmptyArrays(expectedResult: boolean) {
|
||||
return this.addTestCase({
|
||||
name: 'empty array',
|
||||
first: [],
|
||||
second: [],
|
||||
expected: expectedResult,
|
||||
}, true);
|
||||
}
|
||||
|
||||
private addTestCase(testCase: IComparerTestCase<number>, addReversed: boolean) {
|
||||
this.testCases.push(testCase);
|
||||
if (addReversed) {
|
||||
this.testCases.push({
|
||||
name: `${testCase.name} (reversed)`,
|
||||
first: testCase.second,
|
||||
second: testCase.first,
|
||||
expected: testCase.expected,
|
||||
});
|
||||
}
|
||||
return this;
|
||||
public addSameItemsWithSameOrder(expectedResult: boolean) {
|
||||
return this.addTestCase({
|
||||
name: 'same items with same order',
|
||||
first: [1, 2, 3],
|
||||
second: [1, 2, 3],
|
||||
expected: expectedResult,
|
||||
}, true);
|
||||
}
|
||||
|
||||
public addSameItemsWithDifferentOrder(expectedResult: boolean) {
|
||||
return this.addTestCase({
|
||||
name: 'same items with different order',
|
||||
first: [1, 2, 3],
|
||||
second: [2, 3, 1],
|
||||
expected: expectedResult,
|
||||
}, true);
|
||||
}
|
||||
|
||||
public addDifferentItemsWithSameLength(expectedResult: boolean) {
|
||||
return this.addTestCase({
|
||||
name: 'different items with same length',
|
||||
first: [1, 2, 3],
|
||||
second: [4, 5, 6],
|
||||
expected: expectedResult,
|
||||
}, true);
|
||||
}
|
||||
|
||||
public addDifferentItemsWithDifferentLength(expectedResult: boolean) {
|
||||
return this.addTestCase({
|
||||
name: 'different items with different length',
|
||||
first: [1, 2],
|
||||
second: [3, 4, 5],
|
||||
expected: expectedResult,
|
||||
}, true);
|
||||
}
|
||||
|
||||
public forEachCase(handler: (testCase: IComparerTestCase<number>) => void) {
|
||||
for (const testCase of this.testCases) {
|
||||
handler(testCase);
|
||||
}
|
||||
}
|
||||
|
||||
private addTestCase(testCase: IComparerTestCase<number>, addReversed: boolean) {
|
||||
this.testCases.push(testCase);
|
||||
if (addReversed) {
|
||||
this.testCases.push({
|
||||
name: `${testCase.name} (reversed)`,
|
||||
first: testCase.second,
|
||||
second: testCase.first,
|
||||
expected: testCase.expected,
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,67 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { scrambledEqual } from '@/application/Common/Array';
|
||||
import { sequenceEqual } from '@/application/Common/Array';
|
||||
import { scrambledEqual, sequenceEqual } from '@/application/Common/Array';
|
||||
import { ComparerTestScenario } from './Array.ComparerTestScenario';
|
||||
|
||||
describe('Array', () => {
|
||||
describe('scrambledEqual', () => {
|
||||
describe('throws if arguments are undefined', () => {
|
||||
it('first argument is undefined', () => {
|
||||
const expectedError = 'undefined first array';
|
||||
const act = () => scrambledEqual(undefined, []);
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('second arguments is undefined', () => {
|
||||
const expectedError = 'undefined second array';
|
||||
const act = () => scrambledEqual([], undefined);
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('returns as expected', () => {
|
||||
// arrange
|
||||
const scenario = new ComparerTestScenario()
|
||||
.addSameItemsWithSameOrder(true)
|
||||
.addSameItemsWithDifferentOrder(true)
|
||||
.addDifferentItemsWithSameLength(false)
|
||||
.addDifferentItemsWithDifferentLength(false);
|
||||
// act
|
||||
scenario.forEachCase((testCase) => {
|
||||
it(testCase.name, () => {
|
||||
const actual = scrambledEqual(testCase.first, testCase.second);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('scrambledEqual', () => {
|
||||
describe('throws if arguments are undefined', () => {
|
||||
it('first argument is undefined', () => {
|
||||
const expectedError = 'undefined first array';
|
||||
const act = () => scrambledEqual(undefined, []);
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('second arguments is undefined', () => {
|
||||
const expectedError = 'undefined second array';
|
||||
const act = () => scrambledEqual([], undefined);
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('sequenceEqual', () => {
|
||||
describe('throws if arguments are undefined', () => {
|
||||
it('first argument is undefined', () => {
|
||||
const expectedError = 'undefined first array';
|
||||
const act = () => sequenceEqual(undefined, []);
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('second arguments is undefined', () => {
|
||||
const expectedError = 'undefined second array';
|
||||
const act = () => sequenceEqual([], undefined);
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('returns as expected', () => {
|
||||
// arrange
|
||||
const scenario = new ComparerTestScenario()
|
||||
.addSameItemsWithSameOrder(true)
|
||||
.addSameItemsWithDifferentOrder(true)
|
||||
.addDifferentItemsWithSameLength(false)
|
||||
.addDifferentItemsWithDifferentLength(false);
|
||||
// act
|
||||
scenario.forEachCase((testCase) => {
|
||||
it(testCase.name, () => {
|
||||
const actual = scrambledEqual(testCase.first, testCase.second);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
});
|
||||
describe('returns as expected', () => {
|
||||
// arrange
|
||||
const scenario = new ComparerTestScenario()
|
||||
.addSameItemsWithSameOrder(true)
|
||||
.addSameItemsWithDifferentOrder(true)
|
||||
.addDifferentItemsWithSameLength(false)
|
||||
.addDifferentItemsWithDifferentLength(false);
|
||||
// act
|
||||
scenario.forEachCase((testCase) => {
|
||||
it(testCase.name, () => {
|
||||
const actual = scrambledEqual(testCase.first, testCase.second);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('sequenceEqual', () => {
|
||||
describe('throws if arguments are undefined', () => {
|
||||
it('first argument is undefined', () => {
|
||||
const expectedError = 'undefined first array';
|
||||
const act = () => sequenceEqual(undefined, []);
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('second arguments is undefined', () => {
|
||||
const expectedError = 'undefined second array';
|
||||
const act = () => sequenceEqual([], undefined);
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('returns as expected', () => {
|
||||
// arrange
|
||||
const scenario = new ComparerTestScenario()
|
||||
.addSameItemsWithSameOrder(true)
|
||||
.addSameItemsWithDifferentOrder(true)
|
||||
.addDifferentItemsWithSameLength(false)
|
||||
.addDifferentItemsWithDifferentLength(false);
|
||||
// act
|
||||
scenario.forEachCase((testCase) => {
|
||||
it(testCase.name, () => {
|
||||
const actual = scrambledEqual(testCase.first, testCase.second);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,109 +1,111 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { getEnumNames, getEnumValues, createEnumParser, assertInRange } from '@/application/Common/Enum';
|
||||
import { EnumRangeTestRunner } from './EnumRangeTestRunner';
|
||||
import {
|
||||
getEnumNames, getEnumValues, createEnumParser, assertInRange,
|
||||
} from '@/application/Common/Enum';
|
||||
import { scrambledEqual } from '@/application/Common/Array';
|
||||
import { EnumRangeTestRunner } from './EnumRangeTestRunner';
|
||||
|
||||
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(scrambledEqual(expected, actual));
|
||||
});
|
||||
});
|
||||
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(scrambledEqual(expected, actual));
|
||||
});
|
||||
});
|
||||
describe('assertInRange', () => {
|
||||
// arrange
|
||||
enum TestEnum { Red, Green, Blue }
|
||||
const validValue = TestEnum.Red;
|
||||
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
|
||||
const act = (value: TestEnum) => assertInRange(value, TestEnum);
|
||||
// assert
|
||||
new EnumRangeTestRunner(act)
|
||||
.testOutOfRangeThrows()
|
||||
.testUndefinedValueThrows()
|
||||
.testValidValueDoesNotThrow(validValue);
|
||||
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 never,
|
||||
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(scrambledEqual(expected, actual));
|
||||
});
|
||||
});
|
||||
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(scrambledEqual(expected, actual));
|
||||
});
|
||||
});
|
||||
describe('assertInRange', () => {
|
||||
// arrange
|
||||
enum TestEnum { Red, Green, Blue }
|
||||
const validValue = TestEnum.Red;
|
||||
// act
|
||||
const act = (value: TestEnum) => assertInRange(value, TestEnum);
|
||||
// assert
|
||||
new EnumRangeTestRunner(act)
|
||||
.testOutOfRangeThrows()
|
||||
.testUndefinedValueThrows()
|
||||
.testValidValueDoesNotThrow(validValue);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,52 +3,56 @@ import { expect } from 'chai';
|
||||
import { EnumType } from '@/application/Common/Enum';
|
||||
|
||||
export class EnumRangeTestRunner<TEnumValue extends EnumType> {
|
||||
constructor(private readonly runner: (value: TEnumValue) => any) {
|
||||
}
|
||||
public testOutOfRangeThrows() {
|
||||
it('throws when value is out of range', () => {
|
||||
// arrange
|
||||
const value = Number.MAX_SAFE_INTEGER as TEnumValue;
|
||||
const expectedError = `enum value "${value}" is out of range`;
|
||||
// act
|
||||
const act = () => this.runner(value);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
public testUndefinedValueThrows() {
|
||||
it('throws when value is undefined', () => {
|
||||
// arrange
|
||||
const value = undefined;
|
||||
const expectedError = 'undefined enum value';
|
||||
// act
|
||||
const act = () => this.runner(value);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
public testInvalidValueThrows(invalidValue: TEnumValue, expectedError: string) {
|
||||
it(`throws ${expectedError}`, () => {
|
||||
// arrange
|
||||
const value = invalidValue;
|
||||
// act
|
||||
const act = () => this.runner(value);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
public testValidValueDoesNotThrow(validValue: TEnumValue) {
|
||||
it('throws when value is undefined', () => {
|
||||
// arrange
|
||||
const value = validValue;
|
||||
// act
|
||||
const act = () => this.runner(value);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
constructor(private readonly runner: (value: TEnumValue) => void) {
|
||||
}
|
||||
|
||||
public testOutOfRangeThrows() {
|
||||
it('throws when value is out of range', () => {
|
||||
// arrange
|
||||
const value = Number.MAX_SAFE_INTEGER as TEnumValue;
|
||||
const expectedError = `enum value "${value}" is out of range`;
|
||||
// act
|
||||
const act = () => this.runner(value);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public testUndefinedValueThrows() {
|
||||
it('throws when value is undefined', () => {
|
||||
// arrange
|
||||
const value = undefined;
|
||||
const expectedError = 'undefined enum value';
|
||||
// act
|
||||
const act = () => this.runner(value);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public testInvalidValueThrows(invalidValue: TEnumValue, expectedError: string) {
|
||||
it(`throws ${expectedError}`, () => {
|
||||
// arrange
|
||||
const value = invalidValue;
|
||||
// act
|
||||
const act = () => this.runner(value);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public testValidValueDoesNotThrow(validValue: TEnumValue) {
|
||||
it('throws when value is undefined', () => {
|
||||
// arrange
|
||||
const value = validValue;
|
||||
// act
|
||||
const act = () => this.runner(value);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,59 +2,58 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
|
||||
import { ScriptingLanguageFactoryTestRunner } from './ScriptingLanguageFactoryTestRunner';
|
||||
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
|
||||
import { ScriptingLanguageFactoryTestRunner } from './ScriptingLanguageFactoryTestRunner';
|
||||
|
||||
class ScriptingLanguageConcrete extends ScriptingLanguageFactory<number> {
|
||||
public registerGetter(language: ScriptingLanguage, getter: () => number) {
|
||||
super.registerGetter(language, getter);
|
||||
}
|
||||
public registerGetter(language: ScriptingLanguage, getter: () => number) {
|
||||
super.registerGetter(language, getter);
|
||||
}
|
||||
}
|
||||
|
||||
describe('ScriptingLanguageFactory', () => {
|
||||
describe('registerGetter', () => {
|
||||
describe('validates language', () => {
|
||||
// arrange
|
||||
const validValue = ScriptingLanguage.batchfile;
|
||||
const getter = () => undefined;
|
||||
const sut = new ScriptingLanguageConcrete();
|
||||
// act
|
||||
const act = (language: ScriptingLanguage) => sut.registerGetter(language, getter);
|
||||
// assert
|
||||
new EnumRangeTestRunner(act)
|
||||
.testOutOfRangeThrows()
|
||||
.testUndefinedValueThrows()
|
||||
.testValidValueDoesNotThrow(validValue);
|
||||
});
|
||||
it('throw when getter is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = `undefined getter`;
|
||||
const language = ScriptingLanguage.batchfile;
|
||||
const getter = undefined;
|
||||
const sut = new ScriptingLanguageConcrete();
|
||||
// act
|
||||
const act = () => sut.registerGetter(language, getter);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throw when language is already registered', () => {
|
||||
// arrange
|
||||
const language = ScriptingLanguage.batchfile;
|
||||
const expectedError = `${ScriptingLanguage[language]} is already registered`;
|
||||
const getter = () => undefined;
|
||||
const sut = new ScriptingLanguageConcrete();
|
||||
// act
|
||||
sut.registerGetter(language, getter);
|
||||
const reRegister = () => sut.registerGetter(language, getter);
|
||||
// assert
|
||||
expect(reRegister).to.throw(expectedError);
|
||||
});
|
||||
describe('registerGetter', () => {
|
||||
describe('validates language', () => {
|
||||
// arrange
|
||||
const validValue = ScriptingLanguage.batchfile;
|
||||
const getter = () => undefined;
|
||||
const sut = new ScriptingLanguageConcrete();
|
||||
// act
|
||||
const act = (language: ScriptingLanguage) => sut.registerGetter(language, getter);
|
||||
// assert
|
||||
new EnumRangeTestRunner(act)
|
||||
.testOutOfRangeThrows()
|
||||
.testUndefinedValueThrows()
|
||||
.testValidValueDoesNotThrow(validValue);
|
||||
});
|
||||
describe('create', () => {
|
||||
const sut = new ScriptingLanguageConcrete();
|
||||
sut.registerGetter(ScriptingLanguage.batchfile, () => undefined);
|
||||
const runner = new ScriptingLanguageFactoryTestRunner();
|
||||
runner.testCreateMethod(sut);
|
||||
it('throw when getter is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined getter';
|
||||
const language = ScriptingLanguage.batchfile;
|
||||
const getter = undefined;
|
||||
const sut = new ScriptingLanguageConcrete();
|
||||
// act
|
||||
const act = () => sut.registerGetter(language, getter);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throw when language is already registered', () => {
|
||||
// arrange
|
||||
const language = ScriptingLanguage.batchfile;
|
||||
const expectedError = `${ScriptingLanguage[language]} is already registered`;
|
||||
const getter = () => undefined;
|
||||
const sut = new ScriptingLanguageConcrete();
|
||||
// act
|
||||
sut.registerGetter(language, getter);
|
||||
const reRegister = () => sut.registerGetter(language, getter);
|
||||
// assert
|
||||
expect(reRegister).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('create', () => {
|
||||
const sut = new ScriptingLanguageConcrete();
|
||||
sut.registerGetter(ScriptingLanguage.batchfile, () => undefined);
|
||||
const runner = new ScriptingLanguageFactoryTestRunner();
|
||||
runner.testCreateMethod(sut);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -5,45 +5,48 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
|
||||
|
||||
export class ScriptingLanguageFactoryTestRunner<T> {
|
||||
private expectedTypes = new Map<ScriptingLanguage, T>();
|
||||
public expect(language: ScriptingLanguage, resultType: T) {
|
||||
this.expectedTypes.set(language, resultType);
|
||||
return this;
|
||||
}
|
||||
public testCreateMethod(sut: IScriptingLanguageFactory<T>) {
|
||||
if (!sut) { throw new Error('undefined sut'); }
|
||||
testLanguageValidation(sut);
|
||||
testExpectedInstanceTypes(sut, this.expectedTypes);
|
||||
}
|
||||
private expectedTypes = new Map<ScriptingLanguage, T>();
|
||||
|
||||
public expect(language: ScriptingLanguage, resultType: T) {
|
||||
this.expectedTypes.set(language, resultType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public testCreateMethod(sut: IScriptingLanguageFactory<T>) {
|
||||
if (!sut) { throw new Error('undefined sut'); }
|
||||
testLanguageValidation(sut);
|
||||
testExpectedInstanceTypes(sut, this.expectedTypes);
|
||||
}
|
||||
}
|
||||
|
||||
function testExpectedInstanceTypes<T>(
|
||||
sut: IScriptingLanguageFactory<T>,
|
||||
expectedTypes: Map<ScriptingLanguage, T>) {
|
||||
describe('create returns expected instances', () => {
|
||||
// arrange
|
||||
for (const language of Array.from(expectedTypes.keys())) {
|
||||
it(ScriptingLanguage[language], () => {
|
||||
// act
|
||||
const expected = expectedTypes.get(language);
|
||||
const result = sut.create(language);
|
||||
// assert
|
||||
expect(result).to.be.instanceOf(expected, `Actual was: ${result.constructor.name}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
sut: IScriptingLanguageFactory<T>,
|
||||
expectedTypes: Map<ScriptingLanguage, T>,
|
||||
) {
|
||||
describe('create returns expected instances', () => {
|
||||
// arrange
|
||||
for (const language of Array.from(expectedTypes.keys())) {
|
||||
it(ScriptingLanguage[language], () => {
|
||||
// act
|
||||
const expected = expectedTypes.get(language);
|
||||
const result = sut.create(language);
|
||||
// assert
|
||||
expect(result).to.be.instanceOf(expected, `Actual was: ${result.constructor.name}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testLanguageValidation<T>(sut: IScriptingLanguageFactory<T>) {
|
||||
describe('validates language', () => {
|
||||
// arrange
|
||||
const validValue = ScriptingLanguage.batchfile;
|
||||
// act
|
||||
const act = (value: ScriptingLanguage) => sut.create(value);
|
||||
// assert
|
||||
new EnumRangeTestRunner(act)
|
||||
.testOutOfRangeThrows()
|
||||
.testUndefinedValueThrows()
|
||||
.testValidValueDoesNotThrow(validValue);
|
||||
});
|
||||
describe('validates language', () => {
|
||||
// arrange
|
||||
const validValue = ScriptingLanguage.batchfile;
|
||||
// act
|
||||
const act = (value: ScriptingLanguage) => sut.create(value);
|
||||
// assert
|
||||
new EnumRangeTestRunner(act)
|
||||
.testOutOfRangeThrows()
|
||||
.testUndefinedValueThrows()
|
||||
.testValidValueDoesNotThrow(validValue);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,241 +10,250 @@ import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub
|
||||
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
|
||||
|
||||
describe('ApplicationContext', () => {
|
||||
describe('changeContext', () => {
|
||||
describe('when initial os is changed to different one', () => {
|
||||
it('collection is changed as expected', () => {
|
||||
// arrange
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(OperatingSystem.Windows, OperatingSystem.macOS);
|
||||
const expectedCollection = testContext.app.getCollection(OperatingSystem.macOS);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(OperatingSystem.Windows)
|
||||
.construct();
|
||||
sut.changeContext(OperatingSystem.macOS);
|
||||
// assert
|
||||
expect(sut.state.collection).to.equal(expectedCollection);
|
||||
});
|
||||
it('currentOs is changed as expected', () => {
|
||||
// arrange
|
||||
const expectedOs = OperatingSystem.macOS;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(OperatingSystem.Windows, expectedOs);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(OperatingSystem.Windows)
|
||||
.construct();
|
||||
sut.changeContext(expectedOs);
|
||||
// assert
|
||||
expect(sut.state.os).to.equal(expectedOs);
|
||||
});
|
||||
it('new state is empty', () => {
|
||||
// arrange
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(OperatingSystem.Windows, OperatingSystem.macOS);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(OperatingSystem.Windows)
|
||||
.construct();
|
||||
sut.state.filter.setFilter('filtered');
|
||||
sut.changeContext(OperatingSystem.macOS);
|
||||
// assert
|
||||
expectEmptyState(sut.state);
|
||||
});
|
||||
});
|
||||
it('remembers old state when changed backed to same os', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Windows;
|
||||
const changedOs = OperatingSystem.macOS;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(os, changedOs);
|
||||
const expectedFilter = 'first-state';
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
const firstState = sut.state;
|
||||
firstState.filter.setFilter(expectedFilter);
|
||||
sut.changeContext(os);
|
||||
sut.changeContext(changedOs);
|
||||
sut.state.filter.setFilter('second-state');
|
||||
sut.changeContext(os);
|
||||
// assert
|
||||
const actualFilter = sut.state.filter.currentFilter.query;
|
||||
expect(actualFilter).to.equal(expectedFilter);
|
||||
});
|
||||
describe('contextChanged', () => {
|
||||
it('fired as expected on change', () => {
|
||||
// arrange
|
||||
const nextOs = OperatingSystem.macOS;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(OperatingSystem.Windows, nextOs);
|
||||
const expectedCollection = testContext.app.getCollection(OperatingSystem.macOS);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(OperatingSystem.Windows)
|
||||
.construct();
|
||||
const oldState = sut.state;
|
||||
sut.changeContext(nextOs);
|
||||
// assert
|
||||
expect(testContext.firedEvents.length).to.equal(1);
|
||||
expect(testContext.firedEvents[0].newState).to.equal(sut.state);
|
||||
expect(testContext.firedEvents[0].newState.collection).to.equal(expectedCollection);
|
||||
expect(testContext.firedEvents[0].oldState).to.equal(oldState);
|
||||
});
|
||||
it('is not fired when initial os is changed to same one', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Windows;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(os);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
const initialState = sut.state;
|
||||
initialState.filter.setFilter('dirty-state');
|
||||
sut.changeContext(os);
|
||||
// assert
|
||||
expect(testContext.firedEvents.length).to.equal(0);
|
||||
});
|
||||
it('new event is fired for each change', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Windows;
|
||||
const changedOs = OperatingSystem.macOS;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(os, changedOs);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
sut.changeContext(changedOs);
|
||||
sut.changeContext(os);
|
||||
sut.changeContext(changedOs);
|
||||
// assert
|
||||
const duplicates = getDuplicates(testContext.firedEvents);
|
||||
expect(duplicates.length).to.be.equal(0);
|
||||
});
|
||||
});
|
||||
describe('changeContext', () => {
|
||||
describe('when initial os is changed to different one', () => {
|
||||
it('collection is changed as expected', () => {
|
||||
// arrange
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(OperatingSystem.Windows, OperatingSystem.macOS);
|
||||
const expectedCollection = testContext.app.getCollection(OperatingSystem.macOS);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(OperatingSystem.Windows)
|
||||
.construct();
|
||||
sut.changeContext(OperatingSystem.macOS);
|
||||
// assert
|
||||
expect(sut.state.collection).to.equal(expectedCollection);
|
||||
});
|
||||
it('currentOs is changed as expected', () => {
|
||||
// arrange
|
||||
const expectedOs = OperatingSystem.macOS;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(OperatingSystem.Windows, expectedOs);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(OperatingSystem.Windows)
|
||||
.construct();
|
||||
sut.changeContext(expectedOs);
|
||||
// assert
|
||||
expect(sut.state.os).to.equal(expectedOs);
|
||||
});
|
||||
it('new state is empty', () => {
|
||||
// arrange
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(OperatingSystem.Windows, OperatingSystem.macOS);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(OperatingSystem.Windows)
|
||||
.construct();
|
||||
sut.state.filter.setFilter('filtered');
|
||||
sut.changeContext(OperatingSystem.macOS);
|
||||
// assert
|
||||
expectEmptyState(sut.state);
|
||||
});
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('app', () => {
|
||||
it('throw when app is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined app';
|
||||
const app = undefined;
|
||||
const os = OperatingSystem.Windows;
|
||||
// act
|
||||
const act = () => new ApplicationContext(app, os);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('collection', () => {
|
||||
it('returns right collection for expected OS', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Windows;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(os);
|
||||
const expected = testContext.app.getCollection(os);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
// assert
|
||||
const actual = sut.state.collection;
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('state', () => {
|
||||
it('initially returns an empty state', () => {
|
||||
// arrange
|
||||
const sut = new ObservableApplicationContextFactory()
|
||||
.construct();
|
||||
// act
|
||||
const actual = sut.state;
|
||||
// assert
|
||||
expectEmptyState(actual);
|
||||
});
|
||||
});
|
||||
describe('os', () => {
|
||||
it('set as initial OS', () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.Windows;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(OperatingSystem.macOS, expected);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(expected)
|
||||
.construct();
|
||||
// assert
|
||||
const actual = sut.state.os;
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
describe('throws when OS is invalid', () => {
|
||||
// act
|
||||
const act = (os: OperatingSystem) => new ObservableApplicationContextFactory()
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
// assert
|
||||
new EnumRangeTestRunner(act)
|
||||
.testOutOfRangeThrows()
|
||||
.testUndefinedValueThrows()
|
||||
.testInvalidValueThrows(OperatingSystem.Android, 'os "Android" is not defined in application');
|
||||
});
|
||||
});
|
||||
describe('app', () => {
|
||||
it('sets app as expected', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Windows;
|
||||
const expected = new ApplicationStub().withCollection(
|
||||
new CategoryCollectionStub().withOs(os),
|
||||
);
|
||||
// act
|
||||
const sut = new ObservableApplicationContextFactory()
|
||||
.withApp(expected)
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
// assert
|
||||
expect(expected).to.equal(sut.app);
|
||||
});
|
||||
});
|
||||
it('remembers old state when changed backed to same os', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Windows;
|
||||
const changedOs = OperatingSystem.macOS;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(os, changedOs);
|
||||
const expectedFilter = 'first-state';
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
const firstState = sut.state;
|
||||
firstState.filter.setFilter(expectedFilter);
|
||||
sut.changeContext(os);
|
||||
sut.changeContext(changedOs);
|
||||
sut.state.filter.setFilter('second-state');
|
||||
sut.changeContext(os);
|
||||
// assert
|
||||
const actualFilter = sut.state.filter.currentFilter.query;
|
||||
expect(actualFilter).to.equal(expectedFilter);
|
||||
});
|
||||
describe('contextChanged', () => {
|
||||
it('fired as expected on change', () => {
|
||||
// arrange
|
||||
const nextOs = OperatingSystem.macOS;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(OperatingSystem.Windows, nextOs);
|
||||
const expectedCollection = testContext.app.getCollection(OperatingSystem.macOS);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(OperatingSystem.Windows)
|
||||
.construct();
|
||||
const oldState = sut.state;
|
||||
sut.changeContext(nextOs);
|
||||
// assert
|
||||
expect(testContext.firedEvents.length).to.equal(1);
|
||||
expect(testContext.firedEvents[0].newState).to.equal(sut.state);
|
||||
expect(testContext.firedEvents[0].newState.collection).to.equal(expectedCollection);
|
||||
expect(testContext.firedEvents[0].oldState).to.equal(oldState);
|
||||
});
|
||||
it('is not fired when initial os is changed to same one', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Windows;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(os);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
const initialState = sut.state;
|
||||
initialState.filter.setFilter('dirty-state');
|
||||
sut.changeContext(os);
|
||||
// assert
|
||||
expect(testContext.firedEvents.length).to.equal(0);
|
||||
});
|
||||
it('new event is fired for each change', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Windows;
|
||||
const changedOs = OperatingSystem.macOS;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(os, changedOs);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
sut.changeContext(changedOs);
|
||||
sut.changeContext(os);
|
||||
sut.changeContext(changedOs);
|
||||
// assert
|
||||
const duplicates = getDuplicates(testContext.firedEvents);
|
||||
expect(duplicates.length).to.be.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('app', () => {
|
||||
it('throw when app is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined app';
|
||||
const app = undefined;
|
||||
const os = OperatingSystem.Windows;
|
||||
// act
|
||||
const act = () => new ApplicationContext(app, os);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('collection', () => {
|
||||
it('returns right collection for expected OS', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Windows;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(os);
|
||||
const expected = testContext.app.getCollection(os);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
// assert
|
||||
const actual = sut.state.collection;
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('state', () => {
|
||||
it('initially returns an empty state', () => {
|
||||
// arrange
|
||||
const sut = new ObservableApplicationContextFactory()
|
||||
.construct();
|
||||
// act
|
||||
const actual = sut.state;
|
||||
// assert
|
||||
expectEmptyState(actual);
|
||||
});
|
||||
});
|
||||
describe('os', () => {
|
||||
it('set as initial OS', () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.Windows;
|
||||
const testContext = new ObservableApplicationContextFactory()
|
||||
.withAppContainingCollections(OperatingSystem.macOS, expected);
|
||||
// act
|
||||
const sut = testContext
|
||||
.withInitialOs(expected)
|
||||
.construct();
|
||||
// assert
|
||||
const actual = sut.state.os;
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
describe('throws when OS is invalid', () => {
|
||||
// act
|
||||
const act = (os: OperatingSystem) => new ObservableApplicationContextFactory()
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
// assert
|
||||
new EnumRangeTestRunner(act)
|
||||
.testOutOfRangeThrows()
|
||||
.testUndefinedValueThrows()
|
||||
.testInvalidValueThrows(OperatingSystem.Android, 'os "Android" is not defined in application');
|
||||
});
|
||||
});
|
||||
describe('app', () => {
|
||||
it('sets app as expected', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.Windows;
|
||||
const expected = new ApplicationStub().withCollection(
|
||||
new CategoryCollectionStub().withOs(os),
|
||||
);
|
||||
// act
|
||||
const sut = new ObservableApplicationContextFactory()
|
||||
.withApp(expected)
|
||||
.withInitialOs(os)
|
||||
.construct();
|
||||
// assert
|
||||
expect(expected).to.equal(sut.app);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ObservableApplicationContextFactory {
|
||||
private static DefaultOs = OperatingSystem.Windows;
|
||||
public app: IApplication;
|
||||
public firedEvents = new Array<IApplicationContextChangedEvent>();
|
||||
private initialOs = ObservableApplicationContextFactory.DefaultOs;
|
||||
constructor() {
|
||||
this.withAppContainingCollections(ObservableApplicationContextFactory.DefaultOs);
|
||||
}
|
||||
public withAppContainingCollections(...oses: OperatingSystem[]): ObservableApplicationContextFactory {
|
||||
const collectionValues = oses.map((os) => new CategoryCollectionStub().withOs(os));
|
||||
const app = new ApplicationStub().withCollections(...collectionValues);
|
||||
return this.withApp(app);
|
||||
}
|
||||
public withApp(app: IApplication): ObservableApplicationContextFactory {
|
||||
this.app = app;
|
||||
return this;
|
||||
}
|
||||
public withInitialOs(initialOs: OperatingSystem) {
|
||||
this.initialOs = initialOs;
|
||||
return this;
|
||||
}
|
||||
public construct()
|
||||
: IApplicationContext {
|
||||
const sut = new ApplicationContext(this.app, this.initialOs);
|
||||
sut.contextChanged.on((newContext) => this.firedEvents.push(newContext));
|
||||
return sut;
|
||||
}
|
||||
private static DefaultOs = OperatingSystem.Windows;
|
||||
|
||||
public app: IApplication;
|
||||
|
||||
public firedEvents = new Array<IApplicationContextChangedEvent>();
|
||||
|
||||
private initialOs = ObservableApplicationContextFactory.DefaultOs;
|
||||
|
||||
constructor() {
|
||||
this.withAppContainingCollections(ObservableApplicationContextFactory.DefaultOs);
|
||||
}
|
||||
|
||||
public withAppContainingCollections(
|
||||
...oses: OperatingSystem[]
|
||||
): ObservableApplicationContextFactory {
|
||||
const collectionValues = oses.map((os) => new CategoryCollectionStub().withOs(os));
|
||||
const app = new ApplicationStub().withCollections(...collectionValues);
|
||||
return this.withApp(app);
|
||||
}
|
||||
|
||||
public withApp(app: IApplication): ObservableApplicationContextFactory {
|
||||
this.app = app;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withInitialOs(initialOs: OperatingSystem) {
|
||||
this.initialOs = initialOs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public construct(): IApplicationContext {
|
||||
const sut = new ApplicationContext(this.app, this.initialOs);
|
||||
sut.contextChanged.on((newContext) => this.firedEvents.push(newContext));
|
||||
return sut;
|
||||
}
|
||||
}
|
||||
function getDuplicates<T>(list: readonly T[]): T[] {
|
||||
return list.filter((item, index) => list.indexOf(item) !== index);
|
||||
return list.filter((item, index) => list.indexOf(item) !== index);
|
||||
}
|
||||
|
||||
function expectEmptyState(state: ICategoryCollectionState) {
|
||||
expect(!state.code.current);
|
||||
expect(!state.filter.currentFilter);
|
||||
expect(!state.selection);
|
||||
expect(!state.code.current);
|
||||
expect(!state.filter.currentFilter);
|
||||
expect(!state.selection);
|
||||
}
|
||||
|
||||
@@ -10,74 +10,75 @@ import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub';
|
||||
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
|
||||
|
||||
describe('ApplicationContextFactory', () => {
|
||||
describe('buildContext', () => {
|
||||
describe('factory', () => {
|
||||
it('sets application from factory', async () => {
|
||||
// arrange
|
||||
const expected = new ApplicationStub().withCollection(
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.macOS));
|
||||
const factoryMock = mockFactoryWithApp(expected);
|
||||
// act
|
||||
const context = await buildContext(factoryMock);
|
||||
// assert
|
||||
expect(expected).to.equal(context.app);
|
||||
});
|
||||
});
|
||||
describe('environment', () => {
|
||||
describe('sets initial OS as expected', () => {
|
||||
it('returns currentOs if it is supported', async () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.Windows;
|
||||
const environment = new EnvironmentStub().withOs(expected);
|
||||
const collection = new CategoryCollectionStub().withOs(expected);
|
||||
const factoryMock = mockFactoryWithCollection(collection);
|
||||
// act
|
||||
const context = await buildContext(factoryMock, environment);
|
||||
// assert
|
||||
const actual = context.state.os;
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
it('fallbacks to other os if OS in environment is not supported', async () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.Windows;
|
||||
const currentOs = OperatingSystem.macOS;
|
||||
const environment = new EnvironmentStub().withOs(currentOs);
|
||||
const collection = new CategoryCollectionStub().withOs(expected);
|
||||
const factoryMock = mockFactoryWithCollection(collection);
|
||||
// act
|
||||
const context = await buildContext(factoryMock, environment);
|
||||
// assert
|
||||
const actual = context.state.os;
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
it('fallbacks to most supported os if current os is not supported', async () => {
|
||||
// arrange
|
||||
const expectedOs = OperatingSystem.Android;
|
||||
const allCollections = [
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.Linux).withTotalScripts(3),
|
||||
new CategoryCollectionStub().withOs(expectedOs).withTotalScripts(5),
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.Windows).withTotalScripts(4),
|
||||
];
|
||||
const environment = new EnvironmentStub().withOs(OperatingSystem.macOS);
|
||||
const app = new ApplicationStub().withCollections(...allCollections);
|
||||
const factoryMock = mockFactoryWithApp(app);
|
||||
// act
|
||||
const context = await buildContext(factoryMock, environment);
|
||||
// assert
|
||||
const actual = context.state.os;
|
||||
expect(expectedOs).to.equal(actual, `Expected: ${OperatingSystem[expectedOs]}, actual: ${OperatingSystem[actual]}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('buildContext', () => {
|
||||
describe('factory', () => {
|
||||
it('sets application from factory', async () => {
|
||||
// arrange
|
||||
const expected = new ApplicationStub().withCollection(
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.macOS),
|
||||
);
|
||||
const factoryMock = mockFactoryWithApp(expected);
|
||||
// act
|
||||
const context = await buildContext(factoryMock);
|
||||
// assert
|
||||
expect(expected).to.equal(context.app);
|
||||
});
|
||||
});
|
||||
describe('environment', () => {
|
||||
describe('sets initial OS as expected', () => {
|
||||
it('returns currentOs if it is supported', async () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.Windows;
|
||||
const environment = new EnvironmentStub().withOs(expected);
|
||||
const collection = new CategoryCollectionStub().withOs(expected);
|
||||
const factoryMock = mockFactoryWithCollection(collection);
|
||||
// act
|
||||
const context = await buildContext(factoryMock, environment);
|
||||
// assert
|
||||
const actual = context.state.os;
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
it('fallbacks to other os if OS in environment is not supported', async () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.Windows;
|
||||
const currentOs = OperatingSystem.macOS;
|
||||
const environment = new EnvironmentStub().withOs(currentOs);
|
||||
const collection = new CategoryCollectionStub().withOs(expected);
|
||||
const factoryMock = mockFactoryWithCollection(collection);
|
||||
// act
|
||||
const context = await buildContext(factoryMock, environment);
|
||||
// assert
|
||||
const actual = context.state.os;
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
it('fallbacks to most supported os if current os is not supported', async () => {
|
||||
// arrange
|
||||
const expectedOs = OperatingSystem.Android;
|
||||
const allCollections = [
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.Linux).withTotalScripts(3),
|
||||
new CategoryCollectionStub().withOs(expectedOs).withTotalScripts(5),
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.Windows).withTotalScripts(4),
|
||||
];
|
||||
const environment = new EnvironmentStub().withOs(OperatingSystem.macOS);
|
||||
const app = new ApplicationStub().withCollections(...allCollections);
|
||||
const factoryMock = mockFactoryWithApp(app);
|
||||
// act
|
||||
const context = await buildContext(factoryMock, environment);
|
||||
// assert
|
||||
const actual = context.state.os;
|
||||
expect(expectedOs).to.equal(actual, `Expected: ${OperatingSystem[expectedOs]}, actual: ${OperatingSystem[actual]}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockFactoryWithCollection(result: ICategoryCollection): IApplicationFactory {
|
||||
return mockFactoryWithApp(new ApplicationStub().withCollection(result));
|
||||
return mockFactoryWithApp(new ApplicationStub().withCollection(result));
|
||||
}
|
||||
|
||||
function mockFactoryWithApp(app: IApplication): IApplicationFactory {
|
||||
return {
|
||||
getApp: () => Promise.resolve(app),
|
||||
};
|
||||
return {
|
||||
getApp: () => Promise.resolve(app),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,92 +10,94 @@ import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
|
||||
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
|
||||
|
||||
describe('CategoryCollectionState', () => {
|
||||
describe('code', () => {
|
||||
it('initialized with empty code', () => {
|
||||
// arrange
|
||||
const collection = new CategoryCollectionStub();
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
const code = sut.code.current;
|
||||
// assert
|
||||
expect(!code);
|
||||
});
|
||||
it('reacts to selection changes as expected', () => {
|
||||
// arrange
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(0).withScriptIds('scriptId'));
|
||||
const selectionStub = new UserSelection(collection, []);
|
||||
const expectedCodeGenerator = new ApplicationCode(selectionStub, collection.scripting);
|
||||
selectionStub.selectAll();
|
||||
const expectedCode = expectedCodeGenerator.current;
|
||||
// act
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
sut.selection.selectAll();
|
||||
const actualCode = sut.code.current;
|
||||
// assert
|
||||
expect(actualCode).to.equal(expectedCode);
|
||||
});
|
||||
describe('code', () => {
|
||||
it('initialized with empty code', () => {
|
||||
// arrange
|
||||
const collection = new CategoryCollectionStub();
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
const code = sut.code.current;
|
||||
// assert
|
||||
expect(!code);
|
||||
});
|
||||
describe('os', () => {
|
||||
it('same as its collection', () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.macOS;
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withOs(expected);
|
||||
// act
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// assert
|
||||
const actual = sut.os;
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
it('reacts to selection changes as expected', () => {
|
||||
// arrange
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(0).withScriptIds('scriptId'));
|
||||
const selectionStub = new UserSelection(collection, []);
|
||||
const expectedCodeGenerator = new ApplicationCode(selectionStub, collection.scripting);
|
||||
selectionStub.selectAll();
|
||||
const expectedCode = expectedCodeGenerator.current;
|
||||
// act
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
sut.selection.selectAll();
|
||||
const actualCode = sut.code.current;
|
||||
// assert
|
||||
expect(actualCode).to.equal(expectedCode);
|
||||
});
|
||||
describe('selection', () => {
|
||||
it('initialized with no selection', () => {
|
||||
// arrange
|
||||
const collection = new CategoryCollectionStub();
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
const actual = sut.selection.selectedScripts.length;
|
||||
// assert
|
||||
expect(actual).to.equal(0);
|
||||
});
|
||||
it('can select a script from current collection', () => {
|
||||
// arrange
|
||||
const expectedScript = new ScriptStub('scriptId');
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(0).withScript(expectedScript));
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
sut.selection.selectAll();
|
||||
// assert
|
||||
expect(sut.selection.selectedScripts.length).to.equal(1);
|
||||
expect(sut.selection.isSelected(expectedScript.id)).to.equal(true);
|
||||
});
|
||||
});
|
||||
describe('os', () => {
|
||||
it('same as its collection', () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.macOS;
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withOs(expected);
|
||||
// act
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// assert
|
||||
const actual = sut.os;
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
describe('filter', () => {
|
||||
it('initialized with an empty filter', () => {
|
||||
// arrange
|
||||
const collection = new CategoryCollectionStub();
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
const actual = sut.filter.currentFilter;
|
||||
// assert
|
||||
expect(actual).to.equal(undefined);
|
||||
});
|
||||
it('can match a script from current collection', () => {
|
||||
// arrange
|
||||
const scriptNameFilter = 'scriptName';
|
||||
const expectedScript = new ScriptStub('scriptId')
|
||||
.withName(scriptNameFilter);
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(0).withScript(expectedScript));
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
let actualScript: IScript;
|
||||
sut.filter.filtered.on((result) => actualScript = result.scriptMatches[0]);
|
||||
sut.filter.setFilter(scriptNameFilter);
|
||||
// assert
|
||||
expect(expectedScript).to.equal(actualScript);
|
||||
});
|
||||
});
|
||||
describe('selection', () => {
|
||||
it('initialized with no selection', () => {
|
||||
// arrange
|
||||
const collection = new CategoryCollectionStub();
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
const actual = sut.selection.selectedScripts.length;
|
||||
// assert
|
||||
expect(actual).to.equal(0);
|
||||
});
|
||||
it('can select a script from current collection', () => {
|
||||
// arrange
|
||||
const expectedScript = new ScriptStub('scriptId');
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(0).withScript(expectedScript));
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
sut.selection.selectAll();
|
||||
// assert
|
||||
expect(sut.selection.selectedScripts.length).to.equal(1);
|
||||
expect(sut.selection.isSelected(expectedScript.id)).to.equal(true);
|
||||
});
|
||||
});
|
||||
describe('filter', () => {
|
||||
it('initialized with an empty filter', () => {
|
||||
// arrange
|
||||
const collection = new CategoryCollectionStub();
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
const actual = sut.filter.currentFilter;
|
||||
// assert
|
||||
expect(actual).to.equal(undefined);
|
||||
});
|
||||
it('can match a script from current collection', () => {
|
||||
// arrange
|
||||
const scriptNameFilter = 'scriptName';
|
||||
const expectedScript = new ScriptStub('scriptId')
|
||||
.withName(scriptNameFilter);
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(0).withScript(expectedScript));
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
let actualScript: IScript;
|
||||
sut.filter.filtered.on((result) => {
|
||||
[actualScript] = result.scriptMatches;
|
||||
});
|
||||
sut.filter.setFilter(scriptNameFilter);
|
||||
// assert
|
||||
expect(expectedScript).to.equal(actualScript);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,177 +15,194 @@ import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
|
||||
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
|
||||
|
||||
describe('ApplicationCode', () => {
|
||||
describe('ctor', () => {
|
||||
it('empty when selection is empty', () => {
|
||||
// arrange
|
||||
const selection = new UserSelection(new CategoryCollectionStub(), []);
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
const sut = new ApplicationCode(selection, definition);
|
||||
// act
|
||||
const actual = sut.current;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(0);
|
||||
describe('ctor', () => {
|
||||
it('empty when selection is empty', () => {
|
||||
// arrange
|
||||
const selection = new UserSelection(new CategoryCollectionStub(), []);
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
const sut = new ApplicationCode(selection, definition);
|
||||
// act
|
||||
const actual = sut.current;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(0);
|
||||
});
|
||||
it('generates code from script generator when selection is not empty', () => {
|
||||
// arrange
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const selectedScripts = scripts.map((script) => script.toSelectedScript());
|
||||
const selection = new UserSelection(collection, selectedScripts);
|
||||
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.equal(expected.code);
|
||||
});
|
||||
});
|
||||
describe('changed event', () => {
|
||||
describe('code', () => {
|
||||
it('empty when nothing is selected', () => {
|
||||
// arrange
|
||||
let signaled: ICodeChangedEvent;
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const scriptsToSelect = scripts.map((script) => new SelectedScript(script, false));
|
||||
const selection = new UserSelection(collection, scriptsToSelect);
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
const sut = new ApplicationCode(selection, definition);
|
||||
sut.changed.on((code) => {
|
||||
signaled = code;
|
||||
});
|
||||
it('generates code from script generator when selection is not empty', () => {
|
||||
// arrange
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const collection = new CategoryCollectionStub().withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const selection = new UserSelection(collection, scripts.map((script) => script.toSelectedScript()));
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
const expected: IUserScript = {
|
||||
code: 'expected-code',
|
||||
scriptPositions: new Map(),
|
||||
// act
|
||||
selection.changed.notify([]);
|
||||
// assert
|
||||
expect(signaled.code).to.have.lengthOf(0);
|
||||
expect(signaled.code).to.equal(sut.current);
|
||||
});
|
||||
it('has code when some are selected', () => {
|
||||
// arrange
|
||||
let signaled: ICodeChangedEvent;
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const scriptsToSelect = scripts.map((script) => new SelectedScript(script, false));
|
||||
const selection = new UserSelection(collection, scriptsToSelect);
|
||||
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);
|
||||
expect(signaled.code).to.equal(sut.current);
|
||||
});
|
||||
});
|
||||
describe('calls UserScriptGenerator', () => {
|
||||
it('sends scripting definition to generator', () => {
|
||||
// arrange
|
||||
const expectedDefinition = new ScriptingDefinitionStub();
|
||||
const collection = new CategoryCollectionStub();
|
||||
const selection = new UserSelection(collection, []);
|
||||
const generatorMock: IUserScriptGenerator = {
|
||||
buildCode: (selectedScripts, definition) => {
|
||||
if (definition !== expectedDefinition) {
|
||||
throw new Error('Unexpected scripting definition');
|
||||
}
|
||||
return {
|
||||
code: '',
|
||||
scriptPositions: new Map<SelectedScript, ICodePosition>(),
|
||||
};
|
||||
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.equal(expected.code);
|
||||
});
|
||||
});
|
||||
describe('changed event', () => {
|
||||
describe('code', () => {
|
||||
it('empty when nothing is selected', () => {
|
||||
// arrange
|
||||
let signaled: ICodeChangedEvent;
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const collection = new CategoryCollectionStub().withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const scriptsToSelect = scripts.map((script) => new SelectedScript(script, false));
|
||||
const selection = new UserSelection(collection, scriptsToSelect);
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
const sut = new ApplicationCode(selection, definition);
|
||||
sut.changed.on((code) => signaled = code);
|
||||
// act
|
||||
selection.changed.notify([]);
|
||||
// assert
|
||||
expect(signaled.code).to.have.lengthOf(0);
|
||||
expect(signaled.code).to.equal(sut.current);
|
||||
});
|
||||
it('has code when some are selected', () => {
|
||||
// arrange
|
||||
let signaled: ICodeChangedEvent;
|
||||
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
|
||||
const collection = new CategoryCollectionStub().withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const scriptsToSelect = scripts.map((script) => new SelectedScript(script, false));
|
||||
const selection = new UserSelection(collection, scriptsToSelect);
|
||||
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);
|
||||
expect(signaled.code).to.equal(sut.current);
|
||||
});
|
||||
});
|
||||
describe('calls UserScriptGenerator', () => {
|
||||
it('sends scripting definition to generator', () => {
|
||||
// arrange
|
||||
const expectedDefinition = new ScriptingDefinitionStub();
|
||||
const collection = new CategoryCollectionStub();
|
||||
const selection = new UserSelection(collection, []);
|
||||
const generatorMock: IUserScriptGenerator = {
|
||||
buildCode: (selectedScripts, definition) => {
|
||||
if (definition !== expectedDefinition) {
|
||||
throw new Error('Unexpected scripting definition');
|
||||
}
|
||||
return {
|
||||
code: '',
|
||||
scriptPositions: new Map<SelectedScript, ICodePosition>(),
|
||||
};
|
||||
},
|
||||
};
|
||||
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 collection = new CategoryCollectionStub().withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const scriptsToSelect = scripts.map((script) => new SelectedScript(script, false));
|
||||
const selection = new UserSelection(collection, scriptsToSelect);
|
||||
const generatorMock: IUserScriptGenerator = {
|
||||
buildCode: (selectedScripts) => {
|
||||
if (JSON.stringify(selectedScripts) !== JSON.stringify(scriptsToSelect)) {
|
||||
throw new Error('Unexpected scripts');
|
||||
}
|
||||
return {
|
||||
code: '',
|
||||
scriptPositions: new Map<SelectedScript, ICodePosition>(),
|
||||
};
|
||||
},
|
||||
};
|
||||
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 collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const scriptsToSelect = scripts.map((script) => new SelectedScript(script, false));
|
||||
const selection = new UserSelection(collection, scriptsToSelect);
|
||||
const scriptingDefinition = new ScriptingDefinitionStub();
|
||||
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]));
|
||||
});
|
||||
},
|
||||
};
|
||||
// eslint-disable-next-line no-new
|
||||
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 collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const scriptsToSelect = scripts.map((script) => new SelectedScript(script, false));
|
||||
const selection = new UserSelection(collection, scriptsToSelect);
|
||||
const generatorMock: IUserScriptGenerator = {
|
||||
buildCode: (selectedScripts) => {
|
||||
if (JSON.stringify(selectedScripts) !== JSON.stringify(scriptsToSelect)) {
|
||||
throw new Error('Unexpected scripts');
|
||||
}
|
||||
return {
|
||||
code: '',
|
||||
scriptPositions: new Map<SelectedScript, ICodePosition>(),
|
||||
};
|
||||
},
|
||||
};
|
||||
// eslint-disable-next-line no-new
|
||||
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 collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(1).withScripts(...scripts));
|
||||
const scriptsToSelect = scripts.map((script) => new SelectedScript(script, false));
|
||||
const selection = new UserSelection(collection, scriptsToSelect);
|
||||
const scriptingDefinition = new ScriptingDefinitionStub();
|
||||
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;
|
||||
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');
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,140 +8,204 @@ import { SelectedScriptStub } from '@tests/unit/stubs/SelectedScriptStub';
|
||||
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
|
||||
|
||||
describe('CodeChangedEvent', () => {
|
||||
describe('ctor', () => {
|
||||
describe('position validation', () => {
|
||||
it('throws when code position is out of range', () => {
|
||||
const act = () => new CodeChangedEvent(
|
||||
'singleline code', [], new Map<SelectedScript, ICodePosition>([
|
||||
[ new SelectedScriptStub('1'), new CodePosition(0, 2) ],
|
||||
]),
|
||||
);
|
||||
expect(act).to.throw();
|
||||
});
|
||||
it('does not throw with valid code position', () => {
|
||||
const act = () => new CodeChangedEvent(
|
||||
'singleline code', [], new Map<SelectedScript, ICodePosition>([
|
||||
[ new SelectedScriptStub('1'), new CodePosition(0, 1) ],
|
||||
]),
|
||||
);
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('code returns expected', () => {
|
||||
describe('ctor', () => {
|
||||
describe('position validation', () => {
|
||||
it('throws when code position is out of range', () => {
|
||||
// arrange
|
||||
const expected = 'code';
|
||||
const code = 'singleline code';
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>([
|
||||
[new SelectedScriptStub('1'), new CodePosition(0, 2 /* nonexisting line */)],
|
||||
]);
|
||||
// act
|
||||
const sut = new CodeChangedEvent(
|
||||
expected, [], new Map<SelectedScript, ICodePosition>(),
|
||||
);
|
||||
const actual = sut.code;
|
||||
const act = () => new CodeChangedEventBuilder()
|
||||
.withCode(code)
|
||||
.withNewScripts(newScripts)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('addedScripts', () => {
|
||||
it('returns new scripts when scripts are added', () => {
|
||||
// arrange
|
||||
const expected = [ new ScriptStub('3'), new ScriptStub('4') ];
|
||||
const initialScripts = [ new SelectedScriptStub('1'), new SelectedScriptStub('2') ];
|
||||
expect(act).to.throw();
|
||||
});
|
||||
describe('does not throw with valid code position', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'singleline',
|
||||
code: 'singleline code',
|
||||
position: new CodePosition(0, 1),
|
||||
},
|
||||
{
|
||||
name: 'multiline',
|
||||
code: 'multiline code\nsecond line',
|
||||
position: new CodePosition(0, 2),
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>([
|
||||
[initialScripts[0], new CodePosition(0, 1) ],
|
||||
[initialScripts[1], new CodePosition(0, 1) ],
|
||||
[new SelectedScript(expected[0], false), new CodePosition(0, 1) ],
|
||||
[new SelectedScript(expected[1], false), new CodePosition(0, 1) ],
|
||||
[new SelectedScriptStub('1'), testCase.position],
|
||||
]);
|
||||
// act
|
||||
const sut = new CodeChangedEvent(
|
||||
'code', initialScripts, newScripts,
|
||||
);
|
||||
const actual = sut.addedScripts;
|
||||
const act = () => new CodeChangedEventBuilder()
|
||||
.withCode(testCase.code)
|
||||
.withNewScripts(newScripts)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(2);
|
||||
expect(actual[0]).to.deep.equal(expected[0]);
|
||||
expect(actual[1]).to.deep.equal(expected[1]);
|
||||
});
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('removedScripts', () => {
|
||||
it('returns removed scripts when script are removed', () => {
|
||||
// arrange
|
||||
const existingScripts = [ new SelectedScriptStub('0'), new SelectedScriptStub('1') ];
|
||||
const removedScripts = [ new SelectedScriptStub('2') ];
|
||||
const initialScripts = [ ...existingScripts, ...removedScripts ];
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>([
|
||||
[initialScripts[0], new CodePosition(0, 1) ],
|
||||
[initialScripts[1], new CodePosition(0, 1) ],
|
||||
]);
|
||||
// act
|
||||
const sut = new CodeChangedEvent(
|
||||
'code', initialScripts, newScripts,
|
||||
);
|
||||
const actual = sut.removedScripts;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(removedScripts.length);
|
||||
expect(actual[0]).to.deep.equal(removedScripts[0].script);
|
||||
});
|
||||
});
|
||||
it('code returns expected', () => {
|
||||
// arrange
|
||||
const expected = 'code';
|
||||
const sut = new CodeChangedEventBuilder()
|
||||
.withCode(expected)
|
||||
.build();
|
||||
// act
|
||||
const actual = sut.code;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('addedScripts', () => {
|
||||
it('returns new scripts when scripts are added', () => {
|
||||
// arrange
|
||||
const expected = [new ScriptStub('3'), new ScriptStub('4')];
|
||||
const initialScripts = [new SelectedScriptStub('1'), new SelectedScriptStub('2')];
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>([
|
||||
[initialScripts[0], new CodePosition(0, 1)],
|
||||
[initialScripts[1], new CodePosition(0, 1)],
|
||||
[new SelectedScript(expected[0], false), new CodePosition(0, 1)],
|
||||
[new SelectedScript(expected[1], false), new CodePosition(0, 1)],
|
||||
]);
|
||||
const sut = new CodeChangedEventBuilder()
|
||||
.withOldScripts(initialScripts)
|
||||
.withNewScripts(newScripts)
|
||||
.build();
|
||||
// act
|
||||
const actual = sut.addedScripts;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(2);
|
||||
expect(actual[0]).to.deep.equal(expected[0]);
|
||||
expect(actual[1]).to.deep.equal(expected[1]);
|
||||
});
|
||||
describe('changedScripts', () => {
|
||||
it('returns changed scripts when scripts are changed', () => {
|
||||
// arrange
|
||||
const initialScripts = [ new SelectedScriptStub('1', false), new SelectedScriptStub('2', false) ];
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>([
|
||||
[new SelectedScriptStub('1', true), new CodePosition(0, 1) ],
|
||||
[new SelectedScriptStub('2', false), new CodePosition(0, 1) ],
|
||||
]);
|
||||
// act
|
||||
const sut = new CodeChangedEvent(
|
||||
'code', initialScripts, newScripts,
|
||||
);
|
||||
const actual = sut.changedScripts;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(1);
|
||||
expect(actual[0]).to.deep.equal(initialScripts[0].script);
|
||||
});
|
||||
});
|
||||
describe('removedScripts', () => {
|
||||
it('returns removed scripts when script are removed', () => {
|
||||
// arrange
|
||||
const existingScripts = [new SelectedScriptStub('0'), new SelectedScriptStub('1')];
|
||||
const removedScripts = [new SelectedScriptStub('2')];
|
||||
const initialScripts = [...existingScripts, ...removedScripts];
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>([
|
||||
[initialScripts[0], new CodePosition(0, 1)],
|
||||
[initialScripts[1], new CodePosition(0, 1)],
|
||||
]);
|
||||
const sut = new CodeChangedEventBuilder()
|
||||
.withOldScripts(initialScripts)
|
||||
.withNewScripts(newScripts)
|
||||
.build();
|
||||
// act
|
||||
const actual = sut.removedScripts;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(removedScripts.length);
|
||||
expect(actual[0]).to.deep.equal(removedScripts[0].script);
|
||||
});
|
||||
describe('isEmpty', () => {
|
||||
it('returns true when empty', () => {
|
||||
// arrange
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>();
|
||||
const oldScripts = [ new SelectedScriptStub('1', false) ];
|
||||
const sut = new CodeChangedEvent(
|
||||
'code', oldScripts, newScripts,
|
||||
);
|
||||
// act
|
||||
const actual = sut.isEmpty();
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('returns false when not empty', () => {
|
||||
// arrange
|
||||
const oldScripts = [ new SelectedScriptStub('1') ];
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>( [
|
||||
[oldScripts[0], new CodePosition(0, 1) ],
|
||||
]);
|
||||
const sut = new CodeChangedEvent(
|
||||
'code', oldScripts, newScripts,
|
||||
);
|
||||
// act
|
||||
const actual = sut.isEmpty();
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('changedScripts', () => {
|
||||
it('returns changed scripts when scripts are changed', () => {
|
||||
// arrange
|
||||
const initialScripts = [new SelectedScriptStub('1', false), new SelectedScriptStub('2', false)];
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>([
|
||||
[new SelectedScriptStub('1', true), new CodePosition(0, 1)],
|
||||
[new SelectedScriptStub('2', false), new CodePosition(0, 1)],
|
||||
]);
|
||||
const sut = new CodeChangedEventBuilder()
|
||||
.withOldScripts(initialScripts)
|
||||
.withNewScripts(newScripts)
|
||||
.build();
|
||||
// act
|
||||
const actual = sut.changedScripts;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(1);
|
||||
expect(actual[0]).to.deep.equal(initialScripts[0].script);
|
||||
});
|
||||
describe('getScriptPositionInCode', () => {
|
||||
it('returns expected position for existing script', () => {
|
||||
// arrange
|
||||
const script = new ScriptStub('1');
|
||||
const expected = new CodePosition(0, 1);
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>( [
|
||||
[new SelectedScript(script, false), expected ],
|
||||
]);
|
||||
const sut = new CodeChangedEvent(
|
||||
'code', [], newScripts,
|
||||
);
|
||||
// act
|
||||
const actual = sut.getScriptPositionInCode(script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('isEmpty', () => {
|
||||
it('returns true when empty', () => {
|
||||
// arrange
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>();
|
||||
const oldScripts = [new SelectedScriptStub('1', false)];
|
||||
const sut = new CodeChangedEventBuilder()
|
||||
.withOldScripts(oldScripts)
|
||||
.withNewScripts(newScripts)
|
||||
.build();
|
||||
// act
|
||||
const actual = sut.isEmpty();
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('returns false when not empty', () => {
|
||||
// arrange
|
||||
const oldScripts = [new SelectedScriptStub('1')];
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>([
|
||||
[oldScripts[0], new CodePosition(0, 1)],
|
||||
]);
|
||||
const sut = new CodeChangedEventBuilder()
|
||||
.withOldScripts(oldScripts)
|
||||
.withNewScripts(newScripts)
|
||||
.build();
|
||||
// act
|
||||
const actual = sut.isEmpty();
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('getScriptPositionInCode', () => {
|
||||
it('returns expected position for existing script', () => {
|
||||
// arrange
|
||||
const script = new ScriptStub('1');
|
||||
const expected = new CodePosition(0, 1);
|
||||
const newScripts = new Map<SelectedScript, ICodePosition>([
|
||||
[new SelectedScript(script, false), expected],
|
||||
]);
|
||||
const sut = new CodeChangedEventBuilder()
|
||||
.withNewScripts(newScripts)
|
||||
.build();
|
||||
// act
|
||||
const actual = sut.getScriptPositionInCode(script);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Prefer over original ctor in tests for easier future ctor refactorings
|
||||
class CodeChangedEventBuilder {
|
||||
private code = '[CodeChangedEventBuilder] default code';
|
||||
|
||||
private oldScripts: ReadonlyArray<SelectedScript> = [];
|
||||
|
||||
private newScripts = new Map<SelectedScript, ICodePosition>();
|
||||
|
||||
public withCode(code: string) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withOldScripts(oldScripts: ReadonlyArray<SelectedScript>) {
|
||||
this.oldScripts = oldScripts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withNewScripts(newScripts: Map<SelectedScript, ICodePosition>) {
|
||||
this.newScripts = newScripts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build(): CodeChangedEvent {
|
||||
return new CodeChangedEvent(
|
||||
this.code,
|
||||
this.oldScripts,
|
||||
this.newScripts,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,134 +3,137 @@ import { expect } from 'chai';
|
||||
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
|
||||
|
||||
describe('CodeBuilder', () => {
|
||||
class CodeBuilderConcrete extends CodeBuilder {
|
||||
private commentDelimiter = '//';
|
||||
public withCommentDelimiter(delimiter: string): CodeBuilderConcrete {
|
||||
this.commentDelimiter = delimiter;
|
||||
return this;
|
||||
}
|
||||
protected getCommentDelimiter(): string {
|
||||
return this.commentDelimiter;
|
||||
}
|
||||
protected writeStandardOut(text: string): string {
|
||||
return text;
|
||||
}
|
||||
class CodeBuilderConcrete extends CodeBuilder {
|
||||
private commentDelimiter = '//';
|
||||
|
||||
public withCommentDelimiter(delimiter: string): CodeBuilderConcrete {
|
||||
this.commentDelimiter = delimiter;
|
||||
return this;
|
||||
}
|
||||
describe('appendLine', () => {
|
||||
it('when empty appends empty line', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
// act
|
||||
sut.appendLine().appendLine().appendLine();
|
||||
// assert
|
||||
expect(sut.toString()).to.equal('\n\n');
|
||||
});
|
||||
it('when not empty append string in new line', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
const expected = 'str';
|
||||
// act
|
||||
sut.appendLine()
|
||||
.appendLine(expected);
|
||||
// assert
|
||||
const result = sut.toString();
|
||||
const lines = getLines(result);
|
||||
expect(lines[1]).to.equal('str');
|
||||
});
|
||||
|
||||
protected getCommentDelimiter(): string {
|
||||
return this.commentDelimiter;
|
||||
}
|
||||
|
||||
protected writeStandardOut(text: string): string {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
describe('appendLine', () => {
|
||||
it('when empty appends empty line', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
// act
|
||||
sut.appendLine().appendLine().appendLine();
|
||||
// assert
|
||||
expect(sut.toString()).to.equal('\n\n');
|
||||
});
|
||||
it('appendFunction', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
const functionName = 'function';
|
||||
const code = 'code';
|
||||
// act
|
||||
sut.appendFunction(functionName, code);
|
||||
// assert
|
||||
const result = sut.toString();
|
||||
expect(result).to.include(functionName);
|
||||
expect(result).to.include(code);
|
||||
it('when not empty append string in new line', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
const expected = 'str';
|
||||
// act
|
||||
sut.appendLine()
|
||||
.appendLine(expected);
|
||||
// assert
|
||||
const result = sut.toString();
|
||||
const lines = getLines(result);
|
||||
expect(lines[1]).to.equal('str');
|
||||
});
|
||||
it('appendTrailingHyphensCommentLine', () => {
|
||||
// arrange
|
||||
const commentDelimiter = '//';
|
||||
const sut = new CodeBuilderConcrete()
|
||||
.withCommentDelimiter(commentDelimiter);
|
||||
const totalHyphens = 5;
|
||||
const expected = `${commentDelimiter} ${'-'.repeat(totalHyphens)}`;
|
||||
// act
|
||||
sut.appendTrailingHyphensCommentLine(totalHyphens);
|
||||
// assert
|
||||
const result = sut.toString();
|
||||
const lines = getLines(result);
|
||||
expect(lines[0]).to.equal(expected);
|
||||
});
|
||||
it('appendFunction', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
const functionName = 'function';
|
||||
const code = 'code';
|
||||
// act
|
||||
sut.appendFunction(functionName, code);
|
||||
// assert
|
||||
const result = sut.toString();
|
||||
expect(result).to.include(functionName);
|
||||
expect(result).to.include(code);
|
||||
});
|
||||
it('appendTrailingHyphensCommentLine', () => {
|
||||
// arrange
|
||||
const commentDelimiter = '//';
|
||||
const sut = new CodeBuilderConcrete()
|
||||
.withCommentDelimiter(commentDelimiter);
|
||||
const totalHyphens = 5;
|
||||
const expected = `${commentDelimiter} ${'-'.repeat(totalHyphens)}`;
|
||||
// act
|
||||
sut.appendTrailingHyphensCommentLine(totalHyphens);
|
||||
// assert
|
||||
const result = sut.toString();
|
||||
const lines = getLines(result);
|
||||
expect(lines[0]).to.equal(expected);
|
||||
});
|
||||
it('appendCommentLine', () => {
|
||||
// arrange
|
||||
const commentDelimiter = '//';
|
||||
const sut = new CodeBuilderConcrete()
|
||||
.withCommentDelimiter(commentDelimiter);
|
||||
const comment = 'comment';
|
||||
const expected = `${commentDelimiter} comment`;
|
||||
// act
|
||||
const result = sut
|
||||
.appendCommentLine(comment)
|
||||
.toString();
|
||||
// assert
|
||||
const lines = getLines(result);
|
||||
expect(lines[0]).to.equal(expected);
|
||||
});
|
||||
it('appendCommentLineWithHyphensAround', () => {
|
||||
// arrange
|
||||
const commentDelimiter = '//';
|
||||
const sut = new CodeBuilderConcrete()
|
||||
.withCommentDelimiter(commentDelimiter);
|
||||
const sectionName = 'section';
|
||||
const totalHyphens = sectionName.length + 3 * 2;
|
||||
const expected = `${commentDelimiter} ---section---`;
|
||||
// act
|
||||
const result = sut
|
||||
.appendCommentLineWithHyphensAround(sectionName, totalHyphens)
|
||||
.toString();
|
||||
// assert
|
||||
const lines = getLines(result);
|
||||
expect(lines[1]).to.equal(expected);
|
||||
});
|
||||
describe('currentLine', () => {
|
||||
it('no lines returns zero', () => {
|
||||
// arrange & act
|
||||
const sut = new CodeBuilderConcrete();
|
||||
// assert
|
||||
expect(sut.currentLine).to.equal(0);
|
||||
});
|
||||
it('appendCommentLine', () => {
|
||||
// arrange
|
||||
const commentDelimiter = '//';
|
||||
const sut = new CodeBuilderConcrete()
|
||||
.withCommentDelimiter(commentDelimiter);
|
||||
const comment = 'comment';
|
||||
const expected = `${commentDelimiter} comment`;
|
||||
// act
|
||||
const result = sut
|
||||
.appendCommentLine(comment)
|
||||
.toString();
|
||||
// assert
|
||||
const lines = getLines(result);
|
||||
expect(lines[0]).to.equal(expected);
|
||||
it('single line returns one', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
// act
|
||||
sut.appendLine();
|
||||
// assert
|
||||
expect(sut.currentLine).to.equal(1);
|
||||
});
|
||||
it('appendCommentLineWithHyphensAround', () => {
|
||||
// arrange
|
||||
const commentDelimiter = '//';
|
||||
const sut = new CodeBuilderConcrete()
|
||||
.withCommentDelimiter(commentDelimiter);
|
||||
const sectionName = 'section';
|
||||
const totalHyphens = sectionName.length + 3 * 2;
|
||||
const expected = `${commentDelimiter} ---section---`;
|
||||
// act
|
||||
const result = sut
|
||||
.appendCommentLineWithHyphensAround(sectionName, totalHyphens)
|
||||
.toString();
|
||||
// assert
|
||||
const lines = getLines(result);
|
||||
expect(lines[1]).to.equal(expected);
|
||||
it('multiple lines returns as expected', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
// act
|
||||
sut.appendLine('1')
|
||||
.appendCommentLine('2')
|
||||
.appendLine();
|
||||
// assert
|
||||
expect(sut.currentLine).to.equal(3);
|
||||
});
|
||||
describe('currentLine', () => {
|
||||
it('no lines returns zero', () => {
|
||||
// arrange & act
|
||||
const sut = new CodeBuilderConcrete();
|
||||
// assert
|
||||
expect(sut.currentLine).to.equal(0);
|
||||
});
|
||||
it('single line returns one', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
// act
|
||||
sut.appendLine();
|
||||
// assert
|
||||
expect(sut.currentLine).to.equal(1);
|
||||
});
|
||||
it('multiple lines returns as expected', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
// act
|
||||
sut.appendLine('1')
|
||||
.appendCommentLine('2')
|
||||
.appendLine();
|
||||
// assert
|
||||
expect(sut.currentLine).to.equal(3);
|
||||
});
|
||||
it('multiple lines in code', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
// act
|
||||
sut.appendLine('hello\ncode-here\nwith-3-lines');
|
||||
// assert
|
||||
expect(sut.currentLine).to.equal(3);
|
||||
});
|
||||
it('multiple lines in code', () => {
|
||||
// arrange
|
||||
const sut = new CodeBuilderConcrete();
|
||||
// act
|
||||
sut.appendLine('hello\ncode-here\nwith-3-lines');
|
||||
// assert
|
||||
expect(sut.currentLine).to.equal(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getLines(text: string): string[] {
|
||||
return text.split(/\r\n|\r|\n/);
|
||||
return text.split(/\r\n|\r|\n/);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/
|
||||
import { ScriptingLanguageFactoryTestRunner } from '@tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner';
|
||||
|
||||
describe('CodeBuilderFactory', () => {
|
||||
const sut = new CodeBuilderFactory();
|
||||
const runner = new ScriptingLanguageFactoryTestRunner()
|
||||
.expect(ScriptingLanguage.shellscript, ShellBuilder)
|
||||
.expect(ScriptingLanguage.batchfile, BatchBuilder);
|
||||
runner.testCreateMethod(sut);
|
||||
const sut = new CodeBuilderFactory();
|
||||
const runner = new ScriptingLanguageFactoryTestRunner()
|
||||
.expect(ScriptingLanguage.shellscript, ShellBuilder)
|
||||
.expect(ScriptingLanguage.batchfile, BatchBuilder);
|
||||
runner.testCreateMethod(sut);
|
||||
});
|
||||
|
||||
@@ -3,57 +3,58 @@ import { expect } from 'chai';
|
||||
import { BatchBuilder } from '@/application/Context/State/Code/Generation/Languages/BatchBuilder';
|
||||
|
||||
describe('BatchBuilder', () => {
|
||||
class BatchBuilderRevealer extends BatchBuilder {
|
||||
public getCommentDelimiter(): string {
|
||||
return super.getCommentDelimiter();
|
||||
}
|
||||
public writeStandardOut(text: string): string {
|
||||
return super.writeStandardOut(text);
|
||||
}
|
||||
class BatchBuilderRevealer extends BatchBuilder {
|
||||
public getCommentDelimiter(): string {
|
||||
return super.getCommentDelimiter();
|
||||
}
|
||||
describe('getCommentDelimiter', () => {
|
||||
it('returns expected', () => {
|
||||
// arrange
|
||||
const expected = '::';
|
||||
const sut = new BatchBuilderRevealer();
|
||||
// act
|
||||
const actual = sut.getCommentDelimiter();
|
||||
// assert
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
});
|
||||
describe('writeStandardOut', () => {
|
||||
const testData = [
|
||||
{
|
||||
name: 'plain text',
|
||||
text: 'test',
|
||||
expected: 'echo test',
|
||||
},
|
||||
{
|
||||
name: 'text with ampersand',
|
||||
text: 'a & b',
|
||||
expected: 'echo a ^& b',
|
||||
},
|
||||
{
|
||||
name: 'text with percent sign',
|
||||
text: '90%',
|
||||
expected: 'echo 90%%',
|
||||
},
|
||||
{
|
||||
name: 'text with multiple ampersands and percent signs',
|
||||
text: 'Me&you in % ? You & me = 0%',
|
||||
expected: 'echo Me^&you in %% ? You ^& me = 0%%',
|
||||
},
|
||||
];
|
||||
for (const test of testData) {
|
||||
it(test.name, () => {
|
||||
// arrange
|
||||
const sut = new BatchBuilderRevealer();
|
||||
// act
|
||||
const actual = sut.writeStandardOut(test.text);
|
||||
// assert
|
||||
expect(test.expected).to.equal(actual);
|
||||
});
|
||||
}
|
||||
|
||||
public writeStandardOut(text: string): string {
|
||||
return super.writeStandardOut(text);
|
||||
}
|
||||
}
|
||||
describe('getCommentDelimiter', () => {
|
||||
it('returns expected', () => {
|
||||
// arrange
|
||||
const expected = '::';
|
||||
const sut = new BatchBuilderRevealer();
|
||||
// act
|
||||
const actual = sut.getCommentDelimiter();
|
||||
// assert
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
});
|
||||
describe('writeStandardOut', () => {
|
||||
const testData = [
|
||||
{
|
||||
name: 'plain text',
|
||||
text: 'test',
|
||||
expected: 'echo test',
|
||||
},
|
||||
{
|
||||
name: 'text with ampersand',
|
||||
text: 'a & b',
|
||||
expected: 'echo a ^& b',
|
||||
},
|
||||
{
|
||||
name: 'text with percent sign',
|
||||
text: '90%',
|
||||
expected: 'echo 90%%',
|
||||
},
|
||||
{
|
||||
name: 'text with multiple ampersands and percent signs',
|
||||
text: 'Me&you in % ? You & me = 0%',
|
||||
expected: 'echo Me^&you in %% ? You ^& me = 0%%',
|
||||
},
|
||||
];
|
||||
for (const test of testData) {
|
||||
it(test.name, () => {
|
||||
// arrange
|
||||
const sut = new BatchBuilderRevealer();
|
||||
// act
|
||||
const actual = sut.writeStandardOut(test.text);
|
||||
// assert
|
||||
expect(test.expected).to.equal(actual);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,52 +3,53 @@ import { expect } from 'chai';
|
||||
import { ShellBuilder } from '@/application/Context/State/Code/Generation/Languages/ShellBuilder';
|
||||
|
||||
describe('ShellBuilder', () => {
|
||||
class ShellBuilderRevealer extends ShellBuilder {
|
||||
public getCommentDelimiter(): string {
|
||||
return super.getCommentDelimiter();
|
||||
}
|
||||
public writeStandardOut(text: string): string {
|
||||
return super.writeStandardOut(text);
|
||||
}
|
||||
class ShellBuilderRevealer extends ShellBuilder {
|
||||
public getCommentDelimiter(): string {
|
||||
return super.getCommentDelimiter();
|
||||
}
|
||||
describe('getCommentDelimiter', () => {
|
||||
it('returns expected', () => {
|
||||
// arrange
|
||||
const expected = '#';
|
||||
const sut = new ShellBuilderRevealer();
|
||||
// act
|
||||
const actual = sut.getCommentDelimiter();
|
||||
// assert
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
});
|
||||
describe('writeStandardOut', () => {
|
||||
const testData = [
|
||||
{
|
||||
name: 'plain text',
|
||||
text: 'test',
|
||||
expected: 'echo \'test\'',
|
||||
},
|
||||
{
|
||||
name: 'text with single quote',
|
||||
text: 'I\'m not who you think I am',
|
||||
expected: 'echo \'I\'\\\'\'m not who you think I am\'',
|
||||
},
|
||||
{
|
||||
name: 'text with multiple single quotes',
|
||||
text: 'I\'m what you\'re',
|
||||
expected: 'echo \'I\'\\\'\'m what you\'\\\'\'re\'',
|
||||
},
|
||||
];
|
||||
for (const test of testData) {
|
||||
it(test.name, () => {
|
||||
// arrange
|
||||
const sut = new ShellBuilderRevealer();
|
||||
// act
|
||||
const actual = sut.writeStandardOut(test.text);
|
||||
// assert
|
||||
expect(test.expected).to.equal(actual);
|
||||
});
|
||||
}
|
||||
|
||||
public writeStandardOut(text: string): string {
|
||||
return super.writeStandardOut(text);
|
||||
}
|
||||
}
|
||||
describe('getCommentDelimiter', () => {
|
||||
it('returns expected', () => {
|
||||
// arrange
|
||||
const expected = '#';
|
||||
const sut = new ShellBuilderRevealer();
|
||||
// act
|
||||
const actual = sut.getCommentDelimiter();
|
||||
// assert
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
});
|
||||
describe('writeStandardOut', () => {
|
||||
const testData = [
|
||||
{
|
||||
name: 'plain text',
|
||||
text: 'test',
|
||||
expected: 'echo \'test\'',
|
||||
},
|
||||
{
|
||||
name: 'text with single quote',
|
||||
text: 'I\'m not who you think I am',
|
||||
expected: 'echo \'I\'\\\'\'m not who you think I am\'',
|
||||
},
|
||||
{
|
||||
name: 'text with multiple single quotes',
|
||||
text: 'I\'m what you\'re',
|
||||
expected: 'echo \'I\'\\\'\'m what you\'\\\'\'re\'',
|
||||
},
|
||||
];
|
||||
for (const test of testData) {
|
||||
it(test.name, () => {
|
||||
// arrange
|
||||
const sut = new ShellBuilderRevealer();
|
||||
// act
|
||||
const actual = sut.writeStandardOut(test.text);
|
||||
// assert
|
||||
expect(test.expected).to.equal(actual);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,230 +8,239 @@ import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
|
||||
import { ScriptingDefinitionStub } from '@tests/unit/stubs/ScriptingDefinitionStub';
|
||||
|
||||
describe('UserScriptGenerator', () => {
|
||||
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 codeBuilderStub = new CodeBuilderStub();
|
||||
const sut = new UserScriptGenerator(mockCodeBuilderFactory(codeBuilderStub));
|
||||
const script = new ScriptStub('id')
|
||||
.withCode('code\nmulti-lined')
|
||||
.toSelectedScript();
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withStartCode(undefined)
|
||||
.withEndCode(undefined);
|
||||
const expectedStart = codeBuilderStub
|
||||
.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 codeBuilderStub = new CodeBuilderStub();
|
||||
const sut = new UserScriptGenerator(mockCodeBuilderFactory(codeBuilderStub));
|
||||
const script = new ScriptStub('id')
|
||||
.withCode('code\nmulti-lined')
|
||||
.toSelectedScript();
|
||||
const expectedEnd = codeBuilderStub
|
||||
.appendFunction(script.script.name, script.script.code.execute)
|
||||
.toString();
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withEndCode(undefined);
|
||||
// act
|
||||
const code = sut.buildCode([script], definition);
|
||||
// assert
|
||||
const actual = code.code;
|
||||
expect(actual.endsWith(expectedEnd));
|
||||
});
|
||||
});
|
||||
});
|
||||
it('appends revert script', () => {
|
||||
describe('scriptingDefinition', () => {
|
||||
describe('startCode', () => {
|
||||
it('is prepended if not empty', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const scriptName = 'test non-revert script';
|
||||
const scriptCode = 'REM nop';
|
||||
const startCode = 'Start\nCode';
|
||||
const script = new ScriptStub('id')
|
||||
.withName(scriptName)
|
||||
.withRevertCode(scriptCode)
|
||||
.toSelectedScript(true);
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
.withCode('code\nmulti-lined')
|
||||
.toSelectedScript();
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withStartCode(startCode)
|
||||
.withEndCode(undefined);
|
||||
const expectedStart = `${startCode}\n`;
|
||||
// act
|
||||
const actual = sut.buildCode([ script ], definition);
|
||||
const code = sut.buildCode([script], definition);
|
||||
// assert
|
||||
expect(actual.code).to.include(`${scriptName} (revert)`);
|
||||
expect(actual.code).to.include(scriptCode);
|
||||
});
|
||||
it('appends non-revert script', () => {
|
||||
const sut = new UserScriptGenerator();
|
||||
const actual = code.code;
|
||||
expect(actual.startsWith(expectedStart));
|
||||
});
|
||||
it('is not prepended if empty', () => {
|
||||
// arrange
|
||||
const scriptName = 'test non-revert script';
|
||||
const scriptCode = 'REM nop';
|
||||
const script = new ScriptStub('id').withName(scriptName).withCode(scriptCode);
|
||||
const selectedScripts = [ new SelectedScript(script, false)];
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
const codeBuilderStub = new CodeBuilderStub();
|
||||
const sut = new UserScriptGenerator(mockCodeBuilderFactory(codeBuilderStub));
|
||||
const script = new ScriptStub('id')
|
||||
.withCode('code\nmulti-lined')
|
||||
.toSelectedScript();
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withStartCode(undefined)
|
||||
.withEndCode(undefined);
|
||||
const expectedStart = codeBuilderStub
|
||||
.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 codeBuilderStub = new CodeBuilderStub();
|
||||
const sut = new UserScriptGenerator(mockCodeBuilderFactory(codeBuilderStub));
|
||||
const script = new ScriptStub('id')
|
||||
.withCode('code\nmulti-lined')
|
||||
.toSelectedScript();
|
||||
const expectedEnd = codeBuilderStub
|
||||
.appendFunction(script.script.name, script.script.code.execute)
|
||||
.toString();
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withEndCode(undefined);
|
||||
// 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)
|
||||
.toSelectedScript(true);
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
// act
|
||||
const actual = sut.buildCode([script], definition);
|
||||
// assert
|
||||
expect(actual.code).to.include(`${scriptName} (revert)`);
|
||||
expect(actual.code).to.include(scriptCode);
|
||||
});
|
||||
it('appends non-revert script', () => {
|
||||
const sut = new UserScriptGenerator();
|
||||
// arrange
|
||||
const scriptName = 'test non-revert script';
|
||||
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, 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('without script; returns empty', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const selectedScripts = [];
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
// act
|
||||
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
|
||||
expect(actual.code).to.include(scriptName);
|
||||
expect(actual.code).to.not.include(`${scriptName} (revert)`);
|
||||
expect(actual.code).to.include(scriptCode);
|
||||
});
|
||||
describe('scriptPositions', () => {
|
||||
it('without script; returns empty', () => {
|
||||
// arrange
|
||||
const sut = new UserScriptGenerator();
|
||||
const selectedScripts = [ ];
|
||||
const definition = new ScriptingDefinitionStub();
|
||||
// act
|
||||
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)');
|
||||
});
|
||||
});
|
||||
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)');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockCodeBuilderFactory(mock: ICodeBuilder): ICodeBuilderFactory {
|
||||
return {
|
||||
create: () => mock,
|
||||
};
|
||||
return {
|
||||
create: () => mock,
|
||||
};
|
||||
}
|
||||
|
||||
class CodeBuilderStub implements ICodeBuilder {
|
||||
public currentLine = 0;
|
||||
private text = '';
|
||||
public appendLine(code?: string): ICodeBuilder {
|
||||
this.text += this.text ? `${code}\n` : code;
|
||||
this.currentLine++;
|
||||
return this;
|
||||
}
|
||||
public appendTrailingHyphensCommentLine(totalRepeatHyphens: number): ICodeBuilder {
|
||||
return this.appendLine(`trailing-hyphens-${totalRepeatHyphens}`);
|
||||
}
|
||||
public appendCommentLine(commentLine?: string): ICodeBuilder {
|
||||
return this.appendLine(`Comment | ${commentLine}`);
|
||||
}
|
||||
public appendCommentLineWithHyphensAround(sectionName: string, totalRepeatHyphens: number): ICodeBuilder {
|
||||
return this.appendLine(`hyphens-around-${totalRepeatHyphens} | Section name: ${sectionName} | hyphens-around-${totalRepeatHyphens}`);
|
||||
}
|
||||
public appendFunction(name: string, code: string): ICodeBuilder {
|
||||
return this
|
||||
.appendLine(`Function | Name: ${name}`)
|
||||
.appendLine(`Function | Code: ${code}`);
|
||||
}
|
||||
public toString(): string {
|
||||
return this.text;
|
||||
}
|
||||
public currentLine = 0;
|
||||
|
||||
private text = '';
|
||||
|
||||
public appendLine(code?: string): ICodeBuilder {
|
||||
this.text += this.text ? `${code}\n` : code;
|
||||
this.currentLine++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public appendTrailingHyphensCommentLine(totalRepeatHyphens: number): ICodeBuilder {
|
||||
return this.appendLine(`trailing-hyphens-${totalRepeatHyphens}`);
|
||||
}
|
||||
|
||||
public appendCommentLine(commentLine?: string): ICodeBuilder {
|
||||
return this.appendLine(`Comment | ${commentLine}`);
|
||||
}
|
||||
|
||||
public appendCommentLineWithHyphensAround(
|
||||
sectionName: string,
|
||||
totalRepeatHyphens: number,
|
||||
): ICodeBuilder {
|
||||
return this.appendLine(`hyphens-around-${totalRepeatHyphens} | Section name: ${sectionName} | hyphens-around-${totalRepeatHyphens}`);
|
||||
}
|
||||
|
||||
public appendFunction(name: string, code: string): ICodeBuilder {
|
||||
return this
|
||||
.appendLine(`Function | Name: ${name}`)
|
||||
.appendLine(`Function | Code: ${code}`);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,52 +3,52 @@ import { expect } from 'chai';
|
||||
import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition';
|
||||
|
||||
describe('CodePosition', () => {
|
||||
describe('ctor', () => {
|
||||
it('creates with valid parameters', () => {
|
||||
// arrange
|
||||
const startPosition = 0;
|
||||
const endPosition = 5;
|
||||
// act
|
||||
const sut = new CodePosition(startPosition, endPosition);
|
||||
// assert
|
||||
expect(sut.startLine).to.equal(startPosition);
|
||||
expect(sut.endLine).to.equal(endPosition);
|
||||
});
|
||||
it('throws with negative start position', () => {
|
||||
// arrange
|
||||
const startPosition = -1;
|
||||
const endPosition = 5;
|
||||
// act
|
||||
const getSut = () => new CodePosition(startPosition, endPosition);
|
||||
// assert
|
||||
expect(getSut).to.throw('Code cannot start in a negative line');
|
||||
});
|
||||
it('throws with negative end position', () => {
|
||||
// arrange
|
||||
const startPosition = 1;
|
||||
const endPosition = -5;
|
||||
// act
|
||||
const getSut = () => new CodePosition(startPosition, endPosition);
|
||||
// assert
|
||||
expect(getSut).to.throw('Code cannot end in a negative line');
|
||||
});
|
||||
it('throws when start and end position is same', () => {
|
||||
// arrange
|
||||
const startPosition = 0;
|
||||
const endPosition = 0;
|
||||
// act
|
||||
const getSut = () => new CodePosition(startPosition, endPosition);
|
||||
// assert
|
||||
expect(getSut).to.throw('Empty code');
|
||||
});
|
||||
it('throws when ends before start', () => {
|
||||
// arrange
|
||||
const startPosition = 3;
|
||||
const endPosition = 2;
|
||||
// act
|
||||
const getSut = () => new CodePosition(startPosition, endPosition);
|
||||
// assert
|
||||
expect(getSut).to.throw('End line cannot be less than start line');
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('creates with valid parameters', () => {
|
||||
// arrange
|
||||
const startPosition = 0;
|
||||
const endPosition = 5;
|
||||
// act
|
||||
const sut = new CodePosition(startPosition, endPosition);
|
||||
// assert
|
||||
expect(sut.startLine).to.equal(startPosition);
|
||||
expect(sut.endLine).to.equal(endPosition);
|
||||
});
|
||||
it('throws with negative start position', () => {
|
||||
// arrange
|
||||
const startPosition = -1;
|
||||
const endPosition = 5;
|
||||
// act
|
||||
const getSut = () => new CodePosition(startPosition, endPosition);
|
||||
// assert
|
||||
expect(getSut).to.throw('Code cannot start in a negative line');
|
||||
});
|
||||
it('throws with negative end position', () => {
|
||||
// arrange
|
||||
const startPosition = 1;
|
||||
const endPosition = -5;
|
||||
// act
|
||||
const getSut = () => new CodePosition(startPosition, endPosition);
|
||||
// assert
|
||||
expect(getSut).to.throw('Code cannot end in a negative line');
|
||||
});
|
||||
it('throws when start and end position is same', () => {
|
||||
// arrange
|
||||
const startPosition = 0;
|
||||
const endPosition = 0;
|
||||
// act
|
||||
const getSut = () => new CodePosition(startPosition, endPosition);
|
||||
// assert
|
||||
expect(getSut).to.throw('Empty code');
|
||||
});
|
||||
it('throws when ends before start', () => {
|
||||
// arrange
|
||||
const startPosition = 3;
|
||||
const endPosition = 2;
|
||||
// act
|
||||
const getSut = () => new CodePosition(startPosition, endPosition);
|
||||
// assert
|
||||
expect(getSut).to.throw('End line cannot be less than start line');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,42 +5,42 @@ import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
|
||||
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
|
||||
|
||||
describe('FilterResult', () => {
|
||||
describe('hasAnyMatches', () => {
|
||||
it('false when no matches', () => {
|
||||
const sut = new FilterResult(
|
||||
/* scriptMatches */ [],
|
||||
/* categoryMatches */ [],
|
||||
'query',
|
||||
);
|
||||
const actual = sut.hasAnyMatches();
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
it('true when script matches', () => {
|
||||
const sut = new FilterResult(
|
||||
/* scriptMatches */ [ new ScriptStub('id') ],
|
||||
/* categoryMatches */ [],
|
||||
'query',
|
||||
);
|
||||
const actual = sut.hasAnyMatches();
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('true when category matches', () => {
|
||||
const sut = new FilterResult(
|
||||
/* scriptMatches */ [ ],
|
||||
/* categoryMatches */ [ new CategoryStub(5) ],
|
||||
'query',
|
||||
);
|
||||
const actual = sut.hasAnyMatches();
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('true when script + category matches', () => {
|
||||
const sut = new FilterResult(
|
||||
/* scriptMatches */ [ new ScriptStub('id') ],
|
||||
/* categoryMatches */ [ new CategoryStub(5) ],
|
||||
'query',
|
||||
);
|
||||
const actual = sut.hasAnyMatches();
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
describe('hasAnyMatches', () => {
|
||||
it('false when no matches', () => {
|
||||
const sut = new FilterResult(
|
||||
/* scriptMatches */ [],
|
||||
/* categoryMatches */ [],
|
||||
'query',
|
||||
);
|
||||
const actual = sut.hasAnyMatches();
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
it('true when script matches', () => {
|
||||
const sut = new FilterResult(
|
||||
/* scriptMatches */ [new ScriptStub('id')],
|
||||
/* categoryMatches */ [],
|
||||
'query',
|
||||
);
|
||||
const actual = sut.hasAnyMatches();
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('true when category matches', () => {
|
||||
const sut = new FilterResult(
|
||||
/* scriptMatches */ [],
|
||||
/* categoryMatches */ [new CategoryStub(5)],
|
||||
'query',
|
||||
);
|
||||
const actual = sut.hasAnyMatches();
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('true when script + category matches', () => {
|
||||
const sut = new FilterResult(
|
||||
/* scriptMatches */ [new ScriptStub('id')],
|
||||
/* categoryMatches */ [new CategoryStub(5)],
|
||||
'query',
|
||||
);
|
||||
const actual = sut.hasAnyMatches();
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,158 +7,172 @@ import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
|
||||
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
|
||||
|
||||
describe('UserFilter', () => {
|
||||
describe('removeFilter', () => {
|
||||
it('signals when removing filter', () => {
|
||||
// arrange
|
||||
let isCalled = false;
|
||||
const sut = new UserFilter(new CategoryCollectionStub());
|
||||
sut.filterRemoved.on(() => isCalled = true);
|
||||
// act
|
||||
sut.removeFilter();
|
||||
// assert
|
||||
expect(isCalled).to.be.equal(true);
|
||||
});
|
||||
it('sets currentFilter to undefined', () => {
|
||||
// arrange
|
||||
const sut = new UserFilter(new CategoryCollectionStub());
|
||||
// act
|
||||
sut.setFilter('non-important');
|
||||
sut.removeFilter();
|
||||
// assert
|
||||
expect(sut.currentFilter).to.be.equal(undefined);
|
||||
});
|
||||
describe('removeFilter', () => {
|
||||
it('signals when removing filter', () => {
|
||||
// arrange
|
||||
let isCalled = false;
|
||||
const sut = new UserFilter(new CategoryCollectionStub());
|
||||
sut.filterRemoved.on(() => {
|
||||
isCalled = true;
|
||||
});
|
||||
// act
|
||||
sut.removeFilter();
|
||||
// assert
|
||||
expect(isCalled).to.be.equal(true);
|
||||
});
|
||||
describe('setFilter', () => {
|
||||
it('signals when no matches', () => {
|
||||
// arrange
|
||||
let actual: IFilterResult;
|
||||
const nonMatchingFilter = 'non matching filter';
|
||||
const sut = new UserFilter(new CategoryCollectionStub());
|
||||
sut.filtered.on((filterResult) => actual = filterResult);
|
||||
// act
|
||||
sut.setFilter(nonMatchingFilter);
|
||||
// assert
|
||||
expect(actual.hasAnyMatches()).be.equal(false);
|
||||
expect(actual.query).to.equal(nonMatchingFilter);
|
||||
});
|
||||
it('sets currentFilter as expected when no matches', () => {
|
||||
// arrange
|
||||
const nonMatchingFilter = 'non matching filter';
|
||||
const sut = new UserFilter(new CategoryCollectionStub());
|
||||
// act
|
||||
sut.setFilter(nonMatchingFilter);
|
||||
// assert
|
||||
const actual = sut.currentFilter;
|
||||
expect(actual.hasAnyMatches()).be.equal(false);
|
||||
expect(actual.query).to.equal(nonMatchingFilter);
|
||||
});
|
||||
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 CategoryCollectionStub()
|
||||
.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 CategoryCollectionStub()
|
||||
.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 CategoryCollectionStub()
|
||||
.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';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
const category = new CategoryStub(55).withName(categoryName);
|
||||
const sut = new UserFilter(new CategoryCollectionStub()
|
||||
.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(1);
|
||||
expect(actual.categoryMatches[0]).to.deep.equal(category);
|
||||
expect(actual.scriptMatches).to.have.lengthOf(0);
|
||||
expect(actual.query).to.equal(filter);
|
||||
expect(sut.currentFilter).to.deep.equal(actual);
|
||||
});
|
||||
it('signals when category and script matches', () => {
|
||||
// arrange
|
||||
const matchingText = 'HELLO world';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
const script = new ScriptStub('script')
|
||||
.withName(matchingText);
|
||||
const category = new CategoryStub(55)
|
||||
.withName(matchingText)
|
||||
.withScript(script);
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(category);
|
||||
const sut = new UserFilter(collection);
|
||||
sut.filtered.on((filterResult) => actual = filterResult);
|
||||
// act
|
||||
sut.setFilter(filter);
|
||||
// assert
|
||||
expect(actual.hasAnyMatches()).be.equal(true);
|
||||
expect(actual.categoryMatches).to.have.lengthOf(1);
|
||||
expect(actual.categoryMatches[0]).to.deep.equal(category);
|
||||
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('sets currentFilter to undefined', () => {
|
||||
// arrange
|
||||
const sut = new UserFilter(new CategoryCollectionStub());
|
||||
// act
|
||||
sut.setFilter('non-important');
|
||||
sut.removeFilter();
|
||||
// assert
|
||||
expect(sut.currentFilter).to.be.equal(undefined);
|
||||
});
|
||||
});
|
||||
describe('setFilter', () => {
|
||||
it('signals when no matches', () => {
|
||||
// arrange
|
||||
let actual: IFilterResult;
|
||||
const nonMatchingFilter = 'non matching filter';
|
||||
const sut = new UserFilter(new CategoryCollectionStub());
|
||||
sut.filtered.on((filterResult) => {
|
||||
actual = filterResult;
|
||||
});
|
||||
// act
|
||||
sut.setFilter(nonMatchingFilter);
|
||||
// assert
|
||||
expect(actual.hasAnyMatches()).be.equal(false);
|
||||
expect(actual.query).to.equal(nonMatchingFilter);
|
||||
});
|
||||
it('sets currentFilter as expected when no matches', () => {
|
||||
// arrange
|
||||
const nonMatchingFilter = 'non matching filter';
|
||||
const sut = new UserFilter(new CategoryCollectionStub());
|
||||
// act
|
||||
sut.setFilter(nonMatchingFilter);
|
||||
// assert
|
||||
const actual = sut.currentFilter;
|
||||
expect(actual.hasAnyMatches()).be.equal(false);
|
||||
expect(actual.query).to.equal(nonMatchingFilter);
|
||||
});
|
||||
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 CategoryCollectionStub()
|
||||
.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 CategoryCollectionStub()
|
||||
.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 CategoryCollectionStub()
|
||||
.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';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
const category = new CategoryStub(55).withName(categoryName);
|
||||
const sut = new UserFilter(new CategoryCollectionStub()
|
||||
.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(1);
|
||||
expect(actual.categoryMatches[0]).to.deep.equal(category);
|
||||
expect(actual.scriptMatches).to.have.lengthOf(0);
|
||||
expect(actual.query).to.equal(filter);
|
||||
expect(sut.currentFilter).to.deep.equal(actual);
|
||||
});
|
||||
it('signals when category and script matches', () => {
|
||||
// arrange
|
||||
const matchingText = 'HELLO world';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
const script = new ScriptStub('script')
|
||||
.withName(matchingText);
|
||||
const category = new CategoryStub(55)
|
||||
.withName(matchingText)
|
||||
.withScript(script);
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(category);
|
||||
const sut = new UserFilter(collection);
|
||||
sut.filtered.on((filterResult) => {
|
||||
actual = filterResult;
|
||||
});
|
||||
// act
|
||||
sut.setFilter(filter);
|
||||
// assert
|
||||
expect(actual.hasAnyMatches()).be.equal(true);
|
||||
expect(actual.categoryMatches).to.have.lengthOf(1);
|
||||
expect(actual.categoryMatches[0]).to.deep.equal(category);
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,25 +4,25 @@ import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
|
||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||
|
||||
describe('SelectedScript', () => {
|
||||
it('id is same as script id', () => {
|
||||
// arrange
|
||||
const expectedId = 'scriptId';
|
||||
const script = new ScriptStub(expectedId);
|
||||
const sut = new SelectedScript(script, false);
|
||||
// act
|
||||
const actualId = sut.id;
|
||||
// assert
|
||||
expect(actualId).to.equal(expectedId);
|
||||
});
|
||||
it('throws when revert is true for irreversible script', () => {
|
||||
// arrange
|
||||
const expectedId = 'scriptId';
|
||||
const script = new ScriptStub(expectedId)
|
||||
.withRevertCode(undefined);
|
||||
// act
|
||||
function construct() { new SelectedScript(script, true); }
|
||||
// assert
|
||||
expect(construct).to.throw('cannot revert an irreversible script');
|
||||
});
|
||||
|
||||
it('id is same as script id', () => {
|
||||
// arrange
|
||||
const expectedId = 'scriptId';
|
||||
const script = new ScriptStub(expectedId);
|
||||
const sut = new SelectedScript(script, false);
|
||||
// act
|
||||
const actualId = sut.id;
|
||||
// assert
|
||||
expect(actualId).to.equal(expectedId);
|
||||
});
|
||||
it('throws when revert is true for irreversible script', () => {
|
||||
// arrange
|
||||
const expectedId = 'scriptId';
|
||||
const script = new ScriptStub(expectedId)
|
||||
.withRevertCode(undefined);
|
||||
// act
|
||||
// eslint-disable-next-line no-new
|
||||
function construct() { new SelectedScript(script, true); }
|
||||
// assert
|
||||
expect(construct).to.throw('cannot revert an irreversible script');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,390 +9,394 @@ import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
|
||||
import { UserSelectionTestRunner } from './UserSelectionTestRunner';
|
||||
|
||||
describe('UserSelection', () => {
|
||||
describe('ctor', () => {
|
||||
describe('has nothing with no initial selection', () => {
|
||||
// arrange
|
||||
const allScripts = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([])
|
||||
.withCategory(1, allScripts.map((s) => s.script))
|
||||
// act
|
||||
.run()
|
||||
// assert
|
||||
.expectFinalScripts([]);
|
||||
});
|
||||
describe('has initial selection', () => {
|
||||
// arrange
|
||||
const scripts = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
new SelectedScriptStub('s2', false),
|
||||
];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(scripts)
|
||||
.withCategory(1, scripts.map((s) => s.script))
|
||||
// act
|
||||
.run()
|
||||
// assert
|
||||
.expectFinalScripts(scripts);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('has nothing with no initial selection', () => {
|
||||
// arrange
|
||||
const allScripts = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([])
|
||||
.withCategory(1, allScripts.map((s) => s.script))
|
||||
// act
|
||||
.run()
|
||||
// assert
|
||||
.expectFinalScripts([]);
|
||||
});
|
||||
describe('deselectAll removes all items', () => {
|
||||
describe('has initial selection', () => {
|
||||
// arrange
|
||||
const scripts = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
new SelectedScriptStub('s2', false),
|
||||
];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(scripts)
|
||||
.withCategory(1, scripts.map((s) => s.script))
|
||||
// act
|
||||
.run()
|
||||
// assert
|
||||
.expectFinalScripts(scripts);
|
||||
});
|
||||
});
|
||||
describe('deselectAll removes all items', () => {
|
||||
// arrange
|
||||
const allScripts = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
new SelectedScriptStub('s2', false),
|
||||
new SelectedScriptStub('s3', false),
|
||||
new SelectedScriptStub('s4', false),
|
||||
];
|
||||
const selectedScripts = allScripts.filter(
|
||||
(s) => ['s1', 's2', 's3'].includes(s.id),
|
||||
);
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(selectedScripts)
|
||||
.withCategory(1, allScripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.deselectAll();
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts([])
|
||||
.expectFinalScriptsInEvent(0, []);
|
||||
});
|
||||
describe('selectOnly selects expected', () => {
|
||||
// arrange
|
||||
const allScripts = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
new SelectedScriptStub('s2', false),
|
||||
new SelectedScriptStub('s3', false),
|
||||
new SelectedScriptStub('s4', false),
|
||||
];
|
||||
const selectedScripts = allScripts.filter(
|
||||
(s) => ['s1', 's2', 's3'].includes(s.id),
|
||||
);
|
||||
const scriptsToSelect = allScripts.filter(
|
||||
(s) => ['s2', 's3', 's4'].includes(s.id),
|
||||
);
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(selectedScripts)
|
||||
.withCategory(1, allScripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.selectOnly(scriptsToSelect.map((s) => s.script));
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(scriptsToSelect)
|
||||
.expectFinalScriptsInEvent(0, scriptsToSelect);
|
||||
});
|
||||
describe('selectAll selects as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
new SelectedScriptStub('s2', false),
|
||||
];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([])
|
||||
.withCategory(1, expected.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.selectAll();
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(expected)
|
||||
.expectFinalScriptsInEvent(0, expected);
|
||||
});
|
||||
describe('addOrUpdateSelectedScript', () => {
|
||||
describe('adds when item does not exist', () => {
|
||||
// arrange
|
||||
const scripts = [new ScriptStub('s1'), new ScriptStub('s2')];
|
||||
const expected = [new SelectedScript(scripts[0], false)];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([])
|
||||
.withCategory(1, scripts)
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateSelectedScript(scripts[0].id, false);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(expected)
|
||||
.expectFinalScriptsInEvent(0, expected);
|
||||
});
|
||||
describe('updates when item exists', () => {
|
||||
// arrange
|
||||
const scripts = [new ScriptStub('s1'), new ScriptStub('s2')];
|
||||
const existing = new SelectedScript(scripts[0], false);
|
||||
const expected = new SelectedScript(scripts[0], true);
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([existing])
|
||||
.withCategory(1, scripts)
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateSelectedScript(expected.id, expected.revert);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts([expected])
|
||||
.expectFinalScriptsInEvent(0, [expected]);
|
||||
});
|
||||
});
|
||||
describe('removeAllInCategory', () => {
|
||||
describe('does nothing when nothing exists', () => {
|
||||
// arrange
|
||||
const categoryId = 99;
|
||||
const scripts = [new ScriptStub('s1'), new ScriptStub('s2')];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([])
|
||||
.withCategory(categoryId, scripts)
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.removeAllInCategory(categoryId);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(0)
|
||||
.expectFinalScripts([]);
|
||||
});
|
||||
describe('removes all when all exists', () => {
|
||||
// arrange
|
||||
const categoryId = 34;
|
||||
const scripts = [new SelectedScriptStub('s1'), new SelectedScriptStub('s2')];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(scripts)
|
||||
.withCategory(categoryId, scripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.removeAllInCategory(categoryId);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts([]);
|
||||
});
|
||||
describe('removes existing when some exists', () => {
|
||||
// arrange
|
||||
const categoryId = 55;
|
||||
const existing = [new ScriptStub('s1'), new ScriptStub('s2')];
|
||||
const notExisting = [new ScriptStub('s3'), new ScriptStub('s4')];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(existing.map((script) => new SelectedScript(script, false)))
|
||||
.withCategory(categoryId, [...existing, ...notExisting])
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.removeAllInCategory(categoryId);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts([]);
|
||||
});
|
||||
});
|
||||
describe('addOrUpdateAllInCategory', () => {
|
||||
describe('when all already exists', () => {
|
||||
describe('does nothing if nothing is changed', () => {
|
||||
// arrange
|
||||
const allScripts = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
new SelectedScriptStub('s2', false),
|
||||
new SelectedScriptStub('s3', false),
|
||||
new SelectedScriptStub('s4', false),
|
||||
const categoryId = 55;
|
||||
const existingScripts = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
new SelectedScriptStub('s2', false),
|
||||
];
|
||||
const selectedScripts = allScripts.filter(
|
||||
(s) => ['s1', 's2', 's3'].includes(s.id));
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(selectedScripts)
|
||||
.withCategory(1, allScripts.map((s) => s.script))
|
||||
.withSelectedScripts(existingScripts)
|
||||
.withCategory(categoryId, existingScripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.deselectAll();
|
||||
})
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateAllInCategory(categoryId);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts([])
|
||||
.expectFinalScriptsInEvent(0, []);
|
||||
});
|
||||
describe('selectOnly selects expected', () => {
|
||||
.expectTotalFiredEvents(0)
|
||||
.expectFinalScripts(existingScripts);
|
||||
});
|
||||
describe('changes revert status of all', () => {
|
||||
// arrange
|
||||
const allScripts = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
new SelectedScriptStub('s2', false),
|
||||
new SelectedScriptStub('s3', false),
|
||||
new SelectedScriptStub('s4', false),
|
||||
const newStatus = false;
|
||||
const scripts = [
|
||||
new SelectedScriptStub('e1', !newStatus),
|
||||
new SelectedScriptStub('e2', !newStatus),
|
||||
new SelectedScriptStub('e3', newStatus),
|
||||
];
|
||||
const selectedScripts = allScripts.filter(
|
||||
(s) => ['s1', 's2', 's3'].includes(s.id));
|
||||
const scriptsToSelect = allScripts.filter(
|
||||
(s) => ['s2', 's3', 's4'].includes(s.id));
|
||||
const expectedScripts = scripts.map((s) => new SelectedScript(s.script, newStatus));
|
||||
const categoryId = 31;
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(selectedScripts)
|
||||
.withCategory(1, allScripts.map((s) => s.script))
|
||||
.withSelectedScripts(scripts)
|
||||
.withCategory(categoryId, scripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.selectOnly(scriptsToSelect.map((s) => s.script));
|
||||
})
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateAllInCategory(categoryId, newStatus);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(scriptsToSelect)
|
||||
.expectFinalScriptsInEvent(0, scriptsToSelect);
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(expectedScripts)
|
||||
.expectFinalScriptsInEvent(0, expectedScripts);
|
||||
});
|
||||
});
|
||||
describe('selectAll selects as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
new SelectedScriptStub('s2', false),
|
||||
];
|
||||
new UserSelectionTestRunner()
|
||||
describe('when nothing exists; adds all with given revert status', () => {
|
||||
const revertStatuses = [true, false];
|
||||
for (const revertStatus of revertStatuses) {
|
||||
describe(`when revert status is ${revertStatus}`, () => {
|
||||
// arrange
|
||||
const categoryId = 1;
|
||||
const scripts = [
|
||||
new SelectedScriptStub('s1', !revertStatus),
|
||||
new SelectedScriptStub('s2', !revertStatus),
|
||||
];
|
||||
const expected = scripts.map((s) => new SelectedScript(s.script, revertStatus));
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([])
|
||||
.withCategory(1, expected.map((s) => s.script))
|
||||
// act
|
||||
.withCategory(categoryId, scripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.selectAll();
|
||||
sut.addOrUpdateAllInCategory(categoryId, revertStatus);
|
||||
})
|
||||
// assert
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(expected)
|
||||
.expectFinalScriptsInEvent(0, expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('addOrUpdateSelectedScript', () => {
|
||||
describe('adds when item does not exist', () => {
|
||||
// arrange
|
||||
const scripts = [new ScriptStub('s1'), new ScriptStub('s2')];
|
||||
const expected = [ new SelectedScript(scripts[0], false) ];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([])
|
||||
.withCategory(1, scripts)
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateSelectedScript(scripts[0].id, false);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(expected)
|
||||
.expectFinalScriptsInEvent(0, expected);
|
||||
});
|
||||
describe('updates when item exists', () => {
|
||||
// arrange
|
||||
const scripts = [new ScriptStub('s1'), new ScriptStub('s2')];
|
||||
const existing = new SelectedScript(scripts[0], false);
|
||||
const expected = new SelectedScript(scripts[0], true);
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([existing])
|
||||
.withCategory(1, scripts)
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateSelectedScript(expected.id, expected.revert);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts([ expected ])
|
||||
.expectFinalScriptsInEvent(0, [ expected ]);
|
||||
});
|
||||
describe('when some exists; changes revert status of all', () => {
|
||||
// arrange
|
||||
const newStatus = true;
|
||||
const existing = [
|
||||
new SelectedScriptStub('e1', true),
|
||||
new SelectedScriptStub('e2', false),
|
||||
];
|
||||
const notExisting = [
|
||||
new SelectedScriptStub('n3', true),
|
||||
new SelectedScriptStub('n4', false),
|
||||
];
|
||||
const allScripts = [...existing, ...notExisting];
|
||||
const expectedScripts = allScripts.map((s) => new SelectedScript(s.script, newStatus));
|
||||
const categoryId = 77;
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(existing)
|
||||
.withCategory(categoryId, allScripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateAllInCategory(categoryId, newStatus);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(expectedScripts)
|
||||
.expectFinalScriptsInEvent(0, expectedScripts);
|
||||
});
|
||||
describe('removeAllInCategory', () => {
|
||||
describe('does nothing when nothing exists', () => {
|
||||
// arrange
|
||||
const categoryId = 99;
|
||||
const scripts = [new ScriptStub('s1'), new ScriptStub('s2')];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([])
|
||||
.withCategory(categoryId, scripts)
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.removeAllInCategory(categoryId);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(0)
|
||||
.expectFinalScripts([]);
|
||||
});
|
||||
describe('removes all when all exists', () => {
|
||||
// arrange
|
||||
const categoryId = 34;
|
||||
const scripts = [new SelectedScriptStub('s1'), new SelectedScriptStub('s2')];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(scripts)
|
||||
.withCategory(categoryId, scripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.removeAllInCategory(categoryId);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts([]);
|
||||
});
|
||||
describe('removes existing when some exists', () => {
|
||||
// arrange
|
||||
const categoryId = 55;
|
||||
const existing = [new ScriptStub('s1'), new ScriptStub('s2')];
|
||||
const notExisting = [new ScriptStub('s3'), new ScriptStub('s4')];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(existing.map((script) => new SelectedScript(script, false)))
|
||||
.withCategory(categoryId, [ ...existing, ...notExisting ])
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.removeAllInCategory(categoryId);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts([]);
|
||||
});
|
||||
});
|
||||
describe('isSelected', () => {
|
||||
it('returns false when not selected', () => {
|
||||
// arrange
|
||||
const selectedScript = new ScriptStub('selected');
|
||||
const notSelectedScript = new ScriptStub('not selected');
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(1)
|
||||
.withScripts(selectedScript, notSelectedScript));
|
||||
const sut = new UserSelection(collection, [new SelectedScript(selectedScript, false)]);
|
||||
// act
|
||||
const actual = sut.isSelected(notSelectedScript.id);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
describe('addOrUpdateAllInCategory', () => {
|
||||
describe('when all already exists', () => {
|
||||
describe('does nothing if nothing is changed', () => {
|
||||
// arrange
|
||||
const categoryId = 55;
|
||||
const existingScripts = [
|
||||
new SelectedScriptStub('s1', false),
|
||||
new SelectedScriptStub('s2', false),
|
||||
];
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(existingScripts)
|
||||
.withCategory(categoryId, existingScripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateAllInCategory(categoryId);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(0)
|
||||
.expectFinalScripts(existingScripts);
|
||||
});
|
||||
describe('changes revert status of all', () => {
|
||||
// arrange
|
||||
const newStatus = false;
|
||||
const scripts = [
|
||||
new SelectedScriptStub('e1', !newStatus),
|
||||
new SelectedScriptStub('e2', !newStatus),
|
||||
new SelectedScriptStub('e3', newStatus),
|
||||
];
|
||||
const expectedScripts = scripts.map((s) => new SelectedScript(s.script, newStatus));
|
||||
const categoryId = 31;
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(scripts)
|
||||
.withCategory(categoryId, scripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateAllInCategory(categoryId, newStatus);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(expectedScripts)
|
||||
.expectFinalScriptsInEvent(0, expectedScripts);
|
||||
});
|
||||
});
|
||||
describe('when nothing exists; adds all with given revert status', () => {
|
||||
const revertStatuses = [ true, false ];
|
||||
for (const revertStatus of revertStatuses) {
|
||||
describe(`when revert status is ${revertStatus}`, () => {
|
||||
// arrange
|
||||
const categoryId = 1;
|
||||
const scripts = [
|
||||
new SelectedScriptStub('s1', !revertStatus),
|
||||
new SelectedScriptStub('s2', !revertStatus),
|
||||
];
|
||||
const expected = scripts.map((s) => new SelectedScript(s.script, revertStatus));
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts([])
|
||||
.withCategory(categoryId, scripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateAllInCategory(categoryId, revertStatus);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(expected)
|
||||
.expectFinalScriptsInEvent(0, expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('when some exists; changes revert status of all', () => {
|
||||
// arrange
|
||||
const newStatus = true;
|
||||
const existing = [
|
||||
new SelectedScriptStub('e1', true),
|
||||
new SelectedScriptStub('e2', false),
|
||||
];
|
||||
const notExisting = [
|
||||
new SelectedScriptStub('n3', true),
|
||||
new SelectedScriptStub('n4', false),
|
||||
];
|
||||
const allScripts = [ ...existing, ...notExisting ];
|
||||
const expectedScripts = allScripts.map((s) => new SelectedScript(s.script, newStatus));
|
||||
const categoryId = 77;
|
||||
new UserSelectionTestRunner()
|
||||
.withSelectedScripts(existing)
|
||||
.withCategory(categoryId, allScripts.map((s) => s.script))
|
||||
// act
|
||||
.run((sut) => {
|
||||
sut.addOrUpdateAllInCategory(categoryId, newStatus);
|
||||
})
|
||||
// assert
|
||||
.expectTotalFiredEvents(1)
|
||||
.expectFinalScripts(expectedScripts)
|
||||
.expectFinalScriptsInEvent(0, expectedScripts);
|
||||
});
|
||||
it('returns true when selected', () => {
|
||||
// arrange
|
||||
const selectedScript = new ScriptStub('selected');
|
||||
const notSelectedScript = new ScriptStub('not selected');
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(1)
|
||||
.withScripts(selectedScript, notSelectedScript));
|
||||
const sut = new UserSelection(collection, [new SelectedScript(selectedScript, false)]);
|
||||
// act
|
||||
const actual = sut.isSelected(selectedScript.id);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
describe('isSelected', () => {
|
||||
it('returns false when not selected', () => {
|
||||
// arrange
|
||||
const selectedScript = new ScriptStub('selected');
|
||||
const notSelectedScript = new ScriptStub('not selected');
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(1)
|
||||
.withScripts(selectedScript, notSelectedScript));
|
||||
const sut = new UserSelection(collection, [ new SelectedScript(selectedScript, false) ]);
|
||||
// act
|
||||
const actual = sut.isSelected(notSelectedScript.id);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
it('returns true when selected', () => {
|
||||
// arrange
|
||||
const selectedScript = new ScriptStub('selected');
|
||||
const notSelectedScript = new ScriptStub('not selected');
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(new CategoryStub(1)
|
||||
.withScripts(selectedScript, notSelectedScript));
|
||||
const sut = new UserSelection(collection, [ new SelectedScript(selectedScript, false) ]);
|
||||
// act
|
||||
const actual = sut.isSelected(selectedScript.id);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
});
|
||||
describe('category state', () => {
|
||||
describe('when no scripts are selected', () => {
|
||||
// arrange
|
||||
const category = new CategoryStub(1)
|
||||
.withScriptIds('non-selected-script-1', 'non-selected-script-2');
|
||||
const collection = new CategoryCollectionStub().withAction(category);
|
||||
const sut = new UserSelection(collection, []);
|
||||
it('areAllSelected returns false', () => {
|
||||
// act
|
||||
const actual = sut.areAllSelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
it('isAnySelected returns false', () => {
|
||||
// act
|
||||
const actual = sut.isAnySelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('category state', () => {
|
||||
describe('when no scripts are selected', () => {
|
||||
// arrange
|
||||
const category = new CategoryStub(1)
|
||||
.withScriptIds('non-selected-script-1', 'non-selected-script-2');
|
||||
const collection = new CategoryCollectionStub().withAction(category);
|
||||
const sut = new UserSelection(collection, [ ]);
|
||||
it('areAllSelected returns false', () => {
|
||||
// act
|
||||
const actual = sut.areAllSelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
it('isAnySelected returns false', () => {
|
||||
// act
|
||||
const actual = sut.isAnySelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('when no subscript exists in selected scripts', () => {
|
||||
// arrange
|
||||
const category = new CategoryStub(1)
|
||||
.withScriptIds('non-selected-script-1', 'non-selected-script-2');
|
||||
const selectedScript = new ScriptStub('selected');
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(category)
|
||||
.withAction(new CategoryStub(22).withScript(selectedScript));
|
||||
const sut = new UserSelection(collection, [ new SelectedScript(selectedScript, false) ]);
|
||||
it('areAllSelected returns false', () => {
|
||||
// act
|
||||
const actual = sut.areAllSelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
it('isAnySelected returns false', () => {
|
||||
// act
|
||||
const actual = sut.isAnySelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('when one of the scripts are selected', () => {
|
||||
// arrange
|
||||
const selectedScript = new ScriptStub('selected');
|
||||
const category = new CategoryStub(1)
|
||||
.withScriptIds('non-selected-script-1', 'non-selected-script-2')
|
||||
.withCategory(new CategoryStub(12).withScript(selectedScript));
|
||||
const collection = new CategoryCollectionStub().withAction(category);
|
||||
const sut = new UserSelection(collection, [ new SelectedScript(selectedScript, false) ]);
|
||||
it('areAllSelected returns false', () => {
|
||||
// act
|
||||
const actual = sut.areAllSelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
it('isAnySelected returns true', () => {
|
||||
// act
|
||||
const actual = sut.isAnySelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
});
|
||||
describe('when all scripts are selected', () => {
|
||||
// arrange
|
||||
const firstSelectedScript = new ScriptStub('selected1');
|
||||
const secondSelectedScript = new ScriptStub('selected2');
|
||||
const category = new CategoryStub(1)
|
||||
.withScript(firstSelectedScript)
|
||||
.withCategory(new CategoryStub(12).withScript(secondSelectedScript));
|
||||
const collection = new CategoryCollectionStub().withAction(category);
|
||||
const sut = new UserSelection(collection,
|
||||
[ firstSelectedScript, secondSelectedScript ].map((s) => new SelectedScript(s, false)));
|
||||
it('areAllSelected returns true', () => {
|
||||
// act
|
||||
const actual = sut.areAllSelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('isAnySelected returns true', () => {
|
||||
// act
|
||||
const actual = sut.isAnySelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
});
|
||||
describe('when no subscript exists in selected scripts', () => {
|
||||
// arrange
|
||||
const category = new CategoryStub(1)
|
||||
.withScriptIds('non-selected-script-1', 'non-selected-script-2');
|
||||
const selectedScript = new ScriptStub('selected');
|
||||
const collection = new CategoryCollectionStub()
|
||||
.withAction(category)
|
||||
.withAction(new CategoryStub(22).withScript(selectedScript));
|
||||
const sut = new UserSelection(collection, [new SelectedScript(selectedScript, false)]);
|
||||
it('areAllSelected returns false', () => {
|
||||
// act
|
||||
const actual = sut.areAllSelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
it('isAnySelected returns false', () => {
|
||||
// act
|
||||
const actual = sut.isAnySelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('when one of the scripts are selected', () => {
|
||||
// arrange
|
||||
const selectedScript = new ScriptStub('selected');
|
||||
const category = new CategoryStub(1)
|
||||
.withScriptIds('non-selected-script-1', 'non-selected-script-2')
|
||||
.withCategory(new CategoryStub(12).withScript(selectedScript));
|
||||
const collection = new CategoryCollectionStub().withAction(category);
|
||||
const sut = new UserSelection(collection, [new SelectedScript(selectedScript, false)]);
|
||||
it('areAllSelected returns false', () => {
|
||||
// act
|
||||
const actual = sut.areAllSelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
it('isAnySelected returns true', () => {
|
||||
// act
|
||||
const actual = sut.isAnySelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
});
|
||||
describe('when all scripts are selected', () => {
|
||||
// arrange
|
||||
const firstSelectedScript = new ScriptStub('selected1');
|
||||
const secondSelectedScript = new ScriptStub('selected2');
|
||||
const category = new CategoryStub(1)
|
||||
.withScript(firstSelectedScript)
|
||||
.withCategory(new CategoryStub(12).withScript(secondSelectedScript));
|
||||
const collection = new CategoryCollectionStub().withAction(category);
|
||||
const selectedScripts = [firstSelectedScript, secondSelectedScript]
|
||||
.map((s) => new SelectedScript(s, false));
|
||||
const sut = new UserSelection(collection, selectedScripts);
|
||||
it('areAllSelected returns true', () => {
|
||||
// act
|
||||
const actual = sut.areAllSelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('isAnySelected returns true', () => {
|
||||
// act
|
||||
const actual = sut.isAnySelected(category);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,72 +7,83 @@ import { UserSelection } from '@/application/Context/State/Selection/UserSelecti
|
||||
import { IScript } from '@/domain/IScript';
|
||||
|
||||
export class UserSelectionTestRunner {
|
||||
private readonly collection = new CategoryCollectionStub();
|
||||
private existingScripts: readonly SelectedScript[] = [];
|
||||
private events: Array<readonly SelectedScript[]> = [];
|
||||
private sut: UserSelection;
|
||||
private readonly collection = new CategoryCollectionStub();
|
||||
|
||||
public withCategory(categoryId: number, scripts: readonly IScript[]) {
|
||||
const category = new CategoryStub(categoryId)
|
||||
.withScripts(...scripts);
|
||||
this.collection
|
||||
.withAction(category);
|
||||
return this;
|
||||
}
|
||||
public withSelectedScripts(existingScripts: readonly SelectedScript[]) {
|
||||
this.existingScripts = existingScripts;
|
||||
return this;
|
||||
}
|
||||
public run(runner?: (sut: UserSelection) => void) {
|
||||
this.sut = this.createSut();
|
||||
if (runner) {
|
||||
runner(this.sut);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public expectTotalFiredEvents(amount: number) {
|
||||
const testName = amount === 0 ? 'does not fire changed event' : `fires changed event ${amount} times`;
|
||||
it(testName, () => {
|
||||
expect(this.events).to.have.lengthOf(amount);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
public expectFinalScripts(finalScripts: readonly SelectedScript[]) {
|
||||
expectSameScripts(finalScripts, this.sut.selectedScripts);
|
||||
return this;
|
||||
}
|
||||
public expectFinalScriptsInEvent(eventIndex: number, finalScripts: readonly SelectedScript[]) {
|
||||
expectSameScripts(this.events[eventIndex], finalScripts);
|
||||
return this;
|
||||
}
|
||||
private createSut(): UserSelection {
|
||||
const sut = new UserSelection(this.collection, this.existingScripts);
|
||||
sut.changed.on((s) => this.events.push(s));
|
||||
return sut;
|
||||
private existingScripts: readonly SelectedScript[] = [];
|
||||
|
||||
private events: Array<readonly SelectedScript[]> = [];
|
||||
|
||||
private sut: UserSelection;
|
||||
|
||||
public withCategory(categoryId: number, scripts: readonly IScript[]) {
|
||||
const category = new CategoryStub(categoryId)
|
||||
.withScripts(...scripts);
|
||||
this.collection
|
||||
.withAction(category);
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSelectedScripts(existingScripts: readonly SelectedScript[]) {
|
||||
this.existingScripts = existingScripts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public run(runner?: (sut: UserSelection) => void) {
|
||||
this.sut = this.createSut();
|
||||
if (runner) {
|
||||
runner(this.sut);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public expectTotalFiredEvents(amount: number) {
|
||||
const testName = amount === 0 ? 'does not fire changed event' : `fires changed event ${amount} times`;
|
||||
it(testName, () => {
|
||||
expect(this.events).to.have.lengthOf(amount);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public expectFinalScripts(finalScripts: readonly SelectedScript[]) {
|
||||
expectSameScripts(finalScripts, this.sut.selectedScripts);
|
||||
return this;
|
||||
}
|
||||
|
||||
public expectFinalScriptsInEvent(eventIndex: number, finalScripts: readonly SelectedScript[]) {
|
||||
expectSameScripts(this.events[eventIndex], finalScripts);
|
||||
return this;
|
||||
}
|
||||
|
||||
private createSut(): UserSelection {
|
||||
const sut = new UserSelection(this.collection, this.existingScripts);
|
||||
sut.changed.on((s) => this.events.push(s));
|
||||
return sut;
|
||||
}
|
||||
}
|
||||
|
||||
function expectSameScripts(actual: readonly SelectedScript[], expected: readonly SelectedScript[]) {
|
||||
it('has same expected scripts', () => {
|
||||
const existingScriptIds = expected.map((script) => script.id).sort();
|
||||
const expectedScriptIds = actual.map((script) => script.id).sort();
|
||||
expect(existingScriptIds).to.deep.equal(expectedScriptIds);
|
||||
});
|
||||
it('has expected revert state', () => {
|
||||
const scriptsWithDifferentStatus = actual
|
||||
.filter((script) => {
|
||||
const other = expected.find((existing) => existing.id === script.id);
|
||||
if (!other) {
|
||||
throw new Error(`Script "${script.id}" does not exist in expected scripts: ${JSON.stringify(expected, null, '\t')}`);
|
||||
}
|
||||
return script.revert !== other.revert;
|
||||
});
|
||||
expect(scriptsWithDifferentStatus).to.have
|
||||
.lengthOf(0, 'Scripts with different statuses:\n' + scriptsWithDifferentStatus
|
||||
.map((s) =>
|
||||
`[id: ${s.id}, actual status: ${s.revert}, ` +
|
||||
`expected status: ${expected.find((existing) => existing.id === s.id).revert}]`)
|
||||
.join(' , '),
|
||||
);
|
||||
});
|
||||
it('has same expected scripts', () => {
|
||||
const existingScriptIds = expected.map((script) => script.id).sort();
|
||||
const expectedScriptIds = actual.map((script) => script.id).sort();
|
||||
expect(existingScriptIds).to.deep.equal(expectedScriptIds);
|
||||
});
|
||||
it('has expected revert state', () => {
|
||||
const scriptsWithDifferentStatus = actual
|
||||
.filter((script) => {
|
||||
const other = expected.find((existing) => existing.id === script.id);
|
||||
if (!other) {
|
||||
throw new Error(`Script "${script.id}" does not exist in expected scripts: ${JSON.stringify(expected, null, '\t')}`);
|
||||
}
|
||||
return script.revert !== other.revert;
|
||||
});
|
||||
expect(scriptsWithDifferentStatus).to.have.lengthOf(
|
||||
0,
|
||||
`Scripts with different statuses:\n${
|
||||
scriptsWithDifferentStatus
|
||||
.map((s) => `[id: ${s.id}, actual status: ${s.revert}, `
|
||||
+ `expected status: ${expected.find((existing) => existing.id === s.id).revert}]`)
|
||||
.join(' , ')
|
||||
}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,26 +5,28 @@ import { BrowserOsDetector } from '@/application/Environment/BrowserOs/BrowserOs
|
||||
import { BrowserOsTestCases } from './BrowserOsTestCases';
|
||||
|
||||
describe('BrowserOsDetector', () => {
|
||||
it('returns undefined when user agent is undefined', () => {
|
||||
// arrange
|
||||
const expected = undefined;
|
||||
const sut = new BrowserOsDetector();
|
||||
// act
|
||||
const actual = sut.detect(undefined);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('detects as expected', () => {
|
||||
for (const testCase of BrowserOsTestCases) {
|
||||
// arrange
|
||||
const sut = new BrowserOsDetector();
|
||||
// act
|
||||
const actual = sut.detect(testCase.userAgent);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedOs,
|
||||
`Expected: "${OperatingSystem[testCase.expectedOs]}"\n` +
|
||||
`Actual: "${OperatingSystem[actual]}"\n` +
|
||||
`UserAgent: "${testCase.userAgent}"`);
|
||||
}
|
||||
it('returns undefined when user agent is undefined', () => {
|
||||
// arrange
|
||||
const expected = undefined;
|
||||
const sut = new BrowserOsDetector();
|
||||
// act
|
||||
const actual = sut.detect(undefined);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('detects as expected', () => {
|
||||
BrowserOsTestCases.forEach((testCase) => {
|
||||
// arrange
|
||||
const sut = new BrowserOsDetector();
|
||||
// act
|
||||
const actual = sut.detect(testCase.userAgent);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedOs, printMessage());
|
||||
function printMessage(): string {
|
||||
return `Expected: "${OperatingSystem[testCase.expectedOs]}"\n`
|
||||
+ `Actual: "${OperatingSystem[actual]}"\n`
|
||||
+ `UserAgent: "${testCase.userAgent}"`;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,337 +1,337 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
interface IBrowserOsTestCase {
|
||||
userAgent: string;
|
||||
expectedOs: OperatingSystem;
|
||||
userAgent: string;
|
||||
expectedOs: OperatingSystem;
|
||||
}
|
||||
|
||||
export const BrowserOsTestCases: ReadonlyArray<IBrowserOsTestCase> = [
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 6.3; Win64, x64; Trident/7.0; rv:11.0) like Gecko',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/14.14316',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows Phone 10.0; Android 5.1.1; NOKIA; Lumia 1520) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586',
|
||||
expectedOs: OperatingSystem.WindowsPhone,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10136',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (X11; CrOS x86_64 11316.165.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.122 Safari/537.36',
|
||||
expectedOs: OperatingSystem.ChromeOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (X11; CrOS x86_64 8872.76.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.105 Safari/537.36',
|
||||
expectedOs: OperatingSystem.ChromeOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (X11; CrOS armv7l 4537.56.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.38 Safari/537.36',
|
||||
expectedOs: OperatingSystem.ChromeOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 OPR/58.0.3135.114',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36 OPR/53.0.2907.68',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2393.94 Safari/537.36 OPR/42.0.2393.94',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.82 Safari/537.36 OPR/29.0.1795.41 (Edition beta)',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 OPR/15.0.1147.100',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.10',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.27 (Windows NT 5.1; U; en)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Android; Opera Mini/32.0/88.150; U; sr) Presto/2.12 Version/12.16',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Android; Opera Mini/8.0.1807/36.1609; U; en) Presto/2.12.423 Version/12.16',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.4; pt-br; SM-G530BT Build/KTU84P) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; Q40; Android/4.4.2; Release/12.15.2015) AppleWebKit/534.30 (KHTML, like Gecko) Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.1.0.1429 Mobile Safari/537.10+',
|
||||
expectedOs: OperatingSystem.BlackBerry,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.0.0; en-US) AppleWebKit/535.8+ (KHTML, like Gecko) Version/7.2.0.0 Safari/535.8+',
|
||||
expectedOs: OperatingSystem.BlackBerryTabletOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.466 Mobile Safari/534.8+',
|
||||
expectedOs: OperatingSystem.BlackBerryOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 4.4.4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 Mobile OPR/15.0.1147.100',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 2.3.4; MT11i Build/4.0.2.A.0.62) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.123 Mobile Safari/537.22 OPR/14.0.1025.52315',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Android 2.2; Opera Mobi/-2118645896; U; pl) Presto/2.7.60 Version/10.5',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 9; SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 6.0; CAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 4.2.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 4.3; Nexus 10 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/46.0.2490.76 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Android 9; Mobile; rv:64.0) Gecko/64.0 Firefox/64.0',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 625) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537',
|
||||
expectedOs: OperatingSystem.WindowsPhone,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)',
|
||||
expectedOs: OperatingSystem.WindowsPhone,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 6.0; en-US; CPH1609 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.10.2.1164 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'UCWEB/2.0 (Linux; U; Adr 5.1; en-US; Lenovo Z90a40 Build/LMY47O) U2/1.0.0 UCBrowser/11.1.5.890 U2/1.0.0 Mobile',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 5.1; en-US; Lenovo Z90a40 Build/LMY47O) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/11.1.5.890 U3/0.8.0 Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'UCWEB/2.0 (Linux; U; Adr 2.3; en-US; MI-ONEPlus) U2/1.0.0 UCBrowser/8.6.0.199 U2/1.0.0 Mobile',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 2.3; zh-CN; MI-ONEPlus) AppleWebKit/534.13 (KHTML, like Gecko) UCBrowser/8.6.0.199 U3/0.8.0 Mobile Safari/534.13',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G965F Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.0 Chrome/67.0.3396.87 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-J330FN Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/7.2 Chrome/59.0.3071.125 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-G925F Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-G925F Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; vivo X21A Build/OPM1.171019.011) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/9.1 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; GT-I9500 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.0 QQ-URL-Manager Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 9; ONEPLUS A6003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 6.4; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0',
|
||||
expectedOs: OperatingSystem.Linux,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (X11; CrOS x86_64 11316.165.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.122 Safari/537.36',
|
||||
expectedOs: OperatingSystem.ChromeOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Mobile; LYF/F90M/LYF_F90M_000-03-12-110119; Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5',
|
||||
expectedOs: OperatingSystem.KaiOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 6.3; Win64, x64; Trident/7.0; rv:11.0) like Gecko',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/14.14316',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows Phone 10.0; Android 5.1.1; NOKIA; Lumia 1520) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586',
|
||||
expectedOs: OperatingSystem.WindowsPhone,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10136',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (X11; CrOS x86_64 11316.165.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.122 Safari/537.36',
|
||||
expectedOs: OperatingSystem.ChromeOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (X11; CrOS x86_64 8872.76.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.105 Safari/537.36',
|
||||
expectedOs: OperatingSystem.ChromeOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (X11; CrOS armv7l 4537.56.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.38 Safari/537.36',
|
||||
expectedOs: OperatingSystem.ChromeOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 OPR/58.0.3135.114',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36 OPR/53.0.2907.68',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2393.94 Safari/537.36 OPR/42.0.2393.94',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.82 Safari/537.36 OPR/29.0.1795.41 (Edition beta)',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 OPR/15.0.1147.100',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.10',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.27 (Windows NT 5.1; U; en)',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Android; Opera Mini/32.0/88.150; U; sr) Presto/2.12 Version/12.16',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Android; Opera Mini/8.0.1807/36.1609; U; en) Presto/2.12.423 Version/12.16',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.4; pt-br; SM-G530BT Build/KTU84P) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; Q40; Android/4.4.2; Release/12.15.2015) AppleWebKit/534.30 (KHTML, like Gecko) Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.1.0.1429 Mobile Safari/537.10+',
|
||||
expectedOs: OperatingSystem.BlackBerry,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.0.0; en-US) AppleWebKit/535.8+ (KHTML, like Gecko) Version/7.2.0.0 Safari/535.8+',
|
||||
expectedOs: OperatingSystem.BlackBerryTabletOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.466 Mobile Safari/534.8+',
|
||||
expectedOs: OperatingSystem.BlackBerryOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 4.4.4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 Mobile OPR/15.0.1147.100',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 2.3.4; MT11i Build/4.0.2.A.0.62) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.123 Mobile Safari/537.22 OPR/14.0.1025.52315',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Opera/9.80 (Android 2.2; Opera Mobi/-2118645896; U; pl) Presto/2.7.60 Version/10.5',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 9; SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 6.0; CAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 4.2.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 4.3; Nexus 10 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/46.0.2490.76 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Android 9; Mobile; rv:64.0) Gecko/64.0 Firefox/64.0',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 625) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537',
|
||||
expectedOs: OperatingSystem.WindowsPhone,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)',
|
||||
expectedOs: OperatingSystem.WindowsPhone,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 6.0; en-US; CPH1609 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.10.2.1164 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'UCWEB/2.0 (Linux; U; Adr 5.1; en-US; Lenovo Z90a40 Build/LMY47O) U2/1.0.0 UCBrowser/11.1.5.890 U2/1.0.0 Mobile',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 5.1; en-US; Lenovo Z90a40 Build/LMY47O) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/11.1.5.890 U3/0.8.0 Mobile Safari/534.30',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'UCWEB/2.0 (Linux; U; Adr 2.3; en-US; MI-ONEPlus) U2/1.0.0 UCBrowser/8.6.0.199 U2/1.0.0 Mobile',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 2.3; zh-CN; MI-ONEPlus) AppleWebKit/534.13 (KHTML, like Gecko) UCBrowser/8.6.0.199 U3/0.8.0 Mobile Safari/534.13',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G965F Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.0 Chrome/67.0.3396.87 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/8.2 Chrome/63.0.3239.111 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-J330FN Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/7.2 Chrome/59.0.3071.125 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-G925F Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-G925F Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; vivo X21A Build/OPM1.171019.011) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/9.1 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; GT-I9500 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.0 QQ-URL-Manager Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Linux; Android 9; ONEPLUS A6003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Mobile Safari/537.36',
|
||||
expectedOs: OperatingSystem.Android,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 6.4; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1',
|
||||
expectedOs: OperatingSystem.iOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0',
|
||||
expectedOs: OperatingSystem.Linux,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (X11; CrOS x86_64 11316.165.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.122 Safari/537.36',
|
||||
expectedOs: OperatingSystem.ChromeOS,
|
||||
},
|
||||
{
|
||||
userAgent: 'Mozilla/5.0 (Mobile; LYF/F90M/LYF_F90M_000-03-12-110119; Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5',
|
||||
expectedOs: OperatingSystem.KaiOS,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
interface IDesktopTestCase {
|
||||
processPlatform: string;
|
||||
expectedOs: OperatingSystem;
|
||||
processPlatform: string;
|
||||
expectedOs: OperatingSystem;
|
||||
}
|
||||
|
||||
// https://nodejs.org/api/process.html#process_process_platform
|
||||
export const DesktopOsTestCases: ReadonlyArray<IDesktopTestCase> = [
|
||||
{
|
||||
processPlatform: 'aix',
|
||||
expectedOs: undefined,
|
||||
},
|
||||
{
|
||||
processPlatform: 'darwin',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
processPlatform: 'freebsd',
|
||||
expectedOs: undefined,
|
||||
},
|
||||
{
|
||||
processPlatform: 'linux',
|
||||
expectedOs: OperatingSystem.Linux,
|
||||
},
|
||||
{
|
||||
processPlatform: 'openbsd',
|
||||
expectedOs: undefined,
|
||||
},
|
||||
{
|
||||
processPlatform: 'sunos',
|
||||
expectedOs: undefined,
|
||||
},
|
||||
{
|
||||
processPlatform: 'win32',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
{
|
||||
processPlatform: 'aix',
|
||||
expectedOs: undefined,
|
||||
},
|
||||
{
|
||||
processPlatform: 'darwin',
|
||||
expectedOs: OperatingSystem.macOS,
|
||||
},
|
||||
{
|
||||
processPlatform: 'freebsd',
|
||||
expectedOs: undefined,
|
||||
},
|
||||
{
|
||||
processPlatform: 'linux',
|
||||
expectedOs: OperatingSystem.Linux,
|
||||
},
|
||||
{
|
||||
processPlatform: 'openbsd',
|
||||
expectedOs: undefined,
|
||||
},
|
||||
{
|
||||
processPlatform: 'sunos',
|
||||
expectedOs: undefined,
|
||||
},
|
||||
{
|
||||
processPlatform: 'win32',
|
||||
expectedOs: OperatingSystem.Windows,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -2,119 +2,121 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { IBrowserOsDetector } from '@/application/Environment/BrowserOs/IBrowserOsDetector';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { Environment, IEnvironmentVariables } from '@/application/Environment/Environment';
|
||||
import { DesktopOsTestCases } from './DesktopOsTestCases';
|
||||
import { Environment } from '@/application/Environment/Environment';
|
||||
|
||||
interface EnvironmentVariables {
|
||||
window?: any;
|
||||
process?: any;
|
||||
navigator?: any;
|
||||
window?: unknown;
|
||||
process?: unknown;
|
||||
navigator?: unknown;
|
||||
}
|
||||
|
||||
class SystemUnderTest extends Environment {
|
||||
constructor(variables: EnvironmentVariables, browserOsDetector?: IBrowserOsDetector) {
|
||||
super(variables as any, browserOsDetector);
|
||||
}
|
||||
constructor(variables: EnvironmentVariables, browserOsDetector?: IBrowserOsDetector) {
|
||||
super(variables as unknown as IEnvironmentVariables, browserOsDetector);
|
||||
}
|
||||
}
|
||||
|
||||
describe('Environment', () => {
|
||||
describe('isDesktop', () => {
|
||||
it('returns true if process type is renderer', () => {
|
||||
// arrange
|
||||
const window = {
|
||||
process: {
|
||||
type: 'renderer',
|
||||
},
|
||||
};
|
||||
// act
|
||||
const sut = new SystemUnderTest({ window });
|
||||
// assert
|
||||
expect(sut.isDesktop).to.equal(true);
|
||||
});
|
||||
it('returns true if electron is defined as process version', () => {
|
||||
// arrange
|
||||
const process = {
|
||||
versions: {
|
||||
electron: true,
|
||||
},
|
||||
};
|
||||
// act
|
||||
const sut = new SystemUnderTest({ process });
|
||||
// assert
|
||||
expect(sut.isDesktop).to.equal(true);
|
||||
});
|
||||
it('returns true if navigator user agent has electron', () => {
|
||||
// arrange
|
||||
const navigator = {
|
||||
userAgent: 'Electron',
|
||||
};
|
||||
// act
|
||||
const sut = new SystemUnderTest( { navigator });
|
||||
// assert
|
||||
expect(sut.isDesktop).to.equal(true);
|
||||
});
|
||||
it('returns false as default', () => {
|
||||
const sut = new SystemUnderTest({ });
|
||||
expect(sut.isDesktop).to.equal(false);
|
||||
});
|
||||
describe('isDesktop', () => {
|
||||
it('returns true if process type is renderer', () => {
|
||||
// arrange
|
||||
const window = {
|
||||
process: {
|
||||
type: 'renderer',
|
||||
},
|
||||
};
|
||||
// act
|
||||
const sut = new SystemUnderTest({ window });
|
||||
// assert
|
||||
expect(sut.isDesktop).to.equal(true);
|
||||
});
|
||||
describe('os', () => {
|
||||
it('returns undefined without user agent', () => {
|
||||
// arrange
|
||||
const expected = undefined;
|
||||
const mock: IBrowserOsDetector = {
|
||||
detect: (agent) => {
|
||||
throw new Error('should not reach here');
|
||||
},
|
||||
};
|
||||
const sut = new SystemUnderTest({ }, mock);
|
||||
// act
|
||||
const actual = sut.os;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('browser os from BrowserOsDetector', () => {
|
||||
// arrange
|
||||
const givenUserAgent = 'testUserAgent';
|
||||
const expected = OperatingSystem.macOS;
|
||||
const window = {
|
||||
navigator: {
|
||||
userAgent: givenUserAgent,
|
||||
},
|
||||
};
|
||||
const mock: IBrowserOsDetector = {
|
||||
detect: (agent) => {
|
||||
if (agent !== givenUserAgent) {
|
||||
throw new Error('Unexpected user agent');
|
||||
}
|
||||
return expected;
|
||||
},
|
||||
};
|
||||
// act
|
||||
const sut = new SystemUnderTest({ window }, mock);
|
||||
const actual = sut.os;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('desktop os', () => {
|
||||
const navigator = {
|
||||
userAgent: 'Electron',
|
||||
};
|
||||
for (const testCase of DesktopOsTestCases) {
|
||||
it(testCase.processPlatform, () => {
|
||||
// arrange
|
||||
const process = {
|
||||
platform: testCase.processPlatform,
|
||||
};
|
||||
// act
|
||||
const sut = new SystemUnderTest({ navigator, process });
|
||||
// assert
|
||||
expect(sut.os).to.equal(testCase.expectedOs,
|
||||
`Expected: "${OperatingSystem[testCase.expectedOs]}"\n` +
|
||||
`Actual: "${OperatingSystem[sut.os]}"\n` +
|
||||
`Platform: "${testCase.processPlatform}"`);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('returns true if electron is defined as process version', () => {
|
||||
// arrange
|
||||
const process = {
|
||||
versions: {
|
||||
electron: true,
|
||||
},
|
||||
};
|
||||
// act
|
||||
const sut = new SystemUnderTest({ process });
|
||||
// assert
|
||||
expect(sut.isDesktop).to.equal(true);
|
||||
});
|
||||
it('returns true if navigator user agent has electron', () => {
|
||||
// arrange
|
||||
const navigator = {
|
||||
userAgent: 'Electron',
|
||||
};
|
||||
// act
|
||||
const sut = new SystemUnderTest({ navigator });
|
||||
// assert
|
||||
expect(sut.isDesktop).to.equal(true);
|
||||
});
|
||||
it('returns false as default', () => {
|
||||
const sut = new SystemUnderTest({});
|
||||
expect(sut.isDesktop).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('os', () => {
|
||||
it('returns undefined without user agent', () => {
|
||||
// arrange
|
||||
const expected = undefined;
|
||||
const mock: IBrowserOsDetector = {
|
||||
detect: () => {
|
||||
throw new Error('should not reach here');
|
||||
},
|
||||
};
|
||||
const sut = new SystemUnderTest({}, mock);
|
||||
// act
|
||||
const actual = sut.os;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('browser os from BrowserOsDetector', () => {
|
||||
// arrange
|
||||
const givenUserAgent = 'testUserAgent';
|
||||
const expected = OperatingSystem.macOS;
|
||||
const window = {
|
||||
navigator: {
|
||||
userAgent: givenUserAgent,
|
||||
},
|
||||
};
|
||||
const mock: IBrowserOsDetector = {
|
||||
detect: (agent) => {
|
||||
if (agent !== givenUserAgent) {
|
||||
throw new Error('Unexpected user agent');
|
||||
}
|
||||
return expected;
|
||||
},
|
||||
};
|
||||
// act
|
||||
const sut = new SystemUnderTest({ window }, mock);
|
||||
const actual = sut.os;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('desktop os', () => {
|
||||
const navigator = {
|
||||
userAgent: 'Electron',
|
||||
};
|
||||
for (const testCase of DesktopOsTestCases) {
|
||||
it(testCase.processPlatform, () => {
|
||||
// arrange
|
||||
const process = {
|
||||
platform: testCase.processPlatform,
|
||||
};
|
||||
// act
|
||||
const sut = new SystemUnderTest({ navigator, process });
|
||||
// assert
|
||||
expect(sut.os).to.equal(testCase.expectedOs, printMessage());
|
||||
function printMessage(): string {
|
||||
return `Expected: "${OperatingSystem[testCase.expectedOs]}"\n`
|
||||
+ `Actual: "${OperatingSystem[sut.os]}"\n`
|
||||
+ `Platform: "${testCase.processPlatform}"`;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { CollectionData } from 'js-yaml-loader!@/*';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
import { CategoryCollectionParserType, parseApplication } from '@/application/Parser/ApplicationParser';
|
||||
import WindowsData from 'js-yaml-loader!@/application/collections/windows.yaml';
|
||||
import MacOsData from 'js-yaml-loader!@/application/collections/macos.yaml';
|
||||
import { CollectionData } from 'js-yaml-loader!@/*';
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
@@ -15,150 +15,154 @@ import { getProcessEnvironmentStub } from '@tests/unit/stubs/ProcessEnvironmentS
|
||||
import { CollectionDataStub } from '@tests/unit/stubs/CollectionDataStub';
|
||||
|
||||
describe('ApplicationParser', () => {
|
||||
describe('parseApplication', () => {
|
||||
describe('parser', () => {
|
||||
it('returns result from the parser', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.macOS;
|
||||
const data = new CollectionDataStub();
|
||||
const expected = new CategoryCollectionStub()
|
||||
.withOs(os);
|
||||
const parser = new CategoryCollectionParserSpy()
|
||||
.setUpReturnValue(data, expected)
|
||||
.mockParser();
|
||||
const env = getProcessEnvironmentStub();
|
||||
const collections = [ data ];
|
||||
// act
|
||||
const app = parseApplication(parser, env, collections);
|
||||
// assert
|
||||
const actual = app.getCollection(os);
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
});
|
||||
describe('processEnv', () => {
|
||||
it('used to parse expected project information', () => {
|
||||
// arrange
|
||||
const env = getProcessEnvironmentStub();
|
||||
const expected = parseProjectInformation(env);
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
const app = parseApplication(parserMock, env);
|
||||
// assert
|
||||
expect(expected).to.deep.equal(app.info);
|
||||
expect(parserSpy.arguments.map((arg) => arg.info).every((info) => info === expected));
|
||||
});
|
||||
it('defaults to process.env', () => {
|
||||
// arrange
|
||||
const env = process.env;
|
||||
const expected = parseProjectInformation(env);
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
const app = parseApplication(parserMock);
|
||||
// assert
|
||||
expect(expected).to.deep.equal(app.info);
|
||||
expect(parserSpy.arguments.map((arg) => arg.info).every((info) => info === expected));
|
||||
});
|
||||
});
|
||||
describe('collectionsData', () => {
|
||||
describe('set as expected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'single collection',
|
||||
input: [ new CollectionDataStub() ],
|
||||
output: [ new CategoryCollectionStub().withOs(OperatingSystem.macOS) ],
|
||||
},
|
||||
{
|
||||
name: 'multiple collections',
|
||||
input: [
|
||||
new CollectionDataStub().withOs('windows'),
|
||||
new CollectionDataStub().withOs('macos'),
|
||||
],
|
||||
output: [
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.macOS),
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.Windows),
|
||||
],
|
||||
},
|
||||
];
|
||||
// act
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const env = getProcessEnvironmentStub();
|
||||
let parserSpy = new CategoryCollectionParserSpy();
|
||||
for (let i = 0; i < testCase.input.length; i++) {
|
||||
parserSpy = parserSpy.setUpReturnValue(testCase.input[i], testCase.output[i]);
|
||||
}
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
const app = parseApplication(parserMock, env, testCase.input);
|
||||
// assert
|
||||
expect(app.collections).to.deep.equal(testCase.output);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('defaults to expected data', () => {
|
||||
// arrange
|
||||
const expected = [ WindowsData, MacOsData ];
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
parseApplication(parserMock);
|
||||
// assert
|
||||
const actual = parserSpy.arguments.map((args) => args.data);
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
describe('throws when data is invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
expectedError: 'no collection provided',
|
||||
data: [],
|
||||
},
|
||||
{
|
||||
expectedError: 'undefined collection provided',
|
||||
data: [ new CollectionDataStub(), undefined ],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.expectedError, () => {
|
||||
const parserMock = new CategoryCollectionParserSpy().mockParser();
|
||||
const env = getProcessEnvironmentStub();
|
||||
// act
|
||||
const act = () => parseApplication(parserMock, env, testCase.data);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('parseApplication', () => {
|
||||
describe('parser', () => {
|
||||
it('returns result from the parser', () => {
|
||||
// arrange
|
||||
const os = OperatingSystem.macOS;
|
||||
const data = new CollectionDataStub();
|
||||
const expected = new CategoryCollectionStub()
|
||||
.withOs(os);
|
||||
const parser = new CategoryCollectionParserSpy()
|
||||
.setUpReturnValue(data, expected)
|
||||
.mockParser();
|
||||
const env = getProcessEnvironmentStub();
|
||||
const collections = [data];
|
||||
// act
|
||||
const app = parseApplication(parser, env, collections);
|
||||
// assert
|
||||
const actual = app.getCollection(os);
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
});
|
||||
describe('processEnv', () => {
|
||||
it('used to parse expected project information', () => {
|
||||
// arrange
|
||||
const env = getProcessEnvironmentStub();
|
||||
const expected = parseProjectInformation(env);
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
const app = parseApplication(parserMock, env);
|
||||
// assert
|
||||
expect(expected).to.deep.equal(app.info);
|
||||
expect(parserSpy.arguments.map((arg) => arg.info).every((info) => info === expected));
|
||||
});
|
||||
it('defaults to process.env', () => {
|
||||
// arrange
|
||||
const { env } = process;
|
||||
const expected = parseProjectInformation(env);
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
const app = parseApplication(parserMock);
|
||||
// assert
|
||||
expect(expected).to.deep.equal(app.info);
|
||||
expect(parserSpy.arguments.map((arg) => arg.info).every((info) => info === expected));
|
||||
});
|
||||
});
|
||||
describe('collectionsData', () => {
|
||||
describe('set as expected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'single collection',
|
||||
input: [new CollectionDataStub()],
|
||||
output: [new CategoryCollectionStub().withOs(OperatingSystem.macOS)],
|
||||
},
|
||||
{
|
||||
name: 'multiple collections',
|
||||
input: [
|
||||
new CollectionDataStub().withOs('windows'),
|
||||
new CollectionDataStub().withOs('macos'),
|
||||
],
|
||||
output: [
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.macOS),
|
||||
new CategoryCollectionStub().withOs(OperatingSystem.Windows),
|
||||
],
|
||||
},
|
||||
];
|
||||
// act
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const env = getProcessEnvironmentStub();
|
||||
let parserSpy = new CategoryCollectionParserSpy();
|
||||
for (let i = 0; i < testCase.input.length; i++) {
|
||||
parserSpy = parserSpy.setUpReturnValue(testCase.input[i], testCase.output[i]);
|
||||
}
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
const app = parseApplication(parserMock, env, testCase.input);
|
||||
// assert
|
||||
expect(app.collections).to.deep.equal(testCase.output);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('defaults to expected data', () => {
|
||||
// arrange
|
||||
const expected = [WindowsData, MacOsData];
|
||||
const parserSpy = new CategoryCollectionParserSpy();
|
||||
const parserMock = parserSpy.mockParser();
|
||||
// act
|
||||
parseApplication(parserMock);
|
||||
// assert
|
||||
const actual = parserSpy.arguments.map((args) => args.data);
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
describe('throws when data is invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
expectedError: 'no collection provided',
|
||||
data: [],
|
||||
},
|
||||
{
|
||||
expectedError: 'undefined collection provided',
|
||||
data: [new CollectionDataStub(), undefined],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.expectedError, () => {
|
||||
const parserMock = new CategoryCollectionParserSpy().mockParser();
|
||||
const env = getProcessEnvironmentStub();
|
||||
// act
|
||||
const act = () => parseApplication(parserMock, env, testCase.data);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class CategoryCollectionParserSpy {
|
||||
public arguments = new Array<{
|
||||
data: CollectionData,
|
||||
info: ProjectInformation,
|
||||
}>();
|
||||
public arguments = new Array<{
|
||||
data: CollectionData,
|
||||
info: ProjectInformation,
|
||||
}>();
|
||||
|
||||
private returnValues = new Map<CollectionData, ICategoryCollection>();
|
||||
private returnValues = new Map<CollectionData, ICategoryCollection>();
|
||||
|
||||
public setUpReturnValue(data: CollectionData, collection: ICategoryCollection): CategoryCollectionParserSpy {
|
||||
this.returnValues.set(data, collection);
|
||||
return this;
|
||||
}
|
||||
public mockParser(): CategoryCollectionParserType {
|
||||
return (data: CollectionData, info: IProjectInformation) => {
|
||||
this.arguments.push({ data, info });
|
||||
if (this.returnValues.has(data)) {
|
||||
return this.returnValues.get(data);
|
||||
} else {
|
||||
// Get next OS with a unique OS so mock does not result in invalid app (with duplicate OS collections)
|
||||
const currentRun = this.arguments.length - 1;
|
||||
const nextOs = getEnumValues(OperatingSystem)[currentRun];
|
||||
return new CategoryCollectionStub().withOs(nextOs);
|
||||
}
|
||||
};
|
||||
}
|
||||
public setUpReturnValue(
|
||||
data: CollectionData,
|
||||
collection: ICategoryCollection,
|
||||
): CategoryCollectionParserSpy {
|
||||
this.returnValues.set(data, collection);
|
||||
return this;
|
||||
}
|
||||
|
||||
public mockParser(): CategoryCollectionParserType {
|
||||
return (data: CollectionData, info: IProjectInformation) => {
|
||||
this.arguments.push({ data, info });
|
||||
if (this.returnValues.has(data)) {
|
||||
return this.returnValues.get(data);
|
||||
}
|
||||
// Get next OS with a unique OS so mock does not result in an invalid app due to duplicated OS
|
||||
// collections.
|
||||
const currentRun = this.arguments.length - 1;
|
||||
const nextOs = getEnumValues(OperatingSystem)[currentRun];
|
||||
return new CategoryCollectionStub().withOs(nextOs);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,119 +17,120 @@ import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
||||
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
|
||||
|
||||
describe('CategoryCollectionParser', () => {
|
||||
describe('parseCategoryCollection', () => {
|
||||
it('throws when undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'content is null or undefined';
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const act = () => parseCategoryCollection(undefined, info);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('actions', () => {
|
||||
it('throws when undefined actions', () => {
|
||||
// arrange
|
||||
const expectedError = 'content does not define any action';
|
||||
const collection = new CollectionDataStub()
|
||||
.withActions(undefined);
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const act = () => parseCategoryCollection(collection, info);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when has no actions', () => {
|
||||
// arrange
|
||||
const expectedError = 'content does not define any action';
|
||||
const collection = new CollectionDataStub()
|
||||
.withActions([]);
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const act = () => parseCategoryCollection(collection, info);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('parses actions', () => {
|
||||
// arrange
|
||||
const actions = [ getCategoryStub('test1'), getCategoryStub('test2') ];
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
const expected = [ parseCategory(actions[0], context), parseCategory(actions[1], context) ];
|
||||
const collection = new CollectionDataStub()
|
||||
.withActions(actions);
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const actual = parseCategoryCollection(collection, info).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 collection = new CollectionDataStub();
|
||||
const information = parseProjectInformation(process.env);
|
||||
const expected = new ScriptingDefinitionParser()
|
||||
.parse(collection.scripting, information);
|
||||
// act
|
||||
const actual = parseCategoryCollection(collection, information).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 collection = new CollectionDataStub()
|
||||
.withOs(osText);
|
||||
const parserMock = new EnumParserStub<OperatingSystem>()
|
||||
.setup(expectedName, osText, expectedOs);
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const actual = parseCategoryCollection(collection, info, parserMock);
|
||||
// assert
|
||||
expect(actual.os).to.equal(expectedOs);
|
||||
});
|
||||
});
|
||||
describe('functions', () => {
|
||||
it('compiles script call with given function', () => {
|
||||
// arrange
|
||||
const expectedCode = 'code-from-the-function';
|
||||
const functionName = 'function-name';
|
||||
const scriptName = 'script-name';
|
||||
const script = ScriptDataStub.createWithCall()
|
||||
.withCall(new FunctionCallDataStub().withName(functionName).withParameters({}))
|
||||
.withName(scriptName);
|
||||
const func = FunctionDataStub.createWithCode().withParametersObject([])
|
||||
.withName(functionName)
|
||||
.withCode(expectedCode);
|
||||
const category = new CategoryDataStub()
|
||||
.withChildren([ script,
|
||||
ScriptDataStub.createWithCode().withName('2')
|
||||
.withRecommendationLevel(RecommendationLevel.Standard),
|
||||
ScriptDataStub.createWithCode()
|
||||
.withName('3').withRecommendationLevel(RecommendationLevel.Strict),
|
||||
]);
|
||||
const collection = new CollectionDataStub()
|
||||
.withActions([ category ])
|
||||
.withFunctions([ func ]);
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const actual = parseCategoryCollection(collection, info);
|
||||
// assert
|
||||
const actualScript = actual.findScript(scriptName);
|
||||
const actualCode = actualScript.code.execute;
|
||||
expect(actualCode).to.equal(expectedCode);
|
||||
});
|
||||
});
|
||||
describe('parseCategoryCollection', () => {
|
||||
it('throws when undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'content is null or undefined';
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const act = () => parseCategoryCollection(undefined, info);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('actions', () => {
|
||||
it('throws when undefined actions', () => {
|
||||
// arrange
|
||||
const expectedError = 'content does not define any action';
|
||||
const collection = new CollectionDataStub()
|
||||
.withActions(undefined);
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const act = () => parseCategoryCollection(collection, info);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when has no actions', () => {
|
||||
// arrange
|
||||
const expectedError = 'content does not define any action';
|
||||
const collection = new CollectionDataStub()
|
||||
.withActions([]);
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const act = () => parseCategoryCollection(collection, info);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('parses actions', () => {
|
||||
// arrange
|
||||
const actions = [getCategoryStub('test1'), getCategoryStub('test2')];
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
const expected = [parseCategory(actions[0], context), parseCategory(actions[1], context)];
|
||||
const collection = new CollectionDataStub()
|
||||
.withActions(actions);
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const actual = parseCategoryCollection(collection, info).actions;
|
||||
// assert
|
||||
expect(excludingId(actual)).to.be.deep.equal(excludingId(expected));
|
||||
function excludingId<TId>(array: ReadonlyArray<IEntity<TId>>) {
|
||||
return array.map((obj) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { id: omitted, ...rest } = obj;
|
||||
return rest;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('scripting definition', () => {
|
||||
it('parses scripting definition as expected', () => {
|
||||
// arrange
|
||||
const collection = new CollectionDataStub();
|
||||
const information = parseProjectInformation(process.env);
|
||||
const expected = new ScriptingDefinitionParser()
|
||||
.parse(collection.scripting, information);
|
||||
// act
|
||||
const actual = parseCategoryCollection(collection, information).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 collection = new CollectionDataStub()
|
||||
.withOs(osText);
|
||||
const parserMock = new EnumParserStub<OperatingSystem>()
|
||||
.setup(expectedName, osText, expectedOs);
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const actual = parseCategoryCollection(collection, info, parserMock);
|
||||
// assert
|
||||
expect(actual.os).to.equal(expectedOs);
|
||||
});
|
||||
});
|
||||
describe('functions', () => {
|
||||
it('compiles script call with given function', () => {
|
||||
// arrange
|
||||
const expectedCode = 'code-from-the-function';
|
||||
const functionName = 'function-name';
|
||||
const scriptName = 'script-name';
|
||||
const script = ScriptDataStub.createWithCall()
|
||||
.withCall(new FunctionCallDataStub().withName(functionName).withParameters({}))
|
||||
.withName(scriptName);
|
||||
const func = FunctionDataStub.createWithCode().withParametersObject([])
|
||||
.withName(functionName)
|
||||
.withCode(expectedCode);
|
||||
const category = new CategoryDataStub()
|
||||
.withChildren([script,
|
||||
ScriptDataStub.createWithCode().withName('2')
|
||||
.withRecommendationLevel(RecommendationLevel.Standard),
|
||||
ScriptDataStub.createWithCode()
|
||||
.withName('3').withRecommendationLevel(RecommendationLevel.Strict),
|
||||
]);
|
||||
const collection = new CollectionDataStub()
|
||||
.withActions([category])
|
||||
.withFunctions([func]);
|
||||
const info = new ProjectInformationStub();
|
||||
// act
|
||||
const actual = parseCategoryCollection(collection, info);
|
||||
// assert
|
||||
const actualScript = actual.findScript(scriptName);
|
||||
const actualCode = actualScript.code.execute;
|
||||
expect(actualCode).to.equal(expectedCode);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,163 +10,163 @@ import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
|
||||
import { CategoryDataStub } from '@tests/unit/stubs/CategoryDataStub';
|
||||
|
||||
describe('CategoryParser', () => {
|
||||
describe('parseCategory', () => {
|
||||
describe('invalid category', () => {
|
||||
it('throws when undefined', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'category is null or undefined';
|
||||
const category = undefined;
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('throws when children are empty', () => {
|
||||
// arrange
|
||||
const categoryName = 'test';
|
||||
const expectedMessage = `category has no children: "${categoryName}"`;
|
||||
const category = new CategoryDataStub()
|
||||
.withName(categoryName)
|
||||
.withChildren([]);
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('throws when children are undefined', () => {
|
||||
// arrange
|
||||
const categoryName = 'test';
|
||||
const expectedMessage = `category has no children: "${categoryName}"`;
|
||||
const category = new CategoryDataStub()
|
||||
.withName(categoryName)
|
||||
.withChildren(undefined);
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, context);
|
||||
// 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 = new CategoryDataStub()
|
||||
.withName(invalidName);
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('throws when context is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined context';
|
||||
const context = undefined;
|
||||
const category = new CategoryDataStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns expected docs', () => {
|
||||
// arrange
|
||||
const url = 'https://privacy.sexy';
|
||||
const expected = parseDocUrls({ docs: url });
|
||||
const category = new CategoryDataStub()
|
||||
.withDocs(url);
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseCategory(category, context).documentationUrls;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
describe('parses expected subscript', () => {
|
||||
it('single script with code', () => {
|
||||
// arrange
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
const expected = [ parseScript(script, context) ];
|
||||
const category = new CategoryDataStub()
|
||||
.withChildren([ script ]);
|
||||
// act
|
||||
const actual = parseCategory(category, context).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('single script with function call', () => {
|
||||
// arrange
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(script);
|
||||
const context = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
const expected = [ parseScript(script, context) ];
|
||||
const category = new CategoryDataStub()
|
||||
.withChildren([ script ]);
|
||||
// act
|
||||
const actual = parseCategory(category, context).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('multiple scripts with function call and code', () => {
|
||||
// arrange
|
||||
const callableScript = ScriptDataStub.createWithCall();
|
||||
const scripts = [ callableScript, ScriptDataStub.createWithCode() ];
|
||||
const category = new CategoryDataStub()
|
||||
.withChildren(scripts);
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(callableScript);
|
||||
const context = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
const expected = scripts.map((script) => parseScript(script, context));
|
||||
// act
|
||||
const actual = parseCategory(category, context).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('script is created with right context', () => { // test through script validation logic
|
||||
// arrange
|
||||
const commentDelimiter = 'should not throw';
|
||||
const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
|
||||
const category = new CategoryDataStub()
|
||||
.withChildren([
|
||||
new CategoryDataStub()
|
||||
.withName('sub-category')
|
||||
.withChildren([
|
||||
ScriptDataStub
|
||||
.createWithoutCallOrCodes()
|
||||
.withCode(duplicatedCode),
|
||||
]),
|
||||
]);
|
||||
// act
|
||||
const act = () => parseCategory(category, parseContext).scripts;
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
it('returns expected subcategories', () => {
|
||||
// arrange
|
||||
const expected = [ new CategoryDataStub()
|
||||
.withName('test category')
|
||||
.withChildren([ ScriptDataStub.createWithCode() ]),
|
||||
];
|
||||
const category = new CategoryDataStub()
|
||||
.withName('category name')
|
||||
.withChildren(expected);
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseCategory(category, context).subCategories;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(1);
|
||||
expect(actual[0].name).to.equal(expected[0].category);
|
||||
expect(actual[0].scripts.length).to.equal(expected[0].children.length);
|
||||
describe('parseCategory', () => {
|
||||
describe('invalid category', () => {
|
||||
it('throws when undefined', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'category is null or undefined';
|
||||
const category = undefined;
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('throws when children are empty', () => {
|
||||
// arrange
|
||||
const categoryName = 'test';
|
||||
const expectedMessage = `category has no children: "${categoryName}"`;
|
||||
const category = new CategoryDataStub()
|
||||
.withName(categoryName)
|
||||
.withChildren([]);
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('throws when children are undefined', () => {
|
||||
// arrange
|
||||
const categoryName = 'test';
|
||||
const expectedMessage = `category has no children: "${categoryName}"`;
|
||||
const category = new CategoryDataStub()
|
||||
.withName(categoryName)
|
||||
.withChildren(undefined);
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, context);
|
||||
// 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 = new CategoryDataStub()
|
||||
.withName(invalidName);
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('throws when context is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined context';
|
||||
const context = undefined;
|
||||
const category = new CategoryDataStub();
|
||||
// act
|
||||
const act = () => parseCategory(category, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns expected docs', () => {
|
||||
// arrange
|
||||
const url = 'https://privacy.sexy';
|
||||
const expected = parseDocUrls({ docs: url });
|
||||
const category = new CategoryDataStub()
|
||||
.withDocs(url);
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseCategory(category, context).documentationUrls;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
describe('parses expected subscript', () => {
|
||||
it('single script with code', () => {
|
||||
// arrange
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
const expected = [parseScript(script, context)];
|
||||
const category = new CategoryDataStub()
|
||||
.withChildren([script]);
|
||||
// act
|
||||
const actual = parseCategory(category, context).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('single script with function call', () => {
|
||||
// arrange
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(script);
|
||||
const context = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
const expected = [parseScript(script, context)];
|
||||
const category = new CategoryDataStub()
|
||||
.withChildren([script]);
|
||||
// act
|
||||
const actual = parseCategory(category, context).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('multiple scripts with function call and code', () => {
|
||||
// arrange
|
||||
const callableScript = ScriptDataStub.createWithCall();
|
||||
const scripts = [callableScript, ScriptDataStub.createWithCode()];
|
||||
const category = new CategoryDataStub()
|
||||
.withChildren(scripts);
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(callableScript);
|
||||
const context = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
const expected = scripts.map((script) => parseScript(script, context));
|
||||
// act
|
||||
const actual = parseCategory(category, context).scripts;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('script is created with right context', () => { // test through script validation logic
|
||||
// arrange
|
||||
const commentDelimiter = 'should not throw';
|
||||
const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
|
||||
const category = new CategoryDataStub()
|
||||
.withChildren([
|
||||
new CategoryDataStub()
|
||||
.withName('sub-category')
|
||||
.withChildren([
|
||||
ScriptDataStub
|
||||
.createWithoutCallOrCodes()
|
||||
.withCode(duplicatedCode),
|
||||
]),
|
||||
]);
|
||||
// act
|
||||
const act = () => parseCategory(category, parseContext).scripts;
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
it('returns expected subcategories', () => {
|
||||
// arrange
|
||||
const expected = [new CategoryDataStub()
|
||||
.withName('test category')
|
||||
.withChildren([ScriptDataStub.createWithCode()]),
|
||||
];
|
||||
const category = new CategoryDataStub()
|
||||
.withName('category name')
|
||||
.withChildren(expected);
|
||||
const context = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseCategory(category, context).subCategories;
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(1);
|
||||
expect(actual[0].name).to.equal(expected[0].category);
|
||||
expect(actual[0].scripts.length).to.equal(expected[0].children.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,36 +4,36 @@ import { DocumentableData } from 'js-yaml-loader!@/*';
|
||||
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
||||
|
||||
describe('DocumentationParser', () => {
|
||||
describe('parseDocUrls', () => {
|
||||
it('throws when undefined', () => {
|
||||
expect(() => parseDocUrls(undefined)).to.throw('documentable is null or undefined');
|
||||
});
|
||||
it('returns empty when empty', () => {
|
||||
// arrange
|
||||
const empty: DocumentableData = { };
|
||||
// act
|
||||
const actual = parseDocUrls(empty);
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(0);
|
||||
});
|
||||
it('returns single item when string', () => {
|
||||
// arrange
|
||||
const url = 'https://privacy.sexy';
|
||||
const expected = [ url ];
|
||||
const sut: DocumentableData = { docs: url };
|
||||
// act
|
||||
const actual = parseDocUrls(sut);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('returns all when array', () => {
|
||||
// arrange
|
||||
const expected = [ 'https://privacy.sexy', 'https://github.com/undergroundwires/privacy.sexy' ];
|
||||
const sut: DocumentableData = { docs: expected };
|
||||
// act
|
||||
const actual = parseDocUrls(sut);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
describe('parseDocUrls', () => {
|
||||
it('throws when undefined', () => {
|
||||
expect(() => parseDocUrls(undefined)).to.throw('documentable is null or undefined');
|
||||
});
|
||||
it('returns empty when empty', () => {
|
||||
// arrange
|
||||
const empty: DocumentableData = { };
|
||||
// act
|
||||
const actual = parseDocUrls(empty);
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(0);
|
||||
});
|
||||
it('returns single item when string', () => {
|
||||
// arrange
|
||||
const url = 'https://privacy.sexy';
|
||||
const expected = [url];
|
||||
const sut: DocumentableData = { docs: url };
|
||||
// act
|
||||
const actual = parseDocUrls(sut);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('returns all when array', () => {
|
||||
// arrange
|
||||
const expected = ['https://privacy.sexy', 'https://github.com/undergroundwires/privacy.sexy'];
|
||||
const sut: DocumentableData = { docs: expected };
|
||||
// act
|
||||
const actual = parseDocUrls(sut);
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,46 +4,46 @@ import { parseProjectInformation } from '@/application/Parser/ProjectInformation
|
||||
import { getProcessEnvironmentStub } from '@tests/unit/stubs/ProcessEnvironmentStub';
|
||||
|
||||
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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,72 +11,72 @@ import { ScriptingDefinitionStub } from '@tests/unit/stubs/ScriptingDefinitionSt
|
||||
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
||||
|
||||
describe('CategoryCollectionParseContext', () => {
|
||||
describe('ctor', () => {
|
||||
describe('functionsData', () => {
|
||||
it('can create with empty values', () => {
|
||||
// arrange
|
||||
const testData: FunctionData[][] = [ undefined, [] ];
|
||||
const scripting = new ScriptingDefinitionStub();
|
||||
for (const functionsData of testData) {
|
||||
// act
|
||||
const act = () => new CategoryCollectionParseContext(functionsData, scripting);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
}
|
||||
});
|
||||
});
|
||||
it('scripting', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined scripting';
|
||||
const scripting = undefined;
|
||||
const functionsData = [ FunctionDataStub.createWithCode() ];
|
||||
// act
|
||||
const act = () => new CategoryCollectionParseContext(functionsData, scripting);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('functionsData', () => {
|
||||
it('can create with empty values', () => {
|
||||
// arrange
|
||||
const testData: FunctionData[][] = [undefined, []];
|
||||
const scripting = new ScriptingDefinitionStub();
|
||||
for (const functionsData of testData) {
|
||||
// act
|
||||
const act = () => new CategoryCollectionParseContext(functionsData, scripting);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('constructed as expected', () => {
|
||||
// arrange
|
||||
const functionsData = [ FunctionDataStub.createWithCode() ];
|
||||
const syntax = new LanguageSyntaxStub();
|
||||
const expected = new ScriptCompiler(functionsData, syntax);
|
||||
const language = ScriptingLanguage.shellscript;
|
||||
const factoryMock = mockFactory(language, syntax);
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withLanguage(language);
|
||||
// act
|
||||
const sut = new CategoryCollectionParseContext(functionsData, definition, factoryMock);
|
||||
const actual = sut.compiler;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('scripting', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined scripting';
|
||||
const scripting = undefined;
|
||||
const functionsData = [FunctionDataStub.createWithCode()];
|
||||
// act
|
||||
const act = () => new CategoryCollectionParseContext(functionsData, scripting);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('syntax', () => {
|
||||
it('set from syntax factory', () => {
|
||||
// arrange
|
||||
const language = ScriptingLanguage.shellscript;
|
||||
const expected = new LanguageSyntaxStub();
|
||||
const factoryMock = mockFactory(language, expected);
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withLanguage(language);
|
||||
// act
|
||||
const sut = new CategoryCollectionParseContext([], definition, factoryMock);
|
||||
const actual = sut.syntax;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('constructed as expected', () => {
|
||||
// arrange
|
||||
const functionsData = [FunctionDataStub.createWithCode()];
|
||||
const syntax = new LanguageSyntaxStub();
|
||||
const expected = new ScriptCompiler(functionsData, syntax);
|
||||
const language = ScriptingLanguage.shellscript;
|
||||
const factoryMock = mockFactory(language, syntax);
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withLanguage(language);
|
||||
// act
|
||||
const sut = new CategoryCollectionParseContext(functionsData, definition, factoryMock);
|
||||
const actual = sut.compiler;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('syntax', () => {
|
||||
it('set from syntax factory', () => {
|
||||
// arrange
|
||||
const language = ScriptingLanguage.shellscript;
|
||||
const expected = new LanguageSyntaxStub();
|
||||
const factoryMock = mockFactory(language, expected);
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withLanguage(language);
|
||||
// act
|
||||
const sut = new CategoryCollectionParseContext([], definition, factoryMock);
|
||||
const actual = sut.syntax;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockFactory(expectedLanguage: ScriptingLanguage, result: ILanguageSyntax): ISyntaxFactory {
|
||||
return {
|
||||
create: (language: ScriptingLanguage) => {
|
||||
if (language !== expectedLanguage) {
|
||||
throw new Error('unexpected language');
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
return {
|
||||
create: (language: ScriptingLanguage) => {
|
||||
if (language !== expectedLanguage) {
|
||||
throw new Error('unexpected language');
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,235 +12,242 @@ import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub';
|
||||
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||
|
||||
describe('Expression', () => {
|
||||
describe('ctor', () => {
|
||||
describe('position', () => {
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined position';
|
||||
const position = undefined;
|
||||
// act
|
||||
const act = () => new ExpressionBuilder()
|
||||
.withPosition(position)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new ExpressionPosition(0, 5);
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withPosition(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.position).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('parameters', () => {
|
||||
it('defaults to empty array if undefined', () => {
|
||||
// arrange
|
||||
const parameters = undefined;
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withParameters(parameters)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.parameters);
|
||||
expect(actual.parameters.all);
|
||||
expect(actual.parameters.all.length).to.equal(0);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionParameterCollectionStub()
|
||||
.withParameterName('firstParameterName')
|
||||
.withParameterName('secondParameterName');
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withParameters(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.parameters).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('evaluator', () => {
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined evaluator';
|
||||
const evaluator = undefined;
|
||||
// act
|
||||
const act = () => new ExpressionBuilder()
|
||||
.withEvaluator(evaluator)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('position', () => {
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined position';
|
||||
const position = undefined;
|
||||
// act
|
||||
const act = () => new ExpressionBuilder()
|
||||
.withPosition(position)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new ExpressionPosition(0, 5);
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withPosition(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.position).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('evaluate', () => {
|
||||
describe('throws with invalid arguments', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'throws if arguments is undefined',
|
||||
context: undefined,
|
||||
expectedError: 'undefined context',
|
||||
},
|
||||
{
|
||||
name: 'throws when some of the required args are not provided',
|
||||
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b', 'c'], false),
|
||||
context: new ExpressionEvaluationContextStub()
|
||||
.withArgs(new FunctionCallArgumentCollectionStub().withArgument('b', 'provided')),
|
||||
expectedError: 'argument values are provided for required parameters: "a", "c"',
|
||||
},
|
||||
{
|
||||
name: 'throws when none of the required args are not provided',
|
||||
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b'], false),
|
||||
context: new ExpressionEvaluationContextStub()
|
||||
.withArgs(new FunctionCallArgumentCollectionStub().withArgument('c', 'unrelated')),
|
||||
expectedError: 'argument values are provided for required parameters: "a", "b"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const sutBuilder = new ExpressionBuilder();
|
||||
if (testCase.sut) {
|
||||
testCase.sut(sutBuilder);
|
||||
}
|
||||
const sut = sutBuilder.build();
|
||||
// act
|
||||
const act = () => sut.evaluate(testCase.context);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('returns result from evaluator', () => {
|
||||
// arrange
|
||||
const evaluatorMock: ExpressionEvaluator = (c) =>
|
||||
`"${c
|
||||
.args
|
||||
.getAllParameterNames()
|
||||
.map((name) => context.args.getArgument(name))
|
||||
.map((arg) => `${arg.parameterName}': '${arg.argumentValue}'`)
|
||||
.join('", "')}"`;
|
||||
const givenArguments = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter1', 'value1')
|
||||
.withArgument('parameter2', 'value2');
|
||||
const expectedParameterNames = givenArguments.getAllParameterNames();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(givenArguments);
|
||||
const expected = evaluatorMock(context);
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameterNames(expectedParameterNames)
|
||||
.build();
|
||||
// arrange
|
||||
const actual = sut.evaluate(context);
|
||||
// assert
|
||||
expect(expected).to.equal(actual,
|
||||
`\nGiven arguments: ${JSON.stringify(givenArguments)}\n` +
|
||||
`\nExpected parameter names: ${JSON.stringify(expectedParameterNames)}\n`,
|
||||
);
|
||||
});
|
||||
it('sends pipeline compiler as it is', () => {
|
||||
// arrange
|
||||
const expected = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(expected);
|
||||
let actual: IPipelineCompiler;
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => {
|
||||
actual = c.pipelineCompiler;
|
||||
return '';
|
||||
};
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.build();
|
||||
// arrange
|
||||
sut.evaluate(context);
|
||||
// assert
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
describe('filters unused parameters', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'with a provided argument',
|
||||
expressionParameters: new FunctionParameterCollectionStub()
|
||||
.withParameterName('parameterToHave', false),
|
||||
arguments: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameterToHave', 'value-to-have')
|
||||
.withArgument('parameterToIgnore', 'value-to-ignore'),
|
||||
expectedArguments: [
|
||||
new FunctionCallArgumentStub()
|
||||
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'without a provided argument',
|
||||
expressionParameters: new FunctionParameterCollectionStub()
|
||||
.withParameterName('parameterToHave', false)
|
||||
.withParameterName('parameterToIgnore', true),
|
||||
arguments: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameterToHave', 'value-to-have'),
|
||||
expectedArguments: [
|
||||
new FunctionCallArgumentStub()
|
||||
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
let actual: IReadOnlyFunctionCallArgumentCollection;
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => {
|
||||
actual = c.args;
|
||||
return '';
|
||||
};
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(testCase.arguments);
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameters(testCase.expressionParameters)
|
||||
.build();
|
||||
// act
|
||||
sut.evaluate(context);
|
||||
// assert
|
||||
const actualArguments = actual.getAllParameterNames().map((name) => actual.getArgument(name));
|
||||
expect(actualArguments).to.deep.equal(testCase.expectedArguments);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('parameters', () => {
|
||||
it('defaults to empty array if undefined', () => {
|
||||
// arrange
|
||||
const parameters = undefined;
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withParameters(parameters)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.parameters);
|
||||
expect(actual.parameters.all);
|
||||
expect(actual.parameters.all.length).to.equal(0);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionParameterCollectionStub()
|
||||
.withParameterName('firstParameterName')
|
||||
.withParameterName('secondParameterName');
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withParameters(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.parameters).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('evaluator', () => {
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined evaluator';
|
||||
const evaluator = undefined;
|
||||
// act
|
||||
const act = () => new ExpressionBuilder()
|
||||
.withEvaluator(evaluator)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('evaluate', () => {
|
||||
describe('throws with invalid arguments', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'throws if arguments is undefined',
|
||||
context: undefined,
|
||||
expectedError: 'undefined context',
|
||||
},
|
||||
{
|
||||
name: 'throws when some of the required args are not provided',
|
||||
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b', 'c'], false),
|
||||
context: new ExpressionEvaluationContextStub()
|
||||
.withArgs(new FunctionCallArgumentCollectionStub().withArgument('b', 'provided')),
|
||||
expectedError: 'argument values are provided for required parameters: "a", "c"',
|
||||
},
|
||||
{
|
||||
name: 'throws when none of the required args are not provided',
|
||||
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b'], false),
|
||||
context: new ExpressionEvaluationContextStub()
|
||||
.withArgs(new FunctionCallArgumentCollectionStub().withArgument('c', 'unrelated')),
|
||||
expectedError: 'argument values are provided for required parameters: "a", "b"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const sutBuilder = new ExpressionBuilder();
|
||||
if (testCase.sut) {
|
||||
testCase.sut(sutBuilder);
|
||||
}
|
||||
const sut = sutBuilder.build();
|
||||
// act
|
||||
const act = () => sut.evaluate(testCase.context);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('returns result from evaluator', () => {
|
||||
// arrange
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => `"${c
|
||||
.args
|
||||
.getAllParameterNames()
|
||||
.map((name) => context.args.getArgument(name))
|
||||
.map((arg) => `${arg.parameterName}': '${arg.argumentValue}'`)
|
||||
.join('", "')}"`;
|
||||
const givenArguments = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter1', 'value1')
|
||||
.withArgument('parameter2', 'value2');
|
||||
const expectedParameterNames = givenArguments.getAllParameterNames();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(givenArguments);
|
||||
const expected = evaluatorMock(context);
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameterNames(expectedParameterNames)
|
||||
.build();
|
||||
// arrange
|
||||
const actual = sut.evaluate(context);
|
||||
// assert
|
||||
expect(expected).to.equal(actual, printMessage());
|
||||
function printMessage(): string {
|
||||
return `\nGiven arguments: ${JSON.stringify(givenArguments)}\n`
|
||||
+ `\nExpected parameter names: ${JSON.stringify(expectedParameterNames)}\n`;
|
||||
}
|
||||
});
|
||||
it('sends pipeline compiler as it is', () => {
|
||||
// arrange
|
||||
const expected = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(expected);
|
||||
let actual: IPipelineCompiler;
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => {
|
||||
actual = c.pipelineCompiler;
|
||||
return '';
|
||||
};
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.build();
|
||||
// arrange
|
||||
sut.evaluate(context);
|
||||
// assert
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
describe('filters unused parameters', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'with a provided argument',
|
||||
expressionParameters: new FunctionParameterCollectionStub()
|
||||
.withParameterName('parameterToHave', false),
|
||||
arguments: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameterToHave', 'value-to-have')
|
||||
.withArgument('parameterToIgnore', 'value-to-ignore'),
|
||||
expectedArguments: [
|
||||
new FunctionCallArgumentStub()
|
||||
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'without a provided argument',
|
||||
expressionParameters: new FunctionParameterCollectionStub()
|
||||
.withParameterName('parameterToHave', false)
|
||||
.withParameterName('parameterToIgnore', true),
|
||||
arguments: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameterToHave', 'value-to-have'),
|
||||
expectedArguments: [
|
||||
new FunctionCallArgumentStub()
|
||||
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
let actual: IReadOnlyFunctionCallArgumentCollection;
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => {
|
||||
actual = c.args;
|
||||
return '';
|
||||
};
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(testCase.arguments);
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameters(testCase.expressionParameters)
|
||||
.build();
|
||||
// act
|
||||
sut.evaluate(context);
|
||||
// assert
|
||||
const actualArguments = actual.getAllParameterNames()
|
||||
.map((name) => actual.getArgument(name));
|
||||
expect(actualArguments).to.deep.equal(testCase.expectedArguments);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ExpressionBuilder {
|
||||
private position: ExpressionPosition = new ExpressionPosition(0, 5);
|
||||
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||
private position: ExpressionPosition = new ExpressionPosition(0, 5);
|
||||
|
||||
public withPosition(position: ExpressionPosition) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
public withEvaluator(evaluator: ExpressionEvaluator) {
|
||||
this.evaluator = evaluator;
|
||||
return this;
|
||||
}
|
||||
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
public withParameterName(parameterName: string, isOptional: boolean = true) {
|
||||
const collection = new FunctionParameterCollectionStub()
|
||||
.withParameterName(parameterName, isOptional);
|
||||
return this.withParameters(collection);
|
||||
}
|
||||
public withParameterNames(parameterNames: string[], isOptional: boolean = true) {
|
||||
const collection = new FunctionParameterCollectionStub()
|
||||
.withParameterNames(parameterNames, isOptional);
|
||||
return this.withParameters(collection);
|
||||
}
|
||||
public build() {
|
||||
return new Expression(this.position, this.evaluator, this.parameters);
|
||||
}
|
||||
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||
|
||||
private evaluator: ExpressionEvaluator = () => '';
|
||||
public withPosition(position: ExpressionPosition) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withEvaluator(evaluator: ExpressionEvaluator) {
|
||||
this.evaluator = evaluator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withParameterName(parameterName: string, isOptional = true) {
|
||||
const collection = new FunctionParameterCollectionStub()
|
||||
.withParameterName(parameterName, isOptional);
|
||||
return this.withParameters(collection);
|
||||
}
|
||||
|
||||
public withParameterNames(parameterNames: string[], isOptional = true) {
|
||||
const collection = new FunctionParameterCollectionStub()
|
||||
.withParameterNames(parameterNames, isOptional);
|
||||
return this.withParameters(collection);
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new Expression(this.position, this.evaluator, this.parameters);
|
||||
}
|
||||
|
||||
private evaluator: ExpressionEvaluator = () => '';
|
||||
}
|
||||
|
||||
@@ -6,60 +6,63 @@ import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressi
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||
import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub';
|
||||
|
||||
|
||||
describe('ExpressionEvaluationContext', () => {
|
||||
describe('ctor', () => {
|
||||
describe('args', () => {
|
||||
it('throws if args are undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args';
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withArgs(undefined);
|
||||
// act
|
||||
const act = () => builder.build();
|
||||
// assert
|
||||
expect(act).throw(expectedError);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('expectedParameter', 'expectedValue');
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withArgs(expected);
|
||||
// act
|
||||
const sut = builder.build();
|
||||
// assert
|
||||
const actual = sut.args;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('pipelineCompiler', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new PipelineCompilerStub();
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withPipelineCompiler(expected);
|
||||
// act
|
||||
const sut = builder.build();
|
||||
// assert
|
||||
expect(sut.pipelineCompiler).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('args', () => {
|
||||
it('throws if args are undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args';
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withArgs(undefined);
|
||||
// act
|
||||
const act = () => builder.build();
|
||||
// assert
|
||||
expect(act).throw(expectedError);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('expectedParameter', 'expectedValue');
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withArgs(expected);
|
||||
// act
|
||||
const sut = builder.build();
|
||||
// assert
|
||||
const actual = sut.args;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('pipelineCompiler', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new PipelineCompilerStub();
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withPipelineCompiler(expected);
|
||||
// act
|
||||
const sut = builder.build();
|
||||
// assert
|
||||
expect(sut.pipelineCompiler).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ExpressionEvaluationContextBuilder {
|
||||
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
|
||||
private pipelineCompiler: IPipelineCompiler = new PipelineCompilerStub();
|
||||
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
public withPipelineCompiler(pipelineCompiler: IPipelineCompiler) {
|
||||
this.pipelineCompiler = pipelineCompiler;
|
||||
return this;
|
||||
}
|
||||
public build(): IExpressionEvaluationContext {
|
||||
return new ExpressionEvaluationContext(this.args, this.pipelineCompiler);
|
||||
}
|
||||
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
|
||||
|
||||
private pipelineCompiler: IPipelineCompiler = new PipelineCompilerStub();
|
||||
|
||||
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withPipelineCompiler(pipelineCompiler: IPipelineCompiler) {
|
||||
this.pipelineCompiler = pipelineCompiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build(): IExpressionEvaluationContext {
|
||||
return new ExpressionEvaluationContext(this.args, this.pipelineCompiler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,32 +3,32 @@ import { expect } from 'chai';
|
||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||
|
||||
describe('ExpressionPosition', () => {
|
||||
describe('ctor', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedStart = 0;
|
||||
const expectedEnd = 5;
|
||||
// act
|
||||
const sut = new ExpressionPosition(expectedStart, expectedEnd);
|
||||
// assert
|
||||
expect(sut.start).to.equal(expectedStart);
|
||||
expect(sut.end).to.equal(expectedEnd);
|
||||
});
|
||||
describe('throws when invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{ start: 5, end: 5, error: 'no length (start = end = 5)' },
|
||||
{ start: 5, end: 3, error: 'start (5) after end (3)' },
|
||||
{ start: -1, end: 3, error: 'negative start position: -1' },
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.error, () => {
|
||||
// act
|
||||
const act = () => new ExpressionPosition(testCase.start, testCase.end);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.error);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedStart = 0;
|
||||
const expectedEnd = 5;
|
||||
// act
|
||||
const sut = new ExpressionPosition(expectedStart, expectedEnd);
|
||||
// assert
|
||||
expect(sut.start).to.equal(expectedStart);
|
||||
expect(sut.end).to.equal(expectedEnd);
|
||||
});
|
||||
describe('throws when invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{ start: 5, end: 5, error: 'no length (start = end = 5)' },
|
||||
{ start: 5, end: 3, error: 'start (5) after end (3)' },
|
||||
{ start: -1, end: 3, error: 'negative start position: -1' },
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.error, () => {
|
||||
// act
|
||||
const act = () => new ExpressionPosition(testCase.start, testCase.end);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.error);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,181 +7,181 @@ import { ExpressionParserStub } from '@tests/unit/stubs/ExpressionParserStub';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||
|
||||
describe('ExpressionsCompiler', () => {
|
||||
describe('compileExpressions', () => {
|
||||
describe('returns code when it is empty or undefined', () => {
|
||||
// arrange
|
||||
const testCases = [{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
}, {
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
},
|
||||
];
|
||||
for (const test of testCases) {
|
||||
it(`given ${test.name}`, () => {
|
||||
const expected = test.value;
|
||||
const sut = new SystemUnderTest();
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
// act
|
||||
const value = sut.compileExpressions(test.value, args);
|
||||
// assert
|
||||
expect(value).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('combines expressions as expected', () => {
|
||||
// arrange
|
||||
const code = 'part1 {{ a }} part2 {{ b }} part3';
|
||||
const testCases = [
|
||||
{
|
||||
name: 'with ordered expressions',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'unordered expressions',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'with an optional expected argument that is not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a')
|
||||
.withParameterNames(['optionalParameter'], true),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b')
|
||||
.withParameterNames(['optionalParameterTwo'], true),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'with no expressions',
|
||||
expressions: [],
|
||||
expected: code,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const actual = sut.compileExpressions(code, args);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('arguments', () => {
|
||||
it('passes arguments to expressions as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('test-arg', 'test-value');
|
||||
const code = 'non-important';
|
||||
const expressions = [
|
||||
new ExpressionStub(),
|
||||
new ExpressionStub(),
|
||||
];
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(expressions);
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
sut.compileExpressions(code, expected);
|
||||
// assert
|
||||
expect(expressions[0].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[0].callHistory[0].args).to.equal(expected);
|
||||
expect(expressions[1].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[1].callHistory[0].args).to.equal(expected);
|
||||
});
|
||||
it('throws if arguments is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args, send empty collection instead';
|
||||
const args = undefined;
|
||||
const expressionParserMock = new ExpressionParserStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const act = () => sut.compileExpressions('code', args);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws when expected argument is not provided but used in code', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'empty parameters',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub(),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'unnecessary parameter is provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('unnecessaryParameter', 'unnecessaryValue'),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'multiple values are not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub(),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter2", "parameter3" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'some values are provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter2', 'value'),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter3" but used in code',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const code = 'non-important-code';
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const act = () => sut.compileExpressions(code, testCase.args);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('calls parser with expected code', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const expressionParserMock = new ExpressionParserStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
// act
|
||||
sut.compileExpressions(expected, args);
|
||||
// assert
|
||||
expect(expressionParserMock.callHistory).to.have.lengthOf(1);
|
||||
expect(expressionParserMock.callHistory[0]).to.equal(expected);
|
||||
describe('compileExpressions', () => {
|
||||
describe('returns code when it is empty or undefined', () => {
|
||||
// arrange
|
||||
const testCases = [{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
}, {
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
},
|
||||
];
|
||||
for (const test of testCases) {
|
||||
it(`given ${test.name}`, () => {
|
||||
const expected = test.value;
|
||||
const sut = new SystemUnderTest();
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
// act
|
||||
const value = sut.compileExpressions(test.value, args);
|
||||
// assert
|
||||
expect(value).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('combines expressions as expected', () => {
|
||||
// arrange
|
||||
const code = 'part1 {{ a }} part2 {{ b }} part3';
|
||||
const testCases = [
|
||||
{
|
||||
name: 'with ordered expressions',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'unordered expressions',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'with an optional expected argument that is not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a')
|
||||
.withParameterNames(['optionalParameter'], true),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b')
|
||||
.withParameterNames(['optionalParameterTwo'], true),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'with no expressions',
|
||||
expressions: [],
|
||||
expected: code,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const actual = sut.compileExpressions(code, args);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('arguments', () => {
|
||||
it('passes arguments to expressions as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('test-arg', 'test-value');
|
||||
const code = 'non-important';
|
||||
const expressions = [
|
||||
new ExpressionStub(),
|
||||
new ExpressionStub(),
|
||||
];
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(expressions);
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
sut.compileExpressions(code, expected);
|
||||
// assert
|
||||
expect(expressions[0].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[0].callHistory[0].args).to.equal(expected);
|
||||
expect(expressions[1].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[1].callHistory[0].args).to.equal(expected);
|
||||
});
|
||||
it('throws if arguments is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args, send empty collection instead';
|
||||
const args = undefined;
|
||||
const expressionParserMock = new ExpressionParserStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const act = () => sut.compileExpressions('code', args);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws when expected argument is not provided but used in code', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'empty parameters',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub(),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'unnecessary parameter is provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('unnecessaryParameter', 'unnecessaryValue'),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'multiple values are not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub(),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter2", "parameter3" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'some values are provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter2', 'value'),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter3" but used in code',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const code = 'non-important-code';
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const act = () => sut.compileExpressions(code, testCase.args);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('calls parser with expected code', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const expressionParserMock = new ExpressionParserStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
// act
|
||||
sut.compileExpressions(expected, args);
|
||||
// assert
|
||||
expect(expressionParserMock.callHistory).to.have.lengthOf(1);
|
||||
expect(expressionParserMock.callHistory[0]).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class SystemUnderTest extends ExpressionsCompiler {
|
||||
constructor(extractor: IExpressionParser = new ExpressionParserStub()) {
|
||||
super(extractor);
|
||||
}
|
||||
constructor(extractor: IExpressionParser = new ExpressionParserStub()) {
|
||||
super(extractor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,82 +6,82 @@ import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/
|
||||
import { ExpressionStub } from '@tests/unit/stubs/ExpressionStub';
|
||||
|
||||
describe('CompositeExpressionParser', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws if one of the parsers is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined leaf';
|
||||
const parsers: readonly IExpressionParser[] = [ undefined, mockParser() ];
|
||||
// act
|
||||
const act = () => new CompositeExpressionParser(parsers);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('throws if one of the parsers is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined leaf';
|
||||
const parsers: readonly IExpressionParser[] = [undefined, mockParser()];
|
||||
// act
|
||||
const act = () => new CompositeExpressionParser(parsers);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('findExpressions', () => {
|
||||
describe('returns result from parsers as expected', () => {
|
||||
// arrange
|
||||
const pool = [
|
||||
new ExpressionStub(), new ExpressionStub(), new ExpressionStub(),
|
||||
new ExpressionStub(), new ExpressionStub(),
|
||||
];
|
||||
const testCases = [
|
||||
{
|
||||
name: 'from single parsing none',
|
||||
parsers: [ mockParser() ],
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'from single parsing single',
|
||||
parsers: [ mockParser(pool[0]) ],
|
||||
expected: [ pool[0] ],
|
||||
},
|
||||
{
|
||||
name: 'from single parsing multiple',
|
||||
parsers: [ mockParser(pool[0], pool[1]) ],
|
||||
expected: [ pool[0], pool[1] ],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with each parsing single',
|
||||
parsers: [
|
||||
mockParser(pool[0]),
|
||||
mockParser(pool[1]),
|
||||
mockParser(pool[2]),
|
||||
],
|
||||
expected: [ pool[0], pool[1], pool[2] ],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with each parsing multiple',
|
||||
parsers: [
|
||||
mockParser(pool[0], pool[1]),
|
||||
mockParser(pool[2], pool[3], pool[4]) ],
|
||||
expected: [ pool[0], pool[1], pool[2], pool[3], pool[4] ],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with only some parsing',
|
||||
parsers: [
|
||||
mockParser(pool[0], pool[1]),
|
||||
mockParser(),
|
||||
mockParser(pool[2]),
|
||||
mockParser(),
|
||||
],
|
||||
expected: [ pool[0], pool[1], pool[2] ],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new CompositeExpressionParser(testCase.parsers);
|
||||
// act
|
||||
const result = sut.findExpressions('non-important-code');
|
||||
// expect
|
||||
expect(result).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('findExpressions', () => {
|
||||
describe('returns result from parsers as expected', () => {
|
||||
// arrange
|
||||
const pool = [
|
||||
new ExpressionStub(), new ExpressionStub(), new ExpressionStub(),
|
||||
new ExpressionStub(), new ExpressionStub(),
|
||||
];
|
||||
const testCases = [
|
||||
{
|
||||
name: 'from single parsing none',
|
||||
parsers: [mockParser()],
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'from single parsing single',
|
||||
parsers: [mockParser(pool[0])],
|
||||
expected: [pool[0]],
|
||||
},
|
||||
{
|
||||
name: 'from single parsing multiple',
|
||||
parsers: [mockParser(pool[0], pool[1])],
|
||||
expected: [pool[0], pool[1]],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with each parsing single',
|
||||
parsers: [
|
||||
mockParser(pool[0]),
|
||||
mockParser(pool[1]),
|
||||
mockParser(pool[2]),
|
||||
],
|
||||
expected: [pool[0], pool[1], pool[2]],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with each parsing multiple',
|
||||
parsers: [
|
||||
mockParser(pool[0], pool[1]),
|
||||
mockParser(pool[2], pool[3], pool[4])],
|
||||
expected: [pool[0], pool[1], pool[2], pool[3], pool[4]],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with only some parsing',
|
||||
parsers: [
|
||||
mockParser(pool[0], pool[1]),
|
||||
mockParser(),
|
||||
mockParser(pool[2]),
|
||||
mockParser(),
|
||||
],
|
||||
expected: [pool[0], pool[1], pool[2]],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new CompositeExpressionParser(testCase.parsers);
|
||||
// act
|
||||
const result = sut.findExpressions('non-important-code');
|
||||
// expect
|
||||
expect(result).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockParser(...result: IExpression[]): IExpressionParser {
|
||||
return {
|
||||
findExpressions: () => result,
|
||||
};
|
||||
return {
|
||||
findExpressions: () => result,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,133 +3,153 @@ import { expect } from 'chai';
|
||||
import { ExpressionRegexBuilder } from '@/application/Parser/Script/Compiler/Expressions/Parser/Regex/ExpressionRegexBuilder';
|
||||
|
||||
describe('ExpressionRegexBuilder', () => {
|
||||
describe('expectCharacters', () => {
|
||||
describe('escape single as expected', () => {
|
||||
const charactersToEscape = [ '.', '$' ];
|
||||
for (const character of charactersToEscape) {
|
||||
it(character, () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters(character),
|
||||
// assert
|
||||
`\\${character}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('escapes multiple as expected', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters('.I have no $$.'),
|
||||
// assert
|
||||
'\\.I have no \\$\\\$\\.');
|
||||
});
|
||||
it('adds as expected', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters('return as it is'),
|
||||
// assert
|
||||
'return as it is');
|
||||
describe('expectCharacters', () => {
|
||||
describe('escape single as expected', () => {
|
||||
const charactersToEscape = ['.', '$'];
|
||||
for (const character of charactersToEscape) {
|
||||
it(character, () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters(character),
|
||||
// assert
|
||||
`\\${character}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('expectOneOrMoreWhitespaces', () => {
|
||||
it('escapes multiple as expected', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters('.I have no $$.'),
|
||||
// assert
|
||||
'\\.I have no \\$\\$\\.',
|
||||
);
|
||||
});
|
||||
it('adds as expected', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters('return as it is'),
|
||||
// assert
|
||||
'return as it is',
|
||||
);
|
||||
});
|
||||
});
|
||||
it('expectOneOrMoreWhitespaces', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectOneOrMoreWhitespaces(),
|
||||
// assert
|
||||
'\\s+',
|
||||
);
|
||||
});
|
||||
it('matchPipeline', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchPipeline(),
|
||||
// assert
|
||||
'\\s*(\\|\\s*.+?)?',
|
||||
);
|
||||
});
|
||||
it('matchUntilFirstWhitespace', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchUntilFirstWhitespace(),
|
||||
// assert
|
||||
'([^|\\s]+)',
|
||||
);
|
||||
});
|
||||
it('matchAnythingExceptSurroundingWhitespaces', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchAnythingExceptSurroundingWhitespaces(),
|
||||
// assert
|
||||
'\\s*(.+?)\\s*',
|
||||
);
|
||||
});
|
||||
it('expectExpressionStart', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectExpressionStart(),
|
||||
// assert
|
||||
'{{\\s*',
|
||||
);
|
||||
});
|
||||
it('expectExpressionEnd', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectExpressionEnd(),
|
||||
// assert
|
||||
'\\s*}}',
|
||||
);
|
||||
});
|
||||
describe('buildRegExp', () => {
|
||||
it('sets global flag', () => {
|
||||
// arrange
|
||||
const expected = 'g';
|
||||
const sut = new ExpressionRegexBuilder()
|
||||
.expectOneOrMoreWhitespaces();
|
||||
// act
|
||||
const actual = sut.buildRegExp().flags;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('can combine multiple parts', () => {
|
||||
it('with', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectOneOrMoreWhitespaces(),
|
||||
// assert
|
||||
'\\s+');
|
||||
});
|
||||
it('matchPipeline', () => {
|
||||
(sut) => sut
|
||||
// act
|
||||
// {{ $with }}
|
||||
.expectExpressionStart()
|
||||
.expectCharacters('with')
|
||||
.expectOneOrMoreWhitespaces()
|
||||
.expectCharacters('$')
|
||||
.matchUntilFirstWhitespace()
|
||||
.expectExpressionEnd()
|
||||
// scope
|
||||
.matchAnythingExceptSurroundingWhitespaces()
|
||||
// {{ end }}
|
||||
.expectExpressionStart()
|
||||
.expectCharacters('end')
|
||||
.expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*with\\s+\\$([^|\\s]+)\\s*}}\\s*(.+?)\\s*{{\\s*end\\s*}}',
|
||||
);
|
||||
});
|
||||
it('scoped substitution', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchPipeline(),
|
||||
// assert
|
||||
'\\s*(\\|\\s*.+?)?');
|
||||
});
|
||||
it('matchUntilFirstWhitespace', () => {
|
||||
(sut) => sut
|
||||
// act
|
||||
.expectExpressionStart().expectCharacters('.')
|
||||
.matchPipeline()
|
||||
.expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*\\.\\s*(\\|\\s*.+?)?\\s*}}',
|
||||
);
|
||||
});
|
||||
it('parameter substitution', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchUntilFirstWhitespace(),
|
||||
// assert
|
||||
'([^|\\s]+)');
|
||||
});
|
||||
it('matchAnythingExceptSurroundingWhitespaces', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchAnythingExceptSurroundingWhitespaces(),
|
||||
// assert
|
||||
'\\s*(.+?)\\s*');
|
||||
});
|
||||
it('expectExpressionStart', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectExpressionStart(),
|
||||
// assert
|
||||
'{{\\s*');
|
||||
});
|
||||
it('expectExpressionEnd', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectExpressionEnd(),
|
||||
// assert
|
||||
'\\s*}}');
|
||||
});
|
||||
describe('buildRegExp', () => {
|
||||
it('sets global flag', () => {
|
||||
// arrange
|
||||
const expected = 'g';
|
||||
const sut = new ExpressionRegexBuilder()
|
||||
.expectOneOrMoreWhitespaces();
|
||||
// act
|
||||
const actual = sut.buildRegExp().flags;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('can combine multiple parts', () => {
|
||||
it('with', () => {
|
||||
runRegExTest((sut) => sut
|
||||
// act
|
||||
.expectExpressionStart().expectCharacters('with').expectOneOrMoreWhitespaces().expectCharacters('$')
|
||||
.matchUntilFirstWhitespace()
|
||||
.expectExpressionEnd()
|
||||
.matchAnythingExceptSurroundingWhitespaces()
|
||||
.expectExpressionStart().expectCharacters('end').expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*with\\s+\\$([^|\\s]+)\\s*}}\\s*(.+?)\\s*{{\\s*end\\s*}}',
|
||||
);
|
||||
});
|
||||
it('scoped substitution', () => {
|
||||
runRegExTest((sut) => sut
|
||||
// act
|
||||
.expectExpressionStart().expectCharacters('.')
|
||||
.matchPipeline()
|
||||
.expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*\\.\\s*(\\|\\s*.+?)?\\s*}}',
|
||||
);
|
||||
});
|
||||
it('parameter substitution', () => {
|
||||
runRegExTest((sut) => sut
|
||||
// act
|
||||
.expectExpressionStart().expectCharacters('$')
|
||||
.matchUntilFirstWhitespace()
|
||||
.matchPipeline()
|
||||
.expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*\\$([^|\\s]+)\\s*(\\|\\s*.+?)?\\s*}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
(sut) => sut
|
||||
// act
|
||||
.expectExpressionStart().expectCharacters('$')
|
||||
.matchUntilFirstWhitespace()
|
||||
.matchPipeline()
|
||||
.expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*\\$([^|\\s]+)\\s*(\\|\\s*.+?)?\\s*}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function runRegExTest(
|
||||
act: (sut: ExpressionRegexBuilder) => ExpressionRegexBuilder,
|
||||
expected: string,
|
||||
) {
|
||||
// arrange
|
||||
const sut = new ExpressionRegexBuilder();
|
||||
// act
|
||||
const actual = act(sut).buildRegExp().source;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
act: (sut: ExpressionRegexBuilder) => ExpressionRegexBuilder,
|
||||
expected: string,
|
||||
) {
|
||||
// arrange
|
||||
const sut = new ExpressionRegexBuilder();
|
||||
// act
|
||||
const actual = act(sut).buildRegExp().source;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
}
|
||||
|
||||
@@ -6,145 +6,148 @@ import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Express
|
||||
import { FunctionParameterStub } from '@tests/unit/stubs/FunctionParameterStub';
|
||||
|
||||
describe('RegexParser', () => {
|
||||
describe('findExpressions', () => {
|
||||
describe('throws when code is unexpected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
expectedError: 'undefined code',
|
||||
},
|
||||
{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
expectedError: 'undefined code',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(`given ${testCase.name}`, () => {
|
||||
const sut = new RegexParserConcrete(/unimportant/);
|
||||
// act
|
||||
const act = () => sut.findExpressions(testCase.value);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('matches regex as expected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'returns no result when regex does not match',
|
||||
regex: /hello/g,
|
||||
code: 'world',
|
||||
},
|
||||
{
|
||||
name: 'returns expected when regex matches single',
|
||||
regex: /hello/g,
|
||||
code: 'hello world',
|
||||
},
|
||||
{
|
||||
name: 'returns expected when regex matches multiple',
|
||||
regex: /l/g,
|
||||
code: 'hello world',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const expected = Array.from(testCase.code.matchAll(testCase.regex));
|
||||
const matches = new Array<RegExpMatchArray>();
|
||||
const builder = (m: RegExpMatchArray): IPrimitiveExpression => {
|
||||
matches.push(m);
|
||||
return mockPrimitiveExpression();
|
||||
};
|
||||
const sut = new RegexParserConcrete(testCase.regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(matches.length);
|
||||
expect(matches).to.deep.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('sets evaluator as expected', () => {
|
||||
// arrange
|
||||
const expected = getEvaluatorStub();
|
||||
const regex = /hello/g;
|
||||
const code = 'hello';
|
||||
const builder = (): IPrimitiveExpression => ({
|
||||
evaluator: expected,
|
||||
});
|
||||
const sut = new RegexParserConcrete(regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(1);
|
||||
expect(expressions[0].evaluate === expected);
|
||||
});
|
||||
it('sets parameters as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new FunctionParameterStub().withName('parameter1').withOptionality(true),
|
||||
new FunctionParameterStub().withName('parameter2').withOptionality(false),
|
||||
];
|
||||
const regex = /hello/g;
|
||||
const code = 'hello';
|
||||
const builder = (): IPrimitiveExpression => ({
|
||||
evaluator: getEvaluatorStub(),
|
||||
parameters: expected,
|
||||
});
|
||||
const sut = new RegexParserConcrete(regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(1);
|
||||
expect(expressions[0].parameters.all).to.deep.equal(expected);
|
||||
});
|
||||
it('sets expected position', () => {
|
||||
// arrange
|
||||
const code = 'mate date in state is fate';
|
||||
const regex = /ate/g;
|
||||
const expected = [
|
||||
new ExpressionPosition(1, 4),
|
||||
new ExpressionPosition(6, 9),
|
||||
new ExpressionPosition(15, 18),
|
||||
new ExpressionPosition(23, 26),
|
||||
];
|
||||
const sut = new RegexParserConcrete(regex);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.position);
|
||||
expect(actual).to.deep.equal(expected);
|
||||
describe('findExpressions', () => {
|
||||
describe('throws when code is unexpected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
expectedError: 'undefined code',
|
||||
},
|
||||
{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
expectedError: 'undefined code',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(`given ${testCase.name}`, () => {
|
||||
const sut = new RegexParserConcrete(/unimportant/);
|
||||
// act
|
||||
const act = () => sut.findExpressions(testCase.value);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('matches regex as expected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'returns no result when regex does not match',
|
||||
regex: /hello/g,
|
||||
code: 'world',
|
||||
},
|
||||
{
|
||||
name: 'returns expected when regex matches single',
|
||||
regex: /hello/g,
|
||||
code: 'hello world',
|
||||
},
|
||||
{
|
||||
name: 'returns expected when regex matches multiple',
|
||||
regex: /l/g,
|
||||
code: 'hello world',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const expected = Array.from(testCase.code.matchAll(testCase.regex));
|
||||
const matches = new Array<RegExpMatchArray>();
|
||||
const builder = (m: RegExpMatchArray): IPrimitiveExpression => {
|
||||
matches.push(m);
|
||||
return mockPrimitiveExpression();
|
||||
};
|
||||
const sut = new RegexParserConcrete(testCase.regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(matches.length);
|
||||
expect(matches).to.deep.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('sets evaluator as expected', () => {
|
||||
// arrange
|
||||
const expected = getEvaluatorStub();
|
||||
const regex = /hello/g;
|
||||
const code = 'hello';
|
||||
const builder = (): IPrimitiveExpression => ({
|
||||
evaluator: expected,
|
||||
});
|
||||
const sut = new RegexParserConcrete(regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(1);
|
||||
expect(expressions[0].evaluate === expected);
|
||||
});
|
||||
it('sets parameters as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new FunctionParameterStub().withName('parameter1').withOptionality(true),
|
||||
new FunctionParameterStub().withName('parameter2').withOptionality(false),
|
||||
];
|
||||
const regex = /hello/g;
|
||||
const code = 'hello';
|
||||
const builder = (): IPrimitiveExpression => ({
|
||||
evaluator: getEvaluatorStub(),
|
||||
parameters: expected,
|
||||
});
|
||||
const sut = new RegexParserConcrete(regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(1);
|
||||
expect(expressions[0].parameters.all).to.deep.equal(expected);
|
||||
});
|
||||
it('sets expected position', () => {
|
||||
// arrange
|
||||
const code = 'mate date in state is fate';
|
||||
const regex = /ate/g;
|
||||
const expected = [
|
||||
new ExpressionPosition(1, 4),
|
||||
new ExpressionPosition(6, 9),
|
||||
new ExpressionPosition(15, 18),
|
||||
new ExpressionPosition(23, 26),
|
||||
];
|
||||
const sut = new RegexParserConcrete(regex);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.position);
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockBuilder(): (match: RegExpMatchArray) => IPrimitiveExpression {
|
||||
return () => ({
|
||||
evaluator: getEvaluatorStub(),
|
||||
});
|
||||
return () => ({
|
||||
evaluator: getEvaluatorStub(),
|
||||
});
|
||||
}
|
||||
function getEvaluatorStub(): ExpressionEvaluator {
|
||||
return () => undefined;
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
function mockPrimitiveExpression(): IPrimitiveExpression {
|
||||
return {
|
||||
evaluator: getEvaluatorStub(),
|
||||
};
|
||||
return {
|
||||
evaluator: getEvaluatorStub(),
|
||||
};
|
||||
}
|
||||
|
||||
class RegexParserConcrete extends RegexParser {
|
||||
protected regex: RegExp;
|
||||
public constructor(
|
||||
regex: RegExp,
|
||||
private readonly builder = mockBuilder()) {
|
||||
super();
|
||||
this.regex = regex;
|
||||
}
|
||||
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
||||
return this.builder(match);
|
||||
}
|
||||
protected regex: RegExp;
|
||||
|
||||
public constructor(
|
||||
regex: RegExp,
|
||||
private readonly builder = mockBuilder(),
|
||||
) {
|
||||
super();
|
||||
this.regex = regex;
|
||||
}
|
||||
|
||||
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
||||
return this.builder(match);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import 'mocha';
|
||||
import { runPipeTests } from './PipeTestRunner';
|
||||
import { EscapeDoubleQuotes } from '@/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes';
|
||||
import { runPipeTests } from './PipeTestRunner';
|
||||
|
||||
describe('EscapeDoubleQuotes', () => {
|
||||
// arrange
|
||||
const sut = new EscapeDoubleQuotes();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
{
|
||||
name: 'using "',
|
||||
input: 'hello "world"',
|
||||
expectedOutput: 'hello "^""world"^""',
|
||||
},
|
||||
{
|
||||
name: 'not using any double quotes',
|
||||
input: 'hello world',
|
||||
expectedOutput: 'hello world',
|
||||
},
|
||||
{
|
||||
name: 'consecutive double quotes',
|
||||
input: '""hello world""',
|
||||
expectedOutput: '"^"""^""hello world"^"""^""',
|
||||
},
|
||||
{
|
||||
name: 'returns undefined when if input is undefined',
|
||||
input: undefined,
|
||||
expectedOutput: undefined,
|
||||
},
|
||||
]);
|
||||
// arrange
|
||||
const sut = new EscapeDoubleQuotes();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
{
|
||||
name: 'using "',
|
||||
input: 'hello "world"',
|
||||
expectedOutput: 'hello "^""world"^""',
|
||||
},
|
||||
{
|
||||
name: 'not using any double quotes',
|
||||
input: 'hello world',
|
||||
expectedOutput: 'hello world',
|
||||
},
|
||||
{
|
||||
name: 'consecutive double quotes',
|
||||
input: '""hello world""',
|
||||
expectedOutput: '"^"""^""hello world"^"""^""',
|
||||
},
|
||||
{
|
||||
name: 'returns undefined when if input is undefined',
|
||||
input: undefined,
|
||||
expectedOutput: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -3,462 +3,461 @@ import { InlinePowerShell } from '@/application/Parser/Script/Compiler/Expressio
|
||||
import { IPipeTestCase, runPipeTests } from './PipeTestRunner';
|
||||
|
||||
describe('InlinePowerShell', () => {
|
||||
// arrange
|
||||
const sut = new InlinePowerShell();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
{
|
||||
name: 'returns undefined when if input is undefined',
|
||||
input: undefined,
|
||||
expectedOutput: undefined,
|
||||
},
|
||||
...prefixTests('newline', getNewLineCases()),
|
||||
...prefixTests('comment', getCommentCases()),
|
||||
...prefixTests('here-string', hereStringCases()),
|
||||
...prefixTests('backtick', backTickCases()),
|
||||
]);
|
||||
// arrange
|
||||
const sut = new InlinePowerShell();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
{
|
||||
name: 'returns undefined when if input is undefined',
|
||||
input: undefined,
|
||||
expectedOutput: undefined,
|
||||
},
|
||||
...prefixTests('newline', getNewLineCases()),
|
||||
...prefixTests('comment', getCommentCases()),
|
||||
...prefixTests('here-string', hereStringCases()),
|
||||
...prefixTests('backtick', backTickCases()),
|
||||
]);
|
||||
});
|
||||
|
||||
function hereStringCases(): IPipeTestCase[] {
|
||||
const expectLinesInDoubleQuotes = (...lines: string[]) => lines.join('`r`n');
|
||||
const expectLinesInSingleQuotes = (...lines: string[]) => lines.join('\'+"`r`n"+\'');
|
||||
return [
|
||||
{
|
||||
name: 'adds newlines for double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'"Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds newlines for single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with character after here string header',
|
||||
input: getWindowsLines(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match if there\'s character before here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with different here-string header/terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner single quoted here-string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerDoubleQuotedTerminator = @"',
|
||||
'inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$hasInnerDoubleQuotedTerminator = "inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner double quoted string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerSingleQuotedTerminator = @\'',
|
||||
'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'$hasInnerSingleQuotedTerminator = \'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches if there\'s character after here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'\'@ after',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'lorem\' after',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'escapes double quotes inside double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'For help, type "get-help"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"For help, type `"get-help`""',
|
||||
},
|
||||
{
|
||||
name: 'escapes single quotes inside single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'For help, type \'get-help\'',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: '\'For help, type \'\'get-help\'\'\'',
|
||||
},
|
||||
{
|
||||
name: 'converts when here-string header is not at line start',
|
||||
input: getWindowsLines(
|
||||
'$page = [XML] @"',
|
||||
'multi-lined',
|
||||
'and "quoted"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$page = [XML] "multi-lined',
|
||||
'and `"quoted`""',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims after here-string header',
|
||||
input: getWindowsLines(
|
||||
'@" \t',
|
||||
'text with whitespaces at here-string start',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"text with whitespaces at here-string start"',
|
||||
},
|
||||
{
|
||||
name: 'preserves whitespaces in lines',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around ',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around \'',
|
||||
),
|
||||
},
|
||||
];
|
||||
const expectLinesInDoubleQuotes = (...lines: string[]) => lines.join('`r`n');
|
||||
const expectLinesInSingleQuotes = (...lines: string[]) => lines.join('\'+"`r`n"+\'');
|
||||
return [
|
||||
{
|
||||
name: 'adds newlines for double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'"Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds newlines for single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with character after here string header',
|
||||
input: getWindowsLines(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match if there\'s character before here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with different here-string header/terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner single quoted here-string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerDoubleQuotedTerminator = @"',
|
||||
'inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$hasInnerDoubleQuotedTerminator = "inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner double quoted string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerSingleQuotedTerminator = @\'',
|
||||
'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'$hasInnerSingleQuotedTerminator = \'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches if there\'s character after here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'\'@ after',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'lorem\' after',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'escapes double quotes inside double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'For help, type "get-help"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"For help, type `"get-help`""',
|
||||
},
|
||||
{
|
||||
name: 'escapes single quotes inside single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'For help, type \'get-help\'',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: '\'For help, type \'\'get-help\'\'\'',
|
||||
},
|
||||
{
|
||||
name: 'converts when here-string header is not at line start',
|
||||
input: getWindowsLines(
|
||||
'$page = [XML] @"',
|
||||
'multi-lined',
|
||||
'and "quoted"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$page = [XML] "multi-lined',
|
||||
'and `"quoted`""',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims after here-string header',
|
||||
input: getWindowsLines(
|
||||
'@" \t',
|
||||
'text with whitespaces at here-string start',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"text with whitespaces at here-string start"',
|
||||
},
|
||||
{
|
||||
name: 'preserves whitespaces in lines',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around ',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around \'',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function backTickCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick and different line endings',
|
||||
input: 'Get-Service `\n'
|
||||
+ '* `\r'
|
||||
+ '| Sort-Object StartType `\r\n'
|
||||
+ '| Format-Table -AutoSize'
|
||||
,
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'trims tabs and whitespaces on next lines when wrapping with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'\t| Sort-Object StartType `',
|
||||
' | Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'does not wrap without whitespace before backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not wrap with characters after',
|
||||
input: getWindowsLines(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
},
|
||||
];
|
||||
return [
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick and different line endings',
|
||||
input: 'Get-Service `\n'
|
||||
+ '* `\r'
|
||||
+ '| Sort-Object StartType `\r\n'
|
||||
+ '| Format-Table -AutoSize',
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'trims tabs and whitespaces on next lines when wrapping with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'\t| Sort-Object StartType `',
|
||||
' | Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'does not wrap without whitespace before backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not wrap with characters after',
|
||||
input: getWindowsLines(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getCommentCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'converts hash comments in the line end',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t# Comment after tab',
|
||||
'$text+= #Comment without space after hash',
|
||||
'Write-Host $text# Comment without space before hash',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# Comment after tab #>',
|
||||
'$text+= <# Comment without space after hash #>',
|
||||
'Write-Host $text<# Comment without space before hash #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts hash comment line',
|
||||
input: getWindowsLines(
|
||||
'# Comment in first line',
|
||||
'Write-Host "Hello"',
|
||||
'# Comment in the middle',
|
||||
'Write-Host "World"',
|
||||
'# Consecutive comments',
|
||||
'# Last line comment without line ending in the end',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment in first line #>',
|
||||
'Write-Host "Hello"',
|
||||
'<# Comment in the middle #>',
|
||||
'Write-Host "World"',
|
||||
'<# Consecutive comments #>',
|
||||
'<# Last line comment without line ending in the end #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts inside',
|
||||
input: getWindowsLines(
|
||||
'$text+= #Comment with < inside',
|
||||
'$text+= #Comment ending with >',
|
||||
'$text+= #Comment with <# inline comment #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text+= <# Comment with < inside #>',
|
||||
'$text+= <# Comment ending with > #>',
|
||||
'$text+= <# Comment with <# inline comment #> #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts around', // Pretty uncommon
|
||||
input: getWindowsLines(
|
||||
'Write-Host "hi" # Comment ending line inline comment but not one #>',
|
||||
'Write-Host "hi" #>Comment starting like inline comment end but not one',
|
||||
// Following line does not compile as valid PowerShell referring to missing #> for inline comment
|
||||
'Write-Host "hi" <#Comment starting like inline comment start but not one',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "hi" <# Comment ending line inline comment but not one #> #>',
|
||||
'Write-Host "hi" <# >Comment starting like inline comment end but not one #>',
|
||||
'Write-Host "hi" <<# Comment starting like inline comment start but not one #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts empty hash comment',
|
||||
input: getWindowsLines(
|
||||
'Write-Host "Comment without text" #',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "Comment without text" <##>',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds whitespaces around to match',
|
||||
input: getWindowsLines(
|
||||
'#Comment line with no whitespaces around',
|
||||
'Write-Host "Hello"#Comment in the end with no whitespaces around',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment line with no whitespaces around #>',
|
||||
'Write-Host "Hello"<# Comment in the end with no whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces around comment',
|
||||
input: getWindowsLines(
|
||||
'# Comment with whitespaces around ',
|
||||
'#\tComment with tabs around\t\t',
|
||||
'#\t Comment with tabs and whitespaces around \t \t',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment with whitespaces around #>',
|
||||
'<# Comment with tabs around #>',
|
||||
'<# Comment with tabs and whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not convert block comments',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not process if there are no multi lines',
|
||||
input: 'Write-Host \"expected\" # as it is!',
|
||||
expectedOutput: 'Write-Host \"expected\" # as it is!',
|
||||
},
|
||||
];
|
||||
return [
|
||||
{
|
||||
name: 'converts hash comments in the line end',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t# Comment after tab',
|
||||
'$text+= #Comment without space after hash',
|
||||
'Write-Host $text# Comment without space before hash',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# Comment after tab #>',
|
||||
'$text+= <# Comment without space after hash #>',
|
||||
'Write-Host $text<# Comment without space before hash #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts hash comment line',
|
||||
input: getWindowsLines(
|
||||
'# Comment in first line',
|
||||
'Write-Host "Hello"',
|
||||
'# Comment in the middle',
|
||||
'Write-Host "World"',
|
||||
'# Consecutive comments',
|
||||
'# Last line comment without line ending in the end',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment in first line #>',
|
||||
'Write-Host "Hello"',
|
||||
'<# Comment in the middle #>',
|
||||
'Write-Host "World"',
|
||||
'<# Consecutive comments #>',
|
||||
'<# Last line comment without line ending in the end #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts inside',
|
||||
input: getWindowsLines(
|
||||
'$text+= #Comment with < inside',
|
||||
'$text+= #Comment ending with >',
|
||||
'$text+= #Comment with <# inline comment #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text+= <# Comment with < inside #>',
|
||||
'$text+= <# Comment ending with > #>',
|
||||
'$text+= <# Comment with <# inline comment #> #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts around', // Pretty uncommon
|
||||
input: getWindowsLines(
|
||||
'Write-Host "hi" # Comment ending line inline comment but not one #>',
|
||||
'Write-Host "hi" #>Comment starting like inline comment end but not one',
|
||||
// Following line does not compile as valid PowerShell due to missing #> for inline comment.
|
||||
'Write-Host "hi" <#Comment starting like inline comment start but not one',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "hi" <# Comment ending line inline comment but not one #> #>',
|
||||
'Write-Host "hi" <# >Comment starting like inline comment end but not one #>',
|
||||
'Write-Host "hi" <<# Comment starting like inline comment start but not one #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts empty hash comment',
|
||||
input: getWindowsLines(
|
||||
'Write-Host "Comment without text" #',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "Comment without text" <##>',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds whitespaces around to match',
|
||||
input: getWindowsLines(
|
||||
'#Comment line with no whitespaces around',
|
||||
'Write-Host "Hello"#Comment in the end with no whitespaces around',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment line with no whitespaces around #>',
|
||||
'Write-Host "Hello"<# Comment in the end with no whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces around comment',
|
||||
input: getWindowsLines(
|
||||
'# Comment with whitespaces around ',
|
||||
'#\tComment with tabs around\t\t',
|
||||
'#\t Comment with tabs and whitespaces around \t \t',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment with whitespaces around #>',
|
||||
'<# Comment with tabs around #>',
|
||||
'<# Comment with tabs and whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not convert block comments',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not process if there are no multi lines',
|
||||
input: 'Write-Host "expected" # as it is!',
|
||||
expectedOutput: 'Write-Host "expected" # as it is!',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getNewLineCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'no new line',
|
||||
input: 'Write-Host \'Hello, World!\'',
|
||||
expectedOutput: 'Write-Host \'Hello, World!\'',
|
||||
},
|
||||
{
|
||||
name: '\\n new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\n double empty lines are ignored',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\n\nforeach ($thing in $things) {'
|
||||
+ '\n\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\n\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\rforeach ($thing in $things) {'
|
||||
+ '\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r and \\n newlines combined',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\r\nforeach ($thing in $things) {'
|
||||
+ '\n\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces on lines',
|
||||
input:
|
||||
' $things = Get-ChildItem C:\\Windows\\ '
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\n\tWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r \n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
];
|
||||
return [
|
||||
{
|
||||
name: 'no new line',
|
||||
input: 'Write-Host \'Hello, World!\'',
|
||||
expectedOutput: 'Write-Host \'Hello, World!\'',
|
||||
},
|
||||
{
|
||||
name: '\\n new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\n double empty lines are ignored',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\n\nforeach ($thing in $things) {'
|
||||
+ '\n\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\n\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\rforeach ($thing in $things) {'
|
||||
+ '\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r and \\n newlines combined',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\r\nforeach ($thing in $things) {'
|
||||
+ '\n\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces on lines',
|
||||
input:
|
||||
' $things = Get-ChildItem C:\\Windows\\ '
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\n\tWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r \n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function prefixTests(prefix: string, tests: IPipeTestCase[]): IPipeTestCase[] {
|
||||
return tests.map((test) => ({
|
||||
name: `[${prefix}] ${test.name}`,
|
||||
input: test.input,
|
||||
expectedOutput: test.expectedOutput,
|
||||
}));
|
||||
return tests.map((test) => ({
|
||||
name: `[${prefix}] ${test.name}`,
|
||||
input: test.input,
|
||||
expectedOutput: test.expectedOutput,
|
||||
}));
|
||||
}
|
||||
|
||||
function getWindowsLines(...lines: string[]) {
|
||||
return lines.join('\r\n');
|
||||
return lines.join('\r\n');
|
||||
}
|
||||
|
||||
function getSingleLinedOutput(...lines: string[]) {
|
||||
return lines.map((line) => line.trim()).join('; ');
|
||||
return lines.map((line) => line.trim()).join('; ');
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ import { expect } from 'chai';
|
||||
import { IPipe } from '@/application/Parser/Script/Compiler/Expressions/Pipes/IPipe';
|
||||
|
||||
export interface IPipeTestCase {
|
||||
readonly name: string;
|
||||
readonly input: string;
|
||||
readonly expectedOutput: string;
|
||||
readonly name: string;
|
||||
readonly input: string;
|
||||
readonly expectedOutput: string;
|
||||
}
|
||||
|
||||
export function runPipeTests(sut: IPipe, testCases: IPipeTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const actual = sut.apply(testCase.input);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedOutput);
|
||||
});
|
||||
}
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const actual = sut.apply(testCase.input);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedOutput);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,110 +4,110 @@ import { PipeFactory } from '@/application/Parser/Script/Compiler/Expressions/Pi
|
||||
import { PipeStub } from '@tests/unit/stubs/PipeStub';
|
||||
|
||||
describe('PipeFactory', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws when instances with same name is registered', () => {
|
||||
// arrange
|
||||
const duplicateName = 'duplicateName';
|
||||
const expectedError = `Pipe name must be unique: "${duplicateName}"`;
|
||||
const pipes = [
|
||||
new PipeStub().withName(duplicateName),
|
||||
new PipeStub().withName('uniqueName'),
|
||||
new PipeStub().withName(duplicateName),
|
||||
];
|
||||
// act
|
||||
const act = () => new PipeFactory(pipes);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when a pipe is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined pipe in list';
|
||||
const pipes = [ new PipeStub(), undefined ];
|
||||
// act
|
||||
const act = () => new PipeFactory(pipes);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws when name is invalid', () => {
|
||||
// act
|
||||
const act = (invalidName: string) => new PipeFactory([ new PipeStub().withName(invalidName) ]);
|
||||
// assert
|
||||
testPipeNameValidation(act);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('throws when instances with same name is registered', () => {
|
||||
// arrange
|
||||
const duplicateName = 'duplicateName';
|
||||
const expectedError = `Pipe name must be unique: "${duplicateName}"`;
|
||||
const pipes = [
|
||||
new PipeStub().withName(duplicateName),
|
||||
new PipeStub().withName('uniqueName'),
|
||||
new PipeStub().withName(duplicateName),
|
||||
];
|
||||
// act
|
||||
const act = () => new PipeFactory(pipes);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('get', () => {
|
||||
describe('throws when name is invalid', () => {
|
||||
// arrange
|
||||
const sut = new PipeFactory();
|
||||
// act
|
||||
const act = (invalidName: string) => sut.get(invalidName);
|
||||
// assert
|
||||
testPipeNameValidation(act);
|
||||
});
|
||||
it('gets registered instance when it exists', () => {
|
||||
// arrange
|
||||
const expected = new PipeStub().withName('expectedName');
|
||||
const pipes = [ expected, new PipeStub().withName('instanceToConfuse') ];
|
||||
const sut = new PipeFactory(pipes);
|
||||
// act
|
||||
const actual = sut.get(expected.name);
|
||||
// expect
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('throws when instance does not exist', () => {
|
||||
// arrange
|
||||
const missingName = 'missingName';
|
||||
const expectedError = `Unknown pipe: "${missingName}"`;
|
||||
const pipes = [ ];
|
||||
const sut = new PipeFactory(pipes);
|
||||
// act
|
||||
const act = () => sut.get(missingName);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when a pipe is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined pipe in list';
|
||||
const pipes = [new PipeStub(), undefined];
|
||||
// act
|
||||
const act = () => new PipeFactory(pipes);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws when name is invalid', () => {
|
||||
// act
|
||||
const act = (invalidName: string) => new PipeFactory([new PipeStub().withName(invalidName)]);
|
||||
// assert
|
||||
testPipeNameValidation(act);
|
||||
});
|
||||
});
|
||||
describe('get', () => {
|
||||
describe('throws when name is invalid', () => {
|
||||
// arrange
|
||||
const sut = new PipeFactory();
|
||||
// act
|
||||
const act = (invalidName: string) => sut.get(invalidName);
|
||||
// assert
|
||||
testPipeNameValidation(act);
|
||||
});
|
||||
it('gets registered instance when it exists', () => {
|
||||
// arrange
|
||||
const expected = new PipeStub().withName('expectedName');
|
||||
const pipes = [expected, new PipeStub().withName('instanceToConfuse')];
|
||||
const sut = new PipeFactory(pipes);
|
||||
// act
|
||||
const actual = sut.get(expected.name);
|
||||
// expect
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('throws when instance does not exist', () => {
|
||||
// arrange
|
||||
const missingName = 'missingName';
|
||||
const expectedError = `Unknown pipe: "${missingName}"`;
|
||||
const pipes = [];
|
||||
const sut = new PipeFactory(pipes);
|
||||
// act
|
||||
const act = () => sut.get(missingName);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function testPipeNameValidation(testRunner: (invalidName: string) => void) {
|
||||
const testCases = [
|
||||
{
|
||||
exceptionBuilder: () => 'empty pipe name',
|
||||
values: [ null, undefined , ''],
|
||||
},
|
||||
{
|
||||
exceptionBuilder: (name: string) => `Pipe name should be camelCase: "${name}"`,
|
||||
values: [
|
||||
'PascalCase',
|
||||
'snake-case',
|
||||
'includesNumb3rs',
|
||||
'includes Whitespace',
|
||||
'noSpec\'ial',
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
for (const invalidName of testCase.values) {
|
||||
it(`invalid name (${printValue(invalidName)}) throws`, () => {
|
||||
// arrange
|
||||
const expectedError = testCase.exceptionBuilder(invalidName);
|
||||
// act
|
||||
const act = () => testRunner(invalidName);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
}
|
||||
const testCases = [
|
||||
{
|
||||
exceptionBuilder: () => 'empty pipe name',
|
||||
values: [null, undefined, ''],
|
||||
},
|
||||
{
|
||||
exceptionBuilder: (name: string) => `Pipe name should be camelCase: "${name}"`,
|
||||
values: [
|
||||
'PascalCase',
|
||||
'snake-case',
|
||||
'includesNumb3rs',
|
||||
'includes Whitespace',
|
||||
'noSpec\'ial',
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
for (const invalidName of testCase.values) {
|
||||
it(`invalid name (${printValue(invalidName)}) throws`, () => {
|
||||
// arrange
|
||||
const expectedError = testCase.exceptionBuilder(invalidName);
|
||||
// act
|
||||
const act = () => testRunner(invalidName);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printValue(value: string) {
|
||||
switch (value) {
|
||||
case undefined:
|
||||
return 'undefined';
|
||||
case null:
|
||||
return 'null';
|
||||
case '':
|
||||
return 'empty';
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
switch (value) {
|
||||
case undefined:
|
||||
return 'undefined';
|
||||
case null:
|
||||
return 'null';
|
||||
case '':
|
||||
return 'empty';
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,132 +7,134 @@ import { PipeStub } from '@tests/unit/stubs/PipeStub';
|
||||
import { PipeFactoryStub } from '@tests/unit/stubs/PipeFactoryStub';
|
||||
|
||||
describe('PipelineCompiler', () => {
|
||||
describe('compile', () => {
|
||||
describe('throws for invalid arguments', () => {
|
||||
interface ITestCase {
|
||||
name: string;
|
||||
act: (test: PipelineTestRunner) => PipelineTestRunner;
|
||||
expectedError: string;
|
||||
}
|
||||
const testCases: ITestCase[] = [
|
||||
{
|
||||
name: '"value" is empty',
|
||||
act: (test) => test.withValue(''),
|
||||
expectedError: 'undefined value',
|
||||
},
|
||||
{
|
||||
name: '"value" is undefined',
|
||||
act: (test) => test.withValue(undefined),
|
||||
expectedError: 'undefined value',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" is empty',
|
||||
act: (test) => test.withPipeline(''),
|
||||
expectedError: 'undefined pipeline',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" is undefined',
|
||||
act: (test) => test.withPipeline(undefined),
|
||||
expectedError: 'undefined pipeline',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" does not start with pipe',
|
||||
act: (test) => test.withPipeline('pipeline |'),
|
||||
expectedError: 'pipeline does not start with pipe',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const runner = new PipelineTestRunner();
|
||||
testCase.act(runner);
|
||||
const act = () => runner.compile();
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('compiles pipeline as expected', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'compiles single pipe as expected',
|
||||
pipes: [
|
||||
new PipeStub().withName('doublePrint').withApplier((value) => `${value}-${value}`),
|
||||
],
|
||||
pipeline: '| doublePrint',
|
||||
value: 'value',
|
||||
expected: 'value-value',
|
||||
},
|
||||
{
|
||||
name: 'compiles multiple pipes as expected',
|
||||
pipes: [
|
||||
new PipeStub().withName('prependLetterA').withApplier((value) => `A-${value}`),
|
||||
new PipeStub().withName('prependLetterB').withApplier((value) => `B-${value}`),
|
||||
],
|
||||
pipeline: '| prependLetterA | prependLetterB',
|
||||
value: 'value',
|
||||
expected: 'B-A-value',
|
||||
},
|
||||
{
|
||||
name: 'compiles with relaxed whitespace placing',
|
||||
pipes: [
|
||||
new PipeStub().withName('appendNumberOne').withApplier((value) => `${value}1`),
|
||||
new PipeStub().withName('appendNumberTwo').withApplier((value) => `${value}2`),
|
||||
new PipeStub().withName('appendNumberThree').withApplier((value) => `${value}3`),
|
||||
],
|
||||
pipeline: ' | appendNumberOne|appendNumberTwo| appendNumberThree',
|
||||
value: 'value',
|
||||
expected: 'value123',
|
||||
},
|
||||
{
|
||||
name: 'can reuse same pipe',
|
||||
pipes: [
|
||||
new PipeStub().withName('removeFirstChar').withApplier((value) => `${value.slice(1)}`),
|
||||
],
|
||||
pipeline: ' | removeFirstChar | removeFirstChar | removeFirstChar',
|
||||
value: 'value',
|
||||
expected: 'ue',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const runner =
|
||||
new PipelineTestRunner()
|
||||
.withValue(testCase.value)
|
||||
.withPipeline(testCase.pipeline)
|
||||
.withFactory(new PipeFactoryStub().withPipes(testCase.pipes));
|
||||
// act
|
||||
const actual = runner.compile();
|
||||
// expect
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
describe('compile', () => {
|
||||
describe('throws for invalid arguments', () => {
|
||||
interface ITestCase {
|
||||
name: string;
|
||||
act: (test: PipelineTestRunner) => PipelineTestRunner;
|
||||
expectedError: string;
|
||||
}
|
||||
const testCases: ITestCase[] = [
|
||||
{
|
||||
name: '"value" is empty',
|
||||
act: (test) => test.withValue(''),
|
||||
expectedError: 'undefined value',
|
||||
},
|
||||
{
|
||||
name: '"value" is undefined',
|
||||
act: (test) => test.withValue(undefined),
|
||||
expectedError: 'undefined value',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" is empty',
|
||||
act: (test) => test.withPipeline(''),
|
||||
expectedError: 'undefined pipeline',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" is undefined',
|
||||
act: (test) => test.withPipeline(undefined),
|
||||
expectedError: 'undefined pipeline',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" does not start with pipe',
|
||||
act: (test) => test.withPipeline('pipeline |'),
|
||||
expectedError: 'pipeline does not start with pipe',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const runner = new PipelineTestRunner();
|
||||
testCase.act(runner);
|
||||
const act = () => runner.compile();
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('compiles pipeline as expected', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'compiles single pipe as expected',
|
||||
pipes: [
|
||||
new PipeStub().withName('doublePrint').withApplier((value) => `${value}-${value}`),
|
||||
],
|
||||
pipeline: '| doublePrint',
|
||||
value: 'value',
|
||||
expected: 'value-value',
|
||||
},
|
||||
{
|
||||
name: 'compiles multiple pipes as expected',
|
||||
pipes: [
|
||||
new PipeStub().withName('prependLetterA').withApplier((value) => `A-${value}`),
|
||||
new PipeStub().withName('prependLetterB').withApplier((value) => `B-${value}`),
|
||||
],
|
||||
pipeline: '| prependLetterA | prependLetterB',
|
||||
value: 'value',
|
||||
expected: 'B-A-value',
|
||||
},
|
||||
{
|
||||
name: 'compiles with relaxed whitespace placing',
|
||||
pipes: [
|
||||
new PipeStub().withName('appendNumberOne').withApplier((value) => `${value}1`),
|
||||
new PipeStub().withName('appendNumberTwo').withApplier((value) => `${value}2`),
|
||||
new PipeStub().withName('appendNumberThree').withApplier((value) => `${value}3`),
|
||||
],
|
||||
pipeline: ' | appendNumberOne|appendNumberTwo| appendNumberThree',
|
||||
value: 'value',
|
||||
expected: 'value123',
|
||||
},
|
||||
{
|
||||
name: 'can reuse same pipe',
|
||||
pipes: [
|
||||
new PipeStub().withName('removeFirstChar').withApplier((value) => `${value.slice(1)}`),
|
||||
],
|
||||
pipeline: ' | removeFirstChar | removeFirstChar | removeFirstChar',
|
||||
value: 'value',
|
||||
expected: 'ue',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const runner = new PipelineTestRunner()
|
||||
.withValue(testCase.value)
|
||||
.withPipeline(testCase.pipeline)
|
||||
.withFactory(new PipeFactoryStub().withPipes(testCase.pipes));
|
||||
// act
|
||||
const actual = runner.compile();
|
||||
// expect
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
class PipelineTestRunner implements IPipelineCompiler {
|
||||
private value: string = 'non-empty-value';
|
||||
private pipeline: string = '| validPipeline';
|
||||
private factory: IPipeFactory = new PipeFactoryStub();
|
||||
private value = 'non-empty-value';
|
||||
|
||||
public withValue(value: string) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
public withPipeline(pipeline: string) {
|
||||
this.pipeline = pipeline;
|
||||
return this;
|
||||
}
|
||||
public withFactory(factory: IPipeFactory) {
|
||||
this.factory = factory;
|
||||
return this;
|
||||
}
|
||||
private pipeline = '| validPipeline';
|
||||
|
||||
public compile(): string {
|
||||
const sut = new PipelineCompiler(this.factory);
|
||||
return sut.compile(this.value, this.pipeline);
|
||||
}
|
||||
private factory: IPipeFactory = new PipeFactoryStub();
|
||||
|
||||
public withValue(value: string) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withPipeline(pipeline: string) {
|
||||
this.pipeline = pipeline;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withFactory(factory: IPipeFactory) {
|
||||
this.factory = factory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public compile(): string {
|
||||
const sut = new PipelineCompiler(this.factory);
|
||||
return sut.compile(this.value, this.pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,64 +4,64 @@ import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Express
|
||||
import { SyntaxParserTestsRunner } from './SyntaxParserTestsRunner';
|
||||
|
||||
describe('ParameterSubstitutionParser', () => {
|
||||
const sut = new ParameterSubstitutionParser();
|
||||
const runner = new SyntaxParserTestsRunner(sut);
|
||||
describe('finds as expected', () => {
|
||||
runner.expectPosition(
|
||||
{
|
||||
name: 'single parameter',
|
||||
code: '{{ $parameter }}!',
|
||||
expected: [ new ExpressionPosition(0, 16) ],
|
||||
},
|
||||
{
|
||||
name: 'different parameters',
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }}!!',
|
||||
expected: [ new ExpressionPosition(2, 23), new ExpressionPosition(24, 46) ],
|
||||
},
|
||||
{
|
||||
name: 'tolerates lack of spaces around brackets',
|
||||
code: 'He{{$firstParameter}}!!',
|
||||
expected: [new ExpressionPosition(2, 21) ],
|
||||
},
|
||||
{
|
||||
name: 'does not tolerate space after dollar sign',
|
||||
code: 'He{{ $ firstParameter }}!!',
|
||||
expected: [ ],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('evaluates as expected', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'single parameter',
|
||||
code: '{{ $parameter }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world' ],
|
||||
},
|
||||
{
|
||||
name: 'different parameters',
|
||||
code: '{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
args: (args) => args
|
||||
.withArgument('firstParameter', 'Hello')
|
||||
.withArgument('secondParameter', 'World'),
|
||||
expected: [ 'Hello', 'World' ],
|
||||
},
|
||||
{
|
||||
name: 'same parameters used twice',
|
||||
code: '{{ $letterH }}e{{ $letterL }}{{ $letterL }}o Wor{{ $letterL }}d!',
|
||||
args: (args) => args
|
||||
.withArgument('letterL', 'l')
|
||||
.withArgument('letterH', 'H'),
|
||||
expected: [ 'H', 'l', 'l', 'l' ],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('compiles pipes as expected', () => {
|
||||
runner.expectPipeHits({
|
||||
codeBuilder: (pipeline) => `{{ $argument${pipeline}}}`,
|
||||
parameterName: 'argument',
|
||||
parameterValue: 'value',
|
||||
});
|
||||
const sut = new ParameterSubstitutionParser();
|
||||
const runner = new SyntaxParserTestsRunner(sut);
|
||||
describe('finds as expected', () => {
|
||||
runner.expectPosition(
|
||||
{
|
||||
name: 'single parameter',
|
||||
code: '{{ $parameter }}!',
|
||||
expected: [new ExpressionPosition(0, 16)],
|
||||
},
|
||||
{
|
||||
name: 'different parameters',
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }}!!',
|
||||
expected: [new ExpressionPosition(2, 23), new ExpressionPosition(24, 46)],
|
||||
},
|
||||
{
|
||||
name: 'tolerates lack of spaces around brackets',
|
||||
code: 'He{{$firstParameter}}!!',
|
||||
expected: [new ExpressionPosition(2, 21)],
|
||||
},
|
||||
{
|
||||
name: 'does not tolerate space after dollar sign',
|
||||
code: 'He{{ $ firstParameter }}!!',
|
||||
expected: [],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('evaluates as expected', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'single parameter',
|
||||
code: '{{ $parameter }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: ['Hello world'],
|
||||
},
|
||||
{
|
||||
name: 'different parameters',
|
||||
code: '{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
args: (args) => args
|
||||
.withArgument('firstParameter', 'Hello')
|
||||
.withArgument('secondParameter', 'World'),
|
||||
expected: ['Hello', 'World'],
|
||||
},
|
||||
{
|
||||
name: 'same parameters used twice',
|
||||
code: '{{ $letterH }}e{{ $letterL }}{{ $letterL }}o Wor{{ $letterL }}d!',
|
||||
args: (args) => args
|
||||
.withArgument('letterL', 'l')
|
||||
.withArgument('letterH', 'H'),
|
||||
expected: ['H', 'l', 'l', 'l'],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('compiles pipes as expected', () => {
|
||||
runner.expectPipeHits({
|
||||
codeBuilder: (pipeline) => `{{ $argument${pipeline}}}`,
|
||||
parameterName: 'argument',
|
||||
parameterValue: 'value',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,116 +7,121 @@ import { ExpressionEvaluationContextStub } from '@tests/unit/stubs/ExpressionEva
|
||||
import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub';
|
||||
|
||||
export class SyntaxParserTestsRunner {
|
||||
constructor(private readonly sut: IExpressionParser) {
|
||||
constructor(private readonly sut: IExpressionParser) {
|
||||
}
|
||||
|
||||
public expectPosition(...testCases: IExpectPositionTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.position);
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
public expectPosition(...testCases: IExpectPositionTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.position);
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
return this;
|
||||
return this;
|
||||
}
|
||||
|
||||
public expectResults(...testCases: IExpectResultTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const args = testCase.args(new FunctionCallArgumentCollectionStub());
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(args);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.evaluate(context));
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
public expectResults(...testCases: IExpectResultTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const args = testCase.args(new FunctionCallArgumentCollectionStub());
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(args);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.evaluate(context));
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
return this;
|
||||
return this;
|
||||
}
|
||||
|
||||
public expectPipeHits(data: IExpectPipeHitTestData) {
|
||||
for (const validPipePart of PipeTestCases.ValidValues) {
|
||||
this.expectHitPipePart(validPipePart, data);
|
||||
}
|
||||
public expectPipeHits(data: IExpectPipeHitTestData) {
|
||||
for (const validPipePart of PipeTestCases.ValidValues) {
|
||||
this.expectHitPipePart(validPipePart, data);
|
||||
}
|
||||
for (const invalidPipePart of PipeTestCases.InvalidValues) {
|
||||
this.expectMissPipePart(invalidPipePart, data);
|
||||
}
|
||||
}
|
||||
private expectHitPipePart(pipeline: string, data: IExpectPipeHitTestData) {
|
||||
it(`"${pipeline}" hits`, () => {
|
||||
// arrange
|
||||
const expectedPipePart = pipeline.trim();
|
||||
const code = data.codeBuilder(pipeline);
|
||||
const args = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(data.parameterName, data.parameterValue);
|
||||
const pipelineCompiler = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(pipelineCompiler)
|
||||
.withArgs(args);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(code);
|
||||
expressions[0].evaluate(context);
|
||||
// assert
|
||||
expect(expressions).has.lengthOf(1);
|
||||
expect(pipelineCompiler.compileHistory).has.lengthOf(1);
|
||||
const actualPipeNames = pipelineCompiler.compileHistory[0].pipeline;
|
||||
const actualValue = pipelineCompiler.compileHistory[0].value;
|
||||
expect(actualPipeNames).to.equal(expectedPipePart);
|
||||
expect(actualValue).to.equal(data.parameterValue);
|
||||
});
|
||||
}
|
||||
private expectMissPipePart(pipeline: string, data: IExpectPipeHitTestData) {
|
||||
it(`"${pipeline}" misses`, () => {
|
||||
// arrange
|
||||
const args = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(data.parameterName, data.parameterValue);
|
||||
const pipelineCompiler = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(pipelineCompiler)
|
||||
.withArgs(args);
|
||||
const code = data.codeBuilder(pipeline);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(code);
|
||||
expressions[0]?.evaluate(context); // Because an expression may include another with pipes
|
||||
// assert
|
||||
expect(pipelineCompiler.compileHistory).has.lengthOf(0);
|
||||
});
|
||||
for (const invalidPipePart of PipeTestCases.InvalidValues) {
|
||||
this.expectMissPipePart(invalidPipePart, data);
|
||||
}
|
||||
}
|
||||
|
||||
private expectHitPipePart(pipeline: string, data: IExpectPipeHitTestData) {
|
||||
it(`"${pipeline}" hits`, () => {
|
||||
// arrange
|
||||
const expectedPipePart = pipeline.trim();
|
||||
const code = data.codeBuilder(pipeline);
|
||||
const args = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(data.parameterName, data.parameterValue);
|
||||
const pipelineCompiler = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(pipelineCompiler)
|
||||
.withArgs(args);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(code);
|
||||
expressions[0].evaluate(context);
|
||||
// assert
|
||||
expect(expressions).has.lengthOf(1);
|
||||
expect(pipelineCompiler.compileHistory).has.lengthOf(1);
|
||||
const actualPipeNames = pipelineCompiler.compileHistory[0].pipeline;
|
||||
const actualValue = pipelineCompiler.compileHistory[0].value;
|
||||
expect(actualPipeNames).to.equal(expectedPipePart);
|
||||
expect(actualValue).to.equal(data.parameterValue);
|
||||
});
|
||||
}
|
||||
|
||||
private expectMissPipePart(pipeline: string, data: IExpectPipeHitTestData) {
|
||||
it(`"${pipeline}" misses`, () => {
|
||||
// arrange
|
||||
const args = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(data.parameterName, data.parameterValue);
|
||||
const pipelineCompiler = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(pipelineCompiler)
|
||||
.withArgs(args);
|
||||
const code = data.codeBuilder(pipeline);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(code);
|
||||
expressions[0]?.evaluate(context); // Because an expression may include another with pipes
|
||||
// assert
|
||||
expect(pipelineCompiler.compileHistory).has.lengthOf(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
interface IExpectResultTestCase {
|
||||
name: string;
|
||||
code: string;
|
||||
args: (builder: FunctionCallArgumentCollectionStub) => FunctionCallArgumentCollectionStub;
|
||||
expected: readonly string[];
|
||||
name: string;
|
||||
code: string;
|
||||
args: (builder: FunctionCallArgumentCollectionStub) => FunctionCallArgumentCollectionStub;
|
||||
expected: readonly string[];
|
||||
}
|
||||
|
||||
interface IExpectPositionTestCase {
|
||||
name: string;
|
||||
code: string;
|
||||
expected: readonly ExpressionPosition[];
|
||||
name: string;
|
||||
code: string;
|
||||
expected: readonly ExpressionPosition[];
|
||||
}
|
||||
|
||||
interface IExpectPipeHitTestData {
|
||||
codeBuilder: (pipeline: string) => string;
|
||||
parameterName: string;
|
||||
parameterValue: string;
|
||||
codeBuilder: (pipeline: string) => string;
|
||||
parameterName: string;
|
||||
parameterValue: string;
|
||||
}
|
||||
|
||||
const PipeTestCases = {
|
||||
ValidValues: [
|
||||
// Single pipe with different whitespace combinations
|
||||
' | pipe1', ' |pipe1', '|pipe1', ' |pipe1', ' | pipe1',
|
||||
ValidValues: [
|
||||
// Single pipe with different whitespace combinations
|
||||
' | pipe1', ' |pipe1', '|pipe1', ' |pipe1', ' | pipe1',
|
||||
|
||||
// Double pipes with different whitespace combinations
|
||||
' | pipe1 | pipe2', '| pipe1|pipe2', '|pipe1|pipe2', ' |pipe1 |pipe2', '| pipe1 | pipe2| pipe3 |pipe4',
|
||||
// Double pipes with different whitespace combinations
|
||||
' | pipe1 | pipe2', '| pipe1|pipe2', '|pipe1|pipe2', ' |pipe1 |pipe2', '| pipe1 | pipe2| pipe3 |pipe4',
|
||||
|
||||
// Wrong cases, but should match anyway and let pipelineCompiler throw errors
|
||||
'| pip€', '| pip{e} ',
|
||||
],
|
||||
InvalidValues: [
|
||||
' pipe1 |pipe2', ' pipe1',
|
||||
],
|
||||
// Wrong cases, but should match anyway and let pipelineCompiler throw errors
|
||||
'| pip€', '| pip{e} ',
|
||||
],
|
||||
InvalidValues: [
|
||||
' pipe1 |pipe2', ' pipe1',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -4,180 +4,178 @@ import { WithParser } from '@/application/Parser/Script/Compiler/Expressions/Syn
|
||||
import { SyntaxParserTestsRunner } from './SyntaxParserTestsRunner';
|
||||
|
||||
describe('WithParser', () => {
|
||||
const sut = new WithParser();
|
||||
const runner = new SyntaxParserTestsRunner(sut);
|
||||
describe('finds as expected', () => {
|
||||
runner.expectPosition(
|
||||
|
||||
{
|
||||
name: 'when no scope is not used',
|
||||
code: 'hello {{ with $parameter }}no usage{{ end }} here',
|
||||
expected: [ new ExpressionPosition(6, 44) ],
|
||||
},
|
||||
{
|
||||
name: 'when scope is used',
|
||||
code: 'used here ({{ with $parameter }}value: {{.}}{{ end }})',
|
||||
expected: [ new ExpressionPosition(11, 53) ],
|
||||
},
|
||||
{
|
||||
name: 'when used twice',
|
||||
code: 'first: {{ with $parameter }}value: {{ . }}{{ end }}, second: {{ with $parameter }}no usage{{ end }}',
|
||||
expected: [ new ExpressionPosition(7, 51), new ExpressionPosition(61, 99) ],
|
||||
},
|
||||
{
|
||||
name: 'tolerate lack of whitespaces',
|
||||
code: 'no whitespaces {{with $parameter}}value: {{ . }}{{end}}',
|
||||
expected: [ new ExpressionPosition(15, 55) ],
|
||||
},
|
||||
);
|
||||
const sut = new WithParser();
|
||||
const runner = new SyntaxParserTestsRunner(sut);
|
||||
describe('finds as expected', () => {
|
||||
runner.expectPosition(
|
||||
{
|
||||
name: 'when no scope is not used',
|
||||
code: 'hello {{ with $parameter }}no usage{{ end }} here',
|
||||
expected: [new ExpressionPosition(6, 44)],
|
||||
},
|
||||
{
|
||||
name: 'when scope is used',
|
||||
code: 'used here ({{ with $parameter }}value: {{.}}{{ end }})',
|
||||
expected: [new ExpressionPosition(11, 53)],
|
||||
},
|
||||
{
|
||||
name: 'when used twice',
|
||||
code: 'first: {{ with $parameter }}value: {{ . }}{{ end }}, second: {{ with $parameter }}no usage{{ end }}',
|
||||
expected: [new ExpressionPosition(7, 51), new ExpressionPosition(61, 99)],
|
||||
},
|
||||
{
|
||||
name: 'tolerate lack of whitespaces',
|
||||
code: 'no whitespaces {{with $parameter}}value: {{ . }}{{end}}',
|
||||
expected: [new ExpressionPosition(15, 55)],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('ignores when syntax is wrong', () => {
|
||||
describe('ignores expression if "with" syntax is wrong', () => {
|
||||
runner.expectPosition(
|
||||
{
|
||||
name: 'does not tolerate whitespace after with',
|
||||
code: '{{with $ parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'does not tolerate whitespace before dollar',
|
||||
code: '{{ with$parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'wrong text at scope end',
|
||||
code: '{{ with$parameter}}value: {{ . }}{{ fin }}',
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'wrong text at expression start',
|
||||
code: '{{ when $parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('ignores when syntax is wrong', () => {
|
||||
describe('ignores expression if "with" syntax is wrong', () => {
|
||||
runner.expectPosition(
|
||||
{
|
||||
name: 'does not tolerate whitespace after with',
|
||||
code: '{{with $ parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [ ],
|
||||
},
|
||||
{
|
||||
name: 'does not tolerate whitespace before dollar',
|
||||
code: '{{ with$parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [ ],
|
||||
},
|
||||
{
|
||||
name: 'wrong text at scope end',
|
||||
code: '{{ with$parameter}}value: {{ . }}{{ fin }}',
|
||||
expected: [ ],
|
||||
},
|
||||
{
|
||||
name: 'wrong text at expression start',
|
||||
code: '{{ when $parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [ ],
|
||||
},
|
||||
);
|
||||
|
||||
});
|
||||
describe('does not render argument if substitution syntax is wrong', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'comma used instead of dot',
|
||||
code: '{{ with $parameter }}Hello {{ , }}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: [ 'Hello {{ , }}' ],
|
||||
},
|
||||
{
|
||||
name: 'single brackets instead of double',
|
||||
code: '{{ with $parameter }}Hello { . }{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: [ 'Hello { . }' ],
|
||||
},
|
||||
{
|
||||
name: 'double dots instead of single',
|
||||
code: '{{ with $parameter }}Hello {{ .. }}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: [ 'Hello {{ .. }}' ],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('does not render argument if substitution syntax is wrong', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'comma used instead of dot',
|
||||
code: '{{ with $parameter }}Hello {{ , }}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: ['Hello {{ , }}'],
|
||||
},
|
||||
{
|
||||
name: 'single brackets instead of double',
|
||||
code: '{{ with $parameter }}Hello { . }{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: ['Hello { . }'],
|
||||
},
|
||||
{
|
||||
name: 'double dots instead of single',
|
||||
code: '{{ with $parameter }}Hello {{ .. }}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: ['Hello {{ .. }}'],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('renders scope conditionally', () => {
|
||||
describe('does not render scope if argument is undefined', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'does not render when value is undefined',
|
||||
code: '{{ with $parameter }}dark{{ end }} ',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', undefined),
|
||||
expected: [ '' ],
|
||||
},
|
||||
{
|
||||
name: 'does not render when value is empty',
|
||||
code: '{{ with $parameter }}dark {{.}}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', ''),
|
||||
expected: [ '' ],
|
||||
},
|
||||
{
|
||||
name: 'does not render when argument is not provided',
|
||||
code: '{{ with $parameter }}dark{{ end }}',
|
||||
args: (args) => args,
|
||||
expected: [ '' ],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('render scope when variable has value', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'renders scope even if value is not used',
|
||||
code: '{{ with $parameter }}Hello world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'renders value when it has value',
|
||||
code: '{{ with $parameter }}{{ . }} world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'renders value when whitespaces around brackets are missing',
|
||||
code: '{{ with $parameter }}{{.}} world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'renders value multiple times when it\'s used multiple times',
|
||||
code: '{{ with $letterL }}He{{ . }}{{ . }}o wor{{ . }}d!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('letterL', 'l'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('renders scope conditionally', () => {
|
||||
describe('does not render scope if argument is undefined', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'does not render when value is undefined',
|
||||
code: '{{ with $parameter }}dark{{ end }} ',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', undefined),
|
||||
expected: [''],
|
||||
},
|
||||
{
|
||||
name: 'does not render when value is empty',
|
||||
code: '{{ with $parameter }}dark {{.}}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', ''),
|
||||
expected: [''],
|
||||
},
|
||||
{
|
||||
name: 'does not render when argument is not provided',
|
||||
code: '{{ with $parameter }}dark{{ end }}',
|
||||
args: (args) => args,
|
||||
expected: [''],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('ignores trailing and leading whitespaces and newlines inside scope', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'does not render trailing whitespace after value',
|
||||
code: '{{ with $parameter }}{{ . }}! {{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'does not render trailing newline after value',
|
||||
code: '{{ with $parameter }}{{ . }}!\r\n{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'does not render leading newline before value',
|
||||
code: '{{ with $parameter }}\r\n{{ . }}!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'does not render leading whitespace before value',
|
||||
code: '{{ with $parameter }} {{ . }}!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
);
|
||||
describe('render scope when variable has value', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'renders scope even if value is not used',
|
||||
code: '{{ with $parameter }}Hello world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'renders value when it has value',
|
||||
code: '{{ with $parameter }}{{ . }} world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'renders value when whitespaces around brackets are missing',
|
||||
code: '{{ with $parameter }}{{.}} world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'renders value multiple times when it\'s used multiple times',
|
||||
code: '{{ with $letterL }}He{{ . }}{{ . }}o wor{{ . }}d!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('letterL', 'l'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('compiles pipes in scope as expected', () => {
|
||||
runner.expectPipeHits({
|
||||
codeBuilder: (pipeline) => `{{ with $argument }} {{ .${pipeline}}} {{ end }}`,
|
||||
parameterName: 'argument',
|
||||
parameterValue: 'value',
|
||||
});
|
||||
});
|
||||
describe('ignores trailing and leading whitespaces and newlines inside scope', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'does not render trailing whitespace after value',
|
||||
code: '{{ with $parameter }}{{ . }}! {{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'does not render trailing newline after value',
|
||||
code: '{{ with $parameter }}{{ . }}!\r\n{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'does not render leading newline before value',
|
||||
code: '{{ with $parameter }}\r\n{{ . }}!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'does not render leading whitespace before value',
|
||||
code: '{{ with $parameter }} {{ . }}!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('compiles pipes in scope as expected', () => {
|
||||
runner.expectPipeHits({
|
||||
codeBuilder: (pipeline) => `{{ with $argument }} {{ .${pipeline}}} {{ end }}`,
|
||||
parameterName: 'argument',
|
||||
parameterValue: 'value',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,43 +4,47 @@ import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Funct
|
||||
import { testParameterName } from '../../../ParameterNameTestRunner';
|
||||
|
||||
describe('FunctionCallArgument', () => {
|
||||
describe('ctor', () => {
|
||||
describe('parameter name', () => {
|
||||
testParameterName(
|
||||
(parameterName) => new FunctionCallArgumentBuilder()
|
||||
.withParameterName(parameterName)
|
||||
.build()
|
||||
.parameterName,
|
||||
);
|
||||
});
|
||||
it('throws if argument value is undefined', () => {
|
||||
// arrange
|
||||
const parameterName = 'paramName';
|
||||
const expectedError = `undefined argument value for "${parameterName}"`;
|
||||
const argumentValue = undefined;
|
||||
// act
|
||||
const act = () => new FunctionCallArgumentBuilder()
|
||||
.withParameterName(parameterName)
|
||||
.withArgumentValue(argumentValue)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('parameter name', () => {
|
||||
testParameterName(
|
||||
(parameterName) => new FunctionCallArgumentBuilder()
|
||||
.withParameterName(parameterName)
|
||||
.build()
|
||||
.parameterName,
|
||||
);
|
||||
});
|
||||
it('throws if argument value is undefined', () => {
|
||||
// arrange
|
||||
const parameterName = 'paramName';
|
||||
const expectedError = `undefined argument value for "${parameterName}"`;
|
||||
const argumentValue = undefined;
|
||||
// act
|
||||
const act = () => new FunctionCallArgumentBuilder()
|
||||
.withParameterName(parameterName)
|
||||
.withArgumentValue(argumentValue)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class FunctionCallArgumentBuilder {
|
||||
private parameterName = 'default-parameter-name';
|
||||
private argumentValue = 'default-argument-value';
|
||||
public withParameterName(parameterName: string) {
|
||||
this.parameterName = parameterName;
|
||||
return this;
|
||||
}
|
||||
public withArgumentValue(argumentValue: string) {
|
||||
this.argumentValue = argumentValue;
|
||||
return this;
|
||||
}
|
||||
public build() {
|
||||
return new FunctionCallArgument(this.parameterName, this.argumentValue);
|
||||
}
|
||||
private parameterName = 'default-parameter-name';
|
||||
|
||||
private argumentValue = 'default-argument-value';
|
||||
|
||||
public withParameterName(parameterName: string) {
|
||||
this.parameterName = parameterName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withArgumentValue(argumentValue: string) {
|
||||
this.argumentValue = argumentValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new FunctionCallArgument(this.parameterName, this.argumentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,140 +4,140 @@ import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Comp
|
||||
import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub';
|
||||
|
||||
describe('FunctionCallArgumentCollection', () => {
|
||||
describe('addArgument', () => {
|
||||
it('throws if argument is undefined', () => {
|
||||
// arrange
|
||||
const errorMessage = 'undefined argument';
|
||||
const arg = undefined;
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.addArgument(arg);
|
||||
// assert
|
||||
expect(act).to.throw(errorMessage);
|
||||
});
|
||||
it('throws if parameter value is already provided', () => {
|
||||
// arrange
|
||||
const duplicateParameterName = 'duplicateParam';
|
||||
const errorMessage = `argument value for parameter ${duplicateParameterName} is already provided`;
|
||||
const arg1 = new FunctionCallArgumentStub().withParameterName(duplicateParameterName);
|
||||
const arg2 = new FunctionCallArgumentStub().withParameterName(duplicateParameterName);
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
sut.addArgument(arg1);
|
||||
const act = () => sut.addArgument(arg2);
|
||||
// assert
|
||||
expect(act).to.throw(errorMessage);
|
||||
});
|
||||
describe('addArgument', () => {
|
||||
it('throws if argument is undefined', () => {
|
||||
// arrange
|
||||
const errorMessage = 'undefined argument';
|
||||
const arg = undefined;
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.addArgument(arg);
|
||||
// assert
|
||||
expect(act).to.throw(errorMessage);
|
||||
});
|
||||
describe('getAllParameterNames', () => {
|
||||
it('returns as expected', () => {
|
||||
// arrange
|
||||
const testCases = [ {
|
||||
name: 'no args',
|
||||
args: [],
|
||||
expected: [],
|
||||
}, {
|
||||
name: 'with some args',
|
||||
args: [
|
||||
new FunctionCallArgumentStub().withParameterName('a-param-name'),
|
||||
new FunctionCallArgumentStub().withParameterName('b-param-name')],
|
||||
expected: [ 'a-param-name', 'b-param-name'],
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
for (const arg of testCase.args) {
|
||||
sut.addArgument(arg);
|
||||
}
|
||||
const actual = sut.getAllParameterNames();
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('throws if parameter value is already provided', () => {
|
||||
// arrange
|
||||
const duplicateParameterName = 'duplicateParam';
|
||||
const errorMessage = `argument value for parameter ${duplicateParameterName} is already provided`;
|
||||
const arg1 = new FunctionCallArgumentStub().withParameterName(duplicateParameterName);
|
||||
const arg2 = new FunctionCallArgumentStub().withParameterName(duplicateParameterName);
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
sut.addArgument(arg1);
|
||||
const act = () => sut.addArgument(arg2);
|
||||
// assert
|
||||
expect(act).to.throw(errorMessage);
|
||||
});
|
||||
describe('getArgument', () => {
|
||||
it('throws if parameter name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameter name';
|
||||
const undefinedValues = [ '', undefined ];
|
||||
for (const undefinedValue of undefinedValues) {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.getArgument(undefinedValue);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
it('throws if argument does not exist', () => {
|
||||
// arrange
|
||||
const parameterName = 'nonExistingParam';
|
||||
const expectedError = `parameter does not exist: ${parameterName}`;
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.getArgument(parameterName);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns argument as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentStub()
|
||||
.withParameterName('expectedName')
|
||||
.withArgumentValue('expectedValue');
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
sut.addArgument(expected);
|
||||
const actual = sut.getArgument(expected.parameterName);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('getAllParameterNames', () => {
|
||||
it('returns as expected', () => {
|
||||
// arrange
|
||||
const testCases = [{
|
||||
name: 'no args',
|
||||
args: [],
|
||||
expected: [],
|
||||
}, {
|
||||
name: 'with some args',
|
||||
args: [
|
||||
new FunctionCallArgumentStub().withParameterName('a-param-name'),
|
||||
new FunctionCallArgumentStub().withParameterName('b-param-name')],
|
||||
expected: ['a-param-name', 'b-param-name'],
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
for (const arg of testCase.args) {
|
||||
sut.addArgument(arg);
|
||||
}
|
||||
const actual = sut.getAllParameterNames();
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('hasArgument', () => {
|
||||
it('throws if parameter name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameter name';
|
||||
const undefinedValues = [ '', undefined ];
|
||||
for (const undefinedValue of undefinedValues) {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.hasArgument(undefinedValue);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
describe('returns as expected', () => {
|
||||
// arrange
|
||||
const testCases = [ {
|
||||
name: 'argument exists',
|
||||
parameter: 'existing-parameter-name',
|
||||
args: [
|
||||
new FunctionCallArgumentStub().withParameterName('existing-parameter-name'),
|
||||
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name'),
|
||||
],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'argument does not exist',
|
||||
parameter: 'not-existing-parameter-name',
|
||||
args: [
|
||||
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name-b'),
|
||||
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name-a'),
|
||||
],
|
||||
expected: false,
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(`"${testCase.name}" returns "${testCase.expected}"`, () => {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
for (const arg of testCase.args) {
|
||||
sut.addArgument(arg);
|
||||
}
|
||||
const actual = sut.hasArgument(testCase.parameter);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('getArgument', () => {
|
||||
it('throws if parameter name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameter name';
|
||||
const undefinedValues = ['', undefined];
|
||||
for (const undefinedValue of undefinedValues) {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.getArgument(undefinedValue);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
it('throws if argument does not exist', () => {
|
||||
// arrange
|
||||
const parameterName = 'nonExistingParam';
|
||||
const expectedError = `parameter does not exist: ${parameterName}`;
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.getArgument(parameterName);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns argument as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentStub()
|
||||
.withParameterName('expectedName')
|
||||
.withArgumentValue('expectedValue');
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
sut.addArgument(expected);
|
||||
const actual = sut.getArgument(expected.parameterName);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('hasArgument', () => {
|
||||
it('throws if parameter name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameter name';
|
||||
const undefinedValues = ['', undefined];
|
||||
for (const undefinedValue of undefinedValues) {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.hasArgument(undefinedValue);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
describe('returns as expected', () => {
|
||||
// arrange
|
||||
const testCases = [{
|
||||
name: 'argument exists',
|
||||
parameter: 'existing-parameter-name',
|
||||
args: [
|
||||
new FunctionCallArgumentStub().withParameterName('existing-parameter-name'),
|
||||
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name'),
|
||||
],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'argument does not exist',
|
||||
parameter: 'not-existing-parameter-name',
|
||||
args: [
|
||||
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name-b'),
|
||||
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name-a'),
|
||||
],
|
||||
expected: false,
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(`"${testCase.name}" returns "${testCase.expected}"`, () => {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
for (const arg of testCase.args) {
|
||||
sut.addArgument(arg);
|
||||
}
|
||||
const actual = sut.hasArgument(testCase.parameter);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,494 +12,498 @@ import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub';
|
||||
import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
|
||||
|
||||
describe('FunctionCallCompiler', () => {
|
||||
describe('compileCall', () => {
|
||||
describe('parameter validation', () => {
|
||||
describe('call', () => {
|
||||
it('throws with undefined call', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined calls';
|
||||
const call = undefined;
|
||||
const functions = new SharedFunctionCollectionStub();
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall(call, functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if call sequence has undefined call', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined function call';
|
||||
const call = [
|
||||
new FunctionCallStub(),
|
||||
undefined,
|
||||
];
|
||||
const functions = new SharedFunctionCollectionStub();
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall(call, functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws if call parameters does not match function parameters', () => {
|
||||
// arrange
|
||||
const functionName = 'test-function-name';
|
||||
const testCases = [
|
||||
{
|
||||
name: 'provided: single unexpected parameter, when: another expected',
|
||||
functionParameters: [ 'expected-parameter' ],
|
||||
callParameters: [ 'unexpected-parameter' ],
|
||||
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"` +
|
||||
`. Expected parameter(s): "expected-parameter"`
|
||||
,
|
||||
},
|
||||
{
|
||||
name: 'provided: multiple unexpected parameters, when: different one is expected',
|
||||
functionParameters: [ 'expected-parameter' ],
|
||||
callParameters: [ 'unexpected-parameter1', 'unexpected-parameter2' ],
|
||||
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter1", "unexpected-parameter2"` +
|
||||
`. Expected parameter(s): "expected-parameter"`
|
||||
,
|
||||
},
|
||||
{
|
||||
name: 'provided: an unexpected parameter, when: multiple parameters are expected',
|
||||
functionParameters: [ 'expected-parameter1', 'expected-parameter2' ],
|
||||
callParameters: [ 'expected-parameter1', 'expected-parameter2', 'unexpected-parameter' ],
|
||||
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"` +
|
||||
`. Expected parameter(s): "expected-parameter1", "expected-parameter2"`,
|
||||
},
|
||||
{
|
||||
name: 'provided: an unexpected parameter, when: none required',
|
||||
functionParameters: undefined,
|
||||
callParameters: [ 'unexpected-call-parameter' ],
|
||||
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-call-parameter"` +
|
||||
`. Expected parameter(s): none`,
|
||||
},
|
||||
{
|
||||
name: 'provided: expected and unexpected parameter, when: one of them is expected',
|
||||
functionParameters: [ 'expected-parameter' ],
|
||||
callParameters: [ 'expected-parameter', 'unexpected-parameter' ],
|
||||
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"` +
|
||||
`. Expected parameter(s): "expected-parameter"`,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('test-function-name')
|
||||
.withParameterNames(...testCase.functionParameters);
|
||||
let params: FunctionCallParametersData = {};
|
||||
for (const parameter of testCase.callParameters) {
|
||||
params = {...params, [parameter]: 'defined-parameter-value '};
|
||||
}
|
||||
const call = new FunctionCallStub()
|
||||
.withFunctionName(func.name)
|
||||
.withArguments(params);
|
||||
const functions = new SharedFunctionCollectionStub()
|
||||
.withFunction(func);
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('functions', () => {
|
||||
it('throws with undefined functions', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined functions';
|
||||
const call = new FunctionCallStub();
|
||||
const functions = undefined;
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if function does not exist', () => {
|
||||
// arrange
|
||||
const expectedError = 'function does not exist';
|
||||
const call = new FunctionCallStub();
|
||||
const functions: ISharedFunctionCollection = {
|
||||
getFunctionByName: () => { throw new Error(expectedError); },
|
||||
};
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('compileCall', () => {
|
||||
describe('parameter validation', () => {
|
||||
describe('call', () => {
|
||||
it('throws with undefined call', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined calls';
|
||||
const call = undefined;
|
||||
const functions = new SharedFunctionCollectionStub();
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall(call, functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('builds code as expected', () => {
|
||||
describe('builds single call as expected', () => {
|
||||
// arrange
|
||||
const parametersTestCases = [
|
||||
{
|
||||
name: 'empty parameters',
|
||||
parameters: [],
|
||||
callArgs: { },
|
||||
},
|
||||
{
|
||||
name: 'non-empty parameters',
|
||||
parameters: [ 'param1', 'param2' ],
|
||||
callArgs: { param1: 'value1', param2: 'value2' },
|
||||
},
|
||||
];
|
||||
for (const testCase of parametersTestCases) {
|
||||
it(testCase.name, () => {
|
||||
const expected = {
|
||||
execute: 'expected code (execute)',
|
||||
revert: 'expected code (revert)',
|
||||
};
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withParameterNames(...testCase.parameters);
|
||||
const functions = new SharedFunctionCollectionStub().withFunction(func);
|
||||
const call = new FunctionCallStub()
|
||||
.withFunctionName(func.name)
|
||||
.withArguments(testCase.callArgs);
|
||||
const args = new FunctionCallArgumentCollectionStub().withArguments(testCase.callArgs);
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({ givenCode: func.body.code.do, givenArgs: args, result: expected.execute })
|
||||
.setup({ givenCode: func.body.code.revert, givenArgs: args, result: expected.revert });
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.execute);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('builds call sequence as expected', () => {
|
||||
// arrange
|
||||
const firstFunction = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('first-function-name')
|
||||
.withCode('first-function-code')
|
||||
.withRevertCode('first-function-revert-code');
|
||||
const secondFunction = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('second-function-name')
|
||||
.withParameterNames('testParameter')
|
||||
.withCode('second-function-code')
|
||||
.withRevertCode('second-function-revert-code');
|
||||
const secondCallArguments = { testParameter: 'testValue' };
|
||||
const calls = [
|
||||
new FunctionCallStub().withFunctionName(firstFunction.name).withArguments({}),
|
||||
new FunctionCallStub().withFunctionName(secondFunction.name).withArguments(secondCallArguments),
|
||||
];
|
||||
const firstFunctionCallArgs = new FunctionCallArgumentCollectionStub();
|
||||
const secondFunctionCallArgs = new FunctionCallArgumentCollectionStub()
|
||||
.withArguments(secondCallArguments);
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setupToReturnFunctionCode(firstFunction, firstFunctionCallArgs)
|
||||
.setupToReturnFunctionCode(secondFunction, secondFunctionCallArgs);
|
||||
const expectedExecute = `${firstFunction.body.code.do}\n${secondFunction.body.code.do}`;
|
||||
const expectedRevert = `${firstFunction.body.code.revert}\n${secondFunction.body.code.revert}`;
|
||||
const functions = new SharedFunctionCollectionStub()
|
||||
.withFunction(firstFunction)
|
||||
.withFunction(secondFunction);
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall(calls, functions);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expectedExecute);
|
||||
expect(actual.revertCode).to.equal(expectedRevert);
|
||||
});
|
||||
describe('can compile a call tree (function calling another)', () => {
|
||||
describe('single deep function call', () => {
|
||||
it('builds 2nd level of depth without arguments', () => {
|
||||
// arrange
|
||||
const emptyArgs = new FunctionCallArgumentCollectionStub();
|
||||
const deepFunctionName = 'deepFunction';
|
||||
const functions = {
|
||||
deep: new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(deepFunctionName)
|
||||
.withCode('deep function code')
|
||||
.withRevertCode('deep function final code'),
|
||||
front: new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('frontFunction')
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(deepFunctionName)
|
||||
.withArgumentCollection(emptyArgs),
|
||||
),
|
||||
};
|
||||
const expected = {
|
||||
code: 'final code',
|
||||
revert: 'final revert code',
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({
|
||||
givenCode: functions.deep.body.code.do,
|
||||
givenArgs: emptyArgs,
|
||||
result: expected.code,
|
||||
})
|
||||
.setup({
|
||||
givenCode: functions.deep.body.code.revert,
|
||||
givenArgs: emptyArgs,
|
||||
result: expected.revert,
|
||||
});
|
||||
const mainCall = new FunctionCallStub()
|
||||
.withFunctionName(functions.front.name)
|
||||
.withArgumentCollection(emptyArgs);
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
[mainCall],
|
||||
new SharedFunctionCollectionStub().withFunction(functions.deep, functions.front),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
it('builds 2nd level of depth by compiling arguments', () => {
|
||||
// arrange
|
||||
const scenario = {
|
||||
front: {
|
||||
functionName: 'frontFunction',
|
||||
parameterName: 'frontFunctionParameterName',
|
||||
args: {
|
||||
fromMainCall: 'initial argument to be compiled',
|
||||
toNextStatic: 'value from "front" to "deep" in function definition',
|
||||
toNextCompiled: 'argument from "front" to "deep" (compiled)',
|
||||
},
|
||||
callArgs: {
|
||||
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.front.parameterName, scenario.front.args.fromMainCall),
|
||||
expectedCallDeep: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextCompiled),
|
||||
},
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName(scenario.front.functionName)
|
||||
.withParameterNames(scenario.front.parameterName)
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(scenario.deep.functionName)
|
||||
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextStatic),
|
||||
),
|
||||
},
|
||||
deep: {
|
||||
functionName: 'deepFunction',
|
||||
parameterName: 'deepFunctionParameterName',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(scenario.deep.functionName)
|
||||
.withParameterNames(scenario.deep.parameterName)
|
||||
.withCode(`${scenario.deep.functionName} function code`)
|
||||
.withRevertCode(`${scenario.deep.functionName} function revert code`),
|
||||
},
|
||||
};
|
||||
const expected = {
|
||||
code: 'final code',
|
||||
revert: 'final revert code',
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({ // Front ===args===> Deep
|
||||
givenCode: scenario.front.args.toNextStatic,
|
||||
givenArgs: scenario.front.callArgs.initialFromMainCall(),
|
||||
result: scenario.front.args.toNextCompiled,
|
||||
})
|
||||
// set-up compiling of deep, compiled argument should be sent
|
||||
.setup({
|
||||
givenCode: scenario.deep.getFunction().body.code.do,
|
||||
givenArgs: scenario.front.callArgs.expectedCallDeep(),
|
||||
result: expected.code,
|
||||
})
|
||||
.setup({
|
||||
givenCode: scenario.deep.getFunction().body.code.revert,
|
||||
givenArgs: scenario.front.callArgs.expectedCallDeep(),
|
||||
result: expected.revert,
|
||||
});
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
[
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(scenario.front.functionName)
|
||||
.withArgumentCollection(scenario.front.callArgs.initialFromMainCall()),
|
||||
],
|
||||
new SharedFunctionCollectionStub().withFunction(
|
||||
scenario.deep.getFunction(), scenario.front.getFunction()),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
it('builds 3rd level of depth by compiling arguments', () => {
|
||||
// arrange
|
||||
const scenario = {
|
||||
first: {
|
||||
functionName: 'firstFunction',
|
||||
parameter: 'firstParameter',
|
||||
args: {
|
||||
fromMainCall: 'initial argument to be compiled',
|
||||
toNextStatic: 'value from "first" to "second" in function definition',
|
||||
toNextCompiled: 'argument from "first" to "second" (compiled)',
|
||||
},
|
||||
callArgs: {
|
||||
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.first.parameter, scenario.first.args.fromMainCall),
|
||||
expectedToSecond: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.second.parameter, scenario.first.args.toNextCompiled),
|
||||
},
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName(scenario.first.functionName)
|
||||
.withParameterNames(scenario.first.parameter)
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(scenario.second.functionName)
|
||||
.withArgument(scenario.second.parameter, scenario.first.args.toNextStatic),
|
||||
),
|
||||
},
|
||||
second: {
|
||||
functionName: 'secondFunction',
|
||||
parameter: 'secondParameter',
|
||||
args: {
|
||||
toNextCompiled: 'argument second to third (compiled)',
|
||||
toNextStatic: 'calling second to third',
|
||||
},
|
||||
callArgs: {
|
||||
expectedToThird: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.third.parameter, scenario.second.args.toNextCompiled),
|
||||
},
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName(scenario.second.functionName)
|
||||
.withParameterNames(scenario.second.parameter)
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(scenario.third.functionName)
|
||||
.withArgument(scenario.third.parameter, scenario.second.args.toNextStatic),
|
||||
),
|
||||
},
|
||||
third: {
|
||||
functionName: 'thirdFunction',
|
||||
parameter: 'thirdParameter',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(scenario.third.functionName)
|
||||
.withParameterNames(scenario.third.parameter)
|
||||
.withCode(`${scenario.third.functionName} function code`)
|
||||
.withRevertCode(`${scenario.third.functionName} function revert code`),
|
||||
},
|
||||
};
|
||||
const expected = {
|
||||
code: 'final code',
|
||||
revert: 'final revert code',
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({ // First ===args===> Second
|
||||
givenCode: scenario.first.args.toNextStatic,
|
||||
givenArgs: scenario.first.callArgs.initialFromMainCall(),
|
||||
result: scenario.first.args.toNextCompiled,
|
||||
})
|
||||
.setup({ // Second ===args===> third
|
||||
givenCode: scenario.second.args.toNextStatic,
|
||||
givenArgs: scenario.first.callArgs.expectedToSecond(),
|
||||
result: scenario.second.args.toNextCompiled,
|
||||
})
|
||||
// Compiling of third functions code with expected arguments
|
||||
.setup({
|
||||
givenCode: scenario.third.getFunction().body.code.do,
|
||||
givenArgs: scenario.second.callArgs.expectedToThird(),
|
||||
result: expected.code,
|
||||
})
|
||||
.setup({
|
||||
givenCode: scenario.third.getFunction().body.code.revert,
|
||||
givenArgs: scenario.second.callArgs.expectedToThird(),
|
||||
result: expected.revert,
|
||||
});
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
const mainCall = new FunctionCallStub()
|
||||
.withFunctionName(scenario.first.functionName)
|
||||
.withArgumentCollection(scenario.first.callArgs.initialFromMainCall());
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
[mainCall],
|
||||
new SharedFunctionCollectionStub().withFunction(
|
||||
scenario.first.getFunction(),
|
||||
scenario.second.getFunction(),
|
||||
scenario.third.getFunction()),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
});
|
||||
describe('multiple deep function calls', () => {
|
||||
it('builds 2nd level of depth without arguments', () => {
|
||||
// arrange
|
||||
const emptyArgs = new FunctionCallArgumentCollectionStub();
|
||||
const functions = {
|
||||
call1: {
|
||||
deep: {
|
||||
functionName: 'deepFunction',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(functions.call1.deep.functionName)
|
||||
.withCode('deep function (1) code')
|
||||
.withRevertCode('deep function (1) final code'),
|
||||
},
|
||||
front: {
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('frontFunction')
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(functions.call1.deep.functionName)
|
||||
.withArgumentCollection(emptyArgs),
|
||||
),
|
||||
},
|
||||
},
|
||||
call2: {
|
||||
deep: {
|
||||
functionName: 'deepFunction2',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(functions.call2.deep.functionName)
|
||||
.withCode('deep function (2) code')
|
||||
.withRevertCode('deep function (2) final code'),
|
||||
},
|
||||
front: {
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('frontFunction2')
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(functions.call2.deep.functionName)
|
||||
.withArgumentCollection(emptyArgs),
|
||||
),
|
||||
},
|
||||
},
|
||||
getMainCall: () => [
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(functions.call1.front.getFunction().name)
|
||||
.withArgumentCollection(emptyArgs),
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(functions.call2.front.getFunction().name)
|
||||
.withArgumentCollection(emptyArgs),
|
||||
],
|
||||
getCollection: () => new SharedFunctionCollectionStub().withFunction(
|
||||
functions.call1.deep.getFunction(),
|
||||
functions.call1.front.getFunction(),
|
||||
functions.call2.deep.getFunction(),
|
||||
functions.call2.front.getFunction(),
|
||||
),
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setupToReturnFunctionCode(functions.call1.deep.getFunction(), emptyArgs)
|
||||
.setupToReturnFunctionCode(functions.call2.deep.getFunction(), emptyArgs);
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
const expected = {
|
||||
code: `${functions.call1.deep.getFunction().body.code.do}\n${functions.call2.deep.getFunction().body.code.do}`,
|
||||
revert: `${functions.call1.deep.getFunction().body.code.revert}\n${functions.call2.deep.getFunction().body.code.revert}`,
|
||||
};
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
functions.getMainCall(),
|
||||
functions.getCollection(),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('throws if call sequence has undefined call', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined function call';
|
||||
const call = [
|
||||
new FunctionCallStub(),
|
||||
undefined,
|
||||
];
|
||||
const functions = new SharedFunctionCollectionStub();
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall(call, functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws if call parameters does not match function parameters', () => {
|
||||
// arrange
|
||||
const functionName = 'test-function-name';
|
||||
const testCases = [
|
||||
{
|
||||
name: 'provided: single unexpected parameter, when: another expected',
|
||||
functionParameters: ['expected-parameter'],
|
||||
callParameters: ['unexpected-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
|
||||
+ '. Expected parameter(s): "expected-parameter"',
|
||||
},
|
||||
{
|
||||
name: 'provided: multiple unexpected parameters, when: different one is expected',
|
||||
functionParameters: ['expected-parameter'],
|
||||
callParameters: ['unexpected-parameter1', 'unexpected-parameter2'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter1", "unexpected-parameter2"`
|
||||
+ '. Expected parameter(s): "expected-parameter"',
|
||||
},
|
||||
{
|
||||
name: 'provided: an unexpected parameter, when: multiple parameters are expected',
|
||||
functionParameters: ['expected-parameter1', 'expected-parameter2'],
|
||||
callParameters: ['expected-parameter1', 'expected-parameter2', 'unexpected-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
|
||||
+ '. Expected parameter(s): "expected-parameter1", "expected-parameter2"',
|
||||
},
|
||||
{
|
||||
name: 'provided: an unexpected parameter, when: none required',
|
||||
functionParameters: undefined,
|
||||
callParameters: ['unexpected-call-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-call-parameter"`
|
||||
+ '. Expected parameter(s): none',
|
||||
},
|
||||
{
|
||||
name: 'provided: expected and unexpected parameter, when: one of them is expected',
|
||||
functionParameters: ['expected-parameter'],
|
||||
callParameters: ['expected-parameter', 'unexpected-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
|
||||
+ '. Expected parameter(s): "expected-parameter"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('test-function-name')
|
||||
.withParameterNames(...testCase.functionParameters);
|
||||
let params: FunctionCallParametersData = {};
|
||||
for (const parameter of testCase.callParameters) {
|
||||
params = { ...params, [parameter]: 'defined-parameter-value ' };
|
||||
}
|
||||
const call = new FunctionCallStub()
|
||||
.withFunctionName(func.name)
|
||||
.withArguments(params);
|
||||
const functions = new SharedFunctionCollectionStub()
|
||||
.withFunction(func);
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('functions', () => {
|
||||
it('throws with undefined functions', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined functions';
|
||||
const call = new FunctionCallStub();
|
||||
const functions = undefined;
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if function does not exist', () => {
|
||||
// arrange
|
||||
const expectedError = 'function does not exist';
|
||||
const call = new FunctionCallStub();
|
||||
const functions: ISharedFunctionCollection = {
|
||||
getFunctionByName: () => { throw new Error(expectedError); },
|
||||
};
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('builds code as expected', () => {
|
||||
describe('builds single call as expected', () => {
|
||||
// arrange
|
||||
const parametersTestCases = [
|
||||
{
|
||||
name: 'empty parameters',
|
||||
parameters: [],
|
||||
callArgs: { },
|
||||
},
|
||||
{
|
||||
name: 'non-empty parameters',
|
||||
parameters: ['param1', 'param2'],
|
||||
callArgs: { param1: 'value1', param2: 'value2' },
|
||||
},
|
||||
];
|
||||
for (const testCase of parametersTestCases) {
|
||||
it(testCase.name, () => {
|
||||
const expected = {
|
||||
execute: 'expected code (execute)',
|
||||
revert: 'expected code (revert)',
|
||||
};
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withParameterNames(...testCase.parameters);
|
||||
const functions = new SharedFunctionCollectionStub().withFunction(func);
|
||||
const call = new FunctionCallStub()
|
||||
.withFunctionName(func.name)
|
||||
.withArguments(testCase.callArgs);
|
||||
const args = new FunctionCallArgumentCollectionStub().withArguments(testCase.callArgs);
|
||||
const { code } = func.body;
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({ givenCode: code.do, givenArgs: args, result: expected.execute })
|
||||
.setup({ givenCode: code.revert, givenArgs: args, result: expected.revert });
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.execute);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('builds call sequence as expected', () => {
|
||||
// arrange
|
||||
const firstFunction = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('first-function-name')
|
||||
.withCode('first-function-code')
|
||||
.withRevertCode('first-function-revert-code');
|
||||
const secondFunction = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('second-function-name')
|
||||
.withParameterNames('testParameter')
|
||||
.withCode('second-function-code')
|
||||
.withRevertCode('second-function-revert-code');
|
||||
const secondCallArguments = { testParameter: 'testValue' };
|
||||
const calls = [
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(firstFunction.name)
|
||||
.withArguments({}),
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(secondFunction.name)
|
||||
.withArguments(secondCallArguments),
|
||||
];
|
||||
const firstFunctionCallArgs = new FunctionCallArgumentCollectionStub();
|
||||
const secondFunctionCallArgs = new FunctionCallArgumentCollectionStub()
|
||||
.withArguments(secondCallArguments);
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setupToReturnFunctionCode(firstFunction, firstFunctionCallArgs)
|
||||
.setupToReturnFunctionCode(secondFunction, secondFunctionCallArgs);
|
||||
const expectedExecute = `${firstFunction.body.code.do}\n${secondFunction.body.code.do}`;
|
||||
const expectedRevert = `${firstFunction.body.code.revert}\n${secondFunction.body.code.revert}`;
|
||||
const functions = new SharedFunctionCollectionStub()
|
||||
.withFunction(firstFunction)
|
||||
.withFunction(secondFunction);
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall(calls, functions);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expectedExecute);
|
||||
expect(actual.revertCode).to.equal(expectedRevert);
|
||||
});
|
||||
describe('can compile a call tree (function calling another)', () => {
|
||||
describe('single deep function call', () => {
|
||||
it('builds 2nd level of depth without arguments', () => {
|
||||
// arrange
|
||||
const emptyArgs = new FunctionCallArgumentCollectionStub();
|
||||
const deepFunctionName = 'deepFunction';
|
||||
const functions = {
|
||||
deep: new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(deepFunctionName)
|
||||
.withCode('deep function code')
|
||||
.withRevertCode('deep function final code'),
|
||||
front: new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('frontFunction')
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(deepFunctionName)
|
||||
.withArgumentCollection(emptyArgs)),
|
||||
};
|
||||
const expected = {
|
||||
code: 'final code',
|
||||
revert: 'final revert code',
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({
|
||||
givenCode: functions.deep.body.code.do,
|
||||
givenArgs: emptyArgs,
|
||||
result: expected.code,
|
||||
})
|
||||
.setup({
|
||||
givenCode: functions.deep.body.code.revert,
|
||||
givenArgs: emptyArgs,
|
||||
result: expected.revert,
|
||||
});
|
||||
const mainCall = new FunctionCallStub()
|
||||
.withFunctionName(functions.front.name)
|
||||
.withArgumentCollection(emptyArgs);
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
[mainCall],
|
||||
new SharedFunctionCollectionStub().withFunction(functions.deep, functions.front),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
it('builds 2nd level of depth by compiling arguments', () => {
|
||||
// arrange
|
||||
const scenario = {
|
||||
front: {
|
||||
functionName: 'frontFunction',
|
||||
parameterName: 'frontFunctionParameterName',
|
||||
args: {
|
||||
fromMainCall: 'initial argument to be compiled',
|
||||
toNextStatic: 'value from "front" to "deep" in function definition',
|
||||
toNextCompiled: 'argument from "front" to "deep" (compiled)',
|
||||
},
|
||||
callArgs: {
|
||||
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.front.parameterName, scenario.front.args.fromMainCall),
|
||||
expectedCallDeep: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextCompiled),
|
||||
},
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName(scenario.front.functionName)
|
||||
.withParameterNames(scenario.front.parameterName)
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(scenario.deep.functionName)
|
||||
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextStatic)),
|
||||
},
|
||||
deep: {
|
||||
functionName: 'deepFunction',
|
||||
parameterName: 'deepFunctionParameterName',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(scenario.deep.functionName)
|
||||
.withParameterNames(scenario.deep.parameterName)
|
||||
.withCode(`${scenario.deep.functionName} function code`)
|
||||
.withRevertCode(`${scenario.deep.functionName} function revert code`),
|
||||
},
|
||||
};
|
||||
const expected = {
|
||||
code: 'final code',
|
||||
revert: 'final revert code',
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({ // Front ===args===> Deep
|
||||
givenCode: scenario.front.args.toNextStatic,
|
||||
givenArgs: scenario.front.callArgs.initialFromMainCall(),
|
||||
result: scenario.front.args.toNextCompiled,
|
||||
})
|
||||
// set-up compiling of deep, compiled argument should be sent
|
||||
.setup({
|
||||
givenCode: scenario.deep.getFunction().body.code.do,
|
||||
givenArgs: scenario.front.callArgs.expectedCallDeep(),
|
||||
result: expected.code,
|
||||
})
|
||||
.setup({
|
||||
givenCode: scenario.deep.getFunction().body.code.revert,
|
||||
givenArgs: scenario.front.callArgs.expectedCallDeep(),
|
||||
result: expected.revert,
|
||||
});
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
[
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(scenario.front.functionName)
|
||||
.withArgumentCollection(scenario.front.callArgs.initialFromMainCall()),
|
||||
],
|
||||
new SharedFunctionCollectionStub().withFunction(
|
||||
scenario.deep.getFunction(),
|
||||
scenario.front.getFunction(),
|
||||
),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
it('builds 3rd level of depth by compiling arguments', () => {
|
||||
// arrange
|
||||
const scenario = {
|
||||
first: {
|
||||
functionName: 'firstFunction',
|
||||
parameter: 'firstParameter',
|
||||
args: {
|
||||
fromMainCall: 'initial argument to be compiled',
|
||||
toNextStatic: 'value from "first" to "second" in function definition',
|
||||
toNextCompiled: 'argument from "first" to "second" (compiled)',
|
||||
},
|
||||
callArgs: {
|
||||
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.first.parameter, scenario.first.args.fromMainCall),
|
||||
expectedToSecond: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.second.parameter, scenario.first.args.toNextCompiled),
|
||||
},
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName(scenario.first.functionName)
|
||||
.withParameterNames(scenario.first.parameter)
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(scenario.second.functionName)
|
||||
.withArgument(scenario.second.parameter, scenario.first.args.toNextStatic)),
|
||||
},
|
||||
second: {
|
||||
functionName: 'secondFunction',
|
||||
parameter: 'secondParameter',
|
||||
args: {
|
||||
toNextCompiled: 'argument second to third (compiled)',
|
||||
toNextStatic: 'calling second to third',
|
||||
},
|
||||
callArgs: {
|
||||
expectedToThird: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.third.parameter, scenario.second.args.toNextCompiled),
|
||||
},
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName(scenario.second.functionName)
|
||||
.withParameterNames(scenario.second.parameter)
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(scenario.third.functionName)
|
||||
.withArgument(scenario.third.parameter, scenario.second.args.toNextStatic)),
|
||||
},
|
||||
third: {
|
||||
functionName: 'thirdFunction',
|
||||
parameter: 'thirdParameter',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(scenario.third.functionName)
|
||||
.withParameterNames(scenario.third.parameter)
|
||||
.withCode(`${scenario.third.functionName} function code`)
|
||||
.withRevertCode(`${scenario.third.functionName} function revert code`),
|
||||
},
|
||||
};
|
||||
const expected = {
|
||||
code: 'final code',
|
||||
revert: 'final revert code',
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({ // First ===args===> Second
|
||||
givenCode: scenario.first.args.toNextStatic,
|
||||
givenArgs: scenario.first.callArgs.initialFromMainCall(),
|
||||
result: scenario.first.args.toNextCompiled,
|
||||
})
|
||||
.setup({ // Second ===args===> third
|
||||
givenCode: scenario.second.args.toNextStatic,
|
||||
givenArgs: scenario.first.callArgs.expectedToSecond(),
|
||||
result: scenario.second.args.toNextCompiled,
|
||||
})
|
||||
// Compiling of third functions code with expected arguments
|
||||
.setup({
|
||||
givenCode: scenario.third.getFunction().body.code.do,
|
||||
givenArgs: scenario.second.callArgs.expectedToThird(),
|
||||
result: expected.code,
|
||||
})
|
||||
.setup({
|
||||
givenCode: scenario.third.getFunction().body.code.revert,
|
||||
givenArgs: scenario.second.callArgs.expectedToThird(),
|
||||
result: expected.revert,
|
||||
});
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
const mainCall = new FunctionCallStub()
|
||||
.withFunctionName(scenario.first.functionName)
|
||||
.withArgumentCollection(scenario.first.callArgs.initialFromMainCall());
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
[mainCall],
|
||||
new SharedFunctionCollectionStub().withFunction(
|
||||
scenario.first.getFunction(),
|
||||
scenario.second.getFunction(),
|
||||
scenario.third.getFunction(),
|
||||
),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
});
|
||||
describe('multiple deep function calls', () => {
|
||||
it('builds 2nd level of depth without arguments', () => {
|
||||
// arrange
|
||||
const emptyArgs = new FunctionCallArgumentCollectionStub();
|
||||
const functions = {
|
||||
call1: {
|
||||
deep: {
|
||||
functionName: 'deepFunction',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(functions.call1.deep.functionName)
|
||||
.withCode('deep function (1) code')
|
||||
.withRevertCode('deep function (1) final code'),
|
||||
},
|
||||
front: {
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('frontFunction')
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(functions.call1.deep.functionName)
|
||||
.withArgumentCollection(emptyArgs)),
|
||||
},
|
||||
},
|
||||
call2: {
|
||||
deep: {
|
||||
functionName: 'deepFunction2',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(functions.call2.deep.functionName)
|
||||
.withCode('deep function (2) code')
|
||||
.withRevertCode('deep function (2) final code'),
|
||||
},
|
||||
front: {
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('frontFunction2')
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(functions.call2.deep.functionName)
|
||||
.withArgumentCollection(emptyArgs)),
|
||||
},
|
||||
},
|
||||
getMainCall: () => [
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(functions.call1.front.getFunction().name)
|
||||
.withArgumentCollection(emptyArgs),
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(functions.call2.front.getFunction().name)
|
||||
.withArgumentCollection(emptyArgs),
|
||||
],
|
||||
getCollection: () => new SharedFunctionCollectionStub().withFunction(
|
||||
functions.call1.deep.getFunction(),
|
||||
functions.call1.front.getFunction(),
|
||||
functions.call2.deep.getFunction(),
|
||||
functions.call2.front.getFunction(),
|
||||
),
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setupToReturnFunctionCode(functions.call1.deep.getFunction(), emptyArgs)
|
||||
.setupToReturnFunctionCode(functions.call2.deep.getFunction(), emptyArgs);
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
const expected = {
|
||||
code: `${functions.call1.deep.getFunction().body.code.do}\n${functions.call2.deep.getFunction().body.code.do}`,
|
||||
revert: `${functions.call1.deep.getFunction().body.code.revert}\n${functions.call2.deep.getFunction().body.code.revert}`,
|
||||
};
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
functions.getMainCall(),
|
||||
functions.getCollection(),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class MockableFunctionCallCompiler extends FunctionCallCompiler {
|
||||
constructor(expressionsCompiler: IExpressionsCompiler = new ExpressionsCompilerStub()) {
|
||||
super(expressionsCompiler);
|
||||
}
|
||||
constructor(expressionsCompiler: IExpressionsCompiler = new ExpressionsCompilerStub()) {
|
||||
super(expressionsCompiler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,72 +5,73 @@ import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Sc
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||
|
||||
describe('FunctionCall', () => {
|
||||
describe('ctor', () => {
|
||||
describe('args', () => {
|
||||
it('throws when args is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args';
|
||||
const args = undefined;
|
||||
// act
|
||||
const act = () => new FunctionCallBuilder()
|
||||
.withArgs(args)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('sets args as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('testParameter', 'testValue');
|
||||
// act
|
||||
const sut = new FunctionCallBuilder()
|
||||
.withArgs(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.args).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('functionName', () => {
|
||||
it('throws when function name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'empty function name in function call';
|
||||
const functionName = undefined;
|
||||
// act
|
||||
const act = () => new FunctionCallBuilder()
|
||||
.withFunctionName(functionName)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('sets function name as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expectedFunctionName';
|
||||
// act
|
||||
const sut = new FunctionCallBuilder()
|
||||
.withFunctionName(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.functionName).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('args', () => {
|
||||
it('throws when args is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args';
|
||||
const args = undefined;
|
||||
// act
|
||||
const act = () => new FunctionCallBuilder()
|
||||
.withArgs(args)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('sets args as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('testParameter', 'testValue');
|
||||
// act
|
||||
const sut = new FunctionCallBuilder()
|
||||
.withArgs(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.args).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('functionName', () => {
|
||||
it('throws when function name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'empty function name in function call';
|
||||
const functionName = undefined;
|
||||
// act
|
||||
const act = () => new FunctionCallBuilder()
|
||||
.withFunctionName(functionName)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('sets function name as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expectedFunctionName';
|
||||
// act
|
||||
const sut = new FunctionCallBuilder()
|
||||
.withFunctionName(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.functionName).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class FunctionCallBuilder {
|
||||
private functionName = 'functionName';
|
||||
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
|
||||
private functionName = 'functionName';
|
||||
|
||||
public withFunctionName(functionName: string) {
|
||||
this.functionName = functionName;
|
||||
return this;
|
||||
}
|
||||
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
|
||||
|
||||
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
public withFunctionName(functionName: string) {
|
||||
this.functionName = functionName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new FunctionCall(this.functionName, this.args);
|
||||
}
|
||||
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new FunctionCall(this.functionName, this.args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,98 +4,100 @@ import { parseFunctionCalls } from '@/application/Parser/Script/Compiler/Functio
|
||||
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
|
||||
|
||||
describe('FunctionCallParser', () => {
|
||||
describe('parseFunctionCalls', () => {
|
||||
it('throws with undefined call', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined call data';
|
||||
const call = undefined;
|
||||
// act
|
||||
const act = () => parseFunctionCalls(call);
|
||||
// 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 invalidCalls: readonly any[] = ['string', 33];
|
||||
invalidCalls.forEach((invalidCall) => {
|
||||
// act
|
||||
const act = () => parseFunctionCalls(invalidCall);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('throws if call sequence has undefined call', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined function call';
|
||||
const data = [
|
||||
new FunctionCallDataStub(),
|
||||
undefined,
|
||||
];
|
||||
// act
|
||||
const act = () => parseFunctionCalls(data);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if call sequence has undefined function name', () => {
|
||||
// arrange
|
||||
const expectedError = 'empty function name in function call';
|
||||
const data = [
|
||||
new FunctionCallDataStub().withName('function-name'),
|
||||
new FunctionCallDataStub().withName(undefined),
|
||||
];
|
||||
// act
|
||||
const act = () => parseFunctionCalls(data);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('parses single call as expected', () => {
|
||||
// arrange
|
||||
const expectedFunctionName = 'functionName';
|
||||
const expectedParameterName = 'parameterName';
|
||||
const expectedArgumentValue = 'argumentValue';
|
||||
const data = new FunctionCallDataStub()
|
||||
.withName(expectedFunctionName)
|
||||
.withParameters({ [expectedParameterName]: expectedArgumentValue });
|
||||
// act
|
||||
const actual = parseFunctionCalls(data);
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(1);
|
||||
const call = actual[0];
|
||||
expect(call.functionName).to.equal(expectedFunctionName);
|
||||
const args = call.args;
|
||||
expect(args.getAllParameterNames()).to.have.lengthOf(1);
|
||||
expect(args.hasArgument(expectedParameterName)).to.equal(true,
|
||||
`Does not include expected parameter: "${expectedParameterName}"\n` +
|
||||
`But includes: "${args.getAllParameterNames()}"`);
|
||||
const argument = args.getArgument(expectedParameterName);
|
||||
expect(argument.parameterName).to.equal(expectedParameterName);
|
||||
expect(argument.argumentValue).to.equal(expectedArgumentValue);
|
||||
});
|
||||
it('parses multiple calls as expected', () => {
|
||||
// arrange
|
||||
const getFunctionName = (index: number) => `functionName${index}`;
|
||||
const getParameterName = (index: number) => `parameterName${index}`;
|
||||
const getArgumentValue = (index: number) => `argumentValue${index}`;
|
||||
const createCall = (index: number) => new FunctionCallDataStub()
|
||||
.withName(getFunctionName(index))
|
||||
.withParameters({ [getParameterName(index)]: getArgumentValue(index)});
|
||||
const calls = [ createCall(0), createCall(1), createCall(2), createCall(3) ];
|
||||
// act
|
||||
const actual = parseFunctionCalls(calls);
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(calls.length);
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
const call = actual[i];
|
||||
const expectedParameterName = getParameterName(i);
|
||||
const expectedArgumentValue = getArgumentValue(i);
|
||||
expect(call.functionName).to.equal(getFunctionName(i));
|
||||
expect(call.args.getAllParameterNames()).to.have.lengthOf(1);
|
||||
expect(call.args.hasArgument(expectedParameterName)).to.equal(true);
|
||||
const argument = call.args.getArgument(expectedParameterName);
|
||||
expect(argument.parameterName).to.equal(expectedParameterName);
|
||||
expect(argument.argumentValue).to.equal(expectedArgumentValue);
|
||||
}
|
||||
});
|
||||
describe('parseFunctionCalls', () => {
|
||||
it('throws with undefined call', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined call data';
|
||||
const call = undefined;
|
||||
// act
|
||||
const act = () => parseFunctionCalls(call);
|
||||
// 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 invalidCalls = ['string', 33, false];
|
||||
invalidCalls.forEach((invalidCall) => {
|
||||
// act
|
||||
const act = () => parseFunctionCalls(invalidCall as never);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('throws if call sequence has undefined call', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined function call';
|
||||
const data = [
|
||||
new FunctionCallDataStub(),
|
||||
undefined,
|
||||
];
|
||||
// act
|
||||
const act = () => parseFunctionCalls(data);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if call sequence has undefined function name', () => {
|
||||
// arrange
|
||||
const expectedError = 'empty function name in function call';
|
||||
const data = [
|
||||
new FunctionCallDataStub().withName('function-name'),
|
||||
new FunctionCallDataStub().withName(undefined),
|
||||
];
|
||||
// act
|
||||
const act = () => parseFunctionCalls(data);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('parses single call as expected', () => {
|
||||
// arrange
|
||||
const expectedFunctionName = 'functionName';
|
||||
const expectedParameterName = 'parameterName';
|
||||
const expectedArgumentValue = 'argumentValue';
|
||||
const data = new FunctionCallDataStub()
|
||||
.withName(expectedFunctionName)
|
||||
.withParameters({ [expectedParameterName]: expectedArgumentValue });
|
||||
// act
|
||||
const actual = parseFunctionCalls(data);
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(1);
|
||||
const call = actual[0];
|
||||
expect(call.functionName).to.equal(expectedFunctionName);
|
||||
const { args } = call;
|
||||
expect(args.getAllParameterNames()).to.have.lengthOf(1);
|
||||
expect(args.hasArgument(expectedParameterName)).to.equal(
|
||||
true,
|
||||
`Does not include expected parameter: "${expectedParameterName}"\n`
|
||||
+ `But includes: "${args.getAllParameterNames()}"`,
|
||||
);
|
||||
const argument = args.getArgument(expectedParameterName);
|
||||
expect(argument.parameterName).to.equal(expectedParameterName);
|
||||
expect(argument.argumentValue).to.equal(expectedArgumentValue);
|
||||
});
|
||||
it('parses multiple calls as expected', () => {
|
||||
// arrange
|
||||
const getFunctionName = (index: number) => `functionName${index}`;
|
||||
const getParameterName = (index: number) => `parameterName${index}`;
|
||||
const getArgumentValue = (index: number) => `argumentValue${index}`;
|
||||
const createCall = (index: number) => new FunctionCallDataStub()
|
||||
.withName(getFunctionName(index))
|
||||
.withParameters({ [getParameterName(index)]: getArgumentValue(index) });
|
||||
const calls = [createCall(0), createCall(1), createCall(2), createCall(3)];
|
||||
// act
|
||||
const actual = parseFunctionCalls(calls);
|
||||
// assert
|
||||
expect(actual).to.have.lengthOf(calls.length);
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
const call = actual[i];
|
||||
const expectedParameterName = getParameterName(i);
|
||||
const expectedArgumentValue = getArgumentValue(i);
|
||||
expect(call.functionName).to.equal(getFunctionName(i));
|
||||
expect(call.args.getAllParameterNames()).to.have.lengthOf(1);
|
||||
expect(call.args.hasArgument(expectedParameterName)).to.equal(true);
|
||||
const argument = call.args.getArgument(expectedParameterName);
|
||||
expect(argument.parameterName).to.equal(expectedParameterName);
|
||||
expect(argument.argumentValue).to.equal(expectedArgumentValue);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,44 +4,48 @@ import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function
|
||||
import { testParameterName } from '../../ParameterNameTestRunner';
|
||||
|
||||
describe('FunctionParameter', () => {
|
||||
describe('name', () => {
|
||||
testParameterName(
|
||||
(parameterName) => new FunctionParameterBuilder()
|
||||
.withName(parameterName)
|
||||
.build()
|
||||
.name,
|
||||
);
|
||||
});
|
||||
describe('isOptional', () => {
|
||||
describe('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedValues = [ true, false];
|
||||
for (const expected of expectedValues) {
|
||||
it(expected.toString(), () => {
|
||||
// act
|
||||
const sut = new FunctionParameterBuilder()
|
||||
.withIsOptional(expected)
|
||||
.build();
|
||||
// expect
|
||||
expect(sut.isOptional).to.equal(expected);
|
||||
});
|
||||
}
|
||||
describe('name', () => {
|
||||
testParameterName(
|
||||
(parameterName) => new FunctionParameterBuilder()
|
||||
.withName(parameterName)
|
||||
.build()
|
||||
.name,
|
||||
);
|
||||
});
|
||||
describe('isOptional', () => {
|
||||
describe('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedValues = [true, false];
|
||||
for (const expected of expectedValues) {
|
||||
it(expected.toString(), () => {
|
||||
// act
|
||||
const sut = new FunctionParameterBuilder()
|
||||
.withIsOptional(expected)
|
||||
.build();
|
||||
// expect
|
||||
expect(sut.isOptional).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class FunctionParameterBuilder {
|
||||
private name = 'parameterFromParameterBuilder';
|
||||
private isOptional = false;
|
||||
public withName(name: string) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
public withIsOptional(isOptional: boolean) {
|
||||
this.isOptional = isOptional;
|
||||
return this;
|
||||
}
|
||||
public build() {
|
||||
return new FunctionParameter(this.name, this.isOptional);
|
||||
}
|
||||
private name = 'parameterFromParameterBuilder';
|
||||
|
||||
private isOptional = false;
|
||||
|
||||
public withName(name: string) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withIsOptional(isOptional: boolean) {
|
||||
this.isOptional = isOptional;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new FunctionParameter(this.name, this.isOptional);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,44 +4,45 @@ import { FunctionParameterCollection } from '@/application/Parser/Script/Compile
|
||||
import { FunctionParameterStub } from '@tests/unit/stubs/FunctionParameterStub';
|
||||
|
||||
describe('FunctionParameterCollection', () => {
|
||||
it('all returns added parameters as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new FunctionParameterStub().withName('1'),
|
||||
new FunctionParameterStub().withName('2').withOptionality(true),
|
||||
new FunctionParameterStub().withName('3').withOptionality(false),
|
||||
];
|
||||
const sut = new FunctionParameterCollection();
|
||||
for (const parameter of expected) {
|
||||
sut.addParameter(parameter);
|
||||
}
|
||||
// act
|
||||
const actual = sut.all;
|
||||
// assert
|
||||
expect(expected).to.deep.equal(actual);
|
||||
});
|
||||
it('throws when function parameters have same names', () => {
|
||||
// arrange
|
||||
const parameterName = 'duplicate-parameter';
|
||||
const expectedError = `duplicate parameter name: "${parameterName}"`;
|
||||
const sut = new FunctionParameterCollection();
|
||||
sut.addParameter(new FunctionParameterStub().withName(parameterName));
|
||||
// act
|
||||
const act = () =>
|
||||
sut.addParameter(new FunctionParameterStub().withName(parameterName));
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('addParameter', () => {
|
||||
it('throws if parameter is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameter';
|
||||
const value = undefined;
|
||||
const sut = new FunctionParameterCollection();
|
||||
// act
|
||||
const act = () => sut.addParameter(value);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('all returns added parameters as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new FunctionParameterStub().withName('1'),
|
||||
new FunctionParameterStub().withName('2').withOptionality(true),
|
||||
new FunctionParameterStub().withName('3').withOptionality(false),
|
||||
];
|
||||
const sut = new FunctionParameterCollection();
|
||||
for (const parameter of expected) {
|
||||
sut.addParameter(parameter);
|
||||
}
|
||||
// act
|
||||
const actual = sut.all;
|
||||
// assert
|
||||
expect(expected).to.deep.equal(actual);
|
||||
});
|
||||
it('throws when function parameters have same names', () => {
|
||||
// arrange
|
||||
const parameterName = 'duplicate-parameter';
|
||||
const expectedError = `duplicate parameter name: "${parameterName}"`;
|
||||
const sut = new FunctionParameterCollection();
|
||||
sut.addParameter(new FunctionParameterStub().withName(parameterName));
|
||||
// act
|
||||
const act = () => sut.addParameter(
|
||||
new FunctionParameterStub().withName(parameterName),
|
||||
);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('addParameter', () => {
|
||||
it('throws if parameter is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameter';
|
||||
const value = undefined;
|
||||
const sut = new FunctionParameterCollection();
|
||||
// act
|
||||
const act = () => sut.addParameter(value);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,247 +5,256 @@ import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParam
|
||||
import { createCallerFunction, createFunctionWithInlineCode } from '@/application/Parser/Script/Compiler/Function/SharedFunction';
|
||||
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
||||
import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub';
|
||||
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { FunctionBodyType, ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
|
||||
describe('SharedFunction', () => {
|
||||
describe('name', () => {
|
||||
runForEachFactoryMethod((build) => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-function-name';
|
||||
const builder = new SharedFunctionBuilder()
|
||||
.withName(expected);
|
||||
// act
|
||||
const sut = build(builder);
|
||||
// assert
|
||||
expect(sut.name).equal(expected);
|
||||
});
|
||||
it('throws if empty or undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined function name';
|
||||
const invalidValues = [ undefined, '' ];
|
||||
for (const invalidValue of invalidValues) {
|
||||
const builder = new SharedFunctionBuilder()
|
||||
.withName(invalidValue);
|
||||
// act
|
||||
const act = () => build(builder);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('name', () => {
|
||||
runForEachFactoryMethod((build) => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-function-name';
|
||||
const builder = new SharedFunctionBuilder()
|
||||
.withName(expected);
|
||||
// act
|
||||
const sut = build(builder);
|
||||
// assert
|
||||
expect(sut.name).equal(expected);
|
||||
});
|
||||
it('throws if empty or undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined function name';
|
||||
const invalidValues = [undefined, ''];
|
||||
for (const invalidValue of invalidValues) {
|
||||
const builder = new SharedFunctionBuilder()
|
||||
.withName(invalidValue);
|
||||
// act
|
||||
const act = () => build(builder);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('parameters', () => {
|
||||
runForEachFactoryMethod((build) => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionParameterCollectionStub()
|
||||
.withParameterName('test-parameter');
|
||||
const builder = new SharedFunctionBuilder()
|
||||
.withParameters(expected);
|
||||
// act
|
||||
const sut = build(builder);
|
||||
// assert
|
||||
expect(sut.parameters).equal(expected);
|
||||
});
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameters';
|
||||
const parameters = undefined;
|
||||
const builder = new SharedFunctionBuilder()
|
||||
.withParameters(parameters);
|
||||
// act
|
||||
const act = () => build(builder);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('parameters', () => {
|
||||
runForEachFactoryMethod((build) => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionParameterCollectionStub()
|
||||
.withParameterName('test-parameter');
|
||||
const builder = new SharedFunctionBuilder()
|
||||
.withParameters(expected);
|
||||
// act
|
||||
const sut = build(builder);
|
||||
// assert
|
||||
expect(sut.parameters).equal(expected);
|
||||
});
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameters';
|
||||
const parameters = undefined;
|
||||
const builder = new SharedFunctionBuilder()
|
||||
.withParameters(parameters);
|
||||
// act
|
||||
const act = () => build(builder);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('body', () => {
|
||||
describe('createFunctionWithInlineCode', () => {
|
||||
describe('code', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.withCode(expected)
|
||||
.createFunctionWithInlineCode();
|
||||
// assert
|
||||
expect(sut.body.code.do).equal(expected);
|
||||
});
|
||||
it('throws if empty or undefined', () => {
|
||||
// arrange
|
||||
const functionName = 'expected-function-name';
|
||||
const expectedError = `undefined code in function "${functionName}"`;
|
||||
const invalidValues = [ undefined, '' ];
|
||||
for (const invalidValue of invalidValues) {
|
||||
// act
|
||||
const act = () => new SharedFunctionBuilder()
|
||||
.withName(functionName)
|
||||
.withCode(invalidValue)
|
||||
.createFunctionWithInlineCode();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('revertCode', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const testData = [ 'expected-revert-code', undefined, '' ];
|
||||
for (const data of testData) {
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.withRevertCode(data)
|
||||
.createFunctionWithInlineCode();
|
||||
// assert
|
||||
expect(sut.body.code.revert).equal(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('sets type as expected', () => {
|
||||
// arrange
|
||||
const expectedType = FunctionBodyType.Code;
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.createFunctionWithInlineCode();
|
||||
// assert
|
||||
expect(sut.body.type).equal(expectedType);
|
||||
});
|
||||
it('calls are undefined', () => {
|
||||
// arrange
|
||||
const expectedCalls = undefined;
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.createFunctionWithInlineCode();
|
||||
// assert
|
||||
expect(sut.body.calls).equal(expectedCalls);
|
||||
});
|
||||
});
|
||||
describe('body', () => {
|
||||
describe('createFunctionWithInlineCode', () => {
|
||||
describe('code', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.withCode(expected)
|
||||
.createFunctionWithInlineCode();
|
||||
// assert
|
||||
expect(sut.body.code.do).equal(expected);
|
||||
});
|
||||
describe('createCallerFunction', () => {
|
||||
describe('callSequence', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new FunctionCallStub().withFunctionName('firstFunction'),
|
||||
new FunctionCallStub().withFunctionName('secondFunction'),
|
||||
];
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.withCallSequence(expected)
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(sut.body.calls).equal(expected);
|
||||
});
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const functionName = 'invalidFunction';
|
||||
const callSequence = undefined;
|
||||
const expectedError = `undefined call sequence in function "${functionName}"`;
|
||||
// act
|
||||
const act = () => new SharedFunctionBuilder()
|
||||
.withName(functionName)
|
||||
.withCallSequence(callSequence)
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if empty', () => {
|
||||
// arrange
|
||||
const functionName = 'invalidFunction';
|
||||
const callSequence = [ ];
|
||||
const expectedError = `empty call sequence in function "${functionName}"`;
|
||||
// act
|
||||
const act = () => new SharedFunctionBuilder()
|
||||
.withName(functionName)
|
||||
.withCallSequence(callSequence)
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('sets type as expected', () => {
|
||||
// arrange
|
||||
const expectedType = FunctionBodyType.Calls;
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(sut.body.type).equal(expectedType);
|
||||
});
|
||||
it('code is undefined', () => {
|
||||
// arrange
|
||||
const expectedCode = undefined;
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(sut.body.code).equal(expectedCode);
|
||||
});
|
||||
it('throws if empty or undefined', () => {
|
||||
// arrange
|
||||
const functionName = 'expected-function-name';
|
||||
const expectedError = `undefined code in function "${functionName}"`;
|
||||
const invalidValues = [undefined, ''];
|
||||
for (const invalidValue of invalidValues) {
|
||||
// act
|
||||
const act = () => new SharedFunctionBuilder()
|
||||
.withName(functionName)
|
||||
.withCode(invalidValue)
|
||||
.createFunctionWithInlineCode();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('revertCode', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const testData = ['expected-revert-code', undefined, ''];
|
||||
for (const data of testData) {
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.withRevertCode(data)
|
||||
.createFunctionWithInlineCode();
|
||||
// assert
|
||||
expect(sut.body.code.revert).equal(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('sets type as expected', () => {
|
||||
// arrange
|
||||
const expectedType = FunctionBodyType.Code;
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.createFunctionWithInlineCode();
|
||||
// assert
|
||||
expect(sut.body.type).equal(expectedType);
|
||||
});
|
||||
it('calls are undefined', () => {
|
||||
// arrange
|
||||
const expectedCalls = undefined;
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.createFunctionWithInlineCode();
|
||||
// assert
|
||||
expect(sut.body.calls).equal(expectedCalls);
|
||||
});
|
||||
});
|
||||
describe('createCallerFunction', () => {
|
||||
describe('callSequence', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new FunctionCallStub().withFunctionName('firstFunction'),
|
||||
new FunctionCallStub().withFunctionName('secondFunction'),
|
||||
];
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.withCallSequence(expected)
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(sut.body.calls).equal(expected);
|
||||
});
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const functionName = 'invalidFunction';
|
||||
const callSequence = undefined;
|
||||
const expectedError = `undefined call sequence in function "${functionName}"`;
|
||||
// act
|
||||
const act = () => new SharedFunctionBuilder()
|
||||
.withName(functionName)
|
||||
.withCallSequence(callSequence)
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if empty', () => {
|
||||
// arrange
|
||||
const functionName = 'invalidFunction';
|
||||
const callSequence = [];
|
||||
const expectedError = `empty call sequence in function "${functionName}"`;
|
||||
// act
|
||||
const act = () => new SharedFunctionBuilder()
|
||||
.withName(functionName)
|
||||
.withCallSequence(callSequence)
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('sets type as expected', () => {
|
||||
// arrange
|
||||
const expectedType = FunctionBodyType.Calls;
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(sut.body.type).equal(expectedType);
|
||||
});
|
||||
it('code is undefined', () => {
|
||||
// arrange
|
||||
const expectedCode = undefined;
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(sut.body.code).equal(expectedCode);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function runForEachFactoryMethod(
|
||||
act: (action: (sut: SharedFunctionBuilder) => ISharedFunction) => void): void {
|
||||
describe('createCallerFunction', () => {
|
||||
const action = (builder: SharedFunctionBuilder) => builder.createCallerFunction();
|
||||
act(action);
|
||||
});
|
||||
describe('createFunctionWithInlineCode', () => {
|
||||
const action = (builder: SharedFunctionBuilder) => builder.createFunctionWithInlineCode();
|
||||
act(action);
|
||||
});
|
||||
act: (action: (sut: SharedFunctionBuilder) => ISharedFunction) => void,
|
||||
): void {
|
||||
describe('createCallerFunction', () => {
|
||||
const action = (builder: SharedFunctionBuilder) => builder.createCallerFunction();
|
||||
act(action);
|
||||
});
|
||||
describe('createFunctionWithInlineCode', () => {
|
||||
const action = (builder: SharedFunctionBuilder) => builder.createFunctionWithInlineCode();
|
||||
act(action);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Using an abstraction here allows for easy refactorings in
|
||||
parameters or moving between functional and object-oriented
|
||||
solutions without refactorings all tests.
|
||||
Using an abstraction here allows for easy refactorings in
|
||||
parameters or moving between functional and object-oriented
|
||||
solutions without refactorings all tests.
|
||||
*/
|
||||
class SharedFunctionBuilder {
|
||||
private name = 'name';
|
||||
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||
private callSequence: readonly IFunctionCall[] = [ new FunctionCallStub() ];
|
||||
private code = 'code';
|
||||
private revertCode = 'revert-code';
|
||||
private name = 'name';
|
||||
|
||||
public createCallerFunction(): ISharedFunction {
|
||||
return createCallerFunction(
|
||||
this.name,
|
||||
this.parameters,
|
||||
this.callSequence,
|
||||
);
|
||||
}
|
||||
public createFunctionWithInlineCode(): ISharedFunction {
|
||||
return createFunctionWithInlineCode(
|
||||
this.name,
|
||||
this.parameters,
|
||||
this.code,
|
||||
this.revertCode,
|
||||
);
|
||||
}
|
||||
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||
|
||||
public withName(name: string) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
public withCode(code: string) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
public withRevertCode(revertCode: string) {
|
||||
this.revertCode = revertCode;
|
||||
return this;
|
||||
}
|
||||
public withCallSequence(callSequence: readonly IFunctionCall[]) {
|
||||
this.callSequence = callSequence;
|
||||
return this;
|
||||
}
|
||||
private callSequence: readonly IFunctionCall[] = [new FunctionCallStub()];
|
||||
|
||||
private code = 'code';
|
||||
|
||||
private revertCode = 'revert-code';
|
||||
|
||||
public createCallerFunction(): ISharedFunction {
|
||||
return createCallerFunction(
|
||||
this.name,
|
||||
this.parameters,
|
||||
this.callSequence,
|
||||
);
|
||||
}
|
||||
|
||||
public createFunctionWithInlineCode(): ISharedFunction {
|
||||
return createFunctionWithInlineCode(
|
||||
this.name,
|
||||
this.parameters,
|
||||
this.code,
|
||||
this.revertCode,
|
||||
);
|
||||
}
|
||||
|
||||
public withName(name: string) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withCode(code: string) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withRevertCode(revertCode: string) {
|
||||
this.revertCode = revertCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withCallSequence(callSequence: readonly IFunctionCall[]) {
|
||||
this.callSequence = callSequence;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,85 +6,85 @@ import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/
|
||||
import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub';
|
||||
|
||||
describe('SharedFunctionCollection', () => {
|
||||
describe('addFunction', () => {
|
||||
it('throws if function is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined function';
|
||||
const func = undefined;
|
||||
const sut = new SharedFunctionCollection();
|
||||
// act
|
||||
const act = () => sut.addFunction(func);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if function with same name already exists', () => {
|
||||
// arrange
|
||||
const functionName = 'duplicate-function';
|
||||
const expectedError = `function with name ${functionName} already exists`;
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('duplicate-function');
|
||||
const sut = new SharedFunctionCollection();
|
||||
sut.addFunction(func);
|
||||
// act
|
||||
const act = () => sut.addFunction(func);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('addFunction', () => {
|
||||
it('throws if function is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined function';
|
||||
const func = undefined;
|
||||
const sut = new SharedFunctionCollection();
|
||||
// act
|
||||
const act = () => sut.addFunction(func);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('getFunctionByName', () => {
|
||||
it('throws if name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined function name';
|
||||
const invalidValues = [ undefined, '' ];
|
||||
const sut = new SharedFunctionCollection();
|
||||
for (const invalidValue of invalidValues) {
|
||||
const name = invalidValue;
|
||||
// act
|
||||
const act = () => sut.getFunctionByName(name);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
it('throws if function does not exist', () => {
|
||||
// arrange
|
||||
const name = 'unique-name';
|
||||
const expectedError = `called function is not defined "${name}"`;
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('unexpected-name');
|
||||
const sut = new SharedFunctionCollection();
|
||||
sut.addFunction(func);
|
||||
// act
|
||||
const act = () => sut.getFunctionByName(name);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('returns existing function', () => {
|
||||
it('when function with inline code is added', () => {
|
||||
// arrange
|
||||
const expected = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('expected-function-name');
|
||||
const sut = new SharedFunctionCollection();
|
||||
// act
|
||||
sut.addFunction(expected);
|
||||
const actual = sut.getFunctionByName(expected.name);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('when calling function is added', () => {
|
||||
// arrange
|
||||
const callee = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('calleeFunction');
|
||||
const caller = new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('callerFunction')
|
||||
.withCalls(new FunctionCallStub().withFunctionName(callee.name));
|
||||
const sut = new SharedFunctionCollection();
|
||||
// act
|
||||
sut.addFunction(callee);
|
||||
sut.addFunction(caller);
|
||||
const actual = sut.getFunctionByName(caller.name);
|
||||
// assert
|
||||
expect(actual).to.equal(caller);
|
||||
});
|
||||
});
|
||||
it('throws if function with same name already exists', () => {
|
||||
// arrange
|
||||
const functionName = 'duplicate-function';
|
||||
const expectedError = `function with name ${functionName} already exists`;
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('duplicate-function');
|
||||
const sut = new SharedFunctionCollection();
|
||||
sut.addFunction(func);
|
||||
// act
|
||||
const act = () => sut.addFunction(func);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('getFunctionByName', () => {
|
||||
it('throws if name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined function name';
|
||||
const invalidValues = [undefined, ''];
|
||||
const sut = new SharedFunctionCollection();
|
||||
for (const invalidValue of invalidValues) {
|
||||
const name = invalidValue;
|
||||
// act
|
||||
const act = () => sut.getFunctionByName(name);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
it('throws if function does not exist', () => {
|
||||
// arrange
|
||||
const name = 'unique-name';
|
||||
const expectedError = `called function is not defined "${name}"`;
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('unexpected-name');
|
||||
const sut = new SharedFunctionCollection();
|
||||
sut.addFunction(func);
|
||||
// act
|
||||
const act = () => sut.getFunctionByName(name);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('returns existing function', () => {
|
||||
it('when function with inline code is added', () => {
|
||||
// arrange
|
||||
const expected = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('expected-function-name');
|
||||
const sut = new SharedFunctionCollection();
|
||||
// act
|
||||
sut.addFunction(expected);
|
||||
const actual = sut.getFunctionByName(expected.name);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('when calling function is added', () => {
|
||||
// arrange
|
||||
const callee = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('calleeFunction');
|
||||
const caller = new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('callerFunction')
|
||||
.withCalls(new FunctionCallStub().withFunctionName(callee.name));
|
||||
const sut = new SharedFunctionCollection();
|
||||
// act
|
||||
sut.addFunction(callee);
|
||||
sut.addFunction(caller);
|
||||
const actual = sut.getFunctionByName(caller.name);
|
||||
// assert
|
||||
expect(actual).to.equal(caller);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { FunctionData } from 'js-yaml-loader!@/*';
|
||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { SharedFunctionsParser } from '@/application/Parser/Script/Compiler/Function/SharedFunctionsParser';
|
||||
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
||||
import { ParameterDefinitionDataStub } from '@tests/unit/stubs/ParameterDefinitionDataStub';
|
||||
@@ -9,269 +9,270 @@ import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function
|
||||
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
|
||||
|
||||
describe('SharedFunctionsParser', () => {
|
||||
describe('parseFunctions', () => {
|
||||
describe('validates functions', () => {
|
||||
it('throws if one of the functions is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = `some functions are undefined`;
|
||||
const functions = [ FunctionDataStub.createWithCode(), undefined ];
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when functions have same names', () => {
|
||||
// arrange
|
||||
const name = 'same-func-name';
|
||||
const expectedError = `duplicate function name: "${name}"`;
|
||||
const functions = [
|
||||
FunctionDataStub.createWithCode().withName(name),
|
||||
FunctionDataStub.createWithCode().withName(name),
|
||||
];
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws when when function have duplicate code', () => {
|
||||
it('code', () => {
|
||||
// arrange
|
||||
const code = 'duplicate-code';
|
||||
const expectedError = `duplicate "code" in functions: "${code}"`;
|
||||
const functions = [
|
||||
FunctionDataStub.createWithoutCallOrCodes().withName('func-1').withCode(code),
|
||||
FunctionDataStub.createWithoutCallOrCodes().withName('func-2').withCode(code),
|
||||
];
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('revertCode', () => {
|
||||
// arrange
|
||||
const revertCode = 'duplicate-revert-code';
|
||||
const expectedError = `duplicate "revertCode" in functions: "${revertCode}"`;
|
||||
const functions = [
|
||||
FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName('func-1').withCode('code-1').withRevertCode(revertCode),
|
||||
FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName('func-2').withCode('code-2').withRevertCode(revertCode),
|
||||
];
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('ensures either call or code is defined', () => {
|
||||
it('both code and call are defined', () => {
|
||||
// arrange
|
||||
const functionName = 'invalid-function';
|
||||
const expectedError = `both "code" and "call" are defined in "${functionName}"`;
|
||||
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName(functionName)
|
||||
.withCode('code')
|
||||
.withMockCall();
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions([ invalidFunction ]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('neither code and call is defined', () => {
|
||||
// arrange
|
||||
const functionName = 'invalid-function';
|
||||
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
|
||||
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName(functionName);
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions([ invalidFunction ]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws when parameters type is not as expected', () => {
|
||||
const testCases = [
|
||||
{
|
||||
state: 'when not an array',
|
||||
invalidType: 5,
|
||||
},
|
||||
{
|
||||
state: 'when array but not of objects',
|
||||
invalidType: [ 'a', { a: 'b'} ],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.state, () => {
|
||||
// arrange
|
||||
const func = FunctionDataStub
|
||||
.createWithCall()
|
||||
.withParametersObject(testCase.invalidType as any);
|
||||
const expectedError = `parameters must be an array of objects in function(s) "${func.name}"`;
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions([ func ]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('rethrows including function name when FunctionParameter throws', () => {
|
||||
// arrange
|
||||
const invalidParameterName = 'invalid function p@r4meter name';
|
||||
const functionName = 'functionName';
|
||||
let parameterException: Error;
|
||||
try { new FunctionParameter(invalidParameterName, false); } catch (e) { parameterException = e; }
|
||||
const expectedError = `"${functionName}": ${parameterException.message}`;
|
||||
const functionData = FunctionDataStub.createWithCode()
|
||||
.withName(functionName)
|
||||
.withParameters(new ParameterDefinitionDataStub().withName(invalidParameterName));
|
||||
describe('parseFunctions', () => {
|
||||
describe('validates functions', () => {
|
||||
it('throws if one of the functions is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'some functions are undefined';
|
||||
const functions = [FunctionDataStub.createWithCode(), undefined];
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when functions have same names', () => {
|
||||
// arrange
|
||||
const name = 'same-func-name';
|
||||
const expectedError = `duplicate function name: "${name}"`;
|
||||
const functions = [
|
||||
FunctionDataStub.createWithCode().withName(name),
|
||||
FunctionDataStub.createWithCode().withName(name),
|
||||
];
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws when when function have duplicate code', () => {
|
||||
it('code', () => {
|
||||
// arrange
|
||||
const code = 'duplicate-code';
|
||||
const expectedError = `duplicate "code" in functions: "${code}"`;
|
||||
const functions = [
|
||||
FunctionDataStub.createWithoutCallOrCodes().withName('func-1').withCode(code),
|
||||
FunctionDataStub.createWithoutCallOrCodes().withName('func-2').withCode(code),
|
||||
];
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('revertCode', () => {
|
||||
// arrange
|
||||
const revertCode = 'duplicate-revert-code';
|
||||
const expectedError = `duplicate "revertCode" in functions: "${revertCode}"`;
|
||||
const functions = [
|
||||
FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName('func-1').withCode('code-1').withRevertCode(revertCode),
|
||||
FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName('func-2').withCode('code-2').withRevertCode(revertCode),
|
||||
];
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions(functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('ensures either call or code is defined', () => {
|
||||
it('both code and call are defined', () => {
|
||||
// arrange
|
||||
const functionName = 'invalid-function';
|
||||
const expectedError = `both "code" and "call" are defined in "${functionName}"`;
|
||||
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName(functionName)
|
||||
.withCode('code')
|
||||
.withMockCall();
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions([invalidFunction]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('neither code and call is defined', () => {
|
||||
// arrange
|
||||
const functionName = 'invalid-function';
|
||||
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
|
||||
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName(functionName);
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions([invalidFunction]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws when parameters type is not as expected', () => {
|
||||
const testCases = [
|
||||
{
|
||||
state: 'when not an array',
|
||||
invalidType: 5,
|
||||
},
|
||||
{
|
||||
state: 'when array but not of objects',
|
||||
invalidType: ['a', { a: 'b' }],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.state, () => {
|
||||
// arrange
|
||||
const func = FunctionDataStub
|
||||
.createWithCall()
|
||||
.withParametersObject(testCase.invalidType as never);
|
||||
const expectedError = `parameters must be an array of objects in function(s) "${func.name}"`;
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const act = () => sut.parseFunctions([func]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('rethrows including function name when FunctionParameter throws', () => {
|
||||
// arrange
|
||||
const invalidParameterName = 'invalid function p@r4meter name';
|
||||
const functionName = 'functionName';
|
||||
let parameterException: Error;
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new FunctionParameter(invalidParameterName, false);
|
||||
} catch (e) { parameterException = e; }
|
||||
const expectedError = `"${functionName}": ${parameterException.message}`;
|
||||
const functionData = FunctionDataStub.createWithCode()
|
||||
.withName(functionName)
|
||||
.withParameters(new ParameterDefinitionDataStub().withName(invalidParameterName));
|
||||
|
||||
// act
|
||||
const sut = new SharedFunctionsParser();
|
||||
const act = () => sut.parseFunctions([ functionData ]);
|
||||
// act
|
||||
const sut = new SharedFunctionsParser();
|
||||
const act = () => sut.parseFunctions([functionData]);
|
||||
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('empty functions', () => {
|
||||
it('returns empty collection', () => {
|
||||
// arrange
|
||||
const emptyValues = [ [], undefined ];
|
||||
const sut = new SharedFunctionsParser();
|
||||
for (const emptyFunctions of emptyValues) {
|
||||
// act
|
||||
const actual = sut.parseFunctions(emptyFunctions);
|
||||
// assert
|
||||
expect(actual).to.not.equal(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('function with inline code', () => {
|
||||
it('parses single function with code as expected', () => {
|
||||
// arrange
|
||||
const name = 'function-name';
|
||||
const expected = FunctionDataStub
|
||||
.createWithoutCallOrCodes()
|
||||
.withName(name)
|
||||
.withCode('expected-code')
|
||||
.withRevertCode('expected-revert-code')
|
||||
.withParameters(
|
||||
new ParameterDefinitionDataStub().withName('expectedParameter').withOptionality(true),
|
||||
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
|
||||
);
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const collection = sut.parseFunctions([ expected ]);
|
||||
// expect
|
||||
const actual = collection.getFunctionByName(name);
|
||||
expectEqualName(expected, actual);
|
||||
expectEqualParameters(expected, actual);
|
||||
expectEqualFunctionWithInlineCode(expected, actual);
|
||||
});
|
||||
});
|
||||
describe('function with calls', () => {
|
||||
it('parses single function with call as expected', () => {
|
||||
// arrange
|
||||
const call = new FunctionCallDataStub()
|
||||
.withName('calleeFunction')
|
||||
.withParameters({test: 'value'});
|
||||
const data = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName('caller-function')
|
||||
.withCall(call);
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const collection = sut.parseFunctions([ data ]);
|
||||
// expect
|
||||
const actual = collection.getFunctionByName(data.name);
|
||||
expectEqualName(data, actual);
|
||||
expectEqualParameters(data, actual);
|
||||
expectEqualCalls([ call ], actual);
|
||||
});
|
||||
it('parses multiple functions with call as expected', () => {
|
||||
// arrange
|
||||
const call1 = new FunctionCallDataStub()
|
||||
.withName('calleeFunction1')
|
||||
.withParameters({ param: 'value' });
|
||||
const call2 = new FunctionCallDataStub()
|
||||
.withName('calleeFunction2')
|
||||
.withParameters( {param2: 'value2'});
|
||||
const caller1 = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName('caller-function')
|
||||
.withCall(call1);
|
||||
const caller2 = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName('caller-function-2')
|
||||
.withCall([ call1, call2 ]);
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const collection = sut.parseFunctions([ caller1, caller2 ]);
|
||||
// expect
|
||||
const compiledCaller1 = collection.getFunctionByName(caller1.name);
|
||||
expectEqualName(caller1, compiledCaller1);
|
||||
expectEqualParameters(caller1, compiledCaller1);
|
||||
expectEqualCalls([ call1 ], compiledCaller1);
|
||||
const compiledCaller2 = collection.getFunctionByName(caller2.name);
|
||||
expectEqualName(caller2, compiledCaller2);
|
||||
expectEqualParameters(caller2, compiledCaller2);
|
||||
expectEqualCalls([ call1, call2 ], compiledCaller2);
|
||||
});
|
||||
});
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('empty functions', () => {
|
||||
it('returns empty collection', () => {
|
||||
// arrange
|
||||
const emptyValues = [[], undefined];
|
||||
const sut = new SharedFunctionsParser();
|
||||
for (const emptyFunctions of emptyValues) {
|
||||
// act
|
||||
const actual = sut.parseFunctions(emptyFunctions);
|
||||
// assert
|
||||
expect(actual).to.not.equal(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('function with inline code', () => {
|
||||
it('parses single function with code as expected', () => {
|
||||
// arrange
|
||||
const name = 'function-name';
|
||||
const expected = FunctionDataStub
|
||||
.createWithoutCallOrCodes()
|
||||
.withName(name)
|
||||
.withCode('expected-code')
|
||||
.withRevertCode('expected-revert-code')
|
||||
.withParameters(
|
||||
new ParameterDefinitionDataStub().withName('expectedParameter').withOptionality(true),
|
||||
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
|
||||
);
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const collection = sut.parseFunctions([expected]);
|
||||
// expect
|
||||
const actual = collection.getFunctionByName(name);
|
||||
expectEqualName(expected, actual);
|
||||
expectEqualParameters(expected, actual);
|
||||
expectEqualFunctionWithInlineCode(expected, actual);
|
||||
});
|
||||
});
|
||||
describe('function with calls', () => {
|
||||
it('parses single function with call as expected', () => {
|
||||
// arrange
|
||||
const call = new FunctionCallDataStub()
|
||||
.withName('calleeFunction')
|
||||
.withParameters({ test: 'value' });
|
||||
const data = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName('caller-function')
|
||||
.withCall(call);
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const collection = sut.parseFunctions([data]);
|
||||
// expect
|
||||
const actual = collection.getFunctionByName(data.name);
|
||||
expectEqualName(data, actual);
|
||||
expectEqualParameters(data, actual);
|
||||
expectEqualCalls([call], actual);
|
||||
});
|
||||
it('parses multiple functions with call as expected', () => {
|
||||
// arrange
|
||||
const call1 = new FunctionCallDataStub()
|
||||
.withName('calleeFunction1')
|
||||
.withParameters({ param: 'value' });
|
||||
const call2 = new FunctionCallDataStub()
|
||||
.withName('calleeFunction2')
|
||||
.withParameters({ param2: 'value2' });
|
||||
const caller1 = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName('caller-function')
|
||||
.withCall(call1);
|
||||
const caller2 = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName('caller-function-2')
|
||||
.withCall([call1, call2]);
|
||||
const sut = new SharedFunctionsParser();
|
||||
// act
|
||||
const collection = sut.parseFunctions([caller1, caller2]);
|
||||
// expect
|
||||
const compiledCaller1 = collection.getFunctionByName(caller1.name);
|
||||
expectEqualName(caller1, compiledCaller1);
|
||||
expectEqualParameters(caller1, compiledCaller1);
|
||||
expectEqualCalls([call1], compiledCaller1);
|
||||
const compiledCaller2 = collection.getFunctionByName(caller2.name);
|
||||
expectEqualName(caller2, compiledCaller2);
|
||||
expectEqualParameters(caller2, compiledCaller2);
|
||||
expectEqualCalls([call1, call2], compiledCaller2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function expectEqualName(
|
||||
expected: FunctionDataStub, actual: ISharedFunction): void {
|
||||
expect(actual.name).to.equal(expected.name);
|
||||
function expectEqualName(expected: FunctionDataStub, actual: ISharedFunction): void {
|
||||
expect(actual.name).to.equal(expected.name);
|
||||
}
|
||||
|
||||
function expectEqualParameters(
|
||||
expected: FunctionDataStub, actual: ISharedFunction): void {
|
||||
const actualSimplifiedParameters = actual.parameters.all.map((parameter) => ({
|
||||
name: parameter.name,
|
||||
optional: parameter.isOptional,
|
||||
}));
|
||||
const expectedSimplifiedParameters = expected.parameters?.map((parameter) => ({
|
||||
name: parameter.name,
|
||||
optional: parameter.optional || false,
|
||||
})) || [];
|
||||
expect(expectedSimplifiedParameters).to.deep.equal(actualSimplifiedParameters, 'Unequal parameters');
|
||||
function expectEqualParameters(expected: FunctionDataStub, actual: ISharedFunction): void {
|
||||
const actualSimplifiedParameters = actual.parameters.all.map((parameter) => ({
|
||||
name: parameter.name,
|
||||
optional: parameter.isOptional,
|
||||
}));
|
||||
const expectedSimplifiedParameters = expected.parameters?.map((parameter) => ({
|
||||
name: parameter.name,
|
||||
optional: parameter.optional || false,
|
||||
})) || [];
|
||||
expect(expectedSimplifiedParameters).to.deep.equal(actualSimplifiedParameters, 'Unequal parameters');
|
||||
}
|
||||
|
||||
function expectEqualFunctionWithInlineCode(
|
||||
expected: FunctionData, actual: ISharedFunction): void {
|
||||
expect(actual.body,
|
||||
`function "${actual.name}" has no body`);
|
||||
expect(actual.body.code,
|
||||
`function "${actual.name}" has no code`);
|
||||
expect(actual.body.code.do).to.equal(expected.code);
|
||||
expect(actual.body.code.revert).to.equal(expected.revertCode);
|
||||
expected: FunctionData,
|
||||
actual: ISharedFunction,
|
||||
): void {
|
||||
expect(actual.body, `function "${actual.name}" has no body`);
|
||||
expect(actual.body.code, `function "${actual.name}" has no code`);
|
||||
expect(actual.body.code.do).to.equal(expected.code);
|
||||
expect(actual.body.code.revert).to.equal(expected.revertCode);
|
||||
}
|
||||
|
||||
function expectEqualCalls(
|
||||
expected: FunctionCallDataStub[], actual: ISharedFunction) {
|
||||
expect(actual.body,
|
||||
`function "${actual.name}" has no body`);
|
||||
expect(actual.body.calls,
|
||||
`function "${actual.name}" has no calls`);
|
||||
const actualSimplifiedCalls = actual.body.calls
|
||||
.map((call) => ({
|
||||
function: call.functionName,
|
||||
params: call.args.getAllParameterNames().map((name) => ({
|
||||
name, value: call.args.getArgument(name).argumentValue,
|
||||
})),
|
||||
}));
|
||||
const expectedSimplifiedCalls = expected
|
||||
.map((call) => ({
|
||||
function: call.function,
|
||||
params: Object.keys(call.parameters).map((key) => (
|
||||
{ name: key, value: call.parameters[key] }
|
||||
)),
|
||||
}));
|
||||
expect(actualSimplifiedCalls).to.deep.equal(expectedSimplifiedCalls, 'Unequal calls');
|
||||
expected: FunctionCallDataStub[],
|
||||
actual: ISharedFunction,
|
||||
) {
|
||||
expect(actual.body, `function "${actual.name}" has no body`);
|
||||
expect(actual.body.calls, `function "${actual.name}" has no calls`);
|
||||
const actualSimplifiedCalls = actual.body.calls
|
||||
.map((call) => ({
|
||||
function: call.functionName,
|
||||
params: call.args.getAllParameterNames().map((name) => ({
|
||||
name, value: call.args.getArgument(name).argumentValue,
|
||||
})),
|
||||
}));
|
||||
const expectedSimplifiedCalls = expected
|
||||
.map((call) => ({
|
||||
function: call.function,
|
||||
params: Object.keys(call.parameters).map((key) => (
|
||||
{ name: key, value: call.parameters[key] }
|
||||
)),
|
||||
}));
|
||||
expect(actualSimplifiedCalls).to.deep.equal(expectedSimplifiedCalls, 'Unequal calls');
|
||||
}
|
||||
|
||||
@@ -2,55 +2,55 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
export function testParameterName(action: (parameterName: string) => string) {
|
||||
describe('name', () => {
|
||||
describe('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedValues = [
|
||||
'lowercase',
|
||||
'onlyLetters',
|
||||
'l3tt3rsW1thNumb3rs',
|
||||
];
|
||||
for (const expected of expectedValues) {
|
||||
it(expected, () => {
|
||||
// act
|
||||
const value = action(expected);
|
||||
// assert
|
||||
expect(value).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('throws if invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
expectedError: 'undefined parameter name',
|
||||
},
|
||||
{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
expectedError: 'undefined parameter name',
|
||||
},
|
||||
{
|
||||
name: 'has @',
|
||||
value: 'b@d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b@d"',
|
||||
},
|
||||
{
|
||||
name: 'has {',
|
||||
value: 'b{a}d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b{a}d"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const act = () => action(testCase.value);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
describe('name', () => {
|
||||
describe('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedValues = [
|
||||
'lowercase',
|
||||
'onlyLetters',
|
||||
'l3tt3rsW1thNumb3rs',
|
||||
];
|
||||
for (const expected of expectedValues) {
|
||||
it(expected, () => {
|
||||
// act
|
||||
const value = action(expected);
|
||||
// assert
|
||||
expect(value).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('throws if invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
expectedError: 'undefined parameter name',
|
||||
},
|
||||
{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
expectedError: 'undefined parameter name',
|
||||
},
|
||||
{
|
||||
name: 'has @',
|
||||
value: 'b@d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b@d"',
|
||||
},
|
||||
{
|
||||
name: 'has {',
|
||||
value: 'b{a}d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b{a}d"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const act = () => action(testCase.value);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,198 +16,217 @@ import { parseFunctionCalls } from '@/application/Parser/Script/Compiler/Functio
|
||||
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
|
||||
|
||||
describe('ScriptCompiler', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws if syntax is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = `undefined syntax`;
|
||||
// act
|
||||
const act = () => new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withSyntax(undefined)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('throws if syntax is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined syntax';
|
||||
// act
|
||||
const act = () => new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withSyntax(undefined)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('canCompile', () => {
|
||||
it('throws if script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const argument = undefined;
|
||||
const builder = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
// act
|
||||
const act = () => builder.canCompile(argument);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns true if "call" is defined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('returns false if "call" is undefined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('canCompile', () => {
|
||||
it('throws if script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const argument = undefined;
|
||||
const builder = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
// act
|
||||
const act = () => builder.canCompile(argument);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('compile', () => {
|
||||
it('throws if script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const argument = undefined;
|
||||
const builder = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
// act
|
||||
const act = () => builder.compile(argument);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns code as expected', () => {
|
||||
// arrange
|
||||
const expected: ICompiledCode = {
|
||||
code: 'expected-code',
|
||||
revertCode: 'expected-revert-code',
|
||||
};
|
||||
const call = new FunctionCallDataStub();
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
const functions = [ FunctionDataStub.createWithCode().withName('existing-func') ];
|
||||
const compiledFunctions = new SharedFunctionCollectionStub();
|
||||
const functionParserMock = new SharedFunctionsParserStub();
|
||||
functionParserMock.setup(functions, compiledFunctions);
|
||||
const callCompilerMock = new FunctionCallCompilerStub();
|
||||
callCompilerMock.setup(parseFunctionCalls(call), compiledFunctions, expected);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withFunctions(...functions)
|
||||
.withSharedFunctionsParser(functionParserMock)
|
||||
.withFunctionCallCompiler(callCompilerMock)
|
||||
.build();
|
||||
// act
|
||||
const code = sut.compile(script);
|
||||
// assert
|
||||
expect(code.execute).to.equal(expected.code);
|
||||
expect(code.revert).to.equal(expected.revertCode);
|
||||
});
|
||||
it('creates with expected syntax', () => {
|
||||
// arrange
|
||||
let isUsed = false;
|
||||
const syntax: ILanguageSyntax = {
|
||||
get commentDelimiters() {
|
||||
isUsed = true;
|
||||
return [];
|
||||
},
|
||||
get commonCodeParts() {
|
||||
isUsed = true;
|
||||
return [];
|
||||
},
|
||||
};
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withSyntax(syntax)
|
||||
.build();
|
||||
const scriptData = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
sut.compile(scriptData);
|
||||
// assert
|
||||
expect(isUsed).to.equal(true);
|
||||
});
|
||||
it('rethrows error with script name', () => {
|
||||
// arrange
|
||||
const scriptName = 'scriptName';
|
||||
const innerError = 'innerError';
|
||||
const expectedError = `Script "${scriptName}" ${innerError}`;
|
||||
const callCompiler: IFunctionCallCompiler = {
|
||||
compileCall: () => { throw new Error(innerError); },
|
||||
};
|
||||
const scriptData = ScriptDataStub.createWithCall()
|
||||
.withName(scriptName);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withFunctionCallCompiler(callCompiler)
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.compile(scriptData);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('rethrows error from ScriptCode with script name', () => {
|
||||
// arrange
|
||||
const scriptName = 'scriptName';
|
||||
const expectedError = `Script "${scriptName}" code is empty or undefined`;
|
||||
const callCompiler: IFunctionCallCompiler = {
|
||||
compileCall: () => ({ code: undefined, revertCode: undefined }),
|
||||
};
|
||||
const scriptData = ScriptDataStub.createWithCall()
|
||||
.withName(scriptName);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withFunctionCallCompiler(callCompiler)
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.compile(scriptData);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns true if "call" is defined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('returns false if "call" is undefined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('compile', () => {
|
||||
it('throws if script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const argument = undefined;
|
||||
const builder = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
// act
|
||||
const act = () => builder.compile(argument);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns code as expected', () => {
|
||||
// arrange
|
||||
const expected: ICompiledCode = {
|
||||
code: 'expected-code',
|
||||
revertCode: 'expected-revert-code',
|
||||
};
|
||||
const call = new FunctionCallDataStub();
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
const functions = [FunctionDataStub.createWithCode().withName('existing-func')];
|
||||
const compiledFunctions = new SharedFunctionCollectionStub();
|
||||
const functionParserMock = new SharedFunctionsParserStub();
|
||||
functionParserMock.setup(functions, compiledFunctions);
|
||||
const callCompilerMock = new FunctionCallCompilerStub();
|
||||
callCompilerMock.setup(parseFunctionCalls(call), compiledFunctions, expected);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withFunctions(...functions)
|
||||
.withSharedFunctionsParser(functionParserMock)
|
||||
.withFunctionCallCompiler(callCompilerMock)
|
||||
.build();
|
||||
// act
|
||||
const code = sut.compile(script);
|
||||
// assert
|
||||
expect(code.execute).to.equal(expected.code);
|
||||
expect(code.revert).to.equal(expected.revertCode);
|
||||
});
|
||||
it('creates with expected syntax', () => {
|
||||
// arrange
|
||||
let isUsed = false;
|
||||
const syntax: ILanguageSyntax = {
|
||||
get commentDelimiters() {
|
||||
isUsed = true;
|
||||
return [];
|
||||
},
|
||||
get commonCodeParts() {
|
||||
isUsed = true;
|
||||
return [];
|
||||
},
|
||||
};
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withSyntax(syntax)
|
||||
.build();
|
||||
const scriptData = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
sut.compile(scriptData);
|
||||
// assert
|
||||
expect(isUsed).to.equal(true);
|
||||
});
|
||||
it('rethrows error with script name', () => {
|
||||
// arrange
|
||||
const scriptName = 'scriptName';
|
||||
const innerError = 'innerError';
|
||||
const expectedError = `Script "${scriptName}" ${innerError}`;
|
||||
const callCompiler: IFunctionCallCompiler = {
|
||||
compileCall: () => { throw new Error(innerError); },
|
||||
};
|
||||
const scriptData = ScriptDataStub.createWithCall()
|
||||
.withName(scriptName);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withFunctionCallCompiler(callCompiler)
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.compile(scriptData);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('rethrows error from ScriptCode with script name', () => {
|
||||
// arrange
|
||||
const scriptName = 'scriptName';
|
||||
const expectedError = `Script "${scriptName}" code is empty or undefined`;
|
||||
const callCompiler: IFunctionCallCompiler = {
|
||||
compileCall: () => ({ code: undefined, revertCode: undefined }),
|
||||
};
|
||||
const scriptData = ScriptDataStub.createWithCall()
|
||||
.withName(scriptName);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withFunctionCallCompiler(callCompiler)
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.compile(scriptData);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ScriptCompilerBuilder {
|
||||
private static createFunctions(...names: string[]): FunctionData[] {
|
||||
return names.map((functionName) => {
|
||||
return FunctionDataStub.createWithCode().withName(functionName);
|
||||
});
|
||||
}
|
||||
private functions: FunctionData[];
|
||||
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
||||
private sharedFunctionsParser: ISharedFunctionsParser = new SharedFunctionsParserStub();
|
||||
private callCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub();
|
||||
public withFunctions(...functions: FunctionData[]): ScriptCompilerBuilder {
|
||||
this.functions = functions;
|
||||
return this;
|
||||
}
|
||||
public withSomeFunctions(): ScriptCompilerBuilder {
|
||||
this.functions = ScriptCompilerBuilder.createFunctions('test-function');
|
||||
return this;
|
||||
}
|
||||
public withFunctionNames(...functionNames: string[]): ScriptCompilerBuilder {
|
||||
this.functions = ScriptCompilerBuilder.createFunctions(...functionNames);
|
||||
return this;
|
||||
}
|
||||
public withEmptyFunctions(): ScriptCompilerBuilder {
|
||||
this.functions = [];
|
||||
return this;
|
||||
}
|
||||
public withSyntax(syntax: ILanguageSyntax): ScriptCompilerBuilder {
|
||||
this.syntax = syntax;
|
||||
return this;
|
||||
}
|
||||
public withSharedFunctionsParser(SharedFunctionsParser: ISharedFunctionsParser): ScriptCompilerBuilder {
|
||||
this.sharedFunctionsParser = SharedFunctionsParser;
|
||||
return this;
|
||||
}
|
||||
public withFunctionCallCompiler(callCompiler: IFunctionCallCompiler): ScriptCompilerBuilder {
|
||||
this.callCompiler = callCompiler;
|
||||
return this;
|
||||
}
|
||||
public build(): ScriptCompiler {
|
||||
if (!this.functions) {
|
||||
throw new Error('Function behavior not defined');
|
||||
}
|
||||
return new ScriptCompiler(this.functions, this.syntax, this.sharedFunctionsParser, this.callCompiler);
|
||||
private static createFunctions(...names: string[]): FunctionData[] {
|
||||
return names.map((functionName) => {
|
||||
return FunctionDataStub.createWithCode().withName(functionName);
|
||||
});
|
||||
}
|
||||
|
||||
private functions: FunctionData[];
|
||||
|
||||
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
||||
|
||||
private sharedFunctionsParser: ISharedFunctionsParser = new SharedFunctionsParserStub();
|
||||
|
||||
private callCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub();
|
||||
|
||||
public withFunctions(...functions: FunctionData[]): ScriptCompilerBuilder {
|
||||
this.functions = functions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSomeFunctions(): ScriptCompilerBuilder {
|
||||
this.functions = ScriptCompilerBuilder.createFunctions('test-function');
|
||||
return this;
|
||||
}
|
||||
|
||||
public withFunctionNames(...functionNames: string[]): ScriptCompilerBuilder {
|
||||
this.functions = ScriptCompilerBuilder.createFunctions(...functionNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
public withEmptyFunctions(): ScriptCompilerBuilder {
|
||||
this.functions = [];
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSyntax(syntax: ILanguageSyntax): ScriptCompilerBuilder {
|
||||
this.syntax = syntax;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSharedFunctionsParser(
|
||||
sharedFunctionsParser: ISharedFunctionsParser,
|
||||
): ScriptCompilerBuilder {
|
||||
this.sharedFunctionsParser = sharedFunctionsParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withFunctionCallCompiler(callCompiler: IFunctionCallCompiler): ScriptCompilerBuilder {
|
||||
this.callCompiler = callCompiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build(): ScriptCompiler {
|
||||
if (!this.functions) {
|
||||
throw new Error('Function behavior not defined');
|
||||
}
|
||||
return new ScriptCompiler(
|
||||
this.functions,
|
||||
this.syntax,
|
||||
this.callCompiler,
|
||||
this.sharedFunctionsParser,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,176 +12,175 @@ import { CategoryCollectionParseContextStub } from '@tests/unit/stubs/CategoryCo
|
||||
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
|
||||
|
||||
describe('ScriptParser', () => {
|
||||
describe('parseScript', () => {
|
||||
it('parses name as expected', () => {
|
||||
// arrange
|
||||
const expected = 'test-expected-name';
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withName(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.name).to.equal(expected);
|
||||
});
|
||||
it('parses docs as expected', () => {
|
||||
// arrange
|
||||
const docs = [ 'https://expected-doc1.com', 'https://expected-doc2.com' ];
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withDocs(docs);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const expected = parseDocUrls(script);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.documentationUrls).to.deep.equal(expected);
|
||||
});
|
||||
describe('invalid script', () => {
|
||||
it('throws when script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when both function call and code are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot define both "call" and "code"';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub
|
||||
.createWithCall()
|
||||
.withCode('code');
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when both function call and revertCode are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot define "revertCode" if "call" is defined';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub
|
||||
.createWithCall()
|
||||
.withRevertCode('revert-code');
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when neither call or revertCode are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'must define either "call" or "code"';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub.createWithoutCallOrCodes();
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('level', () => {
|
||||
it('accepts undefined level', () => {
|
||||
const undefinedLevels: string[] = [ '', undefined ];
|
||||
undefinedLevels.forEach((undefinedLevel) => {
|
||||
// arrange
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withRecommend(undefinedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.level).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
describe('parses level as expected', () => {
|
||||
// arrange
|
||||
const expectedLevel = RecommendationLevel.Standard;
|
||||
const expectedName = 'level';
|
||||
const levelText = 'standard';
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withRecommend(levelText);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const parserMock = new EnumParserStub<RecommendationLevel>()
|
||||
.setup(expectedName, levelText, expectedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext, parserMock);
|
||||
// assert
|
||||
expect(actual.level).to.equal(expectedLevel);
|
||||
});
|
||||
});
|
||||
describe('code', () => {
|
||||
it('parses "execute" as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const script = ScriptDataStub
|
||||
.createWithCode()
|
||||
.withCode(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code.execute;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('parses "revert" as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-revert-code';
|
||||
const script = ScriptDataStub
|
||||
.createWithCode()
|
||||
.withRevertCode(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code.revert;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('throws when context is not defined', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'undefined context';
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const context: ICategoryCollectionParseContext = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('gets code from compiler', () => {
|
||||
// arrange
|
||||
const expected = new ScriptCodeStub();
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(script, expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('syntax', () => {
|
||||
it('set from the context', () => { // test through script validation logic
|
||||
// arrange
|
||||
const commentDelimiter = 'should not throw';
|
||||
const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
|
||||
const script = ScriptDataStub
|
||||
.createWithoutCallOrCodes()
|
||||
.withCode(duplicatedCode);
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('parseScript', () => {
|
||||
it('parses name as expected', () => {
|
||||
// arrange
|
||||
const expected = 'test-expected-name';
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withName(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.name).to.equal(expected);
|
||||
});
|
||||
it('parses docs as expected', () => {
|
||||
// arrange
|
||||
const docs = ['https://expected-doc1.com', 'https://expected-doc2.com'];
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withDocs(docs);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const expected = parseDocUrls(script);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.documentationUrls).to.deep.equal(expected);
|
||||
});
|
||||
describe('invalid script', () => {
|
||||
it('throws when script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when both function call and code are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot define both "call" and "code"';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub
|
||||
.createWithCall()
|
||||
.withCode('code');
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when both function call and revertCode are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot define "revertCode" if "call" is defined';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub
|
||||
.createWithCall()
|
||||
.withRevertCode('revert-code');
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when neither call or revertCode are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'must define either "call" or "code"';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub.createWithoutCallOrCodes();
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('level', () => {
|
||||
it('accepts undefined level', () => {
|
||||
const undefinedLevels: string[] = ['', undefined];
|
||||
undefinedLevels.forEach((undefinedLevel) => {
|
||||
// arrange
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withRecommend(undefinedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.level).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
describe('parses level as expected', () => {
|
||||
// arrange
|
||||
const expectedLevel = RecommendationLevel.Standard;
|
||||
const expectedName = 'level';
|
||||
const levelText = 'standard';
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withRecommend(levelText);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const parserMock = new EnumParserStub<RecommendationLevel>()
|
||||
.setup(expectedName, levelText, expectedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext, parserMock);
|
||||
// assert
|
||||
expect(actual.level).to.equal(expectedLevel);
|
||||
});
|
||||
});
|
||||
describe('code', () => {
|
||||
it('parses "execute" as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const script = ScriptDataStub
|
||||
.createWithCode()
|
||||
.withCode(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code.execute;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('parses "revert" as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-revert-code';
|
||||
const script = ScriptDataStub
|
||||
.createWithCode()
|
||||
.withRevertCode(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code.revert;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('throws when context is not defined', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'undefined context';
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const context: ICategoryCollectionParseContext = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('gets code from compiler', () => {
|
||||
// arrange
|
||||
const expected = new ScriptCodeStub();
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(script, expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('syntax', () => {
|
||||
it('set from the context', () => { // test through script validation logic
|
||||
// arrange
|
||||
const commentDelimiter = 'should not throw';
|
||||
const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
|
||||
const script = ScriptDataStub
|
||||
.createWithoutCallOrCodes()
|
||||
.withCode(duplicatedCode);
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -5,28 +5,28 @@ import { BatchFileSyntax } from '@/application/Parser/Script/Syntax/BatchFileSyn
|
||||
import { ShellScriptSyntax } from '@/application/Parser/Script/Syntax/ShellScriptSyntax';
|
||||
|
||||
function getSystemsUnderTest(): ILanguageSyntax[] {
|
||||
return [ new BatchFileSyntax(), new ShellScriptSyntax() ];
|
||||
return [new BatchFileSyntax(), new ShellScriptSyntax()];
|
||||
}
|
||||
|
||||
describe('ConcreteSyntaxes', () => {
|
||||
describe('commentDelimiters', () => {
|
||||
for (const sut of getSystemsUnderTest()) {
|
||||
it(`${sut.constructor.name} returns defined value`, () => {
|
||||
// act
|
||||
const value = sut.commentDelimiters;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('commonCodeParts', () => {
|
||||
for (const sut of getSystemsUnderTest()) {
|
||||
it(`${sut.constructor.name} returns defined value`, () => {
|
||||
// act
|
||||
const value = sut.commonCodeParts;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('commentDelimiters', () => {
|
||||
for (const sut of getSystemsUnderTest()) {
|
||||
it(`${sut.constructor.name} returns defined value`, () => {
|
||||
// act
|
||||
const value = sut.commentDelimiters;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('commonCodeParts', () => {
|
||||
for (const sut of getSystemsUnderTest()) {
|
||||
it(`${sut.constructor.name} returns defined value`, () => {
|
||||
// act
|
||||
const value = sut.commonCodeParts;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,9 +6,9 @@ import { BatchFileSyntax } from '@/application/Parser/Script/Syntax/BatchFileSyn
|
||||
import { ScriptingLanguageFactoryTestRunner } from '@tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner';
|
||||
|
||||
describe('SyntaxFactory', () => {
|
||||
const sut = new SyntaxFactory();
|
||||
const runner = new ScriptingLanguageFactoryTestRunner()
|
||||
.expect(ScriptingLanguage.shellscript, ShellScriptSyntax)
|
||||
.expect(ScriptingLanguage.batchfile, BatchFileSyntax);
|
||||
runner.testCreateMethod(sut);
|
||||
const sut = new SyntaxFactory();
|
||||
const runner = new ScriptingLanguageFactoryTestRunner()
|
||||
.expect(ScriptingLanguage.shellscript, ShellScriptSyntax)
|
||||
.expect(ScriptingLanguage.batchfile, BatchFileSyntax);
|
||||
runner.testCreateMethod(sut);
|
||||
});
|
||||
|
||||
@@ -6,94 +6,99 @@ import { ProjectInformationStub } from '@tests/unit/stubs/ProjectInformationStub
|
||||
import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
|
||||
|
||||
describe('CodeSubstituter', () => {
|
||||
describe('throws with invalid parameters', () => {
|
||||
// arrange
|
||||
const testCases = [{
|
||||
expectedError: 'undefined code',
|
||||
parameters: {
|
||||
code: undefined,
|
||||
info: new ProjectInformationStub(),
|
||||
}},
|
||||
{
|
||||
expectedError: 'undefined info',
|
||||
parameters: {
|
||||
code: 'non empty code',
|
||||
info: undefined,
|
||||
},
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(`throws "${testCase.expectedError}" as expected`, () => {
|
||||
const sut = new CodeSubstituterBuilder().build();
|
||||
// act
|
||||
const act = () => sut.substitute(testCase.parameters.code, testCase.parameters.info);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('substitutes parameters as expected values', () => {
|
||||
// arrange
|
||||
const info = new ProjectInformationStub();
|
||||
const date = new Date();
|
||||
const testCases = [
|
||||
{
|
||||
parameter: 'homepage',
|
||||
argument: info.homepage,
|
||||
},
|
||||
{
|
||||
parameter: 'version',
|
||||
argument: info.version,
|
||||
},
|
||||
{
|
||||
parameter: 'date',
|
||||
argument: date.toUTCString(),
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(`substitutes ${testCase.parameter} as expected`, () => {
|
||||
const compilerStub = new ExpressionsCompilerStub();
|
||||
const sut = new CodeSubstituterBuilder()
|
||||
.withCompiler(compilerStub)
|
||||
.withDate(date)
|
||||
.build();
|
||||
// act
|
||||
sut.substitute('non empty code', info);
|
||||
// assert
|
||||
expect(compilerStub.callHistory).to.have.lengthOf(1);
|
||||
const parameters = compilerStub.callHistory[0].parameters;
|
||||
expect(parameters.hasArgument(testCase.parameter));
|
||||
const argumentValue = parameters.getArgument(testCase.parameter).argumentValue;
|
||||
expect(argumentValue).to.equal(testCase.argument);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('returns code as it is', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
describe('throws with invalid parameters', () => {
|
||||
// arrange
|
||||
const testCases = [{
|
||||
expectedError: 'undefined code',
|
||||
parameters: {
|
||||
code: undefined,
|
||||
info: new ProjectInformationStub(),
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedError: 'undefined info',
|
||||
parameters: {
|
||||
code: 'non empty code',
|
||||
info: undefined,
|
||||
},
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(`throws "${testCase.expectedError}" as expected`, () => {
|
||||
const sut = new CodeSubstituterBuilder().build();
|
||||
// act
|
||||
const act = () => sut.substitute(testCase.parameters.code, testCase.parameters.info);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('substitutes parameters as expected values', () => {
|
||||
// arrange
|
||||
const info = new ProjectInformationStub();
|
||||
const date = new Date();
|
||||
const testCases = [
|
||||
{
|
||||
parameter: 'homepage',
|
||||
argument: info.homepage,
|
||||
},
|
||||
{
|
||||
parameter: 'version',
|
||||
argument: info.version,
|
||||
},
|
||||
{
|
||||
parameter: 'date',
|
||||
argument: date.toUTCString(),
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(`substitutes ${testCase.parameter} as expected`, () => {
|
||||
const compilerStub = new ExpressionsCompilerStub();
|
||||
const sut = new CodeSubstituterBuilder()
|
||||
.withCompiler(compilerStub)
|
||||
.build();
|
||||
.withCompiler(compilerStub)
|
||||
.withDate(date)
|
||||
.build();
|
||||
// act
|
||||
sut.substitute(expected, new ProjectInformationStub());
|
||||
sut.substitute('non empty code', info);
|
||||
// assert
|
||||
expect(compilerStub.callHistory).to.have.lengthOf(1);
|
||||
expect(compilerStub.callHistory[0].code).to.equal(expected);
|
||||
});
|
||||
const { parameters } = compilerStub.callHistory[0];
|
||||
expect(parameters.hasArgument(testCase.parameter));
|
||||
const { argumentValue } = parameters.getArgument(testCase.parameter);
|
||||
expect(argumentValue).to.equal(testCase.argument);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('returns code as it is', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const compilerStub = new ExpressionsCompilerStub();
|
||||
const sut = new CodeSubstituterBuilder()
|
||||
.withCompiler(compilerStub)
|
||||
.build();
|
||||
// act
|
||||
sut.substitute(expected, new ProjectInformationStub());
|
||||
// assert
|
||||
expect(compilerStub.callHistory).to.have.lengthOf(1);
|
||||
expect(compilerStub.callHistory[0].code).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
class CodeSubstituterBuilder {
|
||||
private compiler: IExpressionsCompiler = new ExpressionsCompilerStub();
|
||||
private date = new Date();
|
||||
public withCompiler(compiler: IExpressionsCompiler) {
|
||||
this.compiler = compiler;
|
||||
return this;
|
||||
}
|
||||
public withDate(date: Date) {
|
||||
this.date = date;
|
||||
return this;
|
||||
}
|
||||
public build() {
|
||||
return new CodeSubstituter(this.compiler, this.date);
|
||||
}
|
||||
private compiler: IExpressionsCompiler = new ExpressionsCompilerStub();
|
||||
|
||||
private date = new Date();
|
||||
|
||||
public withCompiler(compiler: IExpressionsCompiler) {
|
||||
this.compiler = compiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withDate(date: Date) {
|
||||
this.date = date;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new CodeSubstituter(this.compiler, this.date);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,100 +11,103 @@ import { ScriptingDefinitionDataStub } from '@tests/unit/stubs/ScriptingDefiniti
|
||||
import { CodeSubstituterStub } from '@tests/unit/stubs/CodeSubstituterStub';
|
||||
|
||||
describe('ScriptingDefinitionParser', () => {
|
||||
describe('parseScriptingDefinition', () => {
|
||||
it('throws when info is undefined', () => {
|
||||
// arrange
|
||||
const info = undefined;
|
||||
const definition = new ScriptingDefinitionDataStub();
|
||||
const sut = new ScriptingDefinitionParserBuilder()
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.parse(definition, info);
|
||||
// assert
|
||||
expect(act).to.throw('undefined info');
|
||||
});
|
||||
it('throws when definition is undefined', () => {
|
||||
// arrange
|
||||
const info = new ProjectInformationStub();
|
||||
const definition = undefined;
|
||||
const sut = new ScriptingDefinitionParserBuilder()
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.parse(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 ScriptingDefinitionDataStub()
|
||||
.withLanguage(languageText);
|
||||
const parserMock = new EnumParserStub<ScriptingLanguage>()
|
||||
.setup(expectedName, languageText, expectedLanguage);
|
||||
const sut = new ScriptingDefinitionParserBuilder()
|
||||
.withParser(parserMock)
|
||||
.build();
|
||||
// act
|
||||
const actual = sut.parse(definition, info);
|
||||
// assert
|
||||
expect(actual.language).to.equal(expectedLanguage);
|
||||
});
|
||||
});
|
||||
describe('substitutes code as expected', () => {
|
||||
// arrange
|
||||
const code = 'hello';
|
||||
const expected = 'substituted';
|
||||
const testCases = [
|
||||
{
|
||||
name: 'startCode',
|
||||
getActualValue: (result: IScriptingDefinition) => result.startCode,
|
||||
data: new ScriptingDefinitionDataStub()
|
||||
.withStartCode(code),
|
||||
},
|
||||
{
|
||||
name: 'endCode',
|
||||
getActualValue: (result: IScriptingDefinition) => result.endCode,
|
||||
data: new ScriptingDefinitionDataStub()
|
||||
.withEndCode(code),
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const info = new ProjectInformationStub();
|
||||
const substituterMock = new CodeSubstituterStub()
|
||||
.setup(code, info, expected);
|
||||
const sut = new ScriptingDefinitionParserBuilder()
|
||||
.withSubstituter(substituterMock)
|
||||
.build();
|
||||
// act
|
||||
const definition = sut.parse(testCase.data, info);
|
||||
// assert
|
||||
const actual = testCase.getActualValue(definition);
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('parseScriptingDefinition', () => {
|
||||
it('throws when info is undefined', () => {
|
||||
// arrange
|
||||
const info = undefined;
|
||||
const definition = new ScriptingDefinitionDataStub();
|
||||
const sut = new ScriptingDefinitionParserBuilder()
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.parse(definition, info);
|
||||
// assert
|
||||
expect(act).to.throw('undefined info');
|
||||
});
|
||||
it('throws when definition is undefined', () => {
|
||||
// arrange
|
||||
const info = new ProjectInformationStub();
|
||||
const definition = undefined;
|
||||
const sut = new ScriptingDefinitionParserBuilder()
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.parse(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 ScriptingDefinitionDataStub()
|
||||
.withLanguage(languageText);
|
||||
const parserMock = new EnumParserStub<ScriptingLanguage>()
|
||||
.setup(expectedName, languageText, expectedLanguage);
|
||||
const sut = new ScriptingDefinitionParserBuilder()
|
||||
.withParser(parserMock)
|
||||
.build();
|
||||
// act
|
||||
const actual = sut.parse(definition, info);
|
||||
// assert
|
||||
expect(actual.language).to.equal(expectedLanguage);
|
||||
});
|
||||
});
|
||||
describe('substitutes code as expected', () => {
|
||||
// arrange
|
||||
const code = 'hello';
|
||||
const expected = 'substituted';
|
||||
const testCases = [
|
||||
{
|
||||
name: 'startCode',
|
||||
getActualValue: (result: IScriptingDefinition) => result.startCode,
|
||||
data: new ScriptingDefinitionDataStub()
|
||||
.withStartCode(code),
|
||||
},
|
||||
{
|
||||
name: 'endCode',
|
||||
getActualValue: (result: IScriptingDefinition) => result.endCode,
|
||||
data: new ScriptingDefinitionDataStub()
|
||||
.withEndCode(code),
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const info = new ProjectInformationStub();
|
||||
const substituterMock = new CodeSubstituterStub()
|
||||
.setup(code, info, expected);
|
||||
const sut = new ScriptingDefinitionParserBuilder()
|
||||
.withSubstituter(substituterMock)
|
||||
.build();
|
||||
// act
|
||||
const definition = sut.parse(testCase.data, info);
|
||||
// assert
|
||||
const actual = testCase.getActualValue(definition);
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ScriptingDefinitionParserBuilder {
|
||||
private languageParser: IEnumParser<ScriptingLanguage> = new EnumParserStub<ScriptingLanguage>()
|
||||
.setupDefaultValue(ScriptingLanguage.shellscript);
|
||||
private codeSubstituter: ICodeSubstituter = new CodeSubstituterStub();
|
||||
private languageParser: IEnumParser<ScriptingLanguage> = new EnumParserStub<ScriptingLanguage>()
|
||||
.setupDefaultValue(ScriptingLanguage.shellscript);
|
||||
|
||||
public withParser(parser: IEnumParser<ScriptingLanguage>) {
|
||||
this.languageParser = parser;
|
||||
return this;
|
||||
}
|
||||
public withSubstituter(substituter: ICodeSubstituter) {
|
||||
this.codeSubstituter = substituter;
|
||||
return this;
|
||||
}
|
||||
public build() {
|
||||
return new ScriptingDefinitionParser(this.languageParser, this.codeSubstituter);
|
||||
}
|
||||
private codeSubstituter: ICodeSubstituter = new CodeSubstituterStub();
|
||||
|
||||
public withParser(parser: IEnumParser<ScriptingLanguage>) {
|
||||
this.languageParser = parser;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSubstituter(substituter: ICodeSubstituter) {
|
||||
this.codeSubstituter = substituter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new ScriptingDefinitionParser(this.languageParser, this.codeSubstituter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,66 +4,67 @@ import WindowsData from 'raw-loader!@/application/collections/windows.yaml';
|
||||
import MacOsData from 'raw-loader!@/application/collections/macos.yaml';
|
||||
|
||||
/*
|
||||
A common mistake when working with yaml files to forget mentioning that a value should
|
||||
be interpreted as multi-line string using "|".
|
||||
E.g.
|
||||
```
|
||||
code: |-
|
||||
echo Hello
|
||||
echo World
|
||||
```
|
||||
If "|" is missing then the code is inlined like `echo Hello echo World``, which can be
|
||||
unintended. This test checks for similar issues in collection yaml files.
|
||||
These tests can be considered as "linter" more than "unit-test" and therefore can lead
|
||||
to false-positives.
|
||||
A common mistake when working with yaml files to forget mentioning that a value should
|
||||
be interpreted as multi-line string using "|".
|
||||
E.g.
|
||||
```
|
||||
code: |-
|
||||
echo Hello
|
||||
echo World
|
||||
```
|
||||
If "|" is missing then the code is inlined like `echo Hello echo World``, which can be
|
||||
unintended. This test checks for similar issues in collection yaml files.
|
||||
These tests can be considered as "linter" more than "unit-test" and therefore can lead
|
||||
to false-positives.
|
||||
*/
|
||||
describe('collection files to have no unintended inlining', async () => {
|
||||
// arrange
|
||||
const testCases = [ {
|
||||
name: 'macos',
|
||||
fileContent: MacOsData,
|
||||
}, {
|
||||
name: 'windows',
|
||||
fileContent: WindowsData,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(`${testCase.name}`, async () => {
|
||||
const lines = await findBadLineNumbers(testCase.fileContent);
|
||||
expect(lines).to.be.have.lengthOf(0,
|
||||
`Did you intend to have multi-lined string in lines: `
|
||||
+ lines.map(((line) => line.toString())).join(', '),
|
||||
);
|
||||
});
|
||||
}
|
||||
// arrange
|
||||
const testCases = [{
|
||||
name: 'macos',
|
||||
fileContent: MacOsData,
|
||||
}, {
|
||||
name: 'windows',
|
||||
fileContent: WindowsData,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(`${testCase.name}`, async () => {
|
||||
const lines = await findBadLineNumbers(testCase.fileContent);
|
||||
// assert
|
||||
expect(lines).to.be.have.lengthOf(0, printMessage());
|
||||
function printMessage(): string {
|
||||
return 'Did you intend to have multi-lined string in lines: ' // eslint-disable-line prefer-template
|
||||
+ lines.map(((line) => line.toString())).join(', ');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function findBadLineNumbers(fileContent: string): Promise<number[]> {
|
||||
return [
|
||||
...findLineNumbersEndingWith(fileContent, 'revertCode:'),
|
||||
...findLineNumbersEndingWith(fileContent, 'code:'),
|
||||
];
|
||||
return [
|
||||
...findLineNumbersEndingWith(fileContent, 'revertCode:'),
|
||||
...findLineNumbersEndingWith(fileContent, 'code:'),
|
||||
];
|
||||
}
|
||||
|
||||
function findLineNumbersEndingWith(content: string, ending: string): number[] {
|
||||
sanityCheck(content, ending);
|
||||
const lines = content.split(/\r\n|\r|\n/);
|
||||
const results = new Array<number>();
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.trim().endsWith(ending)) {
|
||||
results.push((i + 1 /* first line is 1 not 0 */));
|
||||
}
|
||||
sanityCheck(content, ending);
|
||||
const lines = content.split(/\r\n|\r|\n/);
|
||||
const results = new Array<number>();
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.trim().endsWith(ending)) {
|
||||
results.push((i + 1 /* first line is 1 not 0 */));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
function sanityCheck(content: string, ending: string): void {
|
||||
if (!content.includes(ending)) {
|
||||
throw new Error(
|
||||
`File does not contain string "${ending}" string at all.`
|
||||
+ `Did the word "${ending}" change? Or is this sanity check wrong?`,
|
||||
);
|
||||
}
|
||||
if (!content.includes(ending)) {
|
||||
throw new Error(
|
||||
`File does not contain string "${ending}" string at all.`
|
||||
+ `Did the word "${ending}" change? Or is this sanity check wrong?`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
declare module 'raw-loader!@/*' {
|
||||
const contents: string;
|
||||
export default contents;
|
||||
const contents: string;
|
||||
export default contents;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user