@memberjunction/ng-entity-relationship-diagram 5.43.0 → 5.45.0
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/lib/components/entity-details/entity-details.component.js +157 -154
- package/dist/lib/components/entity-details/entity-details.component.js.map +1 -1
- package/dist/lib/components/erd-diagram.component.d.ts +4 -0
- package/dist/lib/components/erd-diagram.component.d.ts.map +1 -1
- package/dist/lib/components/erd-diagram.component.js +19 -25
- package/dist/lib/components/erd-diagram.component.js.map +1 -1
- package/dist/lib/entity-relationship-diagram.module.d.ts +1 -1
- package/dist/lib/entity-relationship-diagram.module.d.ts.map +1 -1
- package/dist/lib/entity-relationship-diagram.module.js +9 -3
- package/dist/lib/entity-relationship-diagram.module.js.map +1 -1
- package/package.json +6 -5
- package/dist/__tests__/compute-erd-layout.test.d.ts +0 -2
- package/dist/__tests__/compute-erd-layout.test.d.ts.map +0 -1
- package/dist/__tests__/compute-erd-layout.test.js +0 -207
- package/dist/__tests__/compute-erd-layout.test.js.map +0 -1
- package/dist/__tests__/entity-to-erd-adapter.test.d.ts +0 -2
- package/dist/__tests__/entity-to-erd-adapter.test.d.ts.map +0 -1
- package/dist/__tests__/entity-to-erd-adapter.test.js +0 -252
- package/dist/__tests__/entity-to-erd-adapter.test.js.map +0 -1
- package/dist/__tests__/erd-types.test.d.ts +0 -2
- package/dist/__tests__/erd-types.test.d.ts.map +0 -1
- package/dist/__tests__/erd-types.test.js +0 -104
- package/dist/__tests__/erd-types.test.js.map +0 -1
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.js +0 -7
- package/dist/__tests__/index.test.js.map +0 -1
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { computeErdLayout, pointsToPath, getNeighbors } from '../lib/layout/compute-erd-layout';
|
|
3
|
-
const pk = (id, name = 'ID') => ({ id, name, type: 'uniqueidentifier', isPrimaryKey: true });
|
|
4
|
-
const fk = (id, name, targetId) => ({
|
|
5
|
-
id, name, type: 'uniqueidentifier', isPrimaryKey: false, relatedNodeId: targetId,
|
|
6
|
-
});
|
|
7
|
-
const col = (id, name) => ({ id, name, type: 'nvarchar', isPrimaryKey: false });
|
|
8
|
-
const node = (id, schema, name, fields) => ({
|
|
9
|
-
id, name, schemaName: schema, fields,
|
|
10
|
-
});
|
|
11
|
-
describe('computeErdLayout', () => {
|
|
12
|
-
describe('empty input', () => {
|
|
13
|
-
it('returns empty layout for no nodes', () => {
|
|
14
|
-
const layout = computeErdLayout([]);
|
|
15
|
-
expect(layout.nodes).toEqual([]);
|
|
16
|
-
expect(layout.edges).toEqual([]);
|
|
17
|
-
expect(layout.bands).toEqual([]);
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
describe('single node', () => {
|
|
21
|
-
it('creates one node in one band', () => {
|
|
22
|
-
const n = node('u', 'core', 'Users', [pk('u:id')]);
|
|
23
|
-
const layout = computeErdLayout([n]);
|
|
24
|
-
expect(layout.nodes).toHaveLength(1);
|
|
25
|
-
expect(layout.bands).toHaveLength(1);
|
|
26
|
-
expect(layout.bands[0].schemaName).toBe('core');
|
|
27
|
-
expect(layout.nodes[0].id).toBe('u');
|
|
28
|
-
expect(layout.nodes[0].width).toBeGreaterThan(0);
|
|
29
|
-
expect(layout.nodes[0].height).toBeGreaterThan(0);
|
|
30
|
-
});
|
|
31
|
-
it('uses the synthetic default schema when schemaName is missing', () => {
|
|
32
|
-
const n = node('u', undefined, 'Users', [pk('u:id')]);
|
|
33
|
-
const layout = computeErdLayout([n]);
|
|
34
|
-
expect(layout.bands).toHaveLength(1);
|
|
35
|
-
expect(layout.bands[0].schemaName).toBe('_');
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
describe('column layout rules', () => {
|
|
39
|
-
const make = (count, schema = 'core') => Array.from({ length: count }, (_, i) => node(`${schema}-${i}`, schema, `Entity${i}`, [pk(`${i}:id`)]));
|
|
40
|
-
it('uses 1 column when count <= 3', () => {
|
|
41
|
-
const layout = computeErdLayout(make(3));
|
|
42
|
-
const xs = new Set(layout.nodes.map(n => n.x));
|
|
43
|
-
expect(xs.size).toBe(1);
|
|
44
|
-
});
|
|
45
|
-
it('uses 2 columns when count is between 4 and 8', () => {
|
|
46
|
-
const layout = computeErdLayout(make(6));
|
|
47
|
-
const xs = new Set(layout.nodes.map(n => n.x));
|
|
48
|
-
expect(xs.size).toBe(2);
|
|
49
|
-
});
|
|
50
|
-
it('uses 3 columns when count > 8', () => {
|
|
51
|
-
const layout = computeErdLayout(make(12));
|
|
52
|
-
const xs = new Set(layout.nodes.map(n => n.x));
|
|
53
|
-
expect(xs.size).toBe(3);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
describe('schema bands', () => {
|
|
57
|
-
it('creates one band per distinct schema', () => {
|
|
58
|
-
const nodes = [
|
|
59
|
-
node('a', 'core', 'A', [pk('a:id')]),
|
|
60
|
-
node('b', 'ai', 'B', [pk('b:id')]),
|
|
61
|
-
node('c', 'core', 'C', [pk('c:id')]),
|
|
62
|
-
];
|
|
63
|
-
const layout = computeErdLayout(nodes, { schemaOrder: ['core', 'ai'] });
|
|
64
|
-
expect(layout.bands.map(b => b.schemaName)).toEqual(['core', 'ai']);
|
|
65
|
-
});
|
|
66
|
-
it('aligns all band heights to the tallest', () => {
|
|
67
|
-
const nodes = [
|
|
68
|
-
node('a', 'core', 'A', [pk('a:id')]),
|
|
69
|
-
node('b', 'core', 'B', [pk('b:id')]),
|
|
70
|
-
node('c', 'core', 'C', [pk('c:id')]),
|
|
71
|
-
node('d', 'core', 'D', [pk('d:id')]),
|
|
72
|
-
node('e', 'ai', 'E', [pk('e:id')]),
|
|
73
|
-
];
|
|
74
|
-
const layout = computeErdLayout(nodes);
|
|
75
|
-
const heights = new Set(layout.bands.map(b => b.height));
|
|
76
|
-
expect(heights.size).toBe(1);
|
|
77
|
-
});
|
|
78
|
-
it('sorts entities alphabetically within a band', () => {
|
|
79
|
-
const nodes = [
|
|
80
|
-
node('a', 'core', 'Charlie', [pk('c:id')]),
|
|
81
|
-
node('b', 'core', 'Alpha', [pk('a:id')]),
|
|
82
|
-
node('c', 'core', 'Bravo', [pk('b:id')]),
|
|
83
|
-
];
|
|
84
|
-
const layout = computeErdLayout(nodes);
|
|
85
|
-
expect(layout.nodes.map(n => n.name)).toEqual(['Alpha', 'Bravo', 'Charlie']);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
describe('field visibility', () => {
|
|
89
|
-
it('hides non-PK/FK fields by default (keys-only view)', () => {
|
|
90
|
-
const n = node('u', 'core', 'Users', [
|
|
91
|
-
pk('u:id'),
|
|
92
|
-
col('u:name', 'Name'),
|
|
93
|
-
col('u:email', 'Email'),
|
|
94
|
-
fk('u:role', 'RoleID', 'r'),
|
|
95
|
-
]);
|
|
96
|
-
const layout = computeErdLayout([n]);
|
|
97
|
-
const visible = layout.nodes[0].visibleFields.map(f => f.name);
|
|
98
|
-
expect(visible).toEqual(['ID', 'RoleID']);
|
|
99
|
-
expect(layout.nodes[0].hasMore).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
it('shows every field when showAllFields is true', () => {
|
|
102
|
-
const n = node('u', 'core', 'Users', [
|
|
103
|
-
pk('u:id'),
|
|
104
|
-
col('u:name', 'Name'),
|
|
105
|
-
col('u:email', 'Email'),
|
|
106
|
-
]);
|
|
107
|
-
const layout = computeErdLayout([n], { showAllFields: true });
|
|
108
|
-
expect(layout.nodes[0].visibleFields.map(f => f.name)).toEqual(['ID', 'Name', 'Email']);
|
|
109
|
-
expect(layout.nodes[0].hasMore).toBe(false);
|
|
110
|
-
});
|
|
111
|
-
it('shows every field when the node is in the expanded set', () => {
|
|
112
|
-
const n = node('u', 'core', 'Users', [
|
|
113
|
-
pk('u:id'),
|
|
114
|
-
col('u:name', 'Name'),
|
|
115
|
-
]);
|
|
116
|
-
const layout = computeErdLayout([n], { expandedNodeIds: new Set(['u']) });
|
|
117
|
-
expect(layout.nodes[0].visibleFields.map(f => f.name)).toEqual(['ID', 'Name']);
|
|
118
|
-
expect(layout.nodes[0].hasMore).toBe(false);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
describe('edges', () => {
|
|
122
|
-
it('creates an edge for each FK field that has a resolvable target', () => {
|
|
123
|
-
const nodes = [
|
|
124
|
-
node('u', 'core', 'Users', [pk('u:id'), fk('u:r', 'RoleID', 'r')]),
|
|
125
|
-
node('r', 'core', 'Roles', [pk('r:id')]),
|
|
126
|
-
];
|
|
127
|
-
const layout = computeErdLayout(nodes);
|
|
128
|
-
expect(layout.edges).toHaveLength(1);
|
|
129
|
-
expect(layout.edges[0].sourceId).toBe('u');
|
|
130
|
-
expect(layout.edges[0].targetId).toBe('r');
|
|
131
|
-
});
|
|
132
|
-
it('skips FKs whose target isn\'t in the node list', () => {
|
|
133
|
-
const nodes = [node('u', 'core', 'Users', [pk('u:id'), fk('u:x', 'GhostID', 'ghost')])];
|
|
134
|
-
const layout = computeErdLayout(nodes);
|
|
135
|
-
expect(layout.edges).toEqual([]);
|
|
136
|
-
});
|
|
137
|
-
it('produces a 4-point orthogonal path for a cross-schema edge', () => {
|
|
138
|
-
const nodes = [
|
|
139
|
-
node('u', 'core', 'Users', [pk('u:id'), fk('u:r', 'RoleID', 'r')]),
|
|
140
|
-
node('r', 'ai', 'Roles', [pk('r:id')]),
|
|
141
|
-
];
|
|
142
|
-
const layout = computeErdLayout(nodes, { schemaOrder: ['core', 'ai'] });
|
|
143
|
-
expect(layout.edges[0].points).toHaveLength(4);
|
|
144
|
-
expect(layout.edges[0].selfReference).toBe(false);
|
|
145
|
-
expect(layout.edges[0].goingRight).toBe(true);
|
|
146
|
-
});
|
|
147
|
-
it('produces a 5-point loop path for a self-reference', () => {
|
|
148
|
-
const nodes = [
|
|
149
|
-
node('e', 'core', 'Employees', [pk('e:id'), fk('e:sup', 'SupervisorID', 'e')]),
|
|
150
|
-
];
|
|
151
|
-
const layout = computeErdLayout(nodes);
|
|
152
|
-
expect(layout.edges).toHaveLength(1);
|
|
153
|
-
expect(layout.edges[0].selfReference).toBe(true);
|
|
154
|
-
expect(layout.edges[0].points).toHaveLength(5);
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
describe('schemaOrder override', () => {
|
|
158
|
-
it('honors explicit schema ordering', () => {
|
|
159
|
-
const nodes = [
|
|
160
|
-
node('z', 'zzz', 'Z', [pk('z:id')]),
|
|
161
|
-
node('a', 'aaa', 'A', [pk('a:id')]),
|
|
162
|
-
];
|
|
163
|
-
const layout = computeErdLayout(nodes, { schemaOrder: ['zzz', 'aaa'] });
|
|
164
|
-
expect(layout.bands.map(b => b.schemaName)).toEqual(['zzz', 'aaa']);
|
|
165
|
-
});
|
|
166
|
-
it('appends schemas not mentioned in schemaOrder', () => {
|
|
167
|
-
const nodes = [
|
|
168
|
-
node('x', 'x', 'X', [pk('x:id')]),
|
|
169
|
-
node('y', 'y', 'Y', [pk('y:id')]),
|
|
170
|
-
];
|
|
171
|
-
const layout = computeErdLayout(nodes, { schemaOrder: ['y'] });
|
|
172
|
-
expect(layout.bands.map(b => b.schemaName)).toEqual(['y', 'x']);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
describe('pointsToPath', () => {
|
|
177
|
-
it('builds an SVG M/L path string', () => {
|
|
178
|
-
const d = pointsToPath([[0, 0], [10, 0], [10, 10]]);
|
|
179
|
-
expect(d).toBe('M0 0 L10 0 L10 10');
|
|
180
|
-
});
|
|
181
|
-
it('returns empty string for empty points', () => {
|
|
182
|
-
expect(pointsToPath([])).toBe('');
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
describe('getNeighbors', () => {
|
|
186
|
-
it('returns just the node id when no relationships exist', () => {
|
|
187
|
-
const nodes = [node('a', 'x', 'A', [pk('a:id')])];
|
|
188
|
-
expect([...getNeighbors(nodes, 'a')]).toEqual(['a']);
|
|
189
|
-
});
|
|
190
|
-
it('includes nodes referenced via outgoing FK', () => {
|
|
191
|
-
const nodes = [
|
|
192
|
-
node('u', 'core', 'Users', [pk('u:id'), fk('u:r', 'RoleID', 'r')]),
|
|
193
|
-
node('r', 'core', 'Roles', [pk('r:id')]),
|
|
194
|
-
];
|
|
195
|
-
const set = getNeighbors(nodes, 'u');
|
|
196
|
-
expect(set.has('r')).toBe(true);
|
|
197
|
-
});
|
|
198
|
-
it('includes nodes that reference the target via incoming FK', () => {
|
|
199
|
-
const nodes = [
|
|
200
|
-
node('u', 'core', 'Users', [pk('u:id'), fk('u:r', 'RoleID', 'r')]),
|
|
201
|
-
node('r', 'core', 'Roles', [pk('r:id')]),
|
|
202
|
-
];
|
|
203
|
-
const set = getNeighbors(nodes, 'r');
|
|
204
|
-
expect(set.has('u')).toBe(true);
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
//# sourceMappingURL=compute-erd-layout.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"compute-erd-layout.test.js","sourceRoot":"","sources":["../../src/__tests__/compute-erd-layout.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport { computeErdLayout, pointsToPath, getNeighbors } from '../lib/layout/compute-erd-layout';\nimport type { ERDNode } from '../lib/interfaces/erd-types';\n\nconst pk = (id: string, name = 'ID') => ({ id, name, type: 'uniqueidentifier', isPrimaryKey: true });\nconst fk = (id: string, name: string, targetId: string) => ({\n id, name, type: 'uniqueidentifier', isPrimaryKey: false, relatedNodeId: targetId,\n});\nconst col = (id: string, name: string) => ({ id, name, type: 'nvarchar', isPrimaryKey: false });\n\nconst node = (id: string, schema: string | undefined, name: string, fields: ERDNode['fields']): ERDNode => ({\n id, name, schemaName: schema, fields,\n});\n\ndescribe('computeErdLayout', () => {\n\n describe('empty input', () => {\n it('returns empty layout for no nodes', () => {\n const layout = computeErdLayout([]);\n expect(layout.nodes).toEqual([]);\n expect(layout.edges).toEqual([]);\n expect(layout.bands).toEqual([]);\n });\n });\n\n describe('single node', () => {\n it('creates one node in one band', () => {\n const n = node('u', 'core', 'Users', [pk('u:id')]);\n const layout = computeErdLayout([n]);\n expect(layout.nodes).toHaveLength(1);\n expect(layout.bands).toHaveLength(1);\n expect(layout.bands[0].schemaName).toBe('core');\n expect(layout.nodes[0].id).toBe('u');\n expect(layout.nodes[0].width).toBeGreaterThan(0);\n expect(layout.nodes[0].height).toBeGreaterThan(0);\n });\n\n it('uses the synthetic default schema when schemaName is missing', () => {\n const n = node('u', undefined, 'Users', [pk('u:id')]);\n const layout = computeErdLayout([n]);\n expect(layout.bands).toHaveLength(1);\n expect(layout.bands[0].schemaName).toBe('_');\n });\n });\n\n describe('column layout rules', () => {\n const make = (count: number, schema = 'core'): ERDNode[] =>\n Array.from({ length: count }, (_, i) => node(`${schema}-${i}`, schema, `Entity${i}`, [pk(`${i}:id`)]));\n\n it('uses 1 column when count <= 3', () => {\n const layout = computeErdLayout(make(3));\n const xs = new Set(layout.nodes.map(n => n.x));\n expect(xs.size).toBe(1);\n });\n\n it('uses 2 columns when count is between 4 and 8', () => {\n const layout = computeErdLayout(make(6));\n const xs = new Set(layout.nodes.map(n => n.x));\n expect(xs.size).toBe(2);\n });\n\n it('uses 3 columns when count > 8', () => {\n const layout = computeErdLayout(make(12));\n const xs = new Set(layout.nodes.map(n => n.x));\n expect(xs.size).toBe(3);\n });\n });\n\n describe('schema bands', () => {\n it('creates one band per distinct schema', () => {\n const nodes = [\n node('a', 'core', 'A', [pk('a:id')]),\n node('b', 'ai', 'B', [pk('b:id')]),\n node('c', 'core', 'C', [pk('c:id')]),\n ];\n const layout = computeErdLayout(nodes, { schemaOrder: ['core', 'ai'] });\n expect(layout.bands.map(b => b.schemaName)).toEqual(['core', 'ai']);\n });\n\n it('aligns all band heights to the tallest', () => {\n const nodes = [\n node('a', 'core', 'A', [pk('a:id')]),\n node('b', 'core', 'B', [pk('b:id')]),\n node('c', 'core', 'C', [pk('c:id')]),\n node('d', 'core', 'D', [pk('d:id')]),\n node('e', 'ai', 'E', [pk('e:id')]),\n ];\n const layout = computeErdLayout(nodes);\n const heights = new Set(layout.bands.map(b => b.height));\n expect(heights.size).toBe(1);\n });\n\n it('sorts entities alphabetically within a band', () => {\n const nodes = [\n node('a', 'core', 'Charlie', [pk('c:id')]),\n node('b', 'core', 'Alpha', [pk('a:id')]),\n node('c', 'core', 'Bravo', [pk('b:id')]),\n ];\n const layout = computeErdLayout(nodes);\n expect(layout.nodes.map(n => n.name)).toEqual(['Alpha', 'Bravo', 'Charlie']);\n });\n });\n\n describe('field visibility', () => {\n it('hides non-PK/FK fields by default (keys-only view)', () => {\n const n = node('u', 'core', 'Users', [\n pk('u:id'),\n col('u:name', 'Name'),\n col('u:email', 'Email'),\n fk('u:role', 'RoleID', 'r'),\n ]);\n const layout = computeErdLayout([n]);\n const visible = layout.nodes[0].visibleFields.map(f => f.name);\n expect(visible).toEqual(['ID', 'RoleID']);\n expect(layout.nodes[0].hasMore).toBe(true);\n });\n\n it('shows every field when showAllFields is true', () => {\n const n = node('u', 'core', 'Users', [\n pk('u:id'),\n col('u:name', 'Name'),\n col('u:email', 'Email'),\n ]);\n const layout = computeErdLayout([n], { showAllFields: true });\n expect(layout.nodes[0].visibleFields.map(f => f.name)).toEqual(['ID', 'Name', 'Email']);\n expect(layout.nodes[0].hasMore).toBe(false);\n });\n\n it('shows every field when the node is in the expanded set', () => {\n const n = node('u', 'core', 'Users', [\n pk('u:id'),\n col('u:name', 'Name'),\n ]);\n const layout = computeErdLayout([n], { expandedNodeIds: new Set(['u']) });\n expect(layout.nodes[0].visibleFields.map(f => f.name)).toEqual(['ID', 'Name']);\n expect(layout.nodes[0].hasMore).toBe(false);\n });\n });\n\n describe('edges', () => {\n it('creates an edge for each FK field that has a resolvable target', () => {\n const nodes = [\n node('u', 'core', 'Users', [pk('u:id'), fk('u:r', 'RoleID', 'r')]),\n node('r', 'core', 'Roles', [pk('r:id')]),\n ];\n const layout = computeErdLayout(nodes);\n expect(layout.edges).toHaveLength(1);\n expect(layout.edges[0].sourceId).toBe('u');\n expect(layout.edges[0].targetId).toBe('r');\n });\n\n it('skips FKs whose target isn\\'t in the node list', () => {\n const nodes = [node('u', 'core', 'Users', [pk('u:id'), fk('u:x', 'GhostID', 'ghost')])];\n const layout = computeErdLayout(nodes);\n expect(layout.edges).toEqual([]);\n });\n\n it('produces a 4-point orthogonal path for a cross-schema edge', () => {\n const nodes = [\n node('u', 'core', 'Users', [pk('u:id'), fk('u:r', 'RoleID', 'r')]),\n node('r', 'ai', 'Roles', [pk('r:id')]),\n ];\n const layout = computeErdLayout(nodes, { schemaOrder: ['core', 'ai'] });\n expect(layout.edges[0].points).toHaveLength(4);\n expect(layout.edges[0].selfReference).toBe(false);\n expect(layout.edges[0].goingRight).toBe(true);\n });\n\n it('produces a 5-point loop path for a self-reference', () => {\n const nodes = [\n node('e', 'core', 'Employees', [pk('e:id'), fk('e:sup', 'SupervisorID', 'e')]),\n ];\n const layout = computeErdLayout(nodes);\n expect(layout.edges).toHaveLength(1);\n expect(layout.edges[0].selfReference).toBe(true);\n expect(layout.edges[0].points).toHaveLength(5);\n });\n });\n\n describe('schemaOrder override', () => {\n it('honors explicit schema ordering', () => {\n const nodes = [\n node('z', 'zzz', 'Z', [pk('z:id')]),\n node('a', 'aaa', 'A', [pk('a:id')]),\n ];\n const layout = computeErdLayout(nodes, { schemaOrder: ['zzz', 'aaa'] });\n expect(layout.bands.map(b => b.schemaName)).toEqual(['zzz', 'aaa']);\n });\n\n it('appends schemas not mentioned in schemaOrder', () => {\n const nodes = [\n node('x', 'x', 'X', [pk('x:id')]),\n node('y', 'y', 'Y', [pk('y:id')]),\n ];\n const layout = computeErdLayout(nodes, { schemaOrder: ['y'] });\n expect(layout.bands.map(b => b.schemaName)).toEqual(['y', 'x']);\n });\n });\n});\n\ndescribe('pointsToPath', () => {\n it('builds an SVG M/L path string', () => {\n const d = pointsToPath([[0, 0], [10, 0], [10, 10]]);\n expect(d).toBe('M0 0 L10 0 L10 10');\n });\n\n it('returns empty string for empty points', () => {\n expect(pointsToPath([])).toBe('');\n });\n});\n\ndescribe('getNeighbors', () => {\n it('returns just the node id when no relationships exist', () => {\n const nodes = [node('a', 'x', 'A', [pk('a:id')])];\n expect([...getNeighbors(nodes, 'a')]).toEqual(['a']);\n });\n\n it('includes nodes referenced via outgoing FK', () => {\n const nodes = [\n node('u', 'core', 'Users', [pk('u:id'), fk('u:r', 'RoleID', 'r')]),\n node('r', 'core', 'Roles', [pk('r:id')]),\n ];\n const set = getNeighbors(nodes, 'u');\n expect(set.has('r')).toBe(true);\n });\n\n it('includes nodes that reference the target via incoming FK', () => {\n const nodes = [\n node('u', 'core', 'Users', [pk('u:id'), fk('u:r', 'RoleID', 'r')]),\n node('r', 'core', 'Roles', [pk('r:id')]),\n ];\n const set = getNeighbors(nodes, 'r');\n expect(set.has('u')).toBe(true);\n });\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"entity-to-erd-adapter.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/entity-to-erd-adapter.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { entityFieldToERDField, entityInfoToERDNode, entitiesToERDNodes, getOriginalEntityFromERDNode, findEntityByNodeId, buildERDDataFromEntities } from '../lib/utils/entity-to-erd-adapter';
|
|
3
|
-
// Minimal mock of EntityFieldInfo for testing
|
|
4
|
-
function createMockField(overrides = {}) {
|
|
5
|
-
return {
|
|
6
|
-
ID: 'field-1',
|
|
7
|
-
Name: 'TestField',
|
|
8
|
-
Type: 'nvarchar',
|
|
9
|
-
IsPrimaryKey: false,
|
|
10
|
-
Description: 'A test field',
|
|
11
|
-
AllowsNull: true,
|
|
12
|
-
DefaultValue: null,
|
|
13
|
-
Length: 100,
|
|
14
|
-
Precision: 0,
|
|
15
|
-
Scale: 0,
|
|
16
|
-
IsVirtual: false,
|
|
17
|
-
AutoIncrement: false,
|
|
18
|
-
RelatedEntityID: null,
|
|
19
|
-
RelatedEntity: null,
|
|
20
|
-
RelatedEntityFieldName: null,
|
|
21
|
-
EntityFieldValues: [],
|
|
22
|
-
...overrides
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function createMockEntity(overrides = {}) {
|
|
26
|
-
return {
|
|
27
|
-
ID: 'entity-1',
|
|
28
|
-
Name: 'MJTestEntity',
|
|
29
|
-
SchemaName: 'dbo',
|
|
30
|
-
Description: 'A test entity',
|
|
31
|
-
Status: 'Active',
|
|
32
|
-
BaseTable: 'TestTable',
|
|
33
|
-
Fields: [],
|
|
34
|
-
RelatedEntities: [],
|
|
35
|
-
...overrides
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
describe('entityFieldToERDField', () => {
|
|
39
|
-
it('should convert basic field properties', () => {
|
|
40
|
-
const field = createMockField();
|
|
41
|
-
const erdField = entityFieldToERDField(field);
|
|
42
|
-
expect(erdField.id).toBe('field-1');
|
|
43
|
-
expect(erdField.name).toBe('TestField');
|
|
44
|
-
expect(erdField.type).toBe('nvarchar');
|
|
45
|
-
expect(erdField.isPrimaryKey).toBe(false);
|
|
46
|
-
expect(erdField.description).toBe('A test field');
|
|
47
|
-
expect(erdField.allowsNull).toBe(true);
|
|
48
|
-
expect(erdField.length).toBe(100);
|
|
49
|
-
expect(erdField.isVirtual).toBe(false);
|
|
50
|
-
expect(erdField.autoIncrement).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
it('should set related entity info for foreign keys', () => {
|
|
53
|
-
const field = createMockField({
|
|
54
|
-
RelatedEntityID: 'rel-entity-1',
|
|
55
|
-
RelatedEntity: 'ParentEntity',
|
|
56
|
-
RelatedEntityFieldName: 'ID'
|
|
57
|
-
});
|
|
58
|
-
const erdField = entityFieldToERDField(field);
|
|
59
|
-
expect(erdField.relatedNodeId).toBe('rel-entity-1');
|
|
60
|
-
expect(erdField.relatedNodeName).toBe('ParentEntity');
|
|
61
|
-
expect(erdField.relatedFieldName).toBe('ID');
|
|
62
|
-
});
|
|
63
|
-
it('should not set related info for non-FK fields', () => {
|
|
64
|
-
const field = createMockField({ RelatedEntityID: null });
|
|
65
|
-
const erdField = entityFieldToERDField(field);
|
|
66
|
-
expect(erdField.relatedNodeId).toBeUndefined();
|
|
67
|
-
expect(erdField.relatedNodeName).toBeUndefined();
|
|
68
|
-
});
|
|
69
|
-
it('should convert EntityFieldValues to possibleValues', () => {
|
|
70
|
-
const field = createMockField({
|
|
71
|
-
EntityFieldValues: [
|
|
72
|
-
{ ID: 'v1', Value: 'Active', Code: 'A', Description: 'Active status', Sequence: 1 },
|
|
73
|
-
{ ID: 'v2', Value: 'Inactive', Code: 'I', Description: 'Inactive status', Sequence: 2 }
|
|
74
|
-
]
|
|
75
|
-
});
|
|
76
|
-
const erdField = entityFieldToERDField(field);
|
|
77
|
-
expect(erdField.possibleValues).toHaveLength(2);
|
|
78
|
-
expect(erdField.possibleValues[0].value).toBe('Active');
|
|
79
|
-
expect(erdField.possibleValues[0].code).toBe('A');
|
|
80
|
-
expect(erdField.possibleValues[1].sequence).toBe(2);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
describe('entityInfoToERDNode', () => {
|
|
84
|
-
it('should convert entity to ERD node', () => {
|
|
85
|
-
const entity = createMockEntity({
|
|
86
|
-
Fields: [createMockField()]
|
|
87
|
-
});
|
|
88
|
-
const node = entityInfoToERDNode(entity);
|
|
89
|
-
expect(node.id).toBe('entity-1');
|
|
90
|
-
expect(node.name).toBe('MJTestEntity');
|
|
91
|
-
expect(node.schemaName).toBe('dbo');
|
|
92
|
-
expect(node.description).toBe('A test entity');
|
|
93
|
-
expect(node.status).toBe('Active');
|
|
94
|
-
expect(node.baseTable).toBe('TestTable');
|
|
95
|
-
expect(node.fields).toHaveLength(1);
|
|
96
|
-
expect(node.customData).toBeDefined();
|
|
97
|
-
expect(node.customData['originalEntity']).toBe(entity);
|
|
98
|
-
});
|
|
99
|
-
it('should convert all fields', () => {
|
|
100
|
-
const entity = createMockEntity({
|
|
101
|
-
Fields: [
|
|
102
|
-
createMockField({ ID: 'f1', Name: 'ID', IsPrimaryKey: true }),
|
|
103
|
-
createMockField({ ID: 'f2', Name: 'Name' }),
|
|
104
|
-
createMockField({ ID: 'f3', Name: 'Status' })
|
|
105
|
-
]
|
|
106
|
-
});
|
|
107
|
-
const node = entityInfoToERDNode(entity);
|
|
108
|
-
expect(node.fields).toHaveLength(3);
|
|
109
|
-
expect(node.fields[0].isPrimaryKey).toBe(true);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
describe('entitiesToERDNodes', () => {
|
|
113
|
-
it('should convert an array of entities', () => {
|
|
114
|
-
const entities = [
|
|
115
|
-
createMockEntity({ ID: 'e1', Name: 'Entity1' }),
|
|
116
|
-
createMockEntity({ ID: 'e2', Name: 'Entity2' })
|
|
117
|
-
];
|
|
118
|
-
const nodes = entitiesToERDNodes(entities);
|
|
119
|
-
expect(nodes).toHaveLength(2);
|
|
120
|
-
expect(nodes[0].name).toBe('Entity1');
|
|
121
|
-
expect(nodes[1].name).toBe('Entity2');
|
|
122
|
-
});
|
|
123
|
-
it('should return empty array for empty input', () => {
|
|
124
|
-
expect(entitiesToERDNodes([])).toEqual([]);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
describe('getOriginalEntityFromERDNode', () => {
|
|
128
|
-
it('should extract entity from customData', () => {
|
|
129
|
-
const entity = createMockEntity();
|
|
130
|
-
const node = entityInfoToERDNode(entity);
|
|
131
|
-
const extracted = getOriginalEntityFromERDNode(node);
|
|
132
|
-
expect(extracted).toBe(entity);
|
|
133
|
-
});
|
|
134
|
-
it('should return null when no customData', () => {
|
|
135
|
-
const node = {
|
|
136
|
-
id: '1',
|
|
137
|
-
name: 'Test',
|
|
138
|
-
fields: []
|
|
139
|
-
};
|
|
140
|
-
expect(getOriginalEntityFromERDNode(node)).toBeNull();
|
|
141
|
-
});
|
|
142
|
-
it('should return null when customData has no originalEntity', () => {
|
|
143
|
-
const node = {
|
|
144
|
-
id: '1',
|
|
145
|
-
name: 'Test',
|
|
146
|
-
fields: [],
|
|
147
|
-
customData: { other: 'data' }
|
|
148
|
-
};
|
|
149
|
-
expect(getOriginalEntityFromERDNode(node)).toBeNull();
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
describe('findEntityByNodeId', () => {
|
|
153
|
-
it('should find entity by ID', () => {
|
|
154
|
-
const entities = [
|
|
155
|
-
createMockEntity({ ID: 'e1' }),
|
|
156
|
-
createMockEntity({ ID: 'e2' })
|
|
157
|
-
];
|
|
158
|
-
const found = findEntityByNodeId('e2', entities);
|
|
159
|
-
expect(found).toBe(entities[1]);
|
|
160
|
-
});
|
|
161
|
-
it('should return undefined when not found', () => {
|
|
162
|
-
const entities = [createMockEntity({ ID: 'e1' })];
|
|
163
|
-
expect(findEntityByNodeId('e99', entities)).toBeUndefined();
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
describe('buildERDDataFromEntities', () => {
|
|
167
|
-
it('should build nodes from primary entities', () => {
|
|
168
|
-
const entities = [
|
|
169
|
-
createMockEntity({ ID: 'e1', Name: 'Entity1', Fields: [], RelatedEntities: [] })
|
|
170
|
-
];
|
|
171
|
-
const result = buildERDDataFromEntities(entities);
|
|
172
|
-
expect(result.nodes).toHaveLength(1);
|
|
173
|
-
expect(result.links).toHaveLength(0);
|
|
174
|
-
});
|
|
175
|
-
it('should discover outgoing relationships', () => {
|
|
176
|
-
const parentEntity = createMockEntity({
|
|
177
|
-
ID: 'parent',
|
|
178
|
-
Name: 'Parent',
|
|
179
|
-
Fields: [createMockField({ ID: 'pk', Name: 'ID', IsPrimaryKey: true })],
|
|
180
|
-
RelatedEntities: []
|
|
181
|
-
});
|
|
182
|
-
const childEntity = createMockEntity({
|
|
183
|
-
ID: 'child',
|
|
184
|
-
Name: 'Child',
|
|
185
|
-
Fields: [
|
|
186
|
-
createMockField({ ID: 'ck', Name: 'ID', IsPrimaryKey: true }),
|
|
187
|
-
createMockField({ ID: 'fk', Name: 'ParentID', RelatedEntityID: 'parent' })
|
|
188
|
-
],
|
|
189
|
-
RelatedEntities: []
|
|
190
|
-
});
|
|
191
|
-
const result = buildERDDataFromEntities([childEntity], { allEntities: [parentEntity, childEntity], includeOutgoing: true, includeIncoming: false });
|
|
192
|
-
expect(result.nodes).toHaveLength(2);
|
|
193
|
-
expect(result.links).toHaveLength(1);
|
|
194
|
-
expect(result.links[0].sourceNodeId).toBe('child');
|
|
195
|
-
expect(result.links[0].targetNodeId).toBe('parent');
|
|
196
|
-
expect(result.links[0].relationshipType).toBe('many-to-one');
|
|
197
|
-
});
|
|
198
|
-
it('should discover incoming relationships', () => {
|
|
199
|
-
const parentEntity = createMockEntity({
|
|
200
|
-
ID: 'parent',
|
|
201
|
-
Name: 'Parent',
|
|
202
|
-
Fields: [createMockField({ ID: 'pk', Name: 'ID', IsPrimaryKey: true })],
|
|
203
|
-
RelatedEntities: [
|
|
204
|
-
{ RelatedEntity: 'Child', RelatedEntityJoinField: 'ParentID' }
|
|
205
|
-
]
|
|
206
|
-
});
|
|
207
|
-
const childEntity = createMockEntity({
|
|
208
|
-
ID: 'child',
|
|
209
|
-
Name: 'Child',
|
|
210
|
-
Fields: [
|
|
211
|
-
createMockField({ ID: 'ck', Name: 'ID', IsPrimaryKey: true }),
|
|
212
|
-
createMockField({ ID: 'fk', Name: 'ParentID', RelatedEntityID: 'parent' })
|
|
213
|
-
],
|
|
214
|
-
RelatedEntities: []
|
|
215
|
-
});
|
|
216
|
-
const result = buildERDDataFromEntities([parentEntity], { allEntities: [parentEntity, childEntity], includeOutgoing: false, includeIncoming: true });
|
|
217
|
-
expect(result.nodes).toHaveLength(2);
|
|
218
|
-
expect(result.links).toHaveLength(1);
|
|
219
|
-
expect(result.links[0].sourceNodeId).toBe('child');
|
|
220
|
-
expect(result.links[0].targetNodeId).toBe('parent');
|
|
221
|
-
});
|
|
222
|
-
it('should not duplicate nodes or links', () => {
|
|
223
|
-
const entity = createMockEntity({
|
|
224
|
-
ID: 'e1',
|
|
225
|
-
Name: 'Entity1',
|
|
226
|
-
Fields: [createMockField({ ID: 'pk', Name: 'ID', IsPrimaryKey: true })],
|
|
227
|
-
RelatedEntities: []
|
|
228
|
-
});
|
|
229
|
-
const result = buildERDDataFromEntities([entity, entity], { allEntities: [entity] });
|
|
230
|
-
expect(result.nodes).toHaveLength(1);
|
|
231
|
-
});
|
|
232
|
-
it('should handle self-referencing entities', () => {
|
|
233
|
-
const entity = createMockEntity({
|
|
234
|
-
ID: 'e1',
|
|
235
|
-
Name: 'Category',
|
|
236
|
-
Fields: [
|
|
237
|
-
createMockField({ ID: 'pk', Name: 'ID', IsPrimaryKey: true }),
|
|
238
|
-
createMockField({ ID: 'fk', Name: 'ParentCategoryID', RelatedEntityID: 'e1' })
|
|
239
|
-
],
|
|
240
|
-
RelatedEntities: []
|
|
241
|
-
});
|
|
242
|
-
const result = buildERDDataFromEntities([entity], { allEntities: [entity], includeOutgoing: true });
|
|
243
|
-
expect(result.links).toHaveLength(1);
|
|
244
|
-
expect(result.links[0].isSelfReference).toBe(true);
|
|
245
|
-
});
|
|
246
|
-
it('should handle empty entities array', () => {
|
|
247
|
-
const result = buildERDDataFromEntities([]);
|
|
248
|
-
expect(result.nodes).toEqual([]);
|
|
249
|
-
expect(result.links).toEqual([]);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
//# sourceMappingURL=entity-to-erd-adapter.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"entity-to-erd-adapter.test.js","sourceRoot":"","sources":["../../src/__tests__/entity-to-erd-adapter.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport {\n entityFieldToERDField,\n entityInfoToERDNode,\n entitiesToERDNodes,\n getOriginalEntityFromERDNode,\n findEntityByNodeId,\n buildERDDataFromEntities\n} from '../lib/utils/entity-to-erd-adapter';\nimport type { ERDNode } from '../lib/interfaces/erd-types';\n\n// Minimal mock of EntityFieldInfo for testing\nfunction createMockField(overrides: Record<string, unknown> = {}): Record<string, unknown> {\n return {\n ID: 'field-1',\n Name: 'TestField',\n Type: 'nvarchar',\n IsPrimaryKey: false,\n Description: 'A test field',\n AllowsNull: true,\n DefaultValue: null,\n Length: 100,\n Precision: 0,\n Scale: 0,\n IsVirtual: false,\n AutoIncrement: false,\n RelatedEntityID: null,\n RelatedEntity: null,\n RelatedEntityFieldName: null,\n EntityFieldValues: [],\n ...overrides\n };\n}\n\nfunction createMockEntity(overrides: Record<string, unknown> = {}): Record<string, unknown> {\n return {\n ID: 'entity-1',\n Name: 'MJTestEntity',\n SchemaName: 'dbo',\n Description: 'A test entity',\n Status: 'Active',\n BaseTable: 'TestTable',\n Fields: [],\n RelatedEntities: [],\n ...overrides\n };\n}\n\ndescribe('entityFieldToERDField', () => {\n it('should convert basic field properties', () => {\n const field = createMockField();\n const erdField = entityFieldToERDField(field as never);\n\n expect(erdField.id).toBe('field-1');\n expect(erdField.name).toBe('TestField');\n expect(erdField.type).toBe('nvarchar');\n expect(erdField.isPrimaryKey).toBe(false);\n expect(erdField.description).toBe('A test field');\n expect(erdField.allowsNull).toBe(true);\n expect(erdField.length).toBe(100);\n expect(erdField.isVirtual).toBe(false);\n expect(erdField.autoIncrement).toBe(false);\n });\n\n it('should set related entity info for foreign keys', () => {\n const field = createMockField({\n RelatedEntityID: 'rel-entity-1',\n RelatedEntity: 'ParentEntity',\n RelatedEntityFieldName: 'ID'\n });\n const erdField = entityFieldToERDField(field as never);\n\n expect(erdField.relatedNodeId).toBe('rel-entity-1');\n expect(erdField.relatedNodeName).toBe('ParentEntity');\n expect(erdField.relatedFieldName).toBe('ID');\n });\n\n it('should not set related info for non-FK fields', () => {\n const field = createMockField({ RelatedEntityID: null });\n const erdField = entityFieldToERDField(field as never);\n\n expect(erdField.relatedNodeId).toBeUndefined();\n expect(erdField.relatedNodeName).toBeUndefined();\n });\n\n it('should convert EntityFieldValues to possibleValues', () => {\n const field = createMockField({\n EntityFieldValues: [\n { ID: 'v1', Value: 'Active', Code: 'A', Description: 'Active status', Sequence: 1 },\n { ID: 'v2', Value: 'Inactive', Code: 'I', Description: 'Inactive status', Sequence: 2 }\n ]\n });\n const erdField = entityFieldToERDField(field as never);\n\n expect(erdField.possibleValues).toHaveLength(2);\n expect(erdField.possibleValues![0].value).toBe('Active');\n expect(erdField.possibleValues![0].code).toBe('A');\n expect(erdField.possibleValues![1].sequence).toBe(2);\n });\n});\n\ndescribe('entityInfoToERDNode', () => {\n it('should convert entity to ERD node', () => {\n const entity = createMockEntity({\n Fields: [createMockField()]\n });\n const node = entityInfoToERDNode(entity as never);\n\n expect(node.id).toBe('entity-1');\n expect(node.name).toBe('MJTestEntity');\n expect(node.schemaName).toBe('dbo');\n expect(node.description).toBe('A test entity');\n expect(node.status).toBe('Active');\n expect(node.baseTable).toBe('TestTable');\n expect(node.fields).toHaveLength(1);\n expect(node.customData).toBeDefined();\n expect(node.customData!['originalEntity']).toBe(entity);\n });\n\n it('should convert all fields', () => {\n const entity = createMockEntity({\n Fields: [\n createMockField({ ID: 'f1', Name: 'ID', IsPrimaryKey: true }),\n createMockField({ ID: 'f2', Name: 'Name' }),\n createMockField({ ID: 'f3', Name: 'Status' })\n ]\n });\n const node = entityInfoToERDNode(entity as never);\n expect(node.fields).toHaveLength(3);\n expect(node.fields[0].isPrimaryKey).toBe(true);\n });\n});\n\ndescribe('entitiesToERDNodes', () => {\n it('should convert an array of entities', () => {\n const entities = [\n createMockEntity({ ID: 'e1', Name: 'Entity1' }),\n createMockEntity({ ID: 'e2', Name: 'Entity2' })\n ];\n const nodes = entitiesToERDNodes(entities as never[]);\n expect(nodes).toHaveLength(2);\n expect(nodes[0].name).toBe('Entity1');\n expect(nodes[1].name).toBe('Entity2');\n });\n\n it('should return empty array for empty input', () => {\n expect(entitiesToERDNodes([])).toEqual([]);\n });\n});\n\ndescribe('getOriginalEntityFromERDNode', () => {\n it('should extract entity from customData', () => {\n const entity = createMockEntity();\n const node = entityInfoToERDNode(entity as never);\n const extracted = getOriginalEntityFromERDNode(node);\n expect(extracted).toBe(entity);\n });\n\n it('should return null when no customData', () => {\n const node: ERDNode = {\n id: '1',\n name: 'Test',\n fields: []\n };\n expect(getOriginalEntityFromERDNode(node)).toBeNull();\n });\n\n it('should return null when customData has no originalEntity', () => {\n const node: ERDNode = {\n id: '1',\n name: 'Test',\n fields: [],\n customData: { other: 'data' }\n };\n expect(getOriginalEntityFromERDNode(node)).toBeNull();\n });\n});\n\ndescribe('findEntityByNodeId', () => {\n it('should find entity by ID', () => {\n const entities = [\n createMockEntity({ ID: 'e1' }),\n createMockEntity({ ID: 'e2' })\n ];\n const found = findEntityByNodeId('e2', entities as never[]);\n expect(found).toBe(entities[1]);\n });\n\n it('should return undefined when not found', () => {\n const entities = [createMockEntity({ ID: 'e1' })];\n expect(findEntityByNodeId('e99', entities as never[])).toBeUndefined();\n });\n});\n\ndescribe('buildERDDataFromEntities', () => {\n it('should build nodes from primary entities', () => {\n const entities = [\n createMockEntity({ ID: 'e1', Name: 'Entity1', Fields: [], RelatedEntities: [] })\n ];\n const result = buildERDDataFromEntities(entities as never[]);\n expect(result.nodes).toHaveLength(1);\n expect(result.links).toHaveLength(0);\n });\n\n it('should discover outgoing relationships', () => {\n const parentEntity = createMockEntity({\n ID: 'parent',\n Name: 'Parent',\n Fields: [createMockField({ ID: 'pk', Name: 'ID', IsPrimaryKey: true })],\n RelatedEntities: []\n });\n const childEntity = createMockEntity({\n ID: 'child',\n Name: 'Child',\n Fields: [\n createMockField({ ID: 'ck', Name: 'ID', IsPrimaryKey: true }),\n createMockField({ ID: 'fk', Name: 'ParentID', RelatedEntityID: 'parent' })\n ],\n RelatedEntities: []\n });\n\n const result = buildERDDataFromEntities(\n [childEntity as never],\n { allEntities: [parentEntity as never, childEntity as never], includeOutgoing: true, includeIncoming: false }\n );\n\n expect(result.nodes).toHaveLength(2);\n expect(result.links).toHaveLength(1);\n expect(result.links[0].sourceNodeId).toBe('child');\n expect(result.links[0].targetNodeId).toBe('parent');\n expect(result.links[0].relationshipType).toBe('many-to-one');\n });\n\n it('should discover incoming relationships', () => {\n const parentEntity = createMockEntity({\n ID: 'parent',\n Name: 'Parent',\n Fields: [createMockField({ ID: 'pk', Name: 'ID', IsPrimaryKey: true })],\n RelatedEntities: [\n { RelatedEntity: 'Child', RelatedEntityJoinField: 'ParentID' }\n ]\n });\n const childEntity = createMockEntity({\n ID: 'child',\n Name: 'Child',\n Fields: [\n createMockField({ ID: 'ck', Name: 'ID', IsPrimaryKey: true }),\n createMockField({ ID: 'fk', Name: 'ParentID', RelatedEntityID: 'parent' })\n ],\n RelatedEntities: []\n });\n\n const result = buildERDDataFromEntities(\n [parentEntity as never],\n { allEntities: [parentEntity as never, childEntity as never], includeOutgoing: false, includeIncoming: true }\n );\n\n expect(result.nodes).toHaveLength(2);\n expect(result.links).toHaveLength(1);\n expect(result.links[0].sourceNodeId).toBe('child');\n expect(result.links[0].targetNodeId).toBe('parent');\n });\n\n it('should not duplicate nodes or links', () => {\n const entity = createMockEntity({\n ID: 'e1',\n Name: 'Entity1',\n Fields: [createMockField({ ID: 'pk', Name: 'ID', IsPrimaryKey: true })],\n RelatedEntities: []\n });\n\n const result = buildERDDataFromEntities(\n [entity as never, entity as never],\n { allEntities: [entity as never] }\n );\n expect(result.nodes).toHaveLength(1);\n });\n\n it('should handle self-referencing entities', () => {\n const entity = createMockEntity({\n ID: 'e1',\n Name: 'Category',\n Fields: [\n createMockField({ ID: 'pk', Name: 'ID', IsPrimaryKey: true }),\n createMockField({ ID: 'fk', Name: 'ParentCategoryID', RelatedEntityID: 'e1' })\n ],\n RelatedEntities: []\n });\n\n const result = buildERDDataFromEntities(\n [entity as never],\n { allEntities: [entity as never], includeOutgoing: true }\n );\n expect(result.links).toHaveLength(1);\n expect(result.links[0].isSelfReference).toBe(true);\n });\n\n it('should handle empty entities array', () => {\n const result = buildERDDataFromEntities([]);\n expect(result.nodes).toEqual([]);\n expect(result.links).toEqual([]);\n });\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"erd-types.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/erd-types.test.ts"],"names":[],"mappings":""}
|