Refactor executable IDs to use strings #262

This commit unifies executable ID structure across categories and
scripts, paving the way for more complex ID solutions for #262.
It also refactors related code to adapt to the changes.

Key changes:

- Change numeric IDs to string IDs for categories
- Use named types for string IDs to improve code clarity
- Add unit tests to verify ID uniqueness

Other supporting changes:

- Separate concerns in entities for data access and executables by using
  separate abstractions (`Identifiable` and `RepositoryEntity`)
- Simplify usage and construction of entities.
- Remove `BaseEntity` for simplicity.
- Move creation of categories/scripts to domain layer
- Refactor CategoryCollection for better validation logic isolation
- Rename some categories to keep the names (used as pseudo-IDs) unique
  on Windows.
This commit is contained in:
undergroundwires
2024-08-03 16:54:14 +02:00
parent 6fbc81675f
commit ded55a66d6
124 changed files with 2286 additions and 1331 deletions

View File

@@ -3,12 +3,12 @@ import { CategoryReverter } from '@/presentation/components/Scripts/View/Tree/No
import { CategoryStub } from '@tests/unit/shared/Stubs/CategoryStub';
import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollectionStub';
import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
import { getCategoryNodeId } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter';
import { UserSelectionStub } from '@tests/unit/shared/Stubs/UserSelectionStub';
import { SelectedScriptStub } from '@tests/unit/shared/Stubs/SelectedScriptStub';
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
import { CategorySelectionStub } from '@tests/unit/shared/Stubs/CategorySelectionStub';
import type { Script } from '@/domain/Executables/Script/Script';
import { createExecutableIdFromNodeId } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter';
describe('CategoryReverter', () => {
describe('getState', () => {
@@ -122,8 +122,8 @@ describe('CategoryReverter', () => {
}) => {
it(description, () => {
// arrange
const category = new CategoryStub(1).withScripts(...allScripts);
const categoryNodeId = getCategoryNodeId(category);
const category = new CategoryStub('parent-category-id').withScripts(...allScripts);
const categoryNodeId = createExecutableIdFromNodeId(category.executableId);
const collection = new CategoryCollectionStub().withAction(category);
const categoryReverter = new CategoryReverter(categoryNodeId, collection);
const selectedScripts = selectScripts(allScripts);
@@ -157,8 +157,8 @@ describe('CategoryReverter', () => {
new ScriptStub('reversible').withReversibility(true),
new ScriptStub('reversible2').withReversibility(true),
];
const category = new CategoryStub(1).withScripts(...allScripts);
const nodeId = getCategoryNodeId(category);
const category = new CategoryStub('parent-category').withScripts(...allScripts);
const nodeId = createExecutableIdFromNodeId(category.executableId);
const collection = new CategoryCollectionStub().withAction(category);
const categorySelection = new CategorySelectionStub();
const categoryReverter = new CategoryReverter(nodeId, collection);
@@ -170,7 +170,7 @@ describe('CategoryReverter', () => {
);
// assert
const actualRevertState = categorySelection.isCategorySelected(
category.id,
category.executableId,
expectedRevertState,
);
expect(actualRevertState).to.equal(true);

View File

@@ -5,15 +5,16 @@ import { CategoryReverter } from '@/presentation/components/Scripts/View/Tree/No
import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollectionStub';
import { CategoryStub } from '@tests/unit/shared/Stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
import { getCategoryNodeId, getScriptNodeId } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter';
import { createNodeIdForExecutable } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter';
import { type NodeMetadata, NodeType } from '@/presentation/components/Scripts/View/Tree/NodeContent/NodeMetadata';
import type { TreeNodeId } from '@/presentation/components/Scripts/View/Tree/TreeView/Node/TreeNode';
describe('ReverterFactory', () => {
describe('getReverter', () => {
it('gets CategoryReverter for category node', () => {
it(`gets ${CategoryReverter.name} for category node`, () => {
// arrange
const category = new CategoryStub(0).withScriptIds('55');
const node = getNodeContentStub(getCategoryNodeId(category), NodeType.Category);
const category = new CategoryStub('test-action-category').withScriptIds('55');
const node = getNodeContentStub(createNodeIdForExecutable(category), NodeType.Category);
const collection = new CategoryCollectionStub()
.withAction(category);
// act
@@ -21,21 +22,21 @@ describe('ReverterFactory', () => {
// assert
expect(result instanceof CategoryReverter).to.equal(true);
});
it('gets ScriptReverter for script node', () => {
it(`gets ${ScriptReverter.name} for script node`, () => {
// arrange
const script = new ScriptStub('test');
const node = getNodeContentStub(getScriptNodeId(script), NodeType.Script);
const node = getNodeContentStub(createNodeIdForExecutable(script), NodeType.Script);
const collection = new CategoryCollectionStub()
.withAction(new CategoryStub(0).withScript(script));
.withAction(new CategoryStub('test-action-category').withScript(script));
// act
const result = getReverter(node, collection);
// assert
expect(result instanceof ScriptReverter).to.equal(true);
});
});
function getNodeContentStub(nodeId: string, type: NodeType): NodeMetadata {
function getNodeContentStub(nodeId: TreeNodeId, type: NodeType): NodeMetadata {
return {
id: nodeId,
executableId: nodeId,
text: 'text',
isReversible: false,
docs: [],

View File

@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
import { ScriptReverter } from '@/presentation/components/Scripts/View/Tree/NodeContent/Reverter/ScriptReverter';
import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
import { SelectedScriptStub } from '@tests/unit/shared/Stubs/SelectedScriptStub';
import { getScriptNodeId } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter';
import { createNodeIdForExecutable } from '@/presentation/components/Scripts/View/Tree/TreeViewAdapter/CategoryNodeMetadataConverter';
import { UserSelectionStub } from '@tests/unit/shared/Stubs/UserSelectionStub';
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
import { ScriptSelectionStub } from '@tests/unit/shared/Stubs/ScriptSelectionStub';
@@ -11,7 +11,7 @@ describe('ScriptReverter', () => {
describe('getState', () => {
// arrange
const script = new ScriptStub('id');
const nodeId = getScriptNodeId(script);
const nodeId = createNodeIdForExecutable(script);
const testScenarios: ReadonlyArray<{
readonly description: string;
readonly selectedScripts: readonly SelectedScript[];
@@ -98,7 +98,7 @@ describe('ScriptReverter', () => {
expectedRevert: false,
},
];
const nodeId = getScriptNodeId(script);
const nodeId = createNodeIdForExecutable(script);
testScenarios.forEach((
{ description, selection, expectedRevert },
) => {
@@ -111,7 +111,9 @@ describe('ScriptReverter', () => {
// act
sut.selectWithRevertState(revertState, userSelection);
// assert
expect(scriptSelection.isScriptSelected(script.id, expectedRevert)).to.equal(true);
const isActuallySelected = scriptSelection
.isScriptSelected(script.executableId, expectedRevert);
expect(isActuallySelected).to.equal(true);
});
});
});