Change "grouping" to "view"

1. *Grouping* becomes *view*. Because *view* is more clear and extensible than *grouping*. It increases flexibility to extend by e.g. adding *flat* as a new view as discussed in #50, in this case "flat *view*" would make more sense than "flat *grouping*".
2. *None* becomes *tree*. Because *tree* is more descriptive than *none*.

Updates labels on top menu. As labels are updated, the file structure/names are refactored to follow the same concept. `TheScriptsList` is renamed to `TheScriptsView`. Also refactors `ViewChanger` so view types are presented in same way.
This commit is contained in:
undergroundwires
2021-08-29 11:33:16 +01:00
parent 6dc768817f
commit c0c475ff56
37 changed files with 112 additions and 117 deletions

View File

@@ -0,0 +1,63 @@
import 'mocha';
import { expect } from 'chai';
import { NodeType, INode } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode';
import { NodePredicateFilter } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodePredicateFilter';
import { ILiquorTreeExistingNode } from 'liquor-tree';
describe('NodePredicateFilter', () => {
it('calls predicate with expected node', () => {
// arrange
const object: ILiquorTreeExistingNode = {
id: 'script',
data: {
text: 'script-text',
type: NodeType.Script,
documentationUrls: [],
isReversible: false,
},
states: undefined,
children: [],
};
const expected: INode = {
id: 'script',
text: 'script-text',
isReversible: false,
documentationUrls: [],
children: [],
type: NodeType.Script,
};
let actual: INode;
const predicate = (node: INode) => { actual = node; return true; };
const sut = new NodePredicateFilter(predicate);
// act
sut.matcher('nop query', object);
// assert
expect(actual).to.deep.equal(expected);
});
describe('returns result from the predicate', () => {
for (const expected of [false, true]) {
it(expected.toString(), () => {
// arrange
const sut = new NodePredicateFilter(() => expected);
// act
const actual = sut.matcher('nop query', getExistingNode());
// assert
expect(actual).to.equal(expected);
});
}
});
});
function getExistingNode(): ILiquorTreeExistingNode {
return {
id: 'script',
data: {
text: 'script-text',
type: NodeType.Script,
documentationUrls: [],
isReversible: false,
},
states: undefined,
children: [],
};
}

View File

@@ -0,0 +1,203 @@
import 'mocha';
import { expect } from 'chai';
import { ILiquorTreeNode } from 'liquor-tree';
import { NodeType } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode';
import { getNewState } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodeStateUpdater';
describe('NodeStateUpdater', () => {
describe('getNewState', () => {
describe('checked', () => {
describe('script node', () => {
it('true when selected', () => {
// arrange
const node = getScriptNode();
const selectedScriptNodeIds = [ 'a', 'b', node.id, 'c' ];
// act
const state = getNewState(node, selectedScriptNodeIds);
// assert
expect(state.checked).to.equal(true);
});
it('false when unselected', () => {
// arrange
const node = getScriptNode();
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
// act
const state = getNewState(node, selectedScriptNodeIds);
// assert
expect(state.checked).to.equal(false);
});
});
describe('category node', () => {
it('true when every child selected', () => {
// arrange
const node = {
id: '1',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [
{ id: '2',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('a'), getScriptNode('b') ],
},
{ id: '3',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('c') ],
},
],
};
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
// act
const state = getNewState(node, selectedScriptNodeIds);
// assert
expect(state.checked).to.equal(true);
});
it('false when none of the children is selected', () => {
// arrange
const node = {
id: '1',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [
{ id: '2',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('a'), getScriptNode('b') ],
},
{ id: '3',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('c') ],
},
],
};
const selectedScriptNodeIds = [ 'none', 'of', 'them', 'are', 'selected' ];
// act
const state = getNewState(node, selectedScriptNodeIds);
// assert
expect(state.checked).to.equal(false);
});
it('false when some of the children is selected', () => {
// arrange
const node = {
id: '1',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [
{
id: '2',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('a'), getScriptNode('b') ],
},
{
id: '3',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('c') ],
},
],
};
const selectedScriptNodeIds = [ 'a', 'c', 'unrelated' ];
// act
const state = getNewState(node, selectedScriptNodeIds);
// assert
expect(state.checked).to.equal(false);
});
});
});
describe('indeterminate', () => {
describe('script node', () => {
it('false when selected', () => {
// arrange
const node = getScriptNode();
const selectedScriptNodeIds = [ 'a', 'b', node.id, 'c' ];
// act
const state = getNewState(node, selectedScriptNodeIds);
// assert
expect(state.indeterminate).to.equal(false);
});
it('false when not selected', () => {
// arrange
const node = getScriptNode();
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
// act
const state = getNewState(node, selectedScriptNodeIds);
// assert
expect(state.indeterminate).to.equal(false);
});
});
describe('category node', () => {
it('false when all children are selected', () => {
// arrange
const node = {
id: '1',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [
{ id: '2',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('a'), getScriptNode('b') ],
},
{ id: '3',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('c') ],
},
],
};
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
// act
const state = getNewState(node, selectedScriptNodeIds);
// assert
expect(state.indeterminate).to.equal(false);
});
it('true when all some are selected', () => {
// arrange
const node = {
id: '1',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [
{ id: '2',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('a'), getScriptNode('b') ],
},
{ id: '3',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('c') ],
},
],
};
const selectedScriptNodeIds = [ 'a' ];
// act
const state = getNewState(node, selectedScriptNodeIds);
// assert
expect(state.indeterminate).to.equal(true);
});
it('false when no children are selected', () => {
// arrange
const node = {
id: '1',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [
{ id: '2',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('a'), getScriptNode('b') ],
},
{ id: '3',
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
children: [ getScriptNode('c') ],
},
],
};
const selectedScriptNodeIds = [ 'none', 'of', 'them', 'are', 'selected' ];
// act
const state = getNewState(node, selectedScriptNodeIds);
// assert
expect(state.indeterminate).to.equal(false);
});
});
});
});
function getScriptNode(scriptNodeId: string = 'script'): ILiquorTreeNode {
return {
id: scriptNodeId,
data: {
type: NodeType.Script,
documentationUrls: [],
isReversible: false,
},
children: [],
};
}
});

