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:
undergroundwires
2022-01-02 18:20:14 +01:00
parent 96265b75de
commit 5b1fbe1e2f
341 changed files with 16126 additions and 15101 deletions

View File

@@ -6,113 +6,113 @@ import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub
import { ProjectInformationStub } from '@tests/unit/stubs/ProjectInformationStub';
describe('Application', () => {
describe('getCollection', () => {
it('returns undefined if not found', () => {
// arrange
const expected = undefined;
const info = new ProjectInformationStub();
const collections = [ new CategoryCollectionStub().withOs(OperatingSystem.Windows) ];
// act
const sut = new Application(info, collections);
const actual = sut.getCollection(OperatingSystem.Android);
// assert
expect(actual).to.equals(expected);
});
it('returns expected when multiple collections exist', () => {
// arrange
const os = OperatingSystem.Windows;
const expected = new CategoryCollectionStub().withOs(os);
const info = new ProjectInformationStub();
const collections = [ expected, new CategoryCollectionStub().withOs(OperatingSystem.Android) ];
// act
const sut = new Application(info, collections);
const actual = sut.getCollection(os);
// assert
expect(actual).to.equals(expected);
});
describe('getCollection', () => {
it('returns undefined if not found', () => {
// arrange
const expected = undefined;
const info = new ProjectInformationStub();
const collections = [new CategoryCollectionStub().withOs(OperatingSystem.Windows)];
// act
const sut = new Application(info, collections);
const actual = sut.getCollection(OperatingSystem.Android);
// assert
expect(actual).to.equals(expected);
});
describe('ctor', () => {
describe('info', () => {
it('throws if undefined', () => {
// arrange
const expectedError = 'undefined project information';
const info = undefined;
const collections = [new CategoryCollectionStub()];
// act
const act = () => new Application(info, collections);
// assert
expect(act).to.throw(expectedError);
});
it('sets as expected', () => {
// arrange
const expected = new ProjectInformationStub();
const collections = [new CategoryCollectionStub()];
// act
const sut = new Application(expected, collections);
// assert
expect(sut.info).to.equal(expected);
});
});
describe('collections', () => {
describe('throws on invalid value', () => {
// arrange
const testCases = [
{
name: 'undefined',
expectedError: 'undefined collections',
value: undefined,
},
{
name: 'empty',
expectedError: 'no collection in the list',
value: [],
},
{
name: 'undefined value in list',
expectedError: 'undefined collection in the list',
value: [ new CategoryCollectionStub(), undefined ],
},
{
name: 'two collections with same OS',
expectedError: 'multiple collections with same os: windows',
value: [
new CategoryCollectionStub().withOs(OperatingSystem.Windows),
new CategoryCollectionStub().withOs(OperatingSystem.Windows),
new CategoryCollectionStub().withOs(OperatingSystem.BlackBerry),
],
},
];
for (const testCase of testCases) {
const info = new ProjectInformationStub();
const collections = testCase.value;
// act
const act = () => new Application(info, collections);
// assert
expect(act).to.throw(testCase.expectedError);
}
});
it('sets as expected', () => {
// arrange
const info = new ProjectInformationStub();
const expected = [new CategoryCollectionStub()];
// act
const sut = new Application(info, expected);
// assert
expect(sut.collections).to.equal(expected);
});
});
it('returns expected when multiple collections exist', () => {
// arrange
const os = OperatingSystem.Windows;
const expected = new CategoryCollectionStub().withOs(os);
const info = new ProjectInformationStub();
const collections = [expected, new CategoryCollectionStub().withOs(OperatingSystem.Android)];
// act
const sut = new Application(info, collections);
const actual = sut.getCollection(os);
// assert
expect(actual).to.equals(expected);
});
describe('getSupportedOsList', () => {
it('returns expected', () => {
// arrange
const expected = [ OperatingSystem.Windows, OperatingSystem.macOS ];
const info = new ProjectInformationStub();
const collections = expected.map((os) => new CategoryCollectionStub().withOs(os));
// act
const sut = new Application(info, collections);
const actual = sut.getSupportedOsList();
// assert
expect(actual).to.deep.equal(expected);
});
});
describe('ctor', () => {
describe('info', () => {
it('throws if undefined', () => {
// arrange
const expectedError = 'undefined project information';
const info = undefined;
const collections = [new CategoryCollectionStub()];
// act
const act = () => new Application(info, collections);
// assert
expect(act).to.throw(expectedError);
});
it('sets as expected', () => {
// arrange
const expected = new ProjectInformationStub();
const collections = [new CategoryCollectionStub()];
// act
const sut = new Application(expected, collections);
// assert
expect(sut.info).to.equal(expected);
});
});
describe('collections', () => {
describe('throws on invalid value', () => {
// arrange
const testCases = [
{
name: 'undefined',
expectedError: 'undefined collections',
value: undefined,
},
{
name: 'empty',
expectedError: 'no collection in the list',
value: [],
},
{
name: 'undefined value in list',
expectedError: 'undefined collection in the list',
value: [new CategoryCollectionStub(), undefined],
},
{
name: 'two collections with same OS',
expectedError: 'multiple collections with same os: windows',
value: [
new CategoryCollectionStub().withOs(OperatingSystem.Windows),
new CategoryCollectionStub().withOs(OperatingSystem.Windows),
new CategoryCollectionStub().withOs(OperatingSystem.BlackBerry),
],
},
];
for (const testCase of testCases) {
const info = new ProjectInformationStub();
const collections = testCase.value;
// act
const act = () => new Application(info, collections);
// assert
expect(act).to.throw(testCase.expectedError);
}
});
it('sets as expected', () => {
// arrange
const info = new ProjectInformationStub();
const expected = [new CategoryCollectionStub()];
// act
const sut = new Application(info, expected);
// assert
expect(sut.collections).to.equal(expected);
});
});
});
describe('getSupportedOsList', () => {
it('returns expected', () => {
// arrange
const expected = [OperatingSystem.Windows, OperatingSystem.macOS];
const info = new ProjectInformationStub();
const collections = expected.map((os) => new CategoryCollectionStub().withOs(os));
// act
const sut = new Application(info, collections);
const actual = sut.getSupportedOsList();
// assert
expect(actual).to.deep.equal(expected);
});
});
});

View File

@@ -5,122 +5,117 @@ import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('Category', () => {
describe('ctor', () => {
it('throws when name is empty', () => {
const expectedError = 'undefined or empty name';
const construct = () => new Category(5, '', [], [new CategoryStub(5)], []);
expect(construct).to.throw(expectedError);
});
it('throws when has no children', () => {
const expectedError = 'A category must have at least one sub-category or script';
const construct = () => new Category(5, 'category', [], [], []);
expect(construct).to.throw(expectedError);
});
describe('ctor', () => {
it('throws when name is empty', () => {
const expectedError = 'undefined or empty name';
const construct = () => new Category(5, '', [], [new CategoryStub(5)], []);
expect(construct).to.throw(expectedError);
});
describe('getAllScriptsRecursively', () => {
it('gets child scripts', () => {
// arrange
const expected = [ new ScriptStub('1'), new ScriptStub('2') ];
const sut = new Category(0, 'category', [], [], expected);
// act
const actual = sut.getAllScriptsRecursively();
// assert
expect(actual).to.have.deep.members(expected);
});
it('gets child categories', () => {
// arrange
const expectedScriptIds = ['1', '2', '3', '4'];
const categories = [
new CategoryStub(31).withScriptIds('1', '2'),
new CategoryStub(32).withScriptIds('3', '4'),
];
const sut = new Category(0, 'category', [], categories, []);
// act
const actualIds = sut.getAllScriptsRecursively().map((s) => s.id);
// assert
expect(actualIds).to.have.deep.members(expectedScriptIds);
});
it('gets child scripts and categories', () => {
// arrange
const expectedScriptIds = ['1', '2', '3', '4', '5' , '6'];
const categories = [
new CategoryStub(31).withScriptIds('1', '2'),
new CategoryStub(32).withScriptIds('3', '4'),
];
const scripts = [ new ScriptStub('5'), new ScriptStub('6') ];
const sut = new Category(0, 'category', [], categories, scripts);
// act
const actualIds = sut.getAllScriptsRecursively().map((s) => s.id);
// assert
expect(actualIds).to.have.deep.members(expectedScriptIds);
});
it('gets child categories recursively', () => {
// arrange
const expectedScriptIds = ['1', '2', '3', '4', '5', '6'];
const categories = [
new CategoryStub(31)
.withScriptIds('1', '2')
.withCategory(
new CategoryStub(32)
.withScriptIds('3', '4'),
),
new CategoryStub(33)
.withCategories(
new CategoryStub(34)
.withScriptIds('5')
.withCategory(
new CategoryStub(35)
.withCategory(
new CategoryStub(35).withScriptIds('6'),
),
),
),
];
// assert
const sut = new Category(0, 'category', [], categories, []);
// act
const actualIds = sut.getAllScriptsRecursively().map((s) => s.id);
// assert
expect(actualIds).to.have.deep.members(expectedScriptIds);
});
it('throws when has no children', () => {
const expectedError = 'A category must have at least one sub-category or script';
const construct = () => new Category(5, 'category', [], [], []);
expect(construct).to.throw(expectedError);
});
describe('includes', () => {
it('return false when does not include', () => {
// assert
const script = new ScriptStub('3');
const sut = new Category(0, 'category', [], [new CategoryStub(33).withScriptIds('1', '2')], []);
// act
const actual = sut.includes(script);
// assert
expect(actual).to.equal(false);
});
it('return true when includes as subscript', () => {
// assert
const script = new ScriptStub('3');
const sut = new Category(0, 'category', [], [
new CategoryStub(33).withScript(script).withScriptIds('non-related'),
], []);
// act
const actual = sut.includes(script);
// assert
expect(actual).to.equal(true);
});
it('return true when includes as nested category script', () => {
// assert
const script = new ScriptStub('3');
const sut = new Category(11, 'category', [],
[
new CategoryStub(22)
.withScriptIds('non-relatedd')
.withCategory(new CategoryStub(33).withScript(script)),
],
[]);
// act
const actual = sut.includes(script);
// assert
expect(actual).to.equal(true);
});
});
describe('getAllScriptsRecursively', () => {
it('gets child scripts', () => {
// arrange
const expected = [new ScriptStub('1'), new ScriptStub('2')];
const sut = new Category(0, 'category', [], [], expected);
// act
const actual = sut.getAllScriptsRecursively();
// assert
expect(actual).to.have.deep.members(expected);
});
it('gets child categories', () => {
// arrange
const expectedScriptIds = ['1', '2', '3', '4'];
const categories = [
new CategoryStub(31).withScriptIds('1', '2'),
new CategoryStub(32).withScriptIds('3', '4'),
];
const sut = new Category(0, 'category', [], categories, []);
// act
const actualIds = sut.getAllScriptsRecursively().map((s) => s.id);
// assert
expect(actualIds).to.have.deep.members(expectedScriptIds);
});
it('gets child scripts and categories', () => {
// arrange
const expectedScriptIds = ['1', '2', '3', '4', '5', '6'];
const categories = [
new CategoryStub(31).withScriptIds('1', '2'),
new CategoryStub(32).withScriptIds('3', '4'),
];
const scripts = [new ScriptStub('5'), new ScriptStub('6')];
const sut = new Category(0, 'category', [], categories, scripts);
// act
const actualIds = sut.getAllScriptsRecursively().map((s) => s.id);
// assert
expect(actualIds).to.have.deep.members(expectedScriptIds);
});
it('gets child categories recursively', () => {
// arrange
const expectedScriptIds = ['1', '2', '3', '4', '5', '6'];
const categories = [
new CategoryStub(31)
.withScriptIds('1', '2')
.withCategory(
new CategoryStub(32)
.withScriptIds('3', '4'),
),
new CategoryStub(33)
.withCategories(
new CategoryStub(34)
.withScriptIds('5')
.withCategory(
new CategoryStub(35)
.withCategory(
new CategoryStub(35).withScriptIds('6'),
),
),
),
];
// assert
const sut = new Category(0, 'category', [], categories, []);
// act
const actualIds = sut.getAllScriptsRecursively().map((s) => s.id);
// assert
expect(actualIds).to.have.deep.members(expectedScriptIds);
});
});
describe('includes', () => {
it('return false when does not include', () => {
// assert
const script = new ScriptStub('3');
const sut = new Category(0, 'category', [], [new CategoryStub(33).withScriptIds('1', '2')], []);
// act
const actual = sut.includes(script);
// assert
expect(actual).to.equal(false);
});
it('return true when includes as subscript', () => {
// assert
const script = new ScriptStub('3');
const sut = new Category(0, 'category', [], [
new CategoryStub(33).withScript(script).withScriptIds('non-related'),
], []);
// act
const actual = sut.includes(script);
// assert
expect(actual).to.equal(true);
});
it('return true when includes as nested category script', () => {
// assert
const script = new ScriptStub('3');
const innerCategory = new CategoryStub(22)
.withScriptIds('non-related')
.withCategory(new CategoryStub(33).withScript(script));
const sut = new Category(11, 'category', [], [innerCategory], []);
// act
const actual = sut.includes(script);
// assert
expect(actual).to.equal(true);
});
});
});

View File

@@ -12,248 +12,260 @@ import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
describe('CategoryCollection', () => {
describe('getScriptsByLevel', () => {
it('filters out scripts without levels', () => {
// arrange
const recommendationLevels = getEnumValues(RecommendationLevel);
const scriptsWithLevels = recommendationLevels.map((level, index) =>
new ScriptStub(`Script${index}`).withLevel(level),
);
const toIgnore = new ScriptStub('script-to-ignore').withLevel(undefined);
for (const currentLevel of recommendationLevels) {
const category = new CategoryStub(0)
.withScripts(...scriptsWithLevels)
.withScript(toIgnore);
const sut = new CategoryCollectionBuilder()
.withActions([category])
.construct();
// act
const actual = sut.getScriptsByLevel(currentLevel);
// assert
expect(actual).to.not.include(toIgnore);
}
});
it(`${RecommendationLevel[RecommendationLevel.Standard]} filters ${RecommendationLevel[RecommendationLevel.Strict]}`, () => {
// arrange
const level = RecommendationLevel.Standard;
const expected = [
new ScriptStub('S1').withLevel(level),
new ScriptStub('S2').withLevel(level),
];
const actions = [
new CategoryStub(3).withScripts(...expected,
new ScriptStub('S3').withLevel(RecommendationLevel.Strict)),
];
const sut = new CategoryCollectionBuilder()
.withActions(actions)
.construct();
// act
const actual = sut.getScriptsByLevel(level);
// assert
expect(expected).to.deep.equal(actual);
});
it(`${RecommendationLevel[RecommendationLevel.Strict]} includes ${RecommendationLevel[RecommendationLevel.Standard]}`, () => {
// arrange
const level = RecommendationLevel.Strict;
const expected = [
new ScriptStub('S1').withLevel(RecommendationLevel.Standard),
new ScriptStub('S2').withLevel(RecommendationLevel.Strict),
];
const actions = [
new CategoryStub(3).withScripts(...expected),
];
const sut = new CategoryCollectionBuilder()
.withActions(actions)
.construct();
// act
const actual = sut.getScriptsByLevel(level);
// assert
expect(expected).to.deep.equal(actual);
});
it('throws when level is undefined', () => {
// arrange
const sut = new CategoryCollectionBuilder()
.construct();
// act
const act = () => sut.getScriptsByLevel(undefined);
// assert
expect(act).to.throw('undefined level');
});
it('throws when level is out of range', () => {
// arrange
const invalidValue = 66;
const sut = new CategoryCollectionBuilder()
.construct();
// act
const act = () => sut.getScriptsByLevel(invalidValue);
// assert
expect(act).to.throw(`invalid level: ${invalidValue}`);
});
describe('getScriptsByLevel', () => {
it('filters out scripts without levels', () => {
// arrange
const recommendationLevels = getEnumValues(RecommendationLevel);
const scriptsWithLevels = recommendationLevels.map(
(level, index) => new ScriptStub(`Script${index}`).withLevel(level),
);
const toIgnore = new ScriptStub('script-to-ignore').withLevel(undefined);
for (const currentLevel of recommendationLevels) {
const category = new CategoryStub(0)
.withScripts(...scriptsWithLevels)
.withScript(toIgnore);
const sut = new CategoryCollectionBuilder()
.withActions([category])
.construct();
// act
const actual = sut.getScriptsByLevel(currentLevel);
// assert
expect(actual).to.not.include(toIgnore);
}
});
describe('actions', () => {
it('cannot construct without actions', () => {
// arrange
const categories = [];
// act
function construct() {
new CategoryCollectionBuilder()
.withActions(categories)
.construct();
}
// assert
expect(construct).to.throw('must consist of at least one category');
});
it('cannot construct without scripts', () => {
// arrange
const categories = [
new CategoryStub(3),
new CategoryStub(2),
];
// act
function construct() {
new CategoryCollectionBuilder()
.withActions(categories)
.construct();
}
// assert
expect(construct).to.throw('must consist of at least one script');
});
describe('cannot construct without any recommended scripts', () => {
// arrange
const recommendationLevels = getEnumValues(RecommendationLevel);
for (const missingLevel of recommendationLevels) {
it(`when "${RecommendationLevel[missingLevel]}" is missing`, () => {
const expectedError = `none of the scripts are recommended as ${RecommendationLevel[missingLevel]}`;
const otherLevels = recommendationLevels.filter((level) => level !== missingLevel);
const categories = otherLevels.map((level, index) =>
new CategoryStub(index).withScript(
new ScriptStub(`Script${index}`).withLevel(level),
));
// act
const construct = () => new CategoryCollectionBuilder()
.withActions(categories)
.construct();
// assert
expect(construct).to.throw(expectedError);
});
}
});
it(`${RecommendationLevel[RecommendationLevel.Standard]} filters ${RecommendationLevel[RecommendationLevel.Strict]}`, () => {
// arrange
const level = RecommendationLevel.Standard;
const expected = [
new ScriptStub('S1').withLevel(level),
new ScriptStub('S2').withLevel(level),
];
const actions = [
new CategoryStub(3).withScripts(
...expected,
new ScriptStub('S3').withLevel(RecommendationLevel.Strict),
),
];
const sut = new CategoryCollectionBuilder()
.withActions(actions)
.construct();
// act
const actual = sut.getScriptsByLevel(level);
// assert
expect(expected).to.deep.equal(actual);
});
describe('totalScripts', () => {
it('returns total of initial scripts', () => {
// arrange
const categories = [
new CategoryStub(1).withScripts(
new ScriptStub('S1').withLevel(RecommendationLevel.Standard)),
new CategoryStub(2).withScripts(
new ScriptStub('S2'),
new ScriptStub('S3').withLevel(RecommendationLevel.Strict)),
new CategoryStub(3).withCategories(
new CategoryStub(4).withScripts(new ScriptStub('S4'))),
];
// act
const sut = new CategoryCollectionBuilder()
.withActions(categories)
.construct();
// assert
expect(sut.totalScripts).to.equal(4);
});
it(`${RecommendationLevel[RecommendationLevel.Strict]} includes ${RecommendationLevel[RecommendationLevel.Standard]}`, () => {
// arrange
const level = RecommendationLevel.Strict;
const expected = [
new ScriptStub('S1').withLevel(RecommendationLevel.Standard),
new ScriptStub('S2').withLevel(RecommendationLevel.Strict),
];
const actions = [
new CategoryStub(3).withScripts(...expected),
];
const sut = new CategoryCollectionBuilder()
.withActions(actions)
.construct();
// act
const actual = sut.getScriptsByLevel(level);
// assert
expect(expected).to.deep.equal(actual);
});
describe('totalCategories', () => {
it('returns total of initial categories', () => {
// arrange
const expected = 4;
const categories = [
new CategoryStub(1).withScripts(new ScriptStub('S1').withLevel(RecommendationLevel.Strict)),
new CategoryStub(2).withScripts(new ScriptStub('S2'), new ScriptStub('S3')),
new CategoryStub(3).withCategories(new CategoryStub(4).withScripts(new ScriptStub('S4'))),
];
// act
const sut = new CategoryCollectionBuilder()
.withActions(categories)
.construct();
// assert
expect(sut.totalCategories).to.equal(expected);
});
it('throws when level is undefined', () => {
// arrange
const sut = new CategoryCollectionBuilder()
.construct();
// act
const act = () => sut.getScriptsByLevel(undefined);
// assert
expect(act).to.throw('undefined level');
});
describe('os', () => {
it('sets os as expected', () => {
// arrange
const expected = OperatingSystem.macOS;
// act
const sut = new CategoryCollectionBuilder()
.withOs(expected)
.construct();
// assert
expect(sut.os).to.deep.equal(expected);
});
describe('throws when invalid', () => {
// act
const act = (os: OperatingSystem) => new CategoryCollectionBuilder()
.withOs(os)
.construct();
// assert
new EnumRangeTestRunner(act)
.testOutOfRangeThrows()
.testUndefinedValueThrows();
});
it('throws when level is out of range', () => {
// arrange
const invalidValue = 66;
const sut = new CategoryCollectionBuilder()
.construct();
// act
const act = () => sut.getScriptsByLevel(invalidValue);
// assert
expect(act).to.throw(`invalid level: ${invalidValue}`);
});
describe('scriptingDefinition', () => {
it('sets scriptingDefinition as expected', () => {
// arrange
const expected = getValidScriptingDefinition();
// act
const sut = new CategoryCollectionBuilder()
.withScripting(expected)
.construct();
// assert
expect(sut.scripting).to.deep.equal(expected);
});
it('cannot construct without initial script', () => {
// arrange
const scriptingDefinition = undefined;
// act
function construct() {
return new CategoryCollectionBuilder()
.withScripting(scriptingDefinition)
.construct();
}
// assert
expect(construct).to.throw('undefined scripting definition');
});
});
describe('actions', () => {
it('cannot construct without actions', () => {
// arrange
const categories = [];
// act
function construct() {
new CategoryCollectionBuilder()
.withActions(categories)
.construct();
}
// assert
expect(construct).to.throw('must consist of at least one category');
});
it('cannot construct without scripts', () => {
// arrange
const categories = [
new CategoryStub(3),
new CategoryStub(2),
];
// act
function construct() {
new CategoryCollectionBuilder()
.withActions(categories)
.construct();
}
// assert
expect(construct).to.throw('must consist of at least one script');
});
describe('cannot construct without any recommended scripts', () => {
// arrange
const recommendationLevels = getEnumValues(RecommendationLevel);
for (const missingLevel of recommendationLevels) {
it(`when "${RecommendationLevel[missingLevel]}" is missing`, () => {
const expectedError = `none of the scripts are recommended as ${RecommendationLevel[missingLevel]}`;
const otherLevels = recommendationLevels.filter((level) => level !== missingLevel);
const categories = otherLevels.map(
(level, index) => new CategoryStub(index)
.withScript(new ScriptStub(`Script${index}`).withLevel(level)),
);
// act
const construct = () => new CategoryCollectionBuilder()
.withActions(categories)
.construct();
// assert
expect(construct).to.throw(expectedError);
});
}
});
});
describe('totalScripts', () => {
it('returns total of initial scripts', () => {
// arrange
const categories = [
new CategoryStub(1).withScripts(
new ScriptStub('S1').withLevel(RecommendationLevel.Standard),
),
new CategoryStub(2).withScripts(
new ScriptStub('S2'),
new ScriptStub('S3').withLevel(RecommendationLevel.Strict),
),
new CategoryStub(3).withCategories(
new CategoryStub(4).withScripts(new ScriptStub('S4')),
),
];
// act
const sut = new CategoryCollectionBuilder()
.withActions(categories)
.construct();
// assert
expect(sut.totalScripts).to.equal(4);
});
});
describe('totalCategories', () => {
it('returns total of initial categories', () => {
// arrange
const expected = 4;
const categories = [
new CategoryStub(1).withScripts(new ScriptStub('S1').withLevel(RecommendationLevel.Strict)),
new CategoryStub(2).withScripts(new ScriptStub('S2'), new ScriptStub('S3')),
new CategoryStub(3).withCategories(new CategoryStub(4).withScripts(new ScriptStub('S4'))),
];
// act
const sut = new CategoryCollectionBuilder()
.withActions(categories)
.construct();
// assert
expect(sut.totalCategories).to.equal(expected);
});
});
describe('os', () => {
it('sets os as expected', () => {
// arrange
const expected = OperatingSystem.macOS;
// act
const sut = new CategoryCollectionBuilder()
.withOs(expected)
.construct();
// assert
expect(sut.os).to.deep.equal(expected);
});
describe('throws when invalid', () => {
// act
const act = (os: OperatingSystem) => new CategoryCollectionBuilder()
.withOs(os)
.construct();
// assert
new EnumRangeTestRunner(act)
.testOutOfRangeThrows()
.testUndefinedValueThrows();
});
});
describe('scriptingDefinition', () => {
it('sets scriptingDefinition as expected', () => {
// arrange
const expected = getValidScriptingDefinition();
// act
const sut = new CategoryCollectionBuilder()
.withScripting(expected)
.construct();
// assert
expect(sut.scripting).to.deep.equal(expected);
});
it('cannot construct without initial script', () => {
// arrange
const scriptingDefinition = undefined;
// act
function construct() {
return new CategoryCollectionBuilder()
.withScripting(scriptingDefinition)
.construct();
}
// assert
expect(construct).to.throw('undefined scripting definition');
});
});
});
function getValidScriptingDefinition(): IScriptingDefinition {
return {
fileExtension: '.bat',
language: ScriptingLanguage.batchfile,
startCode: 'start',
endCode: 'end',
};
return {
fileExtension: '.bat',
language: ScriptingLanguage.batchfile,
startCode: 'start',
endCode: 'end',
};
}
class CategoryCollectionBuilder {
private os = OperatingSystem.Windows;
private actions: readonly ICategory[] = [
new CategoryStub(1).withScripts(
new ScriptStub('S1').withLevel(RecommendationLevel.Standard),
new ScriptStub('S2').withLevel(RecommendationLevel.Strict)),
];
private script: IScriptingDefinition = getValidScriptingDefinition();
public withOs(os: OperatingSystem): CategoryCollectionBuilder {
this.os = os;
return this;
}
public withActions(actions: readonly ICategory[]): CategoryCollectionBuilder {
this.actions = actions;
return this;
}
public withScripting(script: IScriptingDefinition): CategoryCollectionBuilder {
this.script = script;
return this;
}
public construct(): CategoryCollection {
return new CategoryCollection(this.os, this.actions, this.script);
}
private os = OperatingSystem.Windows;
private actions: readonly ICategory[] = [
new CategoryStub(1).withScripts(
new ScriptStub('S1').withLevel(RecommendationLevel.Standard),
new ScriptStub('S2').withLevel(RecommendationLevel.Strict),
),
];
private script: IScriptingDefinition = getValidScriptingDefinition();
public withOs(os: OperatingSystem): CategoryCollectionBuilder {
this.os = os;
return this;
}
public withActions(actions: readonly ICategory[]): CategoryCollectionBuilder {
this.actions = actions;
return this;
}
public withScripting(script: IScriptingDefinition): CategoryCollectionBuilder {
this.script = script;
return this;
}
public construct(): CategoryCollection {
return new CategoryCollection(this.os, this.actions, this.script);
}
}

View File

@@ -5,127 +5,127 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
describe('ProjectInformation', () => {
it('sets name as expected', () => {
// arrange
const expected = 'expected-name';
const sut = new ProjectInformation(expected, 'version', 'repositoryUrl', 'homepage');
// act
const actual = sut.name;
// assert
expect(actual).to.equal(expected);
it('sets name as expected', () => {
// arrange
const expected = 'expected-name';
const sut = new ProjectInformation(expected, 'version', 'repositoryUrl', 'homepage');
// act
const actual = sut.name;
// assert
expect(actual).to.equal(expected);
});
it('sets version as expected', () => {
// arrange
const expected = 'expected-version';
const sut = new ProjectInformation('name', expected, 'repositoryUrl', 'homepage');
// act
const actual = sut.version;
// assert
expect(actual).to.equal(expected);
});
it('sets repositoryUrl as expected', () => {
// arrange
const expected = 'expected-repository-url';
const sut = new ProjectInformation('name', 'version', expected, 'homepage');
// act
const actual = sut.repositoryUrl;
// assert
expect(actual).to.equal(expected);
});
describe('sets repositoryWebUrl as expected', () => {
it('sets repositoryUrl when it does not end with .git', () => {
// arrange
const expected = 'expected-repository-url';
const sut = new ProjectInformation('name', 'version', expected, 'homepage');
// act
const actual = sut.repositoryWebUrl;
// assert
expect(actual).to.equal(expected);
});
it('sets version as expected', () => {
// arrange
const expected = 'expected-version';
const sut = new ProjectInformation('name', expected, 'repositoryUrl', 'homepage');
// act
const actual = sut.version;
// assert
expect(actual).to.equal(expected);
it('removes ".git" from the end when it ends with ".git"', () => {
// arrange
const expected = 'expected-repository-url';
const sut = new ProjectInformation('name', 'version', `${expected}.git`, 'homepage');
// act
const actual = sut.repositoryWebUrl;
// assert
expect(actual).to.equal(expected);
});
it('sets repositoryUrl as expected', () => {
// arrange
const expected = 'expected-repository-url';
const sut = new ProjectInformation('name', 'version', expected, 'homepage');
// act
const actual = sut.repositoryUrl;
// assert
expect(actual).to.equal(expected);
});
it('sets homepage as expected', () => {
// arrange
const expected = 'expected-homepage';
const sut = new ProjectInformation('name', 'version', 'repositoryUrl', expected);
// act
const actual = sut.homepage;
// assert
expect(actual).to.equal(expected);
});
it('sets feedbackUrl to github issues page', () => {
// arrange
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
const expected = 'https://github.com/undergroundwires/privacy.sexy/issues';
const sut = new ProjectInformation('name', 'version', repositoryUrl, 'homepage');
// act
const actual = sut.feedbackUrl;
// assert
expect(actual).to.equal(expected);
});
it('sets releaseUrl to github releases page', () => {
// arrange
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
const version = '0.7.2';
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/tag/0.7.2';
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
// act
const actual = sut.releaseUrl;
// assert
expect(actual).to.equal(expected);
});
describe('getDownloadUrl', () => {
it('gets expected url for macOS', () => {
// arrange
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-0.7.2.dmg';
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
const version = '0.7.2';
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
// act
const actual = sut.getDownloadUrl(OperatingSystem.macOS);
// assert
expect(actual).to.equal(expected);
});
describe('sets repositoryWebUrl as expected', () => {
it('sets repositoryUrl when it does not end with .git', () => {
// arrange
const expected = 'expected-repository-url';
const sut = new ProjectInformation('name', 'version', expected, 'homepage');
// act
const actual = sut.repositoryWebUrl;
// assert
expect(actual).to.equal(expected);
});
it('removes ".git" from the end when it ends with ".git"', () => {
// arrange
const expected = 'expected-repository-url';
const sut = new ProjectInformation('name', 'version', `${expected}.git`, 'homepage');
// act
const actual = sut.repositoryWebUrl;
// assert
expect(actual).to.equal(expected);
});
it('gets expected url for Linux', () => {
// arrange
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-0.7.2.AppImage';
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
const version = '0.7.2';
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
// act
const actual = sut.getDownloadUrl(OperatingSystem.Linux);
// assert
expect(actual).to.equal(expected);
});
it('sets homepage as expected', () => {
// arrange
const expected = 'expected-homepage';
const sut = new ProjectInformation('name', 'version', 'repositoryUrl', expected);
// act
const actual = sut.homepage;
// assert
expect(actual).to.equal(expected);
it('gets expected url for Windows', () => {
// arrange
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-Setup-0.7.2.exe';
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
const version = '0.7.2';
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
// act
const actual = sut.getDownloadUrl(OperatingSystem.Windows);
// assert
expect(actual).to.equal(expected);
});
it('sets feedbackUrl to github issues page', () => {
// arrange
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
const expected = 'https://github.com/undergroundwires/privacy.sexy/issues';
const sut = new ProjectInformation('name', 'version', repositoryUrl, 'homepage');
// act
const actual = sut.feedbackUrl;
// assert
expect(actual).to.equal(expected);
});
it('sets releaseUrl to github releases page', () => {
// arrange
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
const version = '0.7.2';
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/tag/0.7.2';
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
// act
const actual = sut.releaseUrl;
// assert
expect(actual).to.equal(expected);
});
describe('getDownloadUrl', () => {
it('gets expected url for macOS', () => {
// arrange
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-0.7.2.dmg';
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
const version = '0.7.2';
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
// act
const actual = sut.getDownloadUrl(OperatingSystem.macOS);
// assert
expect(actual).to.equal(expected);
});
it('gets expected url for Linux', () => {
// arrange
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-0.7.2.AppImage';
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
const version = '0.7.2';
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
// act
const actual = sut.getDownloadUrl(OperatingSystem.Linux);
// assert
expect(actual).to.equal(expected);
});
it('gets expected url for Windows', () => {
// arrange
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-Setup-0.7.2.exe';
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
const version = '0.7.2';
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
// act
const actual = sut.getDownloadUrl(OperatingSystem.Windows);
// assert
expect(actual).to.equal(expected);
});
describe('throws when os is invalid', () => {
// arrange
const sut = new ProjectInformation('name', 'version', 'repositoryUrl', 'homepage');
// act
const act = (os: OperatingSystem) => sut.getDownloadUrl(os);
// assert
new EnumRangeTestRunner(act)
.testOutOfRangeThrows()
.testUndefinedValueThrows()
.testInvalidValueThrows(OperatingSystem.KaiOS, `Unsupported os: ${OperatingSystem[OperatingSystem.KaiOS]}`);
});
describe('throws when os is invalid', () => {
// arrange
const sut = new ProjectInformation('name', 'version', 'repositoryUrl', 'homepage');
// act
const act = (os: OperatingSystem) => sut.getDownloadUrl(os);
// assert
new EnumRangeTestRunner(act)
.testOutOfRangeThrows()
.testUndefinedValueThrows()
.testInvalidValueThrows(OperatingSystem.KaiOS, `Unsupported os: ${OperatingSystem[OperatingSystem.KaiOS]}`);
});
});
});

View File

@@ -7,145 +7,148 @@ import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCodeStub } from '@tests/unit/stubs/ScriptCodeStub';
describe('Script', () => {
describe('ctor', () => {
describe('scriptCode', () => {
it('sets as expected', () => {
// arrange
const expected = new ScriptCodeStub();
const sut = new ScriptBuilder()
.withCode(expected)
.build();
// act
const actual = sut.code;
// assert
expect(actual).to.deep.equal(expected);
});
it('throws if undefined', () => {
// arrange
const name = 'script-name';
const expectedError = `undefined code (script: ${name})`;
const code: IScriptCode = undefined;
// act
const construct = () => new ScriptBuilder()
.withName(name)
.withCode(code)
.build();
// assert
expect(construct).to.throw(expectedError);
});
});
describe('canRevert', () => {
it('returns false without revert code', () => {
// arrange
const sut = new ScriptBuilder()
.withCodes('code')
.build();
// act
const actual = sut.canRevert();
// assert
expect(actual).to.equal(false);
});
it('returns true with revert code', () => {
// arrange
const sut = new ScriptBuilder()
.withCodes('code', 'non empty revert code')
.build();
// act
const actual = sut.canRevert();
// assert
expect(actual).to.equal(true);
});
});
describe('level', () => {
it('cannot construct with invalid wrong value', () => {
// arrange
const invalidValue: RecommendationLevel = 55;
const expectedError = 'invalid level';
// act
const construct = () => new ScriptBuilder()
.withRecommendationLevel(invalidValue)
.build();
// assert
expect(construct).to.throw(expectedError);
});
it('sets undefined as expected', () => {
// arrange
const expected = undefined;
// act
const sut = new ScriptBuilder()
.withRecommendationLevel(expected)
.build();
// assert
expect(sut.level).to.equal(expected);
});
it('sets as expected', () => {
// arrange
for (const expected of getEnumValues(RecommendationLevel)) {
// act
const sut = new ScriptBuilder()
.withRecommendationLevel(expected)
.build();
// assert
const actual = sut.level;
expect(actual).to.equal(expected);
}
});
});
describe('documentationUrls', () => {
it('sets as expected', () => {
// arrange
const expected = [ 'doc1', 'doc2 '];
// act
const sut = new ScriptBuilder()
.withDocumentationUrls(expected)
.build();
const actual = sut.documentationUrls;
// assert
expect(actual).to.equal(expected);
});
});
describe('ctor', () => {
describe('scriptCode', () => {
it('sets as expected', () => {
// arrange
const expected = new ScriptCodeStub();
const sut = new ScriptBuilder()
.withCode(expected)
.build();
// act
const actual = sut.code;
// assert
expect(actual).to.deep.equal(expected);
});
it('throws if undefined', () => {
// arrange
const name = 'script-name';
const expectedError = `undefined code (script: ${name})`;
const code: IScriptCode = undefined;
// act
const construct = () => new ScriptBuilder()
.withName(name)
.withCode(code)
.build();
// assert
expect(construct).to.throw(expectedError);
});
});
describe('canRevert', () => {
it('returns false without revert code', () => {
// arrange
const sut = new ScriptBuilder()
.withCodes('code')
.build();
// act
const actual = sut.canRevert();
// assert
expect(actual).to.equal(false);
});
it('returns true with revert code', () => {
// arrange
const sut = new ScriptBuilder()
.withCodes('code', 'non empty revert code')
.build();
// act
const actual = sut.canRevert();
// assert
expect(actual).to.equal(true);
});
});
describe('level', () => {
it('cannot construct with invalid wrong value', () => {
// arrange
const invalidValue: RecommendationLevel = 55;
const expectedError = 'invalid level';
// act
const construct = () => new ScriptBuilder()
.withRecommendationLevel(invalidValue)
.build();
// assert
expect(construct).to.throw(expectedError);
});
it('sets undefined as expected', () => {
// arrange
const expected = undefined;
// act
const sut = new ScriptBuilder()
.withRecommendationLevel(expected)
.build();
// assert
expect(sut.level).to.equal(expected);
});
it('sets as expected', () => {
// arrange
for (const expected of getEnumValues(RecommendationLevel)) {
// act
const sut = new ScriptBuilder()
.withRecommendationLevel(expected)
.build();
// assert
const actual = sut.level;
expect(actual).to.equal(expected);
}
});
});
describe('documentationUrls', () => {
it('sets as expected', () => {
// arrange
const expected = ['doc1', 'doc2'];
// act
const sut = new ScriptBuilder()
.withDocumentationUrls(expected)
.build();
const actual = sut.documentationUrls;
// assert
expect(actual).to.equal(expected);
});
});
});
});
class ScriptBuilder {
private name = 'test-script';
private code: IScriptCode = new ScriptCodeStub();
private level = RecommendationLevel.Standard;
private documentationUrls: readonly string[] = undefined;
private name = 'test-script';
public withCodes(code: string, revertCode = ''): ScriptBuilder {
this.code = new ScriptCodeStub()
.withExecute(code)
.withRevert(revertCode);
return this;
}
private code: IScriptCode = new ScriptCodeStub();
public withCode(code: IScriptCode): ScriptBuilder {
this.code = code;
return this;
}
private level = RecommendationLevel.Standard;
public withName(name: string): ScriptBuilder {
this.name = name;
return this;
}
private documentationUrls: readonly string[] = undefined;
public withRecommendationLevel(level: RecommendationLevel): ScriptBuilder {
this.level = level;
return this;
}
public withCodes(code: string, revertCode = ''): ScriptBuilder {
this.code = new ScriptCodeStub()
.withExecute(code)
.withRevert(revertCode);
return this;
}
public withDocumentationUrls(urls: readonly string[]): ScriptBuilder {
this.documentationUrls = urls;
return this;
}
public withCode(code: IScriptCode): ScriptBuilder {
this.code = code;
return this;
}
public build(): Script {
return new Script(
this.name,
this.code,
this.documentationUrls,
this.level,
);
}
public withName(name: string): ScriptBuilder {
this.name = name;
return this;
}
public withRecommendationLevel(level: RecommendationLevel): ScriptBuilder {
this.level = level;
return this;
}
public withDocumentationUrls(urls: readonly string[]): ScriptBuilder {
this.documentationUrls = urls;
return this;
}
public build(): Script {
return new Script(
this.name,
this.code,
this.documentationUrls,
this.level,
);
}
}

View File

@@ -1,191 +1,192 @@
import 'mocha';
import { expect } from 'chai';
import { ScriptCode } from '@/domain/ScriptCode';
import { ScriptCode, ILanguageSyntax } from '@/domain/ScriptCode';
import { IScriptCode } from '@/domain/IScriptCode';
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
describe('ScriptCode', () => {
describe('code', () => {
describe('throws with invalid code', () => {
// arrange
const testCases = [
{
name: 'throws when "execute" and "revert" are same',
code: {
execute: 'same',
revert: 'same',
},
expectedError: `(revert): Code itself and its reverting code cannot be the same`,
},
{
name: 'cannot construct with undefined "execute"',
code: {
execute: undefined,
revert: 'code',
},
expectedError: `code is empty or undefined`,
},
{
name: 'cannot construct with empty "execute"',
code: {
execute: '',
revert: 'code',
},
expectedError: `code is empty or undefined`,
},
];
for (const testCase of testCases) {
it(testCase.name, () => {
// act
const act = () => new ScriptCodeBuilder()
.withExecute( testCase.code.execute)
.withRevert(testCase.code.revert)
.build();
// assert
expect(act).to.throw(testCase.expectedError);
});
}
});
describe('throws with invalid code in both "execute" or "revert"', () => {
// arrange
const testCases = [
{
testName: 'cannot construct with duplicate lines',
code: 'duplicate\nduplicate\nunique\nduplicate',
expectedMessage: 'Duplicates detected in script:\n❌ (0,1,3)\t[0] duplicate\n❌ (0,1,3)\t[1] duplicate\n✅ [2] unique\n❌ (0,1,3)\t[3] duplicate',
},
{
testName: 'cannot construct with empty lines',
code: 'line1\n\n\nline2',
expectedMessage: 'Script has empty lines:\n\n (0) line1\n (1) ❌\n (2) ❌\n (3) line2',
},
];
// act
const actions = [];
for (const testCase of testCases) {
actions.push(...[
{
act: () => new ScriptCodeBuilder()
.withExecute(testCase.code)
.build(),
testName: `execute: ${testCase.testName}`,
expectedMessage: testCase.expectedMessage,
},
{
act: () => new ScriptCodeBuilder()
.withRevert(testCase.code)
.build(),
testName: `revert: ${testCase.testName}`,
expectedMessage: `(revert): ${testCase.expectedMessage}`,
},
]);
}
// assert
for (const action of actions) {
it(action.testName, () => {
expect(action.act).to.throw(action.expectedMessage,
`Code used: ${action.code}`);
});
}
});
describe('sets as expected with valid "execute" or "revert"', () => {
// arrange
const syntax = new LanguageSyntaxStub()
.withCommonCodeParts(')', 'else', '(')
.withCommentDelimiters('#', '//');
const testCases = [
{
testName: 'code is a valid string',
code: 'valid code',
},
{
testName: 'code consists of common code parts',
code: syntax.commonCodeParts.join(' '),
},
{
testName: 'code is a common code part',
code: syntax.commonCodeParts[0],
},
{
testName: `code with duplicated comment lines (${syntax.commentDelimiters[0]})`,
code: `${syntax.commentDelimiters[0]} comment\n${syntax.commentDelimiters[0]} comment`,
},
{
testName: `code with duplicated comment lines (${syntax.commentDelimiters[1]})`,
code: `${syntax.commentDelimiters[1]} comment\n${syntax.commentDelimiters[1]} comment`,
},
];
// act
const actions = [];
for (const testCase of testCases) {
actions.push(...[
{
testName: `execute: ${testCase.testName}`,
act: () =>
new ScriptCodeBuilder()
.withSyntax(syntax)
.withExecute(testCase.code)
.build(),
expect: (sut: IScriptCode) => sut.execute === testCase.code,
},
{
testName: `revert: ${testCase.testName}`,
act: () =>
new ScriptCodeBuilder()
.withSyntax(syntax)
.withRevert(testCase.code)
.build(),
expect: (sut: IScriptCode) => sut.revert === testCase.code,
},
]);
}
// assert
for (const action of actions) {
it(action.testName, () => {
const sut = action.act();
expect(action.expect(sut));
});
}
describe('code', () => {
describe('throws with invalid code', () => {
// arrange
const testCases = [
{
name: 'throws when "execute" and "revert" are same',
code: {
execute: 'same',
revert: 'same',
},
expectedError: '(revert): Code itself and its reverting code cannot be the same',
},
{
name: 'cannot construct with undefined "execute"',
code: {
execute: undefined,
revert: 'code',
},
expectedError: 'code is empty or undefined',
},
{
name: 'cannot construct with empty "execute"',
code: {
execute: '',
revert: 'code',
},
expectedError: 'code is empty or undefined',
},
];
for (const testCase of testCases) {
it(testCase.name, () => {
// act
const act = () => new ScriptCodeBuilder()
.withExecute(testCase.code.execute)
.withRevert(testCase.code.revert)
.build();
// assert
expect(act).to.throw(testCase.expectedError);
});
}
});
describe('syntax', () => {
it('throws if undefined', () => {
// arrange
const expectedError = 'undefined syntax';
const syntax = undefined;
// act
const act = () => new ScriptCodeBuilder()
.withSyntax(syntax)
.build();
// assert
expect(act).to.throw(expectedError);
describe('throws with invalid code in both "execute" or "revert"', () => {
// arrange
const testCases = [
{
testName: 'cannot construct with duplicate lines',
code: 'duplicate\nduplicate\nunique\nduplicate',
expectedMessage: 'Duplicates detected in script:\n❌ (0,1,3)\t[0] duplicate\n❌ (0,1,3)\t[1] duplicate\n✅ [2] unique\n❌ (0,1,3)\t[3] duplicate',
},
{
testName: 'cannot construct with empty lines',
code: 'line1\n\n\nline2',
expectedMessage: 'Script has empty lines:\n\n (0) line1\n (1) ❌\n (2) ❌\n (3) line2',
},
];
// act
const actions = [];
for (const testCase of testCases) {
actions.push(...[
{
act: () => new ScriptCodeBuilder()
.withExecute(testCase.code)
.build(),
testName: `execute: ${testCase.testName}`,
expectedMessage: testCase.expectedMessage,
},
{
act: () => new ScriptCodeBuilder()
.withRevert(testCase.code)
.build(),
testName: `revert: ${testCase.testName}`,
expectedMessage: `(revert): ${testCase.expectedMessage}`,
},
]);
}
// assert
for (const action of actions) {
it(action.testName, () => {
expect(action.act).to.throw(action.expectedMessage, `Code used: ${action.code}`);
});
}
});
describe('sets as expected with valid "execute" or "revert"', () => {
// arrange
const syntax = new LanguageSyntaxStub()
.withCommonCodeParts(')', 'else', '(')
.withCommentDelimiters('#', '//');
const testCases = [
{
testName: 'code is a valid string',
code: 'valid code',
},
{
testName: 'code consists of common code parts',
code: syntax.commonCodeParts.join(' '),
},
{
testName: 'code is a common code part',
code: syntax.commonCodeParts[0],
},
{
testName: `code with duplicated comment lines (${syntax.commentDelimiters[0]})`,
code: `${syntax.commentDelimiters[0]} comment\n${syntax.commentDelimiters[0]} comment`,
},
{
testName: `code with duplicated comment lines (${syntax.commentDelimiters[1]})`,
code: `${syntax.commentDelimiters[1]} comment\n${syntax.commentDelimiters[1]} comment`,
},
];
// act
const actions = [];
for (const testCase of testCases) {
actions.push(...[
{
testName: `execute: ${testCase.testName}`,
act: () => new ScriptCodeBuilder()
.withSyntax(syntax)
.withExecute(testCase.code)
.build(),
expect: (sut: IScriptCode) => sut.execute === testCase.code,
},
{
testName: `revert: ${testCase.testName}`,
act: () => new ScriptCodeBuilder()
.withSyntax(syntax)
.withRevert(testCase.code)
.build(),
expect: (sut: IScriptCode) => sut.revert === testCase.code,
},
]);
}
// assert
for (const action of actions) {
it(action.testName, () => {
const sut = action.act();
expect(action.expect(sut));
});
}
});
});
describe('syntax', () => {
it('throws if undefined', () => {
// arrange
const expectedError = 'undefined syntax';
const syntax = undefined;
// act
const act = () => new ScriptCodeBuilder()
.withSyntax(syntax)
.build();
// assert
expect(act).to.throw(expectedError);
});
});
});
class ScriptCodeBuilder {
public execute = 'default-execute-code';
public revert = '';
public syntax: ILanguageSyntax = new LanguageSyntaxStub();
public execute = 'default-execute-code';
public withExecute(execute: string) {
this.execute = execute;
return this;
}
public withRevert(revert: string) {
this.revert = revert;
return this;
}
public withSyntax(syntax: ILanguageSyntax) {
this.syntax = syntax;
return this;
}
public revert = '';
public build(): ScriptCode {
return new ScriptCode(
this.execute,
this.revert,
this.syntax);
}
public syntax: ILanguageSyntax = new LanguageSyntaxStub();
public withExecute(execute: string) {
this.execute = execute;
return this;
}
public withRevert(revert: string) {
this.revert = revert;
return this;
}
public withSyntax(syntax: ILanguageSyntax) {
this.syntax = syntax;
return this;
}
public build(): ScriptCode {
return new ScriptCode(
this.execute,
this.revert,
this.syntax,
);
}
}

View File

@@ -5,129 +5,129 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { getEnumValues } from '@/application/Common/Enum';
describe('ScriptingDefinition', () => {
describe('language', () => {
describe('sets as expected', () => {
// arrange
const expectedValues = getEnumValues(ScriptingLanguage);
expectedValues.forEach((expected) => {
it(ScriptingLanguage[expected], () => {
// act
const sut = new ScriptingDefinitionBuilder()
.withLanguage(expected)
.build();
// assert
expect(sut.language).to.equal(expected);
});
});
});
it('throws if unknown', () => {
// arrange
const unknownValue: ScriptingLanguage = 666;
const errorMessage = `unsupported language: ${unknownValue}`;
// act
const act = () => new ScriptingDefinitionBuilder()
.withLanguage(unknownValue)
.build();
// assert
expect(act).to.throw(errorMessage);
describe('language', () => {
describe('sets as expected', () => {
// arrange
const expectedValues = getEnumValues(ScriptingLanguage);
expectedValues.forEach((expected) => {
it(ScriptingLanguage[expected], () => {
// act
const sut = new ScriptingDefinitionBuilder()
.withLanguage(expected)
.build();
// assert
expect(sut.language).to.equal(expected);
});
});
});
describe('fileExtension', () => {
describe('returns expected for each language', () => {
// arrange
const testCases = new Map<ScriptingLanguage, string>([
[ScriptingLanguage.batchfile, 'bat'],
[ScriptingLanguage.shellscript, 'sh'],
]);
Array.from(testCases.entries()).forEach((test) => {
const language = test[0];
const expectedExtension = test[1];
it(`${ScriptingLanguage[language]} has ${expectedExtension}`, () => {
// act
const sut = new ScriptingDefinitionBuilder()
.withLanguage(language)
.build();
// assert
expect(sut.fileExtension, expectedExtension);
});
});
});
it('throws if unknown', () => {
// arrange
const unknownValue: ScriptingLanguage = 666;
const errorMessage = `unsupported language: ${unknownValue}`;
// act
const act = () => new ScriptingDefinitionBuilder()
.withLanguage(unknownValue)
.build();
// assert
expect(act).to.throw(errorMessage);
});
describe('startCode', () => {
it('sets as expected', () => {
// arrange
const expected = 'REM start-code';
// act
const sut = new ScriptingDefinitionBuilder()
.withStartCode(expected)
.build();
// assert
expect(sut.startCode).to.equal(expected);
});
it('throws when undefined', () => {
// arrange
const expectedError = 'undefined start code';
const undefinedValues = [ '', undefined ];
for (const undefinedValue of undefinedValues) {
// act
const act = () => new ScriptingDefinitionBuilder()
.withStartCode(undefinedValue)
.build();
// assert
expect(act).to.throw(expectedError);
}
});
describe('fileExtension', () => {
describe('returns expected for each language', () => {
// arrange
const testCases = new Map<ScriptingLanguage, string>([
[ScriptingLanguage.batchfile, 'bat'],
[ScriptingLanguage.shellscript, 'sh'],
]);
Array.from(testCases.entries()).forEach((test) => {
const language = test[0];
const expectedExtension = test[1];
it(`${ScriptingLanguage[language]} has ${expectedExtension}`, () => {
// act
const sut = new ScriptingDefinitionBuilder()
.withLanguage(language)
.build();
// assert
expect(sut.fileExtension, expectedExtension);
});
});
});
describe('endCode', () => {
it('sets as expected', () => {
// arrange
const expected = 'REM end-code';
// act
const sut = new ScriptingDefinitionBuilder()
.withEndCode(expected)
.build();
// assert
expect(sut.endCode).to.equal(expected);
});
it('throws when undefined', () => {
// arrange
const expectedError = 'undefined end code';
const undefinedValues = [ '', undefined ];
for (const undefinedValue of undefinedValues) {
// act
const act = () => new ScriptingDefinitionBuilder()
.withEndCode(undefinedValue)
.build();
// assert
expect(act).to.throw(expectedError);
}
});
});
describe('startCode', () => {
it('sets as expected', () => {
// arrange
const expected = 'REM start-code';
// act
const sut = new ScriptingDefinitionBuilder()
.withStartCode(expected)
.build();
// assert
expect(sut.startCode).to.equal(expected);
});
it('throws when undefined', () => {
// arrange
const expectedError = 'undefined start code';
const undefinedValues = ['', undefined];
for (const undefinedValue of undefinedValues) {
// act
const act = () => new ScriptingDefinitionBuilder()
.withStartCode(undefinedValue)
.build();
// assert
expect(act).to.throw(expectedError);
}
});
});
describe('endCode', () => {
it('sets as expected', () => {
// arrange
const expected = 'REM end-code';
// act
const sut = new ScriptingDefinitionBuilder()
.withEndCode(expected)
.build();
// assert
expect(sut.endCode).to.equal(expected);
});
it('throws when undefined', () => {
// arrange
const expectedError = 'undefined end code';
const undefinedValues = ['', undefined];
for (const undefinedValue of undefinedValues) {
// act
const act = () => new ScriptingDefinitionBuilder()
.withEndCode(undefinedValue)
.build();
// assert
expect(act).to.throw(expectedError);
}
});
});
});
class ScriptingDefinitionBuilder {
private language = ScriptingLanguage.shellscript;
private startCode = 'REM start-code';
private endCode = 'REM end-code';
private language = ScriptingLanguage.shellscript;
public withLanguage(language: ScriptingLanguage): ScriptingDefinitionBuilder {
this.language = language;
return this;
}
private startCode = 'REM start-code';
public withStartCode(startCode: string): ScriptingDefinitionBuilder {
this.startCode = startCode;
return this;
}
private endCode = 'REM end-code';
public withEndCode(endCode: string): ScriptingDefinitionBuilder {
this.endCode = endCode;
return this;
}
public withLanguage(language: ScriptingLanguage): ScriptingDefinitionBuilder {
this.language = language;
return this;
}
public build(): ScriptingDefinition {
return new ScriptingDefinition(
this.language, this.startCode, this.endCode);
}
public withStartCode(startCode: string): ScriptingDefinitionBuilder {
this.startCode = startCode;
return this;
}
public withEndCode(endCode: string): ScriptingDefinitionBuilder {
this.endCode = endCode;
return this;
}
public build(): ScriptingDefinition {
return new ScriptingDefinition(this.language, this.startCode, this.endCode);
}
}