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

@@ -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;
}
}

View File

@@ -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);
});
});
});
});
});

View File

@@ -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);
});
});

View File

@@ -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;
}
}

View File

@@ -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);
});
});

View File

@@ -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);
});
}