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:
@@ -0,0 +1,33 @@
|
||||
import { describe } from 'vitest';
|
||||
import { EscapeDoubleQuotes } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes';
|
||||
import { getAbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { runPipeTests } from './PipeTestRunner';
|
||||
|
||||
describe('EscapeDoubleQuotes', () => {
|
||||
// arrange
|
||||
const sut = new EscapeDoubleQuotes();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
...getAbsentStringTestCases({ excludeNull: true, excludeUndefined: true })
|
||||
.map((testCase) => ({
|
||||
name: 'returns as it is when if input is missing',
|
||||
input: testCase.absentValue,
|
||||
expectedOutput: testCase.absentValue,
|
||||
})),
|
||||
{
|
||||
name: 'using "',
|
||||
input: 'hello "world"',
|
||||
expectedOutput: 'hello "^""world"^""',
|
||||
},
|
||||
{
|
||||
name: 'not using any double quotes',
|
||||
input: 'hello world',
|
||||
expectedOutput: 'hello world',
|
||||
},
|
||||
{
|
||||
name: 'consecutive double quotes',
|
||||
input: '""hello world""',
|
||||
expectedOutput: '"^"""^""hello world"^"""^""',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,465 @@
|
||||
import { describe } from 'vitest';
|
||||
import { InlinePowerShell } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/PipeDefinitions/InlinePowerShell';
|
||||
import { getAbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { type IPipeTestCase, runPipeTests } from './PipeTestRunner';
|
||||
|
||||
describe('InlinePowerShell', () => {
|
||||
// arrange
|
||||
const sut = new InlinePowerShell();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
...getAbsentStringTestCases({ excludeNull: true, excludeUndefined: true })
|
||||
.map((testCase) => ({
|
||||
name: 'returns as it is when if input is missing',
|
||||
input: testCase.absentValue,
|
||||
expectedOutput: '',
|
||||
})),
|
||||
...prefixTests('newline', getNewLineCases()),
|
||||
...prefixTests('comment', getCommentCases()),
|
||||
...prefixTests('here-string', hereStringCases()),
|
||||
...prefixTests('backtick', backTickCases()),
|
||||
]);
|
||||
});
|
||||
|
||||
function hereStringCases(): IPipeTestCase[] {
|
||||
const expectLinesInDoubleQuotes = (...lines: string[]) => lines.join('`r`n');
|
||||
const expectLinesInSingleQuotes = (...lines: string[]) => lines.join('\'+"`r`n"+\'');
|
||||
return [
|
||||
{
|
||||
name: 'adds newlines for double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'"Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds newlines for single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with character after here string header',
|
||||
input: getWindowsLines(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match if there\'s character before here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with different here-string header/terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner single quoted here-string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerDoubleQuotedTerminator = @"',
|
||||
'inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$hasInnerDoubleQuotedTerminator = "inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner double quoted string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerSingleQuotedTerminator = @\'',
|
||||
'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'$hasInnerSingleQuotedTerminator = \'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches if there\'s character after here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'\'@ after',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'lorem\' after',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'escapes double quotes inside double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'For help, type "get-help"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"For help, type `"get-help`""',
|
||||
},
|
||||
{
|
||||
name: 'escapes single quotes inside single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'For help, type \'get-help\'',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: '\'For help, type \'\'get-help\'\'\'',
|
||||
},
|
||||
{
|
||||
name: 'converts when here-string header is not at line start',
|
||||
input: getWindowsLines(
|
||||
'$page = [XML] @"',
|
||||
'multi-lined',
|
||||
'and "quoted"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$page = [XML] "multi-lined',
|
||||
'and `"quoted`""',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims after here-string header',
|
||||
input: getWindowsLines(
|
||||
'@" \t',
|
||||
'text with whitespaces at here-string start',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"text with whitespaces at here-string start"',
|
||||
},
|
||||
{
|
||||
name: 'preserves whitespaces in lines',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around ',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around \'',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function backTickCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick and different line endings',
|
||||
input: 'Get-Service `\n'
|
||||
+ '* `\r'
|
||||
+ '| Sort-Object StartType `\r\n'
|
||||
+ '| Format-Table -AutoSize',
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'trims tabs and whitespaces on next lines when wrapping with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'\t| Sort-Object StartType `',
|
||||
' | Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'does not wrap without whitespace before backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not wrap with characters after',
|
||||
input: getWindowsLines(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getCommentCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'converts hash comments in the line end',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t# Comment after tab',
|
||||
'$text+= #Comment without space after hash',
|
||||
'Write-Host $text# Comment without space before hash',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# Comment after tab #>',
|
||||
'$text+= <# Comment without space after hash #>',
|
||||
'Write-Host $text<# Comment without space before hash #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts hash comment line',
|
||||
input: getWindowsLines(
|
||||
'# Comment in first line',
|
||||
'Write-Host "Hello"',
|
||||
'# Comment in the middle',
|
||||
'Write-Host "World"',
|
||||
'# Consecutive comments',
|
||||
'# Last line comment without line ending in the end',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment in first line #>',
|
||||
'Write-Host "Hello"',
|
||||
'<# Comment in the middle #>',
|
||||
'Write-Host "World"',
|
||||
'<# Consecutive comments #>',
|
||||
'<# Last line comment without line ending in the end #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts inside',
|
||||
input: getWindowsLines(
|
||||
'$text+= #Comment with < inside',
|
||||
'$text+= #Comment ending with >',
|
||||
'$text+= #Comment with <# inline comment #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text+= <# Comment with < inside #>',
|
||||
'$text+= <# Comment ending with > #>',
|
||||
'$text+= <# Comment with <# inline comment #> #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts around', // Pretty uncommon
|
||||
input: getWindowsLines(
|
||||
'Write-Host "hi" # Comment ending line inline comment but not one #>',
|
||||
'Write-Host "hi" #>Comment starting like inline comment end but not one',
|
||||
// Following line does not compile as valid PowerShell due to missing #> for inline comment.
|
||||
'Write-Host "hi" <#Comment starting like inline comment start but not one',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "hi" <# Comment ending line inline comment but not one #> #>',
|
||||
'Write-Host "hi" <# >Comment starting like inline comment end but not one #>',
|
||||
'Write-Host "hi" <<# Comment starting like inline comment start but not one #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts empty hash comment',
|
||||
input: getWindowsLines(
|
||||
'Write-Host "Comment without text" #',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "Comment without text" <##>',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds whitespaces around to match',
|
||||
input: getWindowsLines(
|
||||
'#Comment line with no whitespaces around',
|
||||
'Write-Host "Hello"#Comment in the end with no whitespaces around',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment line with no whitespaces around #>',
|
||||
'Write-Host "Hello"<# Comment in the end with no whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces around comment',
|
||||
input: getWindowsLines(
|
||||
'# Comment with whitespaces around ',
|
||||
'#\tComment with tabs around\t\t',
|
||||
'#\t Comment with tabs and whitespaces around \t \t',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment with whitespaces around #>',
|
||||
'<# Comment with tabs around #>',
|
||||
'<# Comment with tabs and whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not convert block comments',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not process if there are no multi lines',
|
||||
input: 'Write-Host "expected" # as it is!',
|
||||
expectedOutput: 'Write-Host "expected" # as it is!',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getNewLineCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'no new line',
|
||||
input: 'Write-Host \'Hello, World!\'',
|
||||
expectedOutput: 'Write-Host \'Hello, World!\'',
|
||||
},
|
||||
{
|
||||
name: '\\n new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\n double empty lines are ignored',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\n\nforeach ($thing in $things) {'
|
||||
+ '\n\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\n\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\rforeach ($thing in $things) {'
|
||||
+ '\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r and \\n newlines combined',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\r\nforeach ($thing in $things) {'
|
||||
+ '\n\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces on lines',
|
||||
input:
|
||||
' $things = Get-ChildItem C:\\Windows\\ '
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\n\tWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r \n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function prefixTests(prefix: string, tests: IPipeTestCase[]): IPipeTestCase[] {
|
||||
return tests.map((test) => ({
|
||||
name: `[${prefix}] ${test.name}`,
|
||||
input: test.input,
|
||||
expectedOutput: test.expectedOutput,
|
||||
}));
|
||||
}
|
||||
|
||||
function getWindowsLines(...lines: string[]) {
|
||||
return lines.join('\r\n');
|
||||
}
|
||||
|
||||
function getSingleLinedOutput(...lines: string[]) {
|
||||
return lines.map((line) => line.trim()).join('; ');
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { it, expect } from 'vitest';
|
||||
import type { IPipe } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/IPipe';
|
||||
|
||||
export interface IPipeTestCase {
|
||||
readonly name: string;
|
||||
readonly input: string;
|
||||
readonly expectedOutput: string;
|
||||
}
|
||||
|
||||
export function runPipeTests(sut: IPipe, testCases: IPipeTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const actual = sut.apply(testCase.input);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedOutput);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { PipeFactory } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/PipeFactory';
|
||||
import { PipeStub } from '@tests/unit/shared/Stubs/PipeStub';
|
||||
import { getAbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
|
||||
describe('PipeFactory', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws when instances with same name is registered', () => {
|
||||
// arrange
|
||||
const duplicateName = 'duplicateName';
|
||||
const expectedError = `Pipe name must be unique: "${duplicateName}"`;
|
||||
const pipes = [
|
||||
new PipeStub().withName(duplicateName),
|
||||
new PipeStub().withName('uniqueName'),
|
||||
new PipeStub().withName(duplicateName),
|
||||
];
|
||||
// act
|
||||
const act = () => new PipeFactory(pipes);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws when name is invalid', () => {
|
||||
// act
|
||||
const act = (invalidName: string) => new PipeFactory([new PipeStub().withName(invalidName)]);
|
||||
// assert
|
||||
testPipeNameValidation(act);
|
||||
});
|
||||
});
|
||||
describe('get', () => {
|
||||
describe('throws when name is invalid', () => {
|
||||
// arrange
|
||||
const sut = new PipeFactory();
|
||||
// act
|
||||
const act = (invalidName: string) => sut.get(invalidName);
|
||||
// assert
|
||||
testPipeNameValidation(act);
|
||||
});
|
||||
it('gets registered instance when it exists', () => {
|
||||
// arrange
|
||||
const expected = new PipeStub().withName('expectedName');
|
||||
const pipes = [expected, new PipeStub().withName('instanceToConfuse')];
|
||||
const sut = new PipeFactory(pipes);
|
||||
// act
|
||||
const actual = sut.get(expected.name);
|
||||
// expect
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('throws when instance does not exist', () => {
|
||||
// arrange
|
||||
const missingName = 'missingName';
|
||||
const expectedError = `Unknown pipe: "${missingName}"`;
|
||||
const pipes = [];
|
||||
const sut = new PipeFactory(pipes);
|
||||
// act
|
||||
const act = () => sut.get(missingName);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function testPipeNameValidation(testRunner: (invalidName: string) => void) {
|
||||
const testCases = [
|
||||
// Validate missing value
|
||||
...getAbsentStringTestCases({ excludeNull: true, excludeUndefined: true })
|
||||
.map((testCase) => ({
|
||||
name: `empty pipe name (${testCase.valueName})`,
|
||||
value: testCase.absentValue,
|
||||
expectedError: 'empty pipe name',
|
||||
})),
|
||||
// Validate camelCase
|
||||
...[
|
||||
'PascalCase',
|
||||
'snake-case',
|
||||
'includesNumb3rs',
|
||||
'includes Whitespace',
|
||||
'noSpec\'ial',
|
||||
].map((nonCamelCaseValue) => ({
|
||||
name: `non camel case value (${nonCamelCaseValue})`,
|
||||
value: nonCamelCaseValue,
|
||||
expectedError: `Pipe name should be camelCase: "${nonCamelCaseValue}"`,
|
||||
})),
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const invalidName = testCase.value;
|
||||
const { expectedError } = testCase;
|
||||
// act
|
||||
const act = () => testRunner(invalidName);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { PipelineCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/PipelineCompiler';
|
||||
import { type IPipelineCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/IPipelineCompiler';
|
||||
import type { IPipeFactory } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/PipeFactory';
|
||||
import { PipeStub } from '@tests/unit/shared/Stubs/PipeStub';
|
||||
import { PipeFactoryStub } from '@tests/unit/shared/Stubs/PipeFactoryStub';
|
||||
import { getAbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
|
||||
describe('PipelineCompiler', () => {
|
||||
describe('compile', () => {
|
||||
describe('throws for invalid arguments', () => {
|
||||
interface ITestCase {
|
||||
readonly name: string;
|
||||
readonly act: (test: PipelineTestRunner) => PipelineTestRunner;
|
||||
readonly expectedError: string;
|
||||
}
|
||||
const testCases: ITestCase[] = [
|
||||
...getAbsentStringTestCases({ excludeNull: true, excludeUndefined: true })
|
||||
.map((testCase) => ({
|
||||
name: `"value" is ${testCase.valueName}`,
|
||||
act: (test) => test.withValue(testCase.absentValue),
|
||||
expectedError: 'missing value',
|
||||
})),
|
||||
...getAbsentStringTestCases({ excludeNull: true, excludeUndefined: true })
|
||||
.map((testCase) => ({
|
||||
name: `"pipeline" is ${testCase.valueName}`,
|
||||
act: (test) => test.withPipeline(testCase.absentValue),
|
||||
expectedError: 'missing pipeline',
|
||||
})),
|
||||
{
|
||||
name: '"pipeline" does not start with pipe',
|
||||
act: (test) => test.withPipeline('pipeline |'),
|
||||
expectedError: 'pipeline does not start with pipe',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const runner = new PipelineTestRunner();
|
||||
testCase.act(runner);
|
||||
const act = () => runner.compile();
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('compiles pipeline as expected', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'compiles single pipe as expected',
|
||||
pipes: [
|
||||
new PipeStub().withName('doublePrint').withApplier((value) => `${value}-${value}`),
|
||||
],
|
||||
pipeline: '| doublePrint',
|
||||
value: 'value',
|
||||
expected: 'value-value',
|
||||
},
|
||||
{
|
||||
name: 'compiles multiple pipes as expected',
|
||||
pipes: [
|
||||
new PipeStub().withName('prependLetterA').withApplier((value) => `A-${value}`),
|
||||
new PipeStub().withName('prependLetterB').withApplier((value) => `B-${value}`),
|
||||
],
|
||||
pipeline: '| prependLetterA | prependLetterB',
|
||||
value: 'value',
|
||||
expected: 'B-A-value',
|
||||
},
|
||||
{
|
||||
name: 'compiles with relaxed whitespace placing',
|
||||
pipes: [
|
||||
new PipeStub().withName('appendNumberOne').withApplier((value) => `${value}1`),
|
||||
new PipeStub().withName('appendNumberTwo').withApplier((value) => `${value}2`),
|
||||
new PipeStub().withName('appendNumberThree').withApplier((value) => `${value}3`),
|
||||
],
|
||||
pipeline: ' | appendNumberOne|appendNumberTwo| appendNumberThree',
|
||||
value: 'value',
|
||||
expected: 'value123',
|
||||
},
|
||||
{
|
||||
name: 'can reuse same pipe',
|
||||
pipes: [
|
||||
new PipeStub().withName('removeFirstChar').withApplier((value) => `${value.slice(1)}`),
|
||||
],
|
||||
pipeline: ' | removeFirstChar | removeFirstChar | removeFirstChar',
|
||||
value: 'value',
|
||||
expected: 'ue',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const runner = new PipelineTestRunner()
|
||||
.withValue(testCase.value)
|
||||
.withPipeline(testCase.pipeline)
|
||||
.withFactory(new PipeFactoryStub().withPipes(testCase.pipes));
|
||||
// act
|
||||
const actual = runner.compile();
|
||||
// expect
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class PipelineTestRunner implements IPipelineCompiler {
|
||||
private value = 'non-empty-value';
|
||||
|
||||
private pipeline = '| validPipeline';
|
||||
|
||||
private factory: IPipeFactory = new PipeFactoryStub();
|
||||
|
||||
public withValue(value: string) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withPipeline(pipeline: string) {
|
||||
this.pipeline = pipeline;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withFactory(factory: IPipeFactory) {
|
||||
this.factory = factory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public compile(): string {
|
||||
const sut = new PipelineCompiler(this.factory);
|
||||
return sut.compile(this.value, this.pipeline);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user