refactor folders to move "/state" (IApplicationState) inside "/context" (IApplicationContext)

This commit is contained in:
undergroundwires
2020-12-28 22:11:23 +01:00
parent f7557bcc0f
commit 34672414c3
64 changed files with 106 additions and 107 deletions

View File

@@ -0,0 +1,84 @@
import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
import { ApplicationCode } from '@/application/Context/State/Code/ApplicationCode';
import { ScriptStub } from '../../../stubs/ScriptStub';
import { CategoryStub } from '../../../stubs/CategoryStub';
import { ApplicationStub } from '../../../stubs/ApplicationStub';
import 'mocha';
import { expect } from 'chai';
import { ApplicationState } from '@/application/Context/State/ApplicationState';
import { IScript } from '@/domain/IScript';
describe('ApplicationState', () => {
describe('code', () => {
it('initialized with empty code', () => {
// arrange
const app = new ApplicationStub();
const sut = new ApplicationState(app);
// act
const code = sut.code.current;
// assert
expect(!code);
});
it('reacts to selection changes as expected', () => {
// arrange
const app = new ApplicationStub().withAction(new CategoryStub(0).withScriptIds('scriptId'));
const selectionStub = new UserSelection(app, []);
const expectedCodeGenerator = new ApplicationCode(selectionStub, app.scripting);
selectionStub.selectAll();
const expectedCode = expectedCodeGenerator.current;
// act
const sut = new ApplicationState(app);
sut.selection.selectAll();
const actualCode = sut.code.current;
// assert
expect(actualCode).to.equal(expectedCode);
});
});
describe('selection', () => {
it('initialized with no selection', () => {
// arrange
const app = new ApplicationStub();
const sut = new ApplicationState(app);
// act
const actual = sut.selection.totalSelected;
// assert
expect(actual).to.equal(0);
});
it('can select a script from current application', () => {
// arrange
const expectedScript = new ScriptStub('scriptId');
const app = new ApplicationStub().withAction(new CategoryStub(0).withScript(expectedScript));
const sut = new ApplicationState(app);
// act
sut.selection.selectAll();
// assert
expect(sut.selection.totalSelected).to.equal(1);
expect(sut.selection.isSelected(expectedScript.id)).to.equal(true);
});
});
describe('filter', () => {
it('initialized with an empty filter', () => {
// arrange
const app = new ApplicationStub();
const sut = new ApplicationState(app);
// act
const actual = sut.filter.currentFilter;
// assert
expect(actual).to.equal(undefined);
});
it('can match a script from current application', () => {
// arrange
const scriptNameFilter = 'scriptName';
const expectedScript = new ScriptStub('scriptId').withName(scriptNameFilter);
const app = new ApplicationStub()
.withAction(new CategoryStub(0).withScript(expectedScript));
const sut = new ApplicationState(app);
// act
let actualScript: IScript;
sut.filter.filtered.on((result) => actualScript = result.scriptMatches[0]);
sut.filter.setFilter(scriptNameFilter);
// assert
expect(expectedScript).to.equal(actualScript);
});
});
});

View File

