mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-24 13:38:43 +02:00
Remove the restriction that prevented users from deleting built-in patterns (those with IDs prefixed 'pattern-'). Built-in patterns are intended as starting points, not permanent fixtures. Changes: - Add deletedBuiltinPatternIds field to WorkspaceState to track which built-in patterns the user has removed - Update mergePatterns to skip deleted built-ins during workspace load, preventing them from being re-added - Remove the isBuiltinPattern guard from AryxAppService.deletePattern; when a built-in is deleted, its ID is recorded in the tracking list - Enable the delete button in SettingsPanel and PatternEditor for all patterns (keep the 'Built-in pattern' label for context) - Add 3 tests covering built-in deletion, custom deletion, and selectedPatternId fallback Users can still restore deleted built-ins via workspace reset. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
145 lines
4.7 KiB
TypeScript
145 lines
4.7 KiB
TypeScript
import { describe, expect, mock, test } from 'bun:test';
|
|
|
|
import type { PatternDefinition } from '@shared/domain/pattern';
|
|
import { resolvePatternGraph } from '@shared/domain/pattern';
|
|
import { createWorkspaceSeed, type WorkspaceState } from '@shared/domain/workspace';
|
|
|
|
const TIMESTAMP = '2026-03-27T00:00:00.000Z';
|
|
|
|
mock.module('electron', () => {
|
|
const electronMock = {
|
|
app: {
|
|
isPackaged: false,
|
|
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
|
|
getPath: () => 'C:\\workspace\\personal\\repositories\\aryx\\tests\\fixtures',
|
|
},
|
|
dialog: {
|
|
showOpenDialog: async () => ({ canceled: true, filePaths: [] }),
|
|
},
|
|
shell: {
|
|
openPath: async () => '',
|
|
},
|
|
};
|
|
|
|
return {
|
|
...electronMock,
|
|
default: electronMock,
|
|
};
|
|
});
|
|
|
|
mock.module('keytar', () => ({
|
|
default: {
|
|
getPassword: async () => null,
|
|
setPassword: async () => undefined,
|
|
deletePassword: async () => false,
|
|
},
|
|
}));
|
|
|
|
const { AryxAppService } = await import('@main/AryxAppService');
|
|
|
|
function createService(
|
|
workspace: WorkspaceState,
|
|
options?: { knownApprovalToolNames?: string[] },
|
|
): InstanceType<typeof AryxAppService> {
|
|
const service = new AryxAppService();
|
|
const internals = service as unknown as Record<string, unknown>;
|
|
internals.loadWorkspace = async () => workspace;
|
|
internals.persistAndBroadcast = async (nextWorkspace: WorkspaceState) => nextWorkspace;
|
|
internals.listKnownApprovalToolNames = async () => options?.knownApprovalToolNames ?? ['read', 'write', 'shell'];
|
|
|
|
return service;
|
|
}
|
|
|
|
function requirePattern(workspace: WorkspaceState, mode: PatternDefinition['mode']): PatternDefinition {
|
|
const pattern = workspace.patterns.find((candidate) => candidate.mode === mode);
|
|
if (!pattern) {
|
|
throw new Error(`Expected workspace seed to include a ${mode} pattern.`);
|
|
}
|
|
|
|
return pattern;
|
|
}
|
|
|
|
describe('AryxAppService deletePattern', () => {
|
|
test('deletes a built-in pattern and tracks its ID', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
const builtinPattern = requirePattern(workspace, 'sequential');
|
|
const service = createService(workspace);
|
|
|
|
const result = await service.deletePattern(builtinPattern.id);
|
|
|
|
expect(result.patterns.find((p) => p.id === builtinPattern.id)).toBeUndefined();
|
|
expect(result.deletedBuiltinPatternIds).toContain(builtinPattern.id);
|
|
});
|
|
|
|
test('deletes a custom pattern without tracking its ID', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
const customPattern: PatternDefinition = {
|
|
...requirePattern(workspace, 'single'),
|
|
id: 'custom-pattern',
|
|
name: 'My Custom Pattern',
|
|
};
|
|
workspace.patterns.push(customPattern);
|
|
const service = createService(workspace);
|
|
|
|
const result = await service.deletePattern('custom-pattern');
|
|
|
|
expect(result.patterns.find((p) => p.id === 'custom-pattern')).toBeUndefined();
|
|
expect(result.deletedBuiltinPatternIds ?? []).not.toContain('custom-pattern');
|
|
});
|
|
|
|
test('selects first remaining pattern when the active pattern is deleted', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
const targetPattern = requirePattern(workspace, 'single');
|
|
workspace.selectedPatternId = targetPattern.id;
|
|
const service = createService(workspace);
|
|
|
|
const result = await service.deletePattern(targetPattern.id);
|
|
|
|
expect(result.selectedPatternId).toBe(result.patterns[0]?.id);
|
|
expect(result.selectedPatternId).not.toBe(targetPattern.id);
|
|
});
|
|
});
|
|
|
|
describe('AryxAppService savePattern', () => {
|
|
test('preserves a provided custom graph instead of re-syncing it', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
const pattern = requirePattern(workspace, 'sequential');
|
|
const baseGraph = resolvePatternGraph(pattern);
|
|
const customGraph = {
|
|
...baseGraph,
|
|
nodes: baseGraph.nodes.map((node, index) => ({
|
|
...node,
|
|
position: {
|
|
x: node.position.x + 37 + index,
|
|
y: node.position.y + 19 + index * 7,
|
|
},
|
|
})),
|
|
};
|
|
const service = createService(workspace);
|
|
|
|
const result = await service.savePattern({
|
|
...pattern,
|
|
graph: customGraph,
|
|
updatedAt: TIMESTAMP,
|
|
});
|
|
|
|
const savedPattern = requirePattern(result, 'sequential');
|
|
expect(savedPattern.graph).toEqual(customGraph);
|
|
});
|
|
|
|
test('still seeds a default graph when the pattern graph is missing', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
const pattern = requirePattern(workspace, 'group-chat');
|
|
const service = createService(workspace);
|
|
|
|
const result = await service.savePattern({
|
|
...pattern,
|
|
graph: undefined,
|
|
updatedAt: TIMESTAMP,
|
|
});
|
|
|
|
const savedPattern = requirePattern(result, 'group-chat');
|
|
expect(savedPattern.graph).toEqual(resolvePatternGraph(pattern));
|
|
});
|
|
});
|