@principal-ai/subsystems-react 0.56.3 → 0.56.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/subsystem/DecorationPanel.d.ts +37 -0
- package/dist/subsystem/DecorationPanel.d.ts.map +1 -0
- package/dist/subsystem/DecorationPanel.js +147 -0
- package/dist/subsystem/DecorationPanel.js.map +1 -0
- package/dist/subsystem/DiagnosticToggle.d.ts +7 -1
- package/dist/subsystem/DiagnosticToggle.d.ts.map +1 -1
- package/dist/subsystem/DiagnosticToggle.js +8 -7
- package/dist/subsystem/DiagnosticToggle.js.map +1 -1
- package/dist/subsystem/GraphSidebar.d.ts +4 -2
- package/dist/subsystem/GraphSidebar.d.ts.map +1 -1
- package/dist/subsystem/GraphSidebar.js +3 -1
- package/dist/subsystem/GraphSidebar.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +10 -13
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +112 -272
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/TrailStepsNav.d.ts.map +1 -1
- package/dist/subsystem/TrailStepsNav.js +6 -4
- package/dist/subsystem/TrailStepsNav.js.map +1 -1
- package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
- package/dist/subsystem/TrailsPanel.js +14 -10
- package/dist/subsystem/TrailsPanel.js.map +1 -1
- package/dist/subsystem/decoration.d.ts +112 -0
- package/dist/subsystem/decoration.d.ts.map +1 -0
- package/dist/subsystem/decoration.js +54 -0
- package/dist/subsystem/decoration.js.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +37 -0
- package/src/stories/Canvases/C4/C4ToComponentDrill.stories.tsx +38 -71
- package/src/stories/Canvases/Subsystem/GraphTitle.stories.tsx +1 -2
- package/src/stories/Canvases/Subsystem/StepEmphasis.stories.tsx +528 -0
- package/src/stories/Components/DecorationPanel.stories.tsx +160 -0
- package/src/subsystem/DecorationPanel.test.tsx +128 -0
- package/src/subsystem/DecorationPanel.tsx +334 -0
- package/src/subsystem/DiagnosticToggle.tsx +20 -5
- package/src/subsystem/GraphSidebar.test.tsx +18 -0
- package/src/subsystem/GraphSidebar.tsx +6 -1
- package/src/subsystem/SubsystemComponentGraph.sidebar.test.tsx +218 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +184 -414
- package/src/subsystem/TrailStepsNav.test.tsx +32 -0
- package/src/subsystem/TrailStepsNav.tsx +6 -4
- package/src/subsystem/TrailsPanel.test.tsx +20 -0
- package/src/subsystem/TrailsPanel.tsx +11 -1
- package/src/subsystem/decoration.test.ts +72 -0
- package/src/subsystem/decoration.ts +151 -0
- package/src/subsystem/stepEmphasis.test.ts +110 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { createElement } from 'react';
|
|
3
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
4
|
+
import { Window } from 'happy-dom';
|
|
5
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
6
|
+
import { SubsystemComponentGraph, type SubsystemComponentGraphProps } from './SubsystemComponentGraph';
|
|
7
|
+
|
|
8
|
+
const components: SubsystemComponentGraphProps['components'] = [
|
|
9
|
+
{ alias: 'entry', name: 'Entry', construct: 'function', file: 'src/entry.ts', purl: 'pkg:github/test/app' },
|
|
10
|
+
{ alias: 'store', name: 'Store', construct: 'function', file: 'src/store.ts', purl: 'pkg:github/test/app' },
|
|
11
|
+
];
|
|
12
|
+
const trails: SubsystemComponentGraphProps['trails'] = [{
|
|
13
|
+
id: 'request',
|
|
14
|
+
title: 'Request flow',
|
|
15
|
+
steps: [{ from: 'entry', to: 'store', mechanism: 'calls', file: 'src/entry.ts', purl: 'pkg:github/test/app', symbol: 'Entry' }],
|
|
16
|
+
}];
|
|
17
|
+
const graphifyRelations: SubsystemComponentGraphProps['graphifyRelations'] = [
|
|
18
|
+
{ id: 'static', from: 'entry', to: 'store', relation: 'references' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function renderGraph(props: Partial<SubsystemComponentGraphProps> = {}) {
|
|
22
|
+
const markup = renderToStaticMarkup(
|
|
23
|
+
createElement(
|
|
24
|
+
ThemeProvider,
|
|
25
|
+
{ theme: defaultEditorTheme },
|
|
26
|
+
createElement(SubsystemComponentGraph, {
|
|
27
|
+
components,
|
|
28
|
+
trails,
|
|
29
|
+
title: 'Request subsystem',
|
|
30
|
+
description: 'Always visible **description**.',
|
|
31
|
+
...props,
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
const dom = new Window();
|
|
36
|
+
dom.document.body.innerHTML = markup;
|
|
37
|
+
return dom.document.body;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('subsystem graph sidebar', () => {
|
|
41
|
+
test('puts the name in a full-width header above the sidebar and graph', () => {
|
|
42
|
+
const body = renderGraph();
|
|
43
|
+
const header = body.querySelector('header');
|
|
44
|
+
expect(header?.querySelector('h2')?.textContent).toBe('Request subsystem');
|
|
45
|
+
expect(body.querySelectorAll('h2').length).toBe(1);
|
|
46
|
+
expect(header?.textContent).not.toContain('Always visible');
|
|
47
|
+
const content = header?.nextElementSibling;
|
|
48
|
+
expect(content?.querySelector('[aria-label="Resize sidebar"]')).not.toBeNull();
|
|
49
|
+
expect(content?.textContent).toContain('Always visible description.');
|
|
50
|
+
expect(content?.textContent).not.toContain('Request subsystem');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('caps the scrollable description at 30% above the trails', () => {
|
|
54
|
+
const body = renderGraph();
|
|
55
|
+
const description = body.querySelector('[style*="max-height:30%"]') as HTMLElement;
|
|
56
|
+
expect(description).not.toBeNull();
|
|
57
|
+
expect(description.textContent).toContain('Always visible description.');
|
|
58
|
+
expect(description.style.overflowY).toBe('auto');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('keeps the Files button in the header when no name is provided', () => {
|
|
62
|
+
const body = renderGraph({ title: undefined });
|
|
63
|
+
expect(body.querySelector('header h2')).toBeNull();
|
|
64
|
+
expect(body.querySelector('header button[aria-label^="Files"]')?.textContent).toBe('Files2');
|
|
65
|
+
expect(body.textContent).toContain('Always visible description.');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('the header Files button starts inactive and leaves trails visible', () => {
|
|
69
|
+
const body = renderGraph();
|
|
70
|
+
expect(body.querySelector('header button[aria-label^="Files"]')?.getAttribute('aria-pressed')).toBe('false');
|
|
71
|
+
expect(body.textContent).toContain('Request flow');
|
|
72
|
+
expect(body.querySelector('[role="tree"]')).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('groups Trails, Files, and Issues into one switch with Trails selected by default', () => {
|
|
76
|
+
const body = renderGraph();
|
|
77
|
+
const group = body.querySelector('header [role="group"][aria-label="Sidebar view"]');
|
|
78
|
+
expect(group?.querySelectorAll('button').length).toBe(3);
|
|
79
|
+
expect(group?.querySelectorAll('button[aria-pressed="true"]').length).toBe(1);
|
|
80
|
+
expect(group?.querySelector('button[aria-label="Trails"]')?.getAttribute('aria-pressed')).toBe('true');
|
|
81
|
+
expect(group?.querySelector('button[aria-label^="Issues:"]')?.textContent).toBe('Issues');
|
|
82
|
+
expect((group as HTMLElement).style.borderStyle).toBe('solid');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('omits the Files button when no components have files', () => {
|
|
86
|
+
const body = renderGraph({
|
|
87
|
+
components: components.map((component) => ({ ...component, file: '' })),
|
|
88
|
+
});
|
|
89
|
+
expect(body.querySelector('button[aria-label^="Files"]')).toBeNull();
|
|
90
|
+
expect(body.textContent).toContain('Request flow');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
for (const active of [true, false]) {
|
|
94
|
+
test(`puts the ${active ? 'active' : 'inactive'} issue toggle in the header, not the sidebar`, () => {
|
|
95
|
+
const body = renderGraph({
|
|
96
|
+
diagnostic: { status: 'issues', issueCount: 3 },
|
|
97
|
+
showIssues: active,
|
|
98
|
+
});
|
|
99
|
+
const header = body.querySelector('header');
|
|
100
|
+
const toggle = header?.querySelector('button[aria-label^="Issues:"]');
|
|
101
|
+
expect(toggle).not.toBeNull();
|
|
102
|
+
expect(toggle?.textContent).toBe('Issues3');
|
|
103
|
+
expect((toggle as HTMLElement).style.borderStyle).toBe('none');
|
|
104
|
+
const filesButton = header?.querySelector('button[aria-label^="Files"]') as HTMLElement;
|
|
105
|
+
expect(filesButton.style.borderStyle).toBe('none');
|
|
106
|
+
expect(filesButton.parentElement?.style.borderStyle).toBe('solid');
|
|
107
|
+
expect(filesButton.parentElement?.style.display).toBe('grid');
|
|
108
|
+
expect(filesButton.parentElement?.style.gridAutoColumns).toBe('1fr');
|
|
109
|
+
expect((toggle as HTMLElement).style.justifyContent).toBe('center');
|
|
110
|
+
expect(toggle?.getAttribute('aria-pressed')).toBe(String(active));
|
|
111
|
+
expect(header?.querySelectorAll('button').length).toBe(3);
|
|
112
|
+
expect(header?.querySelectorAll('button[aria-pressed="true"]').length).toBe(1);
|
|
113
|
+
expect(header?.querySelector('button[aria-label="Trails"]')?.getAttribute('aria-pressed')).toBe(String(!active));
|
|
114
|
+
expect(body.querySelectorAll('button[aria-label^="Issues:"]').length).toBe(1);
|
|
115
|
+
expect(header?.nextElementSibling?.querySelector('button[aria-label^="Issues:"]')).toBeNull();
|
|
116
|
+
expect(body.textContent).toContain('Always visible description.');
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
test('counts unique files rather than components in the same file', () => {
|
|
121
|
+
const body = renderGraph({
|
|
122
|
+
components: [...components, { ...components[0], alias: 'helper' }],
|
|
123
|
+
});
|
|
124
|
+
expect(body.querySelector('header button[aria-label^="Files"]')?.textContent).toBe('Files2');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('counts the same path in separate repositories as separate files', () => {
|
|
128
|
+
const body = renderGraph({
|
|
129
|
+
components: [...components, { ...components[0], alias: 'other-entry', purl: 'pkg:github/test/other' }],
|
|
130
|
+
});
|
|
131
|
+
expect(body.querySelector('header button[aria-label^="Files"]')?.textContent).toBe('Files3');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('a diagnostic alone creates a header without an empty sidebar', () => {
|
|
135
|
+
const body = renderGraph({
|
|
136
|
+
trails: undefined,
|
|
137
|
+
graphifyRelations,
|
|
138
|
+
title: undefined,
|
|
139
|
+
description: undefined,
|
|
140
|
+
diagnostic: { status: 'ok' },
|
|
141
|
+
});
|
|
142
|
+
expect(body.querySelector('header button[aria-pressed="false"]')).not.toBeNull();
|
|
143
|
+
expect(body.querySelector('[aria-label="Resize sidebar"]')).toBeNull();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('a name alone creates a header without an empty sidebar', () => {
|
|
147
|
+
const body = renderGraph({ trails: undefined, graphifyRelations, description: undefined });
|
|
148
|
+
expect(body.querySelector('header h2')?.textContent).toBe('Request subsystem');
|
|
149
|
+
expect(body.querySelector('[aria-label="Resize sidebar"]')).toBeNull();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('shows the description immediately without a description toggle', () => {
|
|
153
|
+
const body = renderGraph();
|
|
154
|
+
expect(body.textContent).toContain('Always visible description.');
|
|
155
|
+
expect(body.querySelector('strong')?.textContent).toBe('description');
|
|
156
|
+
expect(body.querySelector('[aria-label="Show description"]')).toBeNull();
|
|
157
|
+
expect(body.querySelector('[aria-label="Hide description"]')).toBeNull();
|
|
158
|
+
expect(body.textContent).toContain('Request flow');
|
|
159
|
+
expect(body.querySelector('[role="tablist"]')).toBeNull();
|
|
160
|
+
expect(body.querySelector('[role="tree"]')).toBeNull();
|
|
161
|
+
expect(body.textContent).not.toContain('src/entry.ts');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('still shows trails when an old Files tab selection was saved', () => {
|
|
165
|
+
const previousWindow = globalThis.window;
|
|
166
|
+
const dom = new Window();
|
|
167
|
+
dom.localStorage.setItem('principal.subsystems.viewState.sidebar-test', JSON.stringify({
|
|
168
|
+
sidebarView: 'files',
|
|
169
|
+
}));
|
|
170
|
+
globalThis.window = dom as unknown as typeof window;
|
|
171
|
+
try {
|
|
172
|
+
const body = renderGraph({ persistKey: 'sidebar-test' });
|
|
173
|
+
expect(body.textContent).toContain('Request flow');
|
|
174
|
+
expect(body.querySelector('[role="tree"]')).toBeNull();
|
|
175
|
+
expect(body.querySelector('[role="tablist"]')).toBeNull();
|
|
176
|
+
} finally {
|
|
177
|
+
globalThis.window = previousWindow;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('static graphs keep their description but do not show a file panel', () => {
|
|
182
|
+
const body = renderGraph({ trails: undefined, graphifyRelations });
|
|
183
|
+
expect(body.textContent).toContain('Always visible description.');
|
|
184
|
+
expect(body.querySelector('[aria-label="Resize sidebar"]')).not.toBeNull();
|
|
185
|
+
expect(body.querySelector('[role="tree"]')).toBeNull();
|
|
186
|
+
expect(body.textContent).not.toContain('src/entry.ts');
|
|
187
|
+
expect(body.querySelector('header')?.nextElementSibling?.textContent).not.toContain('Files');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('files alone do not create an empty sidebar', () => {
|
|
191
|
+
const body = renderGraph({
|
|
192
|
+
trails: undefined,
|
|
193
|
+
graphifyRelations,
|
|
194
|
+
title: undefined,
|
|
195
|
+
description: undefined,
|
|
196
|
+
});
|
|
197
|
+
expect(body.querySelector('[aria-label="Resize sidebar"]')).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('the description stays visible while diagnostics replace the trails', () => {
|
|
201
|
+
const body = renderGraph({ showIssues: true, issues: [] });
|
|
202
|
+
expect(body.textContent).toContain('Always visible description.');
|
|
203
|
+
expect(body.textContent).not.toContain('Request flow');
|
|
204
|
+
expect(body.querySelector('[aria-label="Resize sidebar"]')).not.toBeNull();
|
|
205
|
+
expect(body.querySelector('[aria-label="Hide description"]')).toBeNull();
|
|
206
|
+
expect(body.querySelector('[role="tree"]')).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('hideSidebar still hides the title, description, and trails', () => {
|
|
210
|
+
const body = renderGraph({ hideSidebar: true, diagnostic: { status: 'issues', issueCount: 3 } });
|
|
211
|
+
expect(body.textContent).not.toContain('Request subsystem');
|
|
212
|
+
expect(body.textContent).not.toContain('Always visible');
|
|
213
|
+
expect(body.textContent).not.toContain('Request flow');
|
|
214
|
+
expect(body.querySelector('[aria-label="Resize sidebar"]')).toBeNull();
|
|
215
|
+
expect(body.querySelector('header')).toBeNull();
|
|
216
|
+
expect(body.querySelector('button[aria-label^="Diagnostics:"]')).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
});
|