Refactor text utilities and expand their usage

This commit refactors existing text utility functions into the
application layer for broad reuse and integrates them across
the codebase. Initially, these utilities were confined to test
code, which limited their application.

Changes:

- Move text utilities to the application layer.
- Centralize text utilities into dedicated files for better
  maintainability.
- Improve robustness of utility functions with added type checks.
- Replace duplicated logic with centralized utility functions
  throughout the codebase.
- Expand unit tests to cover refactored code parts.
This commit is contained in:
undergroundwires
2024-07-18 20:49:21 +02:00
parent 8d7a7eb434
commit 851917e049
45 changed files with 563 additions and 117 deletions

View File

@@ -5,6 +5,7 @@ import { CodePosition } from '@/application/Context/State/Code/Position/CodePosi
import { SelectedScriptStub } from '@tests/unit/shared/Stubs/SelectedScriptStub';
import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
import { collectExceptionMessage } from '@tests/unit/shared/ExceptionCollector';
describe('CodeChangedEvent', () => {
describe('ctor', () => {
@@ -19,16 +20,34 @@ describe('CodeChangedEvent', () => {
[new SelectedScriptStub(new ScriptStub('2')), new CodePosition(0, nonExistingLine2)],
]);
// act
let errorText = '';
try {
const actualErrorMessage = collectExceptionMessage(() => {
new CodeChangedEventBuilder()
.withCode(code)
.withNewScripts(newScripts)
.build();
} catch (error) { errorText = error.message; }
});
// assert
expect(errorText).to.include(nonExistingLine1);
expect(errorText).to.include(nonExistingLine2);
expect(actualErrorMessage).to.include(nonExistingLine1);
expect(actualErrorMessage).to.include(nonExistingLine2);
});
it('invalid line position validation counts empty lines', () => {
// arrange
const totalEmptyLines = 5;
const code = '\n'.repeat(totalEmptyLines);
// If empty lines would not be counted, this would result in error
const existingLineEnd = totalEmptyLines;
const newScripts = new Map<SelectedScript, ICodePosition>([
[new SelectedScriptStub(new ScriptStub('1')), new CodePosition(0, existingLineEnd)],
]);
// act
const act = () => {
new CodeChangedEventBuilder()
.withCode(code)
.withNewScripts(newScripts)
.build();
};
// assert
expect(act).to.not.throw();
});
describe('does not throw with valid code position', () => {
// arrange

View File

@@ -1,5 +1,6 @@
import { describe, it, expect } from 'vitest';
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
import { splitTextIntoLines } from '@/application/Common/Text/SplitTextIntoLines';
describe('CodeBuilder', () => {
class CodeBuilderConcrete extends CodeBuilder {
@@ -47,10 +48,24 @@ describe('CodeBuilder', () => {
.appendLine(expected);
// assert
const result = sut.toString();
const lines = getLines(result);
const lines = splitTextIntoLines(result);
expect(lines[1]).to.equal('str');
});
describe('append multi-line string as multiple lines', () => {
describe('append multi-line string correctly', () => {
it('appends multi-line string with empty lines preserved', () => {
// arrange
const sut = new CodeBuilderConcrete();
const expectedLines: string[] = ['', 'line1', '', 'line2', '', '', 'line3', '', ''];
const multilineInput = expectedLines.join('\n');
// act
sut.appendLine(multilineInput);
const actual = sut.toString();
// assert
const actualLines = splitTextIntoLines(actual);
expect(actualLines).to.deep.equal(expectedLines);
});
describe('recognizes different line terminators', () => {
const delimiters = ['\n', '\r\n', '\r'];
for (const delimiter of delimiters) {
@@ -64,7 +79,7 @@ describe('CodeBuilder', () => {
sut.appendLine(code);
// assert
const result = sut.toString();
const lines = getLines(result);
const lines = splitTextIntoLines(result);
expect(lines).to.have.lengthOf(2);
expect(lines[0]).to.equal(line1);
expect(lines[1]).to.equal(line2);
@@ -111,7 +126,7 @@ describe('CodeBuilder', () => {
sut.appendTrailingHyphensCommentLine(totalHyphens);
// assert
const result = sut.toString();
const lines = getLines(result);
const lines = splitTextIntoLines(result);
expect(lines[0]).to.equal(expected);
});
it('appendCommentLine', () => {
@@ -126,7 +141,7 @@ describe('CodeBuilder', () => {
.appendCommentLine(comment)
.toString();
// assert
const lines = getLines(result);
const lines = splitTextIntoLines(result);
expect(lines[0]).to.equal(expected);
});
it('appendCommentLineWithHyphensAround', () => {
@@ -142,7 +157,7 @@ describe('CodeBuilder', () => {
.appendCommentLineWithHyphensAround(sectionName, totalHyphens)
.toString();
// assert
const lines = getLines(result);
const lines = splitTextIntoLines(result);
expect(lines[1]).to.equal(expected);
});
describe('currentLine', () => {
@@ -180,7 +195,3 @@ describe('CodeBuilder', () => {
});
});
});
function getLines(text: string): string[] {
return text.split(/\r\n|\r|\n/);
}

View File

@@ -1,4 +1,5 @@
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
import { indentText } from '@/application/Common/Text/IndentText';
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
export function expectEqualSelectedScripts(
@@ -37,11 +38,11 @@ function expectSameRevertStates(
expect(scriptsWithDifferentRevertStates).to.have.lengthOf(0, formatAssertionMessage([
'Scripts with different revert states:',
scriptsWithDifferentRevertStates
.map((s) => [
.map((s) => indentText([
`Script ID: "${s.id}"`,
`Actual revert state: "${s.revert}"`,
`Expected revert state: "${expected.find((existing) => existing.id === s.id)?.revert ?? 'unknown'}"`,
].map((line) => `\t${line}`).join('\n'))
].join('\n')))
.join('\n---\n'),
]));
}