@@ -0,0 +1,192 @@
import 'mocha';
import { expect } from 'chai';
import { CategoryStub } from '../../../../stubs/CategoryStub';
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { ApplicationStub } from '../../../../stubs/ApplicationStub';
import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
import { ApplicationCode } from '@/application/Context/State/Code/ApplicationCode';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { ICodeChangedEvent } from '@/application/Context/State/Code/Event/ICodeChangedEvent';
import { IUserScriptGenerator } from '@/application/Context/State/Code/Generation/IUserScriptGenerator';
import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition';
import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
import { ScriptingDefinitionStub } from '../../../../stubs/ScriptingDefinitionStub';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { IUserScript } from '@/application/Context/State/Code/Generation/IUserScript';
// TODO: Test scriptingDefinition: IScriptingDefinition logic
describe('ApplicationCode', () => {
describe('ctor', () => {
it('empty when selection is empty', () => {
// arrange
const selection = new UserSelection(new ApplicationStub(), []);
const definition = new ScriptingDefinitionStub();
const sut = new ApplicationCode(selection, definition);
// act
const actual = sut.current;
// assert
expect(actual).to.have.lengthOf(0);
});
it('generates code from script generator when selection is not empty', () => {
// arrange
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
const selection = new UserSelection(app, scripts.map((script) => script.toSelectedScript()));
const definition = new ScriptingDefinitionStub();
const expected: IUserScript = {
code: 'expected-code',
scriptPositions: new Map(),
};
const generator = new UserScriptGeneratorMock()
.plan({ scripts: selection.selectedScripts, definition }, expected);
const sut = new ApplicationCode(selection, definition, generator);
// act
const actual = sut.current;
// assert
expect(actual).to.equal(expected.code);
});
});
describe('changed event', () => {
describe('code', () => {
it('empty when nothing is selected', () => {
// arrange
let signaled: ICodeChangedEvent;
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
const selection = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
const definition = new ScriptingDefinitionStub();
const sut = new ApplicationCode(selection, definition);
sut.changed.on((code) => signaled = code);
// act
selection.changed.notify([]);
// assert
expect(signaled.code).to.have.lengthOf(0);
expect(signaled.code).to.equal(sut.current);
});
it('has code when some are selected', () => {
// arrange
let signaled: ICodeChangedEvent;
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
const selection = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
const definition = new ScriptingDefinitionStub();
const sut = new ApplicationCode(selection, definition);
sut.changed.on((code) => signaled = code);
// act
selection.changed.notify(scripts.map((s) => new SelectedScript(s, false)));
// assert
expect(signaled.code).to.have.length.greaterThan(0);
expect(signaled.code).to.equal(sut.current);
});
});
describe('calls UserScriptGenerator', () => {
it('sends scripting definition to generator', () => {
// arrange
const expectedDefinition = new ScriptingDefinitionStub();
const app = new ApplicationStub();
const selection = new UserSelection(app, []);
const generatorMock: IUserScriptGenerator = {
buildCode: (selectedScripts, definition) => {
if (definition !== expectedDefinition) {
throw new Error('Unexpected scripting definition');
}
return {
code: '',
scriptPositions: new Map<SelectedScript, ICodePosition>(),
};
},
};
// tslint:disable-next-line:no-unused-expression
new ApplicationCode(selection, expectedDefinition, generatorMock);
// act
const act = () => selection.changed.notify([]);
// assert
expect(act).to.not.throw();
});
it('sends selected scripts to generator', () => {
// arrange
const expectedDefinition = new ScriptingDefinitionStub();
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
const selection = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
const scriptsToSelect = scripts.map((s) => new SelectedScript(s, false));
const generatorMock: IUserScriptGenerator = {
buildCode: (selectedScripts) => {
if (JSON.stringify(selectedScripts) !== JSON.stringify(scriptsToSelect)) {
throw new Error('Unexpected scripts');
}
return {
code: '',
scriptPositions: new Map<SelectedScript, ICodePosition>(),
};
},
};
// tslint:disable-next-line:no-unused-expression
new ApplicationCode(selection, expectedDefinition, generatorMock);
// act
const act = () => selection.changed.notify(scriptsToSelect);
// assert
expect(act).to.not.throw();
});
it('sets positions from the generator', () => {
// arrange
let signaled: ICodeChangedEvent;
const scripts = [new ScriptStub('first'), new ScriptStub('second')];
const app = new ApplicationStub().withAction(new CategoryStub(1).withScripts(...scripts));
const selection = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
const scriptingDefinition = new ScriptingDefinitionStub();
const scriptsToSelect = scripts.map((s) => new SelectedScript(s, false));
const totalLines = 20;
const expected = new Map<SelectedScript, ICodePosition>(
[
[scriptsToSelect[0], new CodePosition(0, totalLines / 2)],
[scriptsToSelect[1], new CodePosition(totalLines / 2, totalLines)],
],
);
const generatorMock: IUserScriptGenerator = {
buildCode: () => {
return {
code: '\nREM LINE'.repeat(totalLines),
scriptPositions: expected,
};
},
};
const sut = new ApplicationCode(selection, scriptingDefinition, generatorMock);
sut.changed.on((code) => signaled = code);
// act
selection.changed.notify(scriptsToSelect);
// assert
expect(signaled.getScriptPositionInCode(scripts[0]))
.to.deep.equal(expected.get(scriptsToSelect[0]));
expect(signaled.getScriptPositionInCode(scripts[1]))
.to.deep.equal(expected.get(scriptsToSelect[1]));
});
});
});
});
interface IScriptGenerationParameters {
scripts: readonly SelectedScript[];
definition: IScriptingDefinition;
}
class UserScriptGeneratorMock implements IUserScriptGenerator {
private prePlanned = new Map<IScriptGenerationParameters, IUserScript>();
public plan(
parameters: IScriptGenerationParameters,
result: IUserScript): UserScriptGeneratorMock {
this.prePlanned.set(parameters, result);
return this;
}
public buildCode(
selectedScripts: readonly SelectedScript[],
scriptingDefinition: IScriptingDefinition): IUserScript {
for (const [parameters, result] of Array.from(this.prePlanned)) {
if (selectedScripts === parameters.scripts
&& scriptingDefinition === parameters.definition) {
return result;
}
}
throw new Error('Unexpected parameters');
}
}

View File

@@ -0,0 +1,147 @@
import 'mocha';
import { expect } from 'chai';
import { CodeChangedEvent } from '@/application/Context/State/Code/Event/CodeChangedEvent';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition';
import { SelectedScriptStub } from '../../../../../stubs/SelectedScriptStub';
import { ScriptStub } from '../../../../../stubs/ScriptStub';
describe('CodeChangedEvent', () => {
describe('ctor', () => {
describe('position validation', () => {
it('throws when code position is out of range', () => {
const act = () => new CodeChangedEvent(
'singleline code', [], new Map<SelectedScript, ICodePosition>([
[ new SelectedScriptStub('1'), new CodePosition(0, 2) ],
]),
);
expect(act).to.throw();
});
it('does not throw with valid code position', () => {
const act = () => new CodeChangedEvent(
'singleline code', [], new Map<SelectedScript, ICodePosition>([
[ new SelectedScriptStub('1'), new CodePosition(0, 1) ],
]),
);
expect(act).to.not.throw();
});
});
});
it('code returns expected', () => {
// arrange
const expected = 'code';
// act
const sut = new CodeChangedEvent(
expected, [], new Map<SelectedScript, ICodePosition>(),
);
const actual = sut.code;
// assert
expect(actual).to.equal(expected);
});
describe('addedScripts', () => {
it('returns new scripts when scripts are added', () => {
// arrange
const expected = [ new ScriptStub('3'), new ScriptStub('4') ];
const initialScripts = [ new SelectedScriptStub('1'), new SelectedScriptStub('2') ];
const newScripts = new Map<SelectedScript, ICodePosition>([
[initialScripts[0], new CodePosition(0, 1) ],
[initialScripts[1], new CodePosition(0, 1) ],
[new SelectedScript(expected[0], false), new CodePosition(0, 1) ],
[new SelectedScript(expected[1], false), new CodePosition(0, 1) ],
]);
// act
const sut = new CodeChangedEvent(
'code', initialScripts, newScripts,
);
const actual = sut.addedScripts;
// assert
expect(actual).to.have.lengthOf(2);
expect(actual[0]).to.deep.equal(expected[0]);
expect(actual[1]).to.deep.equal(expected[1]);
});
});
describe('removedScripts', () => {
it('returns removed scripts when script are removed', () => {
// arrange
const existingScripts = [ new SelectedScriptStub('0'), new SelectedScriptStub('1') ];
const removedScripts = [ new SelectedScriptStub('2') ];
const initialScripts = [ ...existingScripts, ...removedScripts ];
const newScripts = new Map<SelectedScript, ICodePosition>([
[initialScripts[0], new CodePosition(0, 1) ],
[initialScripts[1], new CodePosition(0, 1) ],
]);
// act
const sut = new CodeChangedEvent(
'code', initialScripts, newScripts,
);
const actual = sut.removedScripts;
// assert
expect(actual).to.have.lengthOf(removedScripts.length);
expect(actual[0]).to.deep.equal(removedScripts[0].script);
});
});
describe('changedScripts', () => {
it('returns changed scripts when scripts are changed', () => {
// arrange
const initialScripts = [ new SelectedScriptStub('1', false), new SelectedScriptStub('2', false) ];
const newScripts = new Map<SelectedScript, ICodePosition>([
[new SelectedScriptStub('1', true), new CodePosition(0, 1) ],
[new SelectedScriptStub('2', false), new CodePosition(0, 1) ],
]);
// act
const sut = new CodeChangedEvent(
'code', initialScripts, newScripts,
);
const actual = sut.changedScripts;
// assert
expect(actual).to.have.lengthOf(1);
expect(actual[0]).to.deep.equal(initialScripts[0].script);
});
});
describe('isEmpty', () => {
it('returns true when empty', () => {
// arrange
const newScripts = new Map<SelectedScript, ICodePosition>();
const oldScripts = [ new SelectedScriptStub('1', false) ];
const sut = new CodeChangedEvent(
'code', oldScripts, newScripts,
);
// act
const actual = sut.isEmpty();
// assert
expect(actual).to.equal(true);
});
it('returns false when not empty', () => {
// arrange
const oldScripts = [ new SelectedScriptStub('1') ];
const newScripts = new Map<SelectedScript, ICodePosition>( [
[oldScripts[0], new CodePosition(0, 1) ],
]);
const sut = new CodeChangedEvent(
'code', oldScripts, newScripts,
);
// act
const actual = sut.isEmpty();
// assert
expect(actual).to.equal(false);
});
});
describe('getScriptPositionInCode', () => {
it('returns expected position for existing script', () => {
// arrange
const script = new ScriptStub('1');
const expected = new CodePosition(0, 1);
const newScripts = new Map<SelectedScript, ICodePosition>( [
[new SelectedScript(script, false), expected ],
]);
const sut = new CodeChangedEvent(
'code', [], newScripts,
);
// act
const actual = sut.getScriptPositionInCode(script);
// assert
expect(actual).to.deep.equal(expected);
});
});
});

