Refactor to unify scripts/categories as Executable

This commit consolidates scripts and categories under a unified
'Executable' concept. This simplifies the architecture and improves code
readability.

- Introduce subfolders within `src/domain` to segregate domain elements.
- Update class and interface names by removing the 'I' prefix in
  alignment with new coding standards.
- Replace 'Node' with 'Executable' to clarify usage; reserve 'Node'
  exclusively for the UI's tree component.
This commit is contained in:
undergroundwires
2024-06-12 12:36:40 +02:00
parent 8becc7dbc4
commit c138f74460
230 changed files with 1120 additions and 1039 deletions

View File

@@ -0,0 +1,240 @@
import { describe, it, expect } from 'vitest';
import type { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
import { FunctionParameterCollectionStub } from '@tests/unit/shared/Stubs/FunctionParameterCollectionStub';
import { createCallerFunction, createFunctionWithInlineCode } from '@/application/Parser/Executable/Script/Compiler/Function/SharedFunction';
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
import { type CallFunctionBody, FunctionBodyType, type ISharedFunction } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
import {
getAbsentStringTestCases, itEachAbsentCollectionValue,
itEachAbsentStringValue,
} from '@tests/unit/shared/TestCases/AbsentTests';
import { expectCallsFunctionBody, expectCodeFunctionBody } from './ExpectFunctionBodyType';
describe('SharedFunction', () => {
describe('SharedFunction', () => {
describe('name', () => {
runForEachFactoryMethod((build) => {
it('sets as expected', () => {
// arrange
const expected = 'expected-function-name';
const builder = new SharedFunctionBuilder()
.withName(expected);
// act
const sut = build(builder);
// assert
expect(sut.name).equal(expected);
});
describe('throws when absent', () => {
itEachAbsentStringValue((absentValue) => {
// arrange
const expectedError = 'missing function name';
const builder = new SharedFunctionBuilder()
.withName(absentValue);
// act
const act = () => build(builder);
// assert
expect(act).to.throw(expectedError);
}, { excludeNull: true, excludeUndefined: true });
});
});
});
describe('parameters', () => {
runForEachFactoryMethod((build) => {
it('sets as expected', () => {
// arrange
const expected = new FunctionParameterCollectionStub()
.withParameterName('test-parameter');
const builder = new SharedFunctionBuilder()
.withParameters(expected);
// act
const sut = build(builder);
// assert
expect(sut.parameters).equal(expected);
});
});
});
});
describe('createFunctionWithInlineCode', () => {
describe('code', () => {
it('sets as expected', () => {
// arrange
const expected = 'expected-code';
// act
const sut = new SharedFunctionBuilder()
.withCode(expected)
.createFunctionWithInlineCode();
// assert
expectCodeFunctionBody(sut.body);
expect(sut.body.code.execute).equal(expected);
});
describe('throws if absent', () => {
itEachAbsentStringValue((absentValue) => {
// arrange
const functionName = 'expected-function-name';
const expectedError = `undefined code in function "${functionName}"`;
const invalidValue = absentValue;
// act
const act = () => new SharedFunctionBuilder()
.withName(functionName)
.withCode(invalidValue)
.createFunctionWithInlineCode();
// assert
expect(act).to.throw(expectedError);
}, { excludeUndefined: true, excludeNull: true });
});
});
describe('revertCode', () => {
it('sets as expected', () => {
// arrange
const revertCodeTestValues: readonly (string | undefined)[] = [
'expected-revert-code',
...getAbsentStringTestCases({
excludeNull: true,
}).map((testCase) => testCase.absentValue),
];
for (const revertCode of revertCodeTestValues) {
// act
const sut = new SharedFunctionBuilder()
.withRevertCode(revertCode)
.createFunctionWithInlineCode();
// assert
expectCodeFunctionBody(sut.body);
expect(sut.body.code.revert).equal(revertCode);
}
});
});
it('sets type as expected', () => {
// arrange
const expectedType = FunctionBodyType.Code;
// act
const sut = new SharedFunctionBuilder()
.createFunctionWithInlineCode();
// assert
expect(sut.body.type).equal(expectedType);
});
it('calls are undefined', () => {
// arrange
const expectedCalls = undefined;
// act
const sut = new SharedFunctionBuilder()
.createFunctionWithInlineCode();
// assert
expect((sut.body as CallFunctionBody).calls).equal(expectedCalls);
});
});
describe('createCallerFunction', () => {
describe('rootCallSequence', () => {
it('sets as expected', () => {
// arrange
const expected = [
new FunctionCallStub().withFunctionName('firstFunction'),
new FunctionCallStub().withFunctionName('secondFunction'),
];
// act
const sut = new SharedFunctionBuilder()
.withRootCallSequence(expected)
.createCallerFunction();
// assert
expectCallsFunctionBody(sut.body);
expect(sut.body.calls).equal(expected);
});
describe('throws if missing', () => {
itEachAbsentCollectionValue<FunctionCall>((absentValue) => {
// arrange
const functionName = 'invalidFunction';
const rootCallSequence = absentValue;
const expectedError = `missing call sequence in function "${functionName}"`;
// act
const act = () => new SharedFunctionBuilder()
.withName(functionName)
.withRootCallSequence(rootCallSequence)
.createCallerFunction();
// assert
expect(act).to.throw(expectedError);
}, { excludeUndefined: true, excludeNull: true });
});
});
it('sets type as expected', () => {
// arrange
const expectedType = FunctionBodyType.Calls;
// act
const sut = new SharedFunctionBuilder()
.createCallerFunction();
// assert
expect(sut.body.type).equal(expectedType);
});
});
});
function runForEachFactoryMethod(
act: (action: (sut: SharedFunctionBuilder) => ISharedFunction) => void,
): void {
describe('createCallerFunction', () => {
const action = (builder: SharedFunctionBuilder) => builder.createCallerFunction();
act(action);
});
describe('createFunctionWithInlineCode', () => {
const action = (builder: SharedFunctionBuilder) => builder.createFunctionWithInlineCode();
act(action);
});
}
/*
Using an abstraction here allows for easy refactorings in
parameters or moving between functional and object-oriented
solutions without refactorings all tests.
*/
class SharedFunctionBuilder {
private name = 'name';
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
private callSequence: readonly FunctionCall[] = [new FunctionCallStub()];
private code = `[${SharedFunctionBuilder.name}] code`;
private revertCode: string | undefined = `[${SharedFunctionBuilder.name}] revert-code`;
public createCallerFunction(): ISharedFunction {
return createCallerFunction(
this.name,
this.parameters,
this.callSequence,
);
}
public createFunctionWithInlineCode(): ISharedFunction {
return createFunctionWithInlineCode(
this.name,
this.parameters,
this.code,
this.revertCode,
);
}
public withName(name: string) {
this.name = name;
return this;
}
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
this.parameters = parameters;
return this;
}
public withCode(code: string) {
this.code = code;
return this;
}
public withRevertCode(revertCode: string | undefined) {
this.revertCode = revertCode;
return this;
}
public withRootCallSequence(callSequence: readonly FunctionCall[]) {
this.callSequence = callSequence;
return this;
}
}