View File

@@ -0,0 +1,142 @@
import 'mocha';
import { expect } from 'chai';
import { NodeType, INode } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode';
import { ILiquorTreeExistingNode, ILiquorTreeNewNode, ILiquorTreeNodeData, ICustomLiquorTreeData } from 'liquor-tree';
import { convertExistingToNode, toNewLiquorTreeNode } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodeTranslator';
describe('NodeTranslator', () => {
it('convertExistingToNode', () => {
// arrange
const existingNode = getExistingNode();
const expected = getNode();
// act
const actual = convertExistingToNode(existingNode);
// assert
expect(actual).to.deep.equal(expected);
});
it('toNewLiquorTreeNode', () => {
// arrange
const node = getNode();
const expected = getNewNode();
// act
const actual = toNewLiquorTreeNode(node);
// assert
expect(actual).to.deep.equal(expected);
});
});
function getNode(): INode {
return {
id: '1',
text: 'parentcategory',
isReversible: true,
type: NodeType.Category,
documentationUrls: [ 'parentcategory-url1', 'parentcategory-url2 '],
children: [
{
id: '2',
text: 'subcategory',
isReversible: true,
documentationUrls: [ 'subcategory-url1', 'subcategory-url2 '],
type: NodeType.Category,
children: [
{
id: 'script1',
text: 'cool script 1',
isReversible: true,
documentationUrls: [ 'script1url1', 'script1url2'],
children: [],
type: NodeType.Script,
},
{
id: 'script2',
text: 'cool script 2',
isReversible: true,
documentationUrls: [ 'script2url1', 'script2url2'],
children: [],
type: NodeType.Script,
}],
}],
};
}
function getExpectedExistingNodeData(node: INode): ILiquorTreeNodeData {
return {
text: node.text,
type: node.type,
documentationUrls: node.documentationUrls,
isReversible: node.isReversible,
};
}
function getExpectedNewNodeData(node: INode): ICustomLiquorTreeData {
return {
type: node.type,
documentationUrls: node.documentationUrls,
isReversible: node.isReversible,
};
}
function getExistingNode(): ILiquorTreeExistingNode {
const base = getNode();
return {
id: base.id,
data: getExpectedExistingNodeData(base),
states: undefined,
children: [
{
id: base.children[0].id,
data: getExpectedExistingNodeData(base.children[0]),
states: undefined,
children: [
{
id: base.children[0].children[0].id,
data: getExpectedExistingNodeData(base.children[0].children[0]),
states: undefined,
children: [],
},
{
id: base.children[0].children[1].id,
data: getExpectedExistingNodeData(base.children[0].children[1]),
states: undefined,
children: [],
}],
}],
};
}
function getNewNode(): ILiquorTreeNewNode {
const base = getNode();
const commonState = {
checked: false,
indeterminate: false,
};
return {
id: base.id,
text: base.text,
data: getExpectedNewNodeData(base),
state: commonState,
children: [
{
id: base.children[0].id,
text: base.children[0].text,
data: getExpectedNewNodeData(base.children[0]),
state: commonState,
children: [
{
id: base.children[0].children[0].id,
text: base.children[0].children[0].text,
data: getExpectedNewNodeData(base.children[0].children[0]),
state: commonState,
children: [],
},
{
id: base.children[0].children[1].id,
text: base.children[0].children[1].text,
data: getExpectedNewNodeData(base.children[0].children[1]),
state: commonState,
children: [],
}],
}],
};
}