View File

@@ -0,0 +1,112 @@
import 'mocha';
import { expect } from 'chai';
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
describe('CodeBuilder', () => {
describe('appendLine', () => {
it('when empty appends empty line', () => {
// arrange
const sut = new CodeBuilder();
// act
sut.appendLine().appendLine().appendLine();
// assert
expect(sut.toString()).to.equal('\n\n');
});
it('when not empty append string in new line', () => {
// arrange
const sut = new CodeBuilder();
const expected = 'str';
// act
sut.appendLine()
.appendLine(expected);
// assert
const result = sut.toString();
const lines = getLines(result);
expect(lines[1]).to.equal('str');
});
});
it('appendFunction', () => {
// arrange
const sut = new CodeBuilder();
const functionName = 'function';
const code = 'code';
// act
sut.appendFunction(functionName, code);
// assert
const result = sut.toString();
expect(result).to.include(functionName);
expect(result).to.include(code);
});
it('appendTrailingHyphensCommentLine', () => {
// arrange
const sut = new CodeBuilder();
const totalHypens = 5;
const expected = `:: ${'-'.repeat(totalHypens)}`;
// act
sut.appendTrailingHyphensCommentLine(totalHypens);
// assert
const result = sut.toString();
const lines = getLines(result);
expect(lines[0]).to.equal(expected);
});
it('appendCommentLine', () => {
// arrange
const sut = new CodeBuilder();
const comment = 'comment';
const expected = ':: comment';
// act
sut.appendCommentLine(comment);
// assert
const result = sut.toString();
const lines = getLines(result);
expect(lines[0]).to.equal(expected);
});
it('appendCommentLineWithHyphensAround', () => {
// arrange
const sut = new CodeBuilder();
const sectionName = 'section';
const totalHypens = sectionName.length + 3 * 2;
const expected = ':: ---section---';
sut.appendCommentLineWithHyphensAround(sectionName, totalHypens);
// assert
const result = sut.toString();
const lines = getLines(result);
expect(lines[1]).to.equal(expected);
});
describe('currentLine', () => {
it('no lines returns zero', () => {
// arrange & act
const sut = new CodeBuilder();
// assert
expect(sut.currentLine).to.equal(0);
});
it('single line returns one', () => {
// arrange
const sut = new CodeBuilder();
// act
sut.appendLine();
// assert
expect(sut.currentLine).to.equal(1);
});
it('multiple lines returns as expected', () => {
// arrange
const sut = new CodeBuilder();
// act
sut.appendLine('1').appendCommentLine('2').appendLine();
// assert
expect(sut.currentLine).to.equal(3);
});
it('multiple lines in code', () => {
// arrange
const sut = new CodeBuilder();
// act
sut.appendLine('hello\ncode-here\nwith-3-lines');
// assert
expect(sut.currentLine).to.equal(3);
});
});
});
function getLines(text: string): string[] {
return text.split(/\r\n|\r|\n/);
}

View File

