Files
privacy.sexy/tests/unit/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeTooLongLines.spec.ts
undergroundwires e17744faf0 Bump to TypeScript 5.5 and enable noImplicitAny
This commit upgrades TypeScript from 5.4 to 5.5 and enables the
`noImplicitAny` option for stricter type checking. It refactors code to
comply with `noImplicitAny` and adapts to new TypeScript features and
limitations.

Key changes:

- Migrate from TypeScript 5.4 to 5.5
- Enable `noImplicitAny` for stricter type checking
- Refactor code to comply with new TypeScript features and limitations

Other supporting changes:

- Refactor progress bar handling for type safety
- Drop 'I' prefix from interfaces to align with new code convention
- Update TypeScript target from `ES2017` and `ES2018`.
  This allows named capturing groups. Otherwise, new TypeScript compiler
  does not compile the project and shows the following error:
  ```
  ...
  TimestampedFilenameGenerator.spec.ts:105:23 - error TS1503: Named capturing groups are only available when targeting 'ES2018' or later
  const pattern = /^(?<timestamp>\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})-(?<scriptName>[^.]+?)(?:\.(?<extension>[^.]+))?$/;// timestamp-scriptName.extension
  ...
  ```
- Refactor usage of `electron-progressbar` for type safety and
  less complexity.
2024-09-26 16:07:37 +02:00

200 lines
7.1 KiB
TypeScript

import { describe } from 'vitest';
import type { CodeLine, InvalidCodeLine } from '@/application/Parser/Executable/Script/Validation/Analyzers/CodeValidationAnalyzer';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { analyzeTooLongLines } from '@/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeTooLongLines';
import { createCodeLines } from './CreateCodeLines';
import { expectInvalidCodeLines, expectSameInvalidCodeLines } from './ExpectSameInvalidCodeLines';
describe('AnalyzeTooLongLines', () => {
describe('analyzeTooLongLines', () => {
describe('returns no results for lines within the maximum length', () => {
createScriptLanguageScenarios().forEach((scenario) => {
it(scenario.description, () => {
// arrange
const expected: InvalidCodeLine[] = [];
const context = new TestContext()
.withLanguage(scenario.language)
.withLines([
'A'.repeat(scenario.maxLength),
'B'.repeat(scenario.maxLength - 1),
'C'.repeat(100),
]);
// act
const actual = context.analyze();
// assert
expectSameInvalidCodeLines(actual, expected);
});
});
});
describe('identifies a single line exceeding maximum length', () => {
createScriptLanguageScenarios().forEach((scenario) => {
it(scenario.description, () => {
// arrange
const expectedLength = scenario.maxLength + 1;
const expectedLineNumber = 2;
const context = new TestContext()
.withLanguage(scenario.language)
.withLines([
'A'.repeat(scenario.maxLength),
'B'.repeat(expectedLength),
'C'.repeat(100),
]);
// act
const actual = context.analyze();
// assert
expectInvalidCodeLines(actual, [expectedLineNumber]);
});
});
});
describe('identifies multiple lines exceeding maximum length', () => {
createScriptLanguageScenarios().forEach((scenario) => {
it(scenario.description, () => {
// arrange
const expectedInvalidLines: readonly {
readonly lineNumber: number,
readonly length: number,
}[] = [
{ lineNumber: 1, length: scenario.maxLength + 1 },
{ lineNumber: 3, length: scenario.maxLength + 2 },
];
const context = new TestContext()
.withLanguage(scenario.language)
.withLines([
'A'.repeat(expectedInvalidLines[0].length),
'B'.repeat(scenario.maxLength),
'C'.repeat(expectedInvalidLines[1].length),
]);
// act
const actual = context.analyze();
// assert
expectInvalidCodeLines(actual, expectedInvalidLines.map((l) => l.lineNumber));
});
});
});
describe('error construction', () => {
describe('outputs actual character length', () => {
createScriptLanguageScenarios().forEach((scenario) => {
it(scenario.description, () => {
// arrange
const expectedLength = scenario.maxLength + 1;
const expectedErrorPart = `Line is too long (${expectedLength}).`;
const context = new TestContext()
.withLanguage(scenario.language)
.withLines([
'B'.repeat(expectedLength),
]);
// act
const actual = context.analyze();
// assert
expect(actual).to.have.lengthOf(1);
const actualError = actual[0].error;
expect(actualError).to.include(expectedErrorPart);
});
});
});
describe('outputs maximum allowed character length', () => {
createScriptLanguageScenarios().forEach((scenario) => {
it(scenario.description, () => {
// arrange
const expectedLength = scenario.maxLength + 1;
const expectedErrorPart = `It exceed maximum allowed length ${scenario.maxLength}`;
const context = new TestContext()
.withLanguage(scenario.language)
.withLines([
'B'.repeat(expectedLength),
]);
// act
const actual = context.analyze();
// assert
expect(actual).to.have.lengthOf(1);
const actualError = actual[0].error;
expect(actualError).to.include(expectedErrorPart);
});
});
});
describe('outputs total exceeding characters', () => {
createScriptLanguageScenarios().forEach((scenario) => {
it(scenario.description, () => {
// arrange
const expectedExceedingCharacters = 5;
const expectedErrorPart = `by ${expectedExceedingCharacters} characters.`;
const lineLength = scenario.maxLength + expectedExceedingCharacters;
const context = new TestContext()
.withLanguage(scenario.language)
.withLines([
'B'.repeat(lineLength),
]);
// act
const actual = context.analyze();
// assert
expect(actual).to.have.lengthOf(1);
const actualError = actual[0].error;
expect(actualError).to.include(expectedErrorPart);
});
});
});
});
it('throws an error for unsupported language', () => {
// arrange
const unsupportedLanguage = 'unsupported' as unknown as ScriptingLanguage;
const expectedError = `Unsupported language: ${ScriptingLanguage[unsupportedLanguage]} (${unsupportedLanguage})`;
const context = new TestContext()
.withLanguage('unsupported' as unknown as ScriptingLanguage)
.withLines(['A', 'B', 'C']);
// act
const act = () => context.analyze();
// assert
expect(act).to.throw(expectedError);
});
});
});
interface ScriptLanguageScenario {
readonly description: string;
readonly maxLength: number;
readonly language: ScriptingLanguage;
}
function createScriptLanguageScenarios(): readonly ScriptLanguageScenario[] {
const maxLengths: Record< // `Record` catches missing entries at compile-time
ScriptingLanguage, number> = {
[ScriptingLanguage.batchfile]: 8191,
[ScriptingLanguage.shellscript]: 1048576,
};
return Object.entries(maxLengths).map(([language, length]): ScriptLanguageScenario => {
const languageValue = Number.parseInt(language, 10) as ScriptingLanguage;
return {
description: `${ScriptingLanguage[languageValue]} (max: ${length})`,
language: languageValue,
maxLength: length,
};
});
}
class TestContext {
private codeLines: readonly CodeLine[] = createCodeLines(['test-code-line']);
private language = ScriptingLanguage.batchfile;
public withLines(lines: readonly string[]): this {
this.codeLines = createCodeLines(lines);
return this;
}
public withLanguage(language: ScriptingLanguage): this {
this.language = language;
return this;
}
public analyze() {
return analyzeTooLongLines(
this.codeLines,
this.language,
);
}
}