View File

@@ -0,0 +1,105 @@
import 'mocha';
import { expect } from 'chai';
import { CategoryReverter } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/CategoryReverter';
import { getCategoryNodeId } from '@/presentation/components/Scripts/View/ScriptsTree/ScriptNodeParser';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('CategoryReverter', () => {
describe('getState', () => {
// arrange
const scripts = [
new ScriptStub('revertable').withRevertCode('REM revert me'),
new ScriptStub('revertable2').withRevertCode('REM revert me 2'),
];
const category = new CategoryStub(1).withScripts(...scripts);
const nodeId = getCategoryNodeId(category);
const collection = new CategoryCollectionStub().withAction(category);
const sut = new CategoryReverter(nodeId, collection);
const testCases = [
{
name: 'false when subscripts are not reverted',
state: scripts.map((script) => new SelectedScript(script, false)),
expected: false,
},
{
name: 'false when some subscripts are reverted',
state: [new SelectedScript(scripts[0], false), new SelectedScript(scripts[0], true)],
expected: false,
},
{
name: 'false when subscripts are not reverted',
state: scripts.map((script) => new SelectedScript(script, true)),
expected: true,
},
];
for (const testCase of testCases) {
it(testCase.name, () => {
// act
const actual = sut.getState(testCase.state);
// assert
expect(actual).to.equal(testCase.expected);
});
}
});
describe('selectWithRevertState', () => {
// arrange
const scripts = [
new ScriptStub('revertable').withRevertCode('REM revert me'),
new ScriptStub('revertable2').withRevertCode('REM revert me 2'),
];
const category = new CategoryStub(1).withScripts(...scripts);
const collection = new CategoryCollectionStub().withAction(category);
const testCases = [
{
name: 'selects with revert state when not selected',
selection: [],
revert: true, expectRevert: true,
},
{
name: 'selects with non-revert state when not selected',
selection: [],
revert: false, expectRevert: false,
},
{
name: 'switches when already selected with revert state',
selection: scripts.map((script) => new SelectedScript(script, true)),
revert: false, expectRevert: false,
},
{
name: 'switches when already selected with not revert state',
selection: scripts.map((script) => new SelectedScript(script, false)),
revert: true, expectRevert: true,
},
{
name: 'keeps revert state when already selected with revert state',
selection: scripts.map((script) => new SelectedScript(script, true)),
revert: true, expectRevert: true,
},
{
name: 'keeps revert state deselected when already selected wtih non revert state',
selection: scripts.map((script) => new SelectedScript(script, false)),
revert: false, expectRevert: false,
},
];
const nodeId = getCategoryNodeId(category);
for (const testCase of testCases) {
it(testCase.name, () => {
const selection = new UserSelection(collection, testCase.selection);
const sut = new CategoryReverter(nodeId, collection);
// act
sut.selectWithRevertState(testCase.revert, selection);
// assert
expect(sut.getState(selection.selectedScripts)).to.equal(testCase.expectRevert);
expect(selection.selectedScripts).has.lengthOf(2);
expect(selection.selectedScripts[0].id).equal(scripts[0].id);
expect(selection.selectedScripts[1].id).equal(scripts[1].id);
expect(selection.selectedScripts[0].revert).equal(testCase.expectRevert);
expect(selection.selectedScripts[1].revert).equal(testCase.expectRevert);
});
}
});
});

View File