@@ -0,0 +1,201 @@
import 'mocha';
import { expect } from 'chai';
import { UserScriptGenerator } from '@/application/Context/State/Code/Generation/UserScriptGenerator';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
import { ScriptStub } from '../../../../../stubs/ScriptStub';
import { ScriptingDefinitionStub } from '../../../../../stubs/ScriptingDefinitionStub';
describe('UserScriptGenerator', () => {
describe('scriptingDefinition', () => {
describe('startCode', () => {
it('is prepended if not empty', () => {
// arrange
const sut = new UserScriptGenerator();
const startCode = 'Start\nCode';
const script = new ScriptStub('id')
.withCode('code\nmulti-lined')
.toSelectedScript();
const definition = new ScriptingDefinitionStub()
.withStartCode(startCode)
.withEndCode(undefined);
const expectedStart = `${startCode}\n`;
// act
const code = sut.buildCode([script], definition);
// assert
const actual = code.code;
expect(actual.startsWith(expectedStart));
});
it('is not prepended if empty', () => {
// arrange
const sut = new UserScriptGenerator();
const script = new ScriptStub('id')
.withCode('code\nmulti-lined')
.toSelectedScript();
const definition = new ScriptingDefinitionStub()
.withStartCode(undefined)
.withEndCode(undefined);
const expectedStart = new CodeBuilder()
.appendFunction(script.script.name, script.script.code.execute)
.toString();
// act
const code = sut.buildCode([script], definition);
// assert
const actual = code.code;
expect(actual.startsWith(expectedStart));
});
});
describe('endCode', () => {
it('is appended if not empty', () => {
// arrange
const sut = new UserScriptGenerator();
const endCode = 'End\nCode';
const script = new ScriptStub('id')
.withCode('code\nmulti-lined')
.toSelectedScript();
const definition = new ScriptingDefinitionStub()
.withEndCode(endCode);
const expectedEnd = `${endCode}\n`;
// act
const code = sut.buildCode([script], definition);
// assert
const actual = code.code;
expect(actual.endsWith(expectedEnd));
});
it('is not appended if empty', () => {
// arrange
const sut = new UserScriptGenerator();
const script = new ScriptStub('id')
.withCode('code\nmulti-lined')
.toSelectedScript();
const definition = new ScriptingDefinitionStub()
.withEndCode(undefined);
const expectedEnd = new CodeBuilder()
.appendFunction(script.script.name, script.script.code.execute)
.toString();
// act
const code = sut.buildCode([script], definition);
// assert
const actual = code.code;
expect(actual.endsWith(expectedEnd));
});
});
});
it('appends revert script', () => {
// arrange
const sut = new UserScriptGenerator();
const scriptName = 'test non-revert script';
const scriptCode = 'REM nop';
const script = new ScriptStub('id')
.withName(scriptName)
.withRevertCode(scriptCode)
.toSelectedScript(true);
const definition = new ScriptingDefinitionStub();
// act
const actual = sut.buildCode([ script ], definition);
// assert
expect(actual.code).to.include(`${scriptName} (revert)`);
expect(actual.code).to.include(scriptCode);
});
it('appends non-revert script', () => {
const sut = new UserScriptGenerator();
// arrange
const scriptName = 'test non-revert script';
const scriptCode = 'REM nop';
const script = new ScriptStub('id').withName(scriptName).withCode(scriptCode);
const selectedScripts = [ new SelectedScript(script, false)];
const definition = new ScriptingDefinitionStub();
// act
const actual = sut.buildCode(selectedScripts, definition);
// assert
expect(actual.code).to.include(scriptName);
expect(actual.code).to.not.include(`${scriptName} (revert)`);
expect(actual.code).to.include(scriptCode);
});
describe('scriptPositions', () => {
it('without script; returns empty', () => {
// arrange
const sut = new UserScriptGenerator();
const selectedScripts = [ ];
const definition = new ScriptingDefinitionStub();
// act
const actual = sut.buildCode(selectedScripts, definition);
// assert
expect(actual.scriptPositions.size).to.equal(0);
});
describe('with scripts', () => {
// arrange
const totalStartCodeLines = 2;
const totalFunctionNameLines = 4;
const definition = new ScriptingDefinitionStub()
.withStartCode('First line\nSecond line');
describe('single script', () => {
const testCases = [
{
name: 'single-lined',
scriptCode: 'only line',
codeLines: 1,
},
{
name: 'multi-lined',
scriptCode: 'first line\nsecond line',
codeLines: 2,
},
];
const sut = new UserScriptGenerator();
for (const testCase of testCases) {
it(testCase.name, () => {
const expectedStartLine = totalStartCodeLines
+ 1 // empty line code begin
+ 1; // code begin
const expectedEndLine = expectedStartLine
+ totalFunctionNameLines
+ testCase.codeLines;
const selectedScript = new ScriptStub(`script-id`)
.withName(`script`)
.withCode(testCase.scriptCode)
.toSelectedScript(false);
// act
const actual = sut.buildCode([ selectedScript ], definition);
// expect
expect(1).to.equal(actual.scriptPositions.size);
const position = actual.scriptPositions.get(selectedScript);
expect(expectedStartLine).to.equal(position.startLine, 'Unexpected start line position');
expect(expectedEndLine).to.equal(position.endLine, 'Unexpected end line position');
});
}
});
it('multiple scripts', () => {
const sut = new UserScriptGenerator();
const selectedScripts = [
new ScriptStub('1').withCode('only line'),
new ScriptStub('2').withCode('first line\nsecond line'),
].map((s) => s.toSelectedScript());
const expectedFirstScriptStart = totalStartCodeLines
+ 1 // empty line code begin
+ 1; // code begin
const expectedFirstScriptEnd = expectedFirstScriptStart
+ totalFunctionNameLines
+ 1; // total code lines
const expectedSecondScriptStart = expectedFirstScriptEnd
+ 1 // code end hyphens
+ 1 // new line
+ 1; // code begin
const expectedSecondScriptEnd =
expectedSecondScriptStart
+ totalFunctionNameLines
+ 2; // total lines of second script
// act
const actual = sut.buildCode(selectedScripts, definition);
// assert
const firstPosition = actual.scriptPositions.get(selectedScripts[0]);
const secondPosition = actual.scriptPositions.get(selectedScripts[1]);
expect(actual.scriptPositions.size).to.equal(2);
expect(expectedFirstScriptStart).to.equal(firstPosition.startLine, 'Unexpected start line position (first script)');
expect(expectedFirstScriptEnd).to.equal(firstPosition.endLine, 'Unexpected end line position (first script)');
expect(expectedSecondScriptStart).to.equal(secondPosition.startLine, 'Unexpected start line position (second script)');
expect(expectedSecondScriptEnd).to.equal(secondPosition.endLine, 'Unexpected end line position (second script)');
});
});
});
});

