This commit adds validation logic in compiler to check for max allowed characters per line for scripts. This allows preventing bugs caused by limitation of terminal emulators. Other supporting changes: - Rename/refactor related code for clarity and better maintainability. - Drop `I` prefix from interfaces to align with latest convention. - Refactor CodeValidator to be functional rather than object-oriented for simplicity. - Refactor syntax definition construction to be functional and be part of rule for better separation of concerns. - Refactored validation logic to use an enum-based factory pattern for improved maintainability and scalability.
125 lines
5.0 KiB
TypeScript
125 lines
5.0 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { CodeValidationRule } from '@/application/Parser/Executable/Script/Validation/CodeValidationRule';
|
|
import { analyzeEmptyLines } from '@/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeEmptyLines';
|
|
import { analyzeDuplicateLines } from '@/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeDuplicateLines';
|
|
import { analyzeTooLongLines } from '@/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeTooLongLines';
|
|
import { createValidationAnalyzers } from '@/application/Parser/Executable/Script/Validation/ValidationRuleAnalyzerFactory';
|
|
import type { CodeValidationAnalyzer } from '@/application/Parser/Executable/Script/Validation/Analyzers/CodeValidationAnalyzer';
|
|
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
|
|
|
describe('ValidationRuleAnalyzerFactory', () => {
|
|
describe('createValidationAnalyzers', () => {
|
|
it('throws error when no rules are provided', () => {
|
|
// arrange
|
|
const expectedErrorMessage = 'missing rules';
|
|
const rules: readonly CodeValidationRule[] = [];
|
|
const context = new TestContext()
|
|
.withRules(rules);
|
|
// act
|
|
const act = () => context.create();
|
|
// assert
|
|
expect(act).to.throw(expectedErrorMessage);
|
|
});
|
|
|
|
it('creates correct analyzers for all valid rules', () => {
|
|
// arrange
|
|
const expectedAnalyzersForRules: Record<CodeValidationRule, CodeValidationAnalyzer> = {
|
|
[CodeValidationRule.NoEmptyLines]: analyzeEmptyLines,
|
|
[CodeValidationRule.NoDuplicatedLines]: analyzeDuplicateLines,
|
|
[CodeValidationRule.NoTooLongLines]: analyzeTooLongLines,
|
|
};
|
|
const givenRules: CodeValidationRule[] = Object
|
|
.keys(expectedAnalyzersForRules)
|
|
.map((r) => Number(r) as CodeValidationRule);
|
|
const context = new TestContext()
|
|
.withRules(givenRules);
|
|
// act
|
|
const actualAnalyzers = context.create();
|
|
// assert
|
|
expect(actualAnalyzers).to.have.lengthOf(Object.entries(expectedAnalyzersForRules).length);
|
|
const expectedAnalyzers = Object.values(expectedAnalyzersForRules);
|
|
expect(actualAnalyzers).to.deep.equal(expectedAnalyzers);
|
|
});
|
|
|
|
it('throws error for unknown rule', () => {
|
|
// arrange
|
|
const unknownRule = 9999 as CodeValidationRule;
|
|
const expectedErrorMessage = `Unknown rule: ${unknownRule}`;
|
|
const context = new TestContext()
|
|
.withRules([unknownRule]);
|
|
// arrange
|
|
const act = () => context.create();
|
|
// assert
|
|
expect(act).to.throw(expectedErrorMessage);
|
|
});
|
|
|
|
it('throws error for duplicate rules', () => {
|
|
// arrange
|
|
const duplicate1 = CodeValidationRule.NoEmptyLines;
|
|
const duplicate2 = CodeValidationRule.NoDuplicatedLines;
|
|
const rules: CodeValidationRule[] = [
|
|
duplicate1, duplicate1,
|
|
duplicate2, duplicate2,
|
|
];
|
|
const expectedErrorMessage: string = [
|
|
'Duplicate rules are not allowed.',
|
|
`Duplicates found: ${CodeValidationRule[duplicate1]} (2 times), ${CodeValidationRule[duplicate2]} (2 times)`,
|
|
].join(' ');
|
|
const context = new TestContext()
|
|
.withRules(rules);
|
|
// act
|
|
const act = () => context.create();
|
|
// assert
|
|
expect(act).to.throw(expectedErrorMessage);
|
|
});
|
|
|
|
it('handles single rule correctly', () => {
|
|
// arrange
|
|
const givenRule = CodeValidationRule.NoEmptyLines;
|
|
const expectedAnalyzer = analyzeEmptyLines;
|
|
const context = new TestContext()
|
|
.withRules([givenRule]);
|
|
// act
|
|
const analyzers = context.create();
|
|
// assert
|
|
expect(analyzers).to.have.lengthOf(1);
|
|
expect(analyzers[0]).toBe(expectedAnalyzer);
|
|
});
|
|
|
|
it('handles multiple unique rules correctly', () => {
|
|
// arrange
|
|
const expectedRuleAnalyzerPairs = new Map<CodeValidationRule, CodeValidationAnalyzer>([
|
|
[CodeValidationRule.NoEmptyLines, analyzeEmptyLines],
|
|
[CodeValidationRule.NoDuplicatedLines, analyzeDuplicateLines],
|
|
]);
|
|
const rules = Array.from(expectedRuleAnalyzerPairs.keys());
|
|
const context = new TestContext()
|
|
.withRules(rules);
|
|
// act
|
|
const actualAnalyzers = context.create();
|
|
// assert
|
|
expect(actualAnalyzers).to.have.lengthOf(expectedRuleAnalyzerPairs.size);
|
|
actualAnalyzers.forEach((analyzer, index) => {
|
|
const rule = rules[index];
|
|
const expectedAnalyzer = expectedRuleAnalyzerPairs.get(rule);
|
|
expect(analyzer).to.equal(expectedAnalyzer, formatAssertionMessage([
|
|
`Analyzer for rule ${CodeValidationRule[rule]} does not match the expected analyzer`,
|
|
]));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
class TestContext {
|
|
private rules: readonly CodeValidationRule[] = [CodeValidationRule.NoDuplicatedLines];
|
|
|
|
public withRules(rules: readonly CodeValidationRule[]): this {
|
|
this.rules = rules;
|
|
return this;
|
|
}
|
|
|
|
public create(): ReturnType<typeof createValidationAnalyzers> {
|
|
return createValidationAnalyzers(this.rules);
|
|
}
|
|
}
|