@@ -0,0 +1,49 @@
import 'mocha';
import { expect } from 'chai';
import { INode, NodeType } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode';
// tslint:disable-next-line:max-line-length
import { getReverter } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ReverterFactory';
import { ScriptReverter } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ScriptReverter';
import { CategoryReverter } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/CategoryReverter';
// tslint:disable-next-line:max-line-length
import { getScriptNodeId, getCategoryNodeId } from '@/presentation/components/Scripts/View/ScriptsTree/ScriptNodeParser';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('ReverterFactory', () => {
describe('getReverter', () => {
it('gets CategoryReverter for category node', () => {
// arrange
const category = new CategoryStub(0).withScriptIds('55');
const node = getNodeStub(getCategoryNodeId(category), NodeType.Category);
const collection = new CategoryCollectionStub()
.withAction(category);
// act
const result = getReverter(node, collection);
// assert
expect(result instanceof CategoryReverter).to.equal(true);
});
it('gets ScriptReverter for script node', () => {
// arrange
const script = new ScriptStub('test');
const node = getNodeStub(getScriptNodeId(script), NodeType.Script);
const collection = new CategoryCollectionStub()
.withAction(new CategoryStub(0).withScript(script));
// act
const result = getReverter(node, collection);
// assert
expect(result instanceof ScriptReverter).to.equal(true);
});
});
function getNodeStub(nodeId: string, type: NodeType): INode {
return {
id: nodeId,
text: 'text',
isReversible: false,
documentationUrls: [],
children: [],
type,
};
}
});

View File

@@ -0,0 +1,89 @@
import 'mocha';
import { expect } from 'chai';
import { ScriptReverter } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Reverter/ScriptReverter';
import { getScriptNodeId } from '@/presentation/components/Scripts/View/ScriptsTree/ScriptNodeParser';
import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { SelectedScriptStub } from '@tests/unit/stubs/SelectedScriptStub';
describe('ScriptReverter', () => {
describe('getState', () => {
it('false when script is not selected', () => {
// arrange
const script = new ScriptStub('id');
const nodeId = getScriptNodeId(script);
const sut = new ScriptReverter(nodeId);
// act
const actual = sut.getState([]);
// assert
expect(actual).to.equal(false);
});
it('false when script is selected but not reverted', () => {
// arrange
const scripts = [ new SelectedScriptStub('id'), new SelectedScriptStub('dummy') ];
const nodeId = getScriptNodeId(scripts[0].script);
const sut = new ScriptReverter(nodeId);
// act
const actual = sut.getState(scripts);
// assert
expect(actual).to.equal(false);
});
it('true when script is selected and reverted', () => {
// arrange
const scripts = [ new SelectedScriptStub('id', true), new SelectedScriptStub('dummy') ];
const nodeId = getScriptNodeId(scripts[0].script);
const sut = new ScriptReverter(nodeId);
// act
const actual = sut.getState(scripts);
// assert
expect(actual).to.equal(true);
});
});
describe('selectWithRevertState', () => {
// arrange
const script = new ScriptStub('id');
const collection = new CategoryCollectionStub()
.withAction(new CategoryStub(5).withScript(script));
const testCases = [
{
name: 'selects with revert state when not selected',
selection: [], revert: true, expectRevert: true,
},
{
name: 'selects with non-revert state when not selected',
selection: [], revert: false, expectRevert: false,
},
{
name: 'switches when already selected with revert state',
selection: [ new SelectedScript(script, true)], revert: false, expectRevert: false,
},
{
name: 'switches when already selected with not revert state',
selection: [ new SelectedScript(script, false)], revert: true, expectRevert: true,
},
{
name: 'keeps revert state when already selected with revert state',
selection: [ new SelectedScript(script, true)], revert: true, expectRevert: true,
},
{
name: 'keeps revert state deselected when already selected with non revert state',
selection: [ new SelectedScript(script, false)], revert: false, expectRevert: false,
},
];
const nodeId = getScriptNodeId(script);
for (const testCase of testCases) {
it(testCase.name, () => {
const selection = new UserSelection(collection, testCase.selection);
const sut = new ScriptReverter(nodeId);
// act
sut.selectWithRevertState(testCase.revert, selection);
// assert
expect(selection.isSelected(script.id)).to.equal(true);
expect(selection.selectedScripts[0].revert).equal(testCase.expectRevert);
});
}
});
});