View File

@@ -0,0 +1,54 @@
import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition';
import 'mocha';
import { expect } from 'chai';
describe('CodePosition', () => {
describe('ctor', () => {
it('creates with valid parameters', () => {
// arrange
const startPosition = 0;
const endPosition = 5;
// act
const sut = new CodePosition(startPosition, endPosition);
// assert
expect(sut.startLine).to.equal(startPosition);
expect(sut.endLine).to.equal(endPosition);
});
it('throws with negative start position', () => {
// arrange
const startPosition = -1;
const endPosition = 5;
// act
const getSut = () => new CodePosition(startPosition, endPosition);
// assert
expect(getSut).to.throw('Code cannot start in a negative line');
});
it('throws with negative end position', () => {
// arrange
const startPosition = 1;
const endPosition = -5;
// act
const getSut = () => new CodePosition(startPosition, endPosition);
// assert
expect(getSut).to.throw('Code cannot end in a negative line');
});
it('throws when start and end position is same', () => {
// arrange
const startPosition = 0;
const endPosition = 0;
// act
const getSut = () => new CodePosition(startPosition, endPosition);
// assert
expect(getSut).to.throw('Empty code');
});
it('throws when ends before start', () => {
// arrange
const startPosition = 3;
const endPosition = 2;
// act
const getSut = () => new CodePosition(startPosition, endPosition);
// assert
expect(getSut).to.throw('End line cannot be less than start line');
});
});
});

View File

@@ -0,0 +1,46 @@
import { CategoryStub } from '../../../../stubs/CategoryStub';
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { FilterResult } from '@/application/Context/State/Filter/FilterResult';
import 'mocha';
import { expect } from 'chai';
describe('FilterResult', () => {
describe('hasAnyMatches', () => {
it('false when no matches', () => {
const sut = new FilterResult(
/* scriptMatches */ [],
/* categoryMatches */ [],
'query',
);
const actual = sut.hasAnyMatches();
expect(actual).to.equal(false);
});
it('true when script matches', () => {
const sut = new FilterResult(
/* scriptMatches */ [ new ScriptStub('id') ],
/* categoryMatches */ [],
'query',
);
const actual = sut.hasAnyMatches();
expect(actual).to.equal(true);
});
it('true when category matches', () => {
const sut = new FilterResult(
/* scriptMatches */ [ ],
/* categoryMatches */ [ new CategoryStub(5) ],
'query',
);
const actual = sut.hasAnyMatches();
expect(actual).to.equal(true);
});
it('true when script + category matches', () => {
const sut = new FilterResult(
/* scriptMatches */ [ new ScriptStub('id') ],
/* categoryMatches */ [ new CategoryStub(5) ],
'query',
);
const actual = sut.hasAnyMatches();
expect(actual).to.equal(true);
});
});
});

View File

@@ -0,0 +1,164 @@
import { CategoryStub } from '../../../../stubs/CategoryStub';
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { ApplicationStub } from '../../../../stubs/ApplicationStub';
import { UserFilter } from '@/application/Context/State/Filter/UserFilter';
import 'mocha';
import { expect } from 'chai';
describe('UserFilter', () => {
describe('removeFilter', () => {
it('signals when removing filter', () => {
// arrange
let isCalled = false;
const sut = new UserFilter(new ApplicationStub());
sut.filterRemoved.on(() => isCalled = true);
// act
sut.removeFilter();
// assert
expect(isCalled).to.be.equal(true);
});
it('sets currentFilter to undefined', () => {
// arrange
const sut = new UserFilter(new ApplicationStub());
// act
sut.setFilter('non-important');
sut.removeFilter();
// assert
expect(sut.currentFilter).to.be.equal(undefined);
});
});
describe('setFilter', () => {
it('signals when no matches', () => {
// arrange
let actual: IFilterResult;
const nonMatchingFilter = 'non matching filter';
const sut = new UserFilter(new ApplicationStub());
sut.filtered.on((filterResult) => actual = filterResult);
// act
sut.setFilter(nonMatchingFilter);
// assert
expect(actual.hasAnyMatches()).be.equal(false);
expect(actual.query).to.equal(nonMatchingFilter);
});
it('sets currentFilter as expected when no matches', () => {
// arrange
const nonMatchingFilter = 'non matching filter';
const sut = new UserFilter(new ApplicationStub());
// act
sut.setFilter(nonMatchingFilter);
// assert
const actual = sut.currentFilter;
expect(actual.hasAnyMatches()).be.equal(false);
expect(actual.query).to.equal(nonMatchingFilter);
});
describe('signals when matches', () => {
describe('signals when script matches', () => {
it('code matches', () => {
// arrange
const code = 'HELLO world';
const filter = 'Hello WoRLD';
let actual: IFilterResult;
const script = new ScriptStub('id').withCode(code);
const category = new CategoryStub(33).withScript(script);
const sut = new UserFilter(new ApplicationStub()
.withAction(category));
sut.filtered.on((filterResult) => actual = filterResult);
// act
sut.setFilter(filter);
// assert
expect(actual.hasAnyMatches()).be.equal(true);
expect(actual.categoryMatches).to.have.lengthOf(0);
expect(actual.scriptMatches).to.have.lengthOf(1);
expect(actual.scriptMatches[0]).to.deep.equal(script);
expect(actual.query).to.equal(filter);
expect(sut.currentFilter).to.deep.equal(actual);
});
it('revertCode matches', () => {
// arrange
const revertCode = 'HELLO world';
const filter = 'Hello WoRLD';
let actual: IFilterResult;
const script = new ScriptStub('id').withRevertCode(revertCode);
const category = new CategoryStub(33).withScript(script);
const sut = new UserFilter(new ApplicationStub()
.withAction(category));
sut.filtered.on((filterResult) => actual = filterResult);
// act
sut.setFilter(filter);
// assert
expect(actual.hasAnyMatches()).be.equal(true);
expect(actual.categoryMatches).to.have.lengthOf(0);
expect(actual.scriptMatches).to.have.lengthOf(1);
expect(actual.scriptMatches[0]).to.deep.equal(script);
expect(actual.query).to.equal(filter);
expect(sut.currentFilter).to.deep.equal(actual);
});
it('name matches', () => {
// arrange
const name = 'HELLO world';
const filter = 'Hello WoRLD';
let actual: IFilterResult;
const script = new ScriptStub('id').withName(name);
const category = new CategoryStub(33).withScript(script);
const sut = new UserFilter(new ApplicationStub()
.withAction(category));
sut.filtered.on((filterResult) => actual = filterResult);
// act
sut.setFilter(filter);
// assert
expect(actual.hasAnyMatches()).be.equal(true);
expect(actual.categoryMatches).to.have.lengthOf(0);
expect(actual.scriptMatches).to.have.lengthOf(1);
expect(actual.scriptMatches[0]).to.deep.equal(script);
expect(actual.query).to.equal(filter);
expect(sut.currentFilter).to.deep.equal(actual);
});
});
it('signals when category matches', () => {
// arrange
const categoryName = 'HELLO world';
const filter = 'Hello WoRLD';
let actual: IFilterResult;
const category = new CategoryStub(55).withName(categoryName);
const sut = new UserFilter(new ApplicationStub()
.withAction(category));
sut.filtered.on((filterResult) => actual = filterResult);
// act
sut.setFilter(filter);
// assert
expect(actual.hasAnyMatches()).be.equal(true);
expect(actual.categoryMatches).to.have.lengthOf(1);
expect(actual.categoryMatches[0]).to.deep.equal(category);
expect(actual.scriptMatches).to.have.lengthOf(0);
expect(actual.query).to.equal(filter);
expect(sut.currentFilter).to.deep.equal(actual);
});
it('signals when category and script matches', () => {
// arrange
const matchingText = 'HELLO world';
const filter = 'Hello WoRLD';
let actual: IFilterResult;
const script = new ScriptStub('script')
.withName(matchingText);
const category = new CategoryStub(55)
.withName(matchingText)
.withScript(script);
const app = new ApplicationStub()
.withAction(category);
const sut = new UserFilter(app);
sut.filtered.on((filterResult) => actual = filterResult);
// act
sut.setFilter(filter);
// assert
expect(actual.hasAnyMatches()).be.equal(true);
expect(actual.categoryMatches).to.have.lengthOf(1);
expect(actual.categoryMatches[0]).to.deep.equal(category);
expect(actual.scriptMatches).to.have.lengthOf(1);
expect(actual.scriptMatches[0]).to.deep.equal(script);
expect(actual.query).to.equal(filter);
expect(sut.currentFilter).to.deep.equal(actual);
});
});
});
});

View File

@@ -0,0 +1,28 @@
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import 'mocha';
import { expect } from 'chai';
describe('SelectedScript', () => {
it('id is same as script id', () => {
// arrange
const expectedId = 'scriptId';
const script = new ScriptStub(expectedId);
const sut = new SelectedScript(script, false);
// act
const actualId = sut.id;
// assert
expect(actualId).to.equal(expectedId);
});
it('throws when revert is true for irreversible script', () => {
// arrange
const expectedId = 'scriptId';
const script = new ScriptStub(expectedId)
.withRevertCode(undefined);
// act
function construct() { new SelectedScript(script, true); } // tslint:disable-line:no-unused-expression
// assert
expect(construct).to.throw('cannot revert an irreversible script');
});
});

View File

@@ -0,0 +1,383 @@
import 'mocha';
import { expect } from 'chai';
import { IScript } from '@/domain/IScript';
import { SelectedScriptStub } from '../../../../stubs/SelectedScriptStub';
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { CategoryStub } from '../../../../stubs/CategoryStub';
import { ApplicationStub } from '../../../../stubs/ApplicationStub';
import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
describe('UserSelection', () => {
describe('ctor', () => {
it('has nothing with no initial selection', () => {
// arrange
const app = new ApplicationStub().withAction(new CategoryStub(1).withScriptIds('s1'));
const selection = [];
// act
const sut = new UserSelection(app, selection);
// assert
expect(sut.selectedScripts).to.have.lengthOf(0);
});
it('has initial selection', () => {
// arrange
const firstScript = new ScriptStub('1');
const secondScript = new ScriptStub('2');
const app = new ApplicationStub().withAction(
new CategoryStub(1).withScript(firstScript).withScripts(secondScript));
const expected = [ new SelectedScript(firstScript, false), new SelectedScript(secondScript, true) ];
// act
const sut = new UserSelection(app, expected);
// assert
expect(sut.selectedScripts).to.deep.include(expected[0]);
expect(sut.selectedScripts).to.deep.include(expected[1]);
});
});
it('deselectAll removes all items', () => {
// arrange
const events: Array<readonly SelectedScript[]> = [];
const app = new ApplicationStub()
.withAction(new CategoryStub(1)
.withScriptIds('s1', 's2', 's3', 's4'));
const selectedScripts = [
new SelectedScriptStub('s1'), new SelectedScriptStub('s2'), new SelectedScriptStub('s3'),
];
const sut = new UserSelection(app, selectedScripts);
sut.changed.on((newScripts) => events.push(newScripts));
// act
sut.deselectAll();
// assert
expect(sut.selectedScripts).to.have.length(0);
expect(events).to.have.lengthOf(1);
expect(events[0]).to.have.length(0);
});
it('selectOnly selects expected', () => {
// arrange
const events: Array<readonly SelectedScript[]> = [];
const app = new ApplicationStub()
.withAction(new CategoryStub(1)
.withScriptIds('s1', 's2', 's3', 's4'));
const selectedScripts = [
new SelectedScriptStub('s1'), new SelectedScriptStub('s2'), new SelectedScriptStub('s3'),
];
const sut = new UserSelection(app, selectedScripts);
sut.changed.on((newScripts) => events.push(newScripts));
const scripts = [new ScriptStub('s2'), new ScriptStub('s3'), new ScriptStub('s4')];
const expected = [ new SelectedScriptStub('s2'), new SelectedScriptStub('s3'),
new SelectedScript(scripts[2], false)];
// act
sut.selectOnly(scripts);
// assert
expect(sut.selectedScripts).to.have.deep.members(expected,
`Expected: ${JSON.stringify(sut.selectedScripts)}\n` +
`Actual: ${JSON.stringify(expected)}`);
expect(events).to.have.lengthOf(1);
expect(events[0]).to.deep.equal(expected);
});
it('selectAll selects as expected', () => {
// arrange
const events: Array<readonly SelectedScript[]> = [];
const scripts: IScript[] = [new ScriptStub('s1'), new ScriptStub('s2'), new ScriptStub('s3'), new ScriptStub('s4')];
const app = new ApplicationStub()
.withAction(new CategoryStub(1)
.withScripts(...scripts));
const sut = new UserSelection(app, []);
sut.changed.on((newScripts) => events.push(newScripts));
const expected = scripts.map((script) => new SelectedScript(script, false));
// act
sut.selectAll();
// assert
expect(sut.selectedScripts).to.deep.equal(expected);
expect(events).to.have.lengthOf(1);
expect(events[0]).to.deep.equal(expected);
});
describe('addOrUpdateSelectedScript', () => {
it('adds when item does not exist', () => {
// arrange
const events: Array<readonly SelectedScript[]> = [];
const app = new ApplicationStub()
.withAction(new CategoryStub(1)
.withScripts(new ScriptStub('s1'), new ScriptStub('s2')));
const sut = new UserSelection(app, []);
sut.changed.on((scripts) => events.push(scripts));
const expected = [ new SelectedScript(new ScriptStub('s1'), false) ];
// act
sut.addOrUpdateSelectedScript('s1', false);
// assert
expect(sut.selectedScripts).to.deep.equal(expected);
expect(events).to.have.lengthOf(1);
expect(events[0]).to.deep.equal(expected);
});
it('updates when item exists', () => {
// arrange
const events: Array<readonly SelectedScript[]> = [];
const app = new ApplicationStub()
.withAction(new CategoryStub(1)
.withScripts(new ScriptStub('s1'), new ScriptStub('s2')));
const sut = new UserSelection(app, []);
sut.changed.on((scripts) => events.push(scripts));
const expected = [ new SelectedScript(new ScriptStub('s1'), true) ];
// act
sut.addOrUpdateSelectedScript('s1', true);
// assert
expect(sut.selectedScripts).to.deep.equal(expected);
expect(events).to.have.lengthOf(1);
expect(events[0]).to.deep.equal(expected);
});
});
describe('removeAllInCategory', () => {
it('does nothing when nothing exists', () => {
// arrange
const events: Array<readonly SelectedScript[]> = [];
const categoryId = 1;
const app = new ApplicationStub()
.withAction(new CategoryStub(categoryId)
.withScripts(new ScriptStub('s1'), new ScriptStub('s2')));
const sut = new UserSelection(app, []);
sut.changed.on((s) => events.push(s));
// act
sut.removeAllInCategory(categoryId);
// assert
expect(events).to.have.lengthOf(0);
expect(sut.selectedScripts).to.have.lengthOf(0);
});
it('removes all when all exists', () => {
// arrange
const categoryId = 1;
const scripts = [new SelectedScriptStub('s1'), new SelectedScriptStub('s2')];
const app = new ApplicationStub()
.withAction(new CategoryStub(categoryId)
.withScripts(...scripts.map((script) => script.script)));
const sut = new UserSelection(app, scripts);
// act
sut.removeAllInCategory(categoryId);
// assert
expect(sut.totalSelected).to.equal(0);
expect(sut.selectedScripts.length).to.equal(0);
});
it('removes existing some exists', () => {
// arrange
const categoryId = 1;
const existing = [new ScriptStub('s1'), new ScriptStub('s2')];
const notExisting = [new ScriptStub('s3'), new ScriptStub('s4')];
const app = new ApplicationStub()
.withAction(new CategoryStub(categoryId)
.withScripts(...existing, ...notExisting));
const sut = new UserSelection(app, existing.map((script) => new SelectedScript(script, false)));
// act
sut.removeAllInCategory(categoryId);
// assert
expect(sut.totalSelected).to.equal(0);
expect(sut.selectedScripts.length).to.equal(0);
});
});
describe('addOrUpdateAllInCategory', () => {
it('does nothing when all already exists', () => {
// arrange
const events: Array<readonly SelectedScript[]> = [];
const categoryId = 1;
const scripts = [new ScriptStub('s1'), new ScriptStub('s2')];
const app = new ApplicationStub()
.withAction(new CategoryStub(categoryId)
.withScripts(...scripts));
const sut = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
sut.changed.on((s) => events.push(s));
// act
sut.addOrUpdateAllInCategory(categoryId);
// assert
expect(events).to.have.lengthOf(0);
expect(sut.selectedScripts.map((script) => script.id))
.to.have.deep.members(scripts.map((script) => script.id));
});
it('adds all when nothing exists', () => {
// arrange
const categoryId = 1;
const expected = [new ScriptStub('s1'), new ScriptStub('s2')];
const app = new ApplicationStub()
.withAction(new CategoryStub(categoryId)
.withScripts(...expected));
const sut = new UserSelection(app, []);
// act
sut.addOrUpdateAllInCategory(categoryId);
// assert
expect(sut.selectedScripts.map((script) => script.id))
.to.have.deep.members(expected.map((script) => script.id));
});
it('adds all with given revert status when nothing exists', () => {
// arrange
const categoryId = 1;
const expected = [new ScriptStub('s1'), new ScriptStub('s2')];
const app = new ApplicationStub()
.withAction(new CategoryStub(categoryId)
.withScripts(...expected));
const sut = new UserSelection(app, []);
// act
sut.addOrUpdateAllInCategory(categoryId, true);
// assert
expect(sut.selectedScripts.every((script) => script.revert))
.to.equal(true);
});
it('changes revert status of all when some exists', () => {
// arrange
const categoryId = 1;
const notExisting = [ new ScriptStub('notExisting1'), new ScriptStub('notExisting2') ];
const existing = [ new ScriptStub('existing1'), new ScriptStub('existing2') ];
const allScripts = [ ...existing, ...notExisting ];
const app = new ApplicationStub()
.withAction(new CategoryStub(categoryId)
.withScripts(...allScripts));
const sut = new UserSelection(app, existing.map((script) => new SelectedScript(script, false)));
// act
sut.addOrUpdateAllInCategory(categoryId, true);
// assert
expect(sut.selectedScripts.every((script) => script.revert))
.to.equal(true);
});
it('changes revert status of all when some exists', () => {
// arrange
const categoryId = 1;
const notExisting = [ new ScriptStub('notExisting1'), new ScriptStub('notExisting2') ];
const existing = [ new ScriptStub('existing1'), new ScriptStub('existing2') ];
const allScripts = [ ...existing, ...notExisting ];
const app = new ApplicationStub()
.withAction(new CategoryStub(categoryId)
.withScripts(...allScripts));
const sut = new UserSelection(app, existing.map((script) => new SelectedScript(script, false)));
// act
sut.addOrUpdateAllInCategory(categoryId, true);
// assert
expect(sut.selectedScripts.every((script) => script.revert))
.to.equal(true);
});
it('changes revert status of all when all already exists', () => {
// arrange
const categoryId = 1;
const scripts = [ new ScriptStub('existing1'), new ScriptStub('existing2') ];
const app = new ApplicationStub()
.withAction(new CategoryStub(categoryId)
.withScripts(...scripts));
const sut = new UserSelection(app, scripts.map((script) => new SelectedScript(script, false)));
// act
sut.addOrUpdateAllInCategory(categoryId, true);
// assert
expect(sut.selectedScripts.every((script) => script.revert))
.to.equal(true);
});
});
describe('isSelected', () => {
it('returns false when not selected', () => {
// arrange
const selectedScript = new ScriptStub('selected');
const notSelectedScript = new ScriptStub('not selected');
const app = new ApplicationStub()
.withAction(new CategoryStub(1)
.withScripts(selectedScript, notSelectedScript));
const sut = new UserSelection(app, [ new SelectedScript(selectedScript, false) ]);
// act
const actual = sut.isSelected(notSelectedScript.id);
// assert
expect(actual).to.equal(false);
});
it('returns true when selected', () => {
// arrange
const selectedScript = new ScriptStub('selected');
const notSelectedScript = new ScriptStub('not selected');
const app = new ApplicationStub()
.withAction(new CategoryStub(1)
.withScripts(selectedScript, notSelectedScript));
const sut = new UserSelection(app, [ new SelectedScript(selectedScript, false) ]);
// act
const actual = sut.isSelected(selectedScript.id);
// assert
expect(actual).to.equal(true);
});
});
describe('category state', () => {
describe('when no scripts are selected', () => {
// arrange
const category = new CategoryStub(1)
.withScriptIds('non-selected-script-1', 'non-selected-script-2');
const app = new ApplicationStub().withAction(category);
const sut = new UserSelection(app, [ ]);
it('areAllSelected returns false', () => {
// act
const actual = sut.areAllSelected(category);
// assert
expect(actual).to.equal(false);
});
it('isAnySelected returns false', () => {
// act
const actual = sut.isAnySelected(category);
// assert
expect(actual).to.equal(false);
});
});
describe('when no subscript exists in selected scripts', () => {
// arrange
const category = new CategoryStub(1)
.withScriptIds('non-selected-script-1', 'non-selected-script-2');
const selectedScript = new ScriptStub('selected');
const app = new ApplicationStub()
.withAction(category)
.withAction(new CategoryStub(22).withScript(selectedScript));
const sut = new UserSelection(app, [ new SelectedScript(selectedScript, false) ]);
it('areAllSelected returns false', () => {
// act
const actual = sut.areAllSelected(category);
// assert
expect(actual).to.equal(false);
});
it('isAnySelected returns false', () => {
// act
const actual = sut.isAnySelected(category);
// assert
expect(actual).to.equal(false);
});
});
describe('when one of the scripts are selected', () => {
// arrange
const selectedScript = new ScriptStub('selected');
const category = new CategoryStub(1)
.withScriptIds('non-selected-script-1', 'non-selected-script-2')
.withCategory(new CategoryStub(12).withScript(selectedScript));
const app = new ApplicationStub().withAction(category);
const sut = new UserSelection(app, [ new SelectedScript(selectedScript, false) ]);
it('areAllSelected returns false', () => {
// act
const actual = sut.areAllSelected(category);
// assert
expect(actual).to.equal(false);
});
it('isAnySelected returns true', () => {
// act
const actual = sut.isAnySelected(category);
// assert
expect(actual).to.equal(true);
});
});
describe('when all scripts are selected', () => {
// arrange
const firstSelectedScript = new ScriptStub('selected1');
const secondSelectedScript = new ScriptStub('selected2');
const category = new CategoryStub(1)
.withScript(firstSelectedScript)
.withCategory(new CategoryStub(12).withScript(secondSelectedScript));
const app = new ApplicationStub().withAction(category);
const sut = new UserSelection(app,
[ firstSelectedScript, secondSelectedScript ].map((s) => new SelectedScript(s, false)));
it('areAllSelected returns true', () => {
// act
const actual = sut.areAllSelected(category);
// assert
expect(actual).to.equal(true);
});
it('isAnySelected returns true', () => {
// act
const actual = sut.isAnySelected(category);
// assert
expect(actual).to.equal(true);
});
});
});
});