@lodado/sdui-template 1.0.0 → 1.0.2

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.
Files changed (83) hide show
  1. package/README.md +253 -3
  2. package/dist/cjs/client/index.cjs +1 -1
  3. package/dist/es/client/_virtual/_tslib.mjs +1 -0
  4. package/dist/es/client/node_modules/.pnpm/@testing-library_dom@10.4.1/node_modules/@testing-library/dom/dist/@testing-library/dom.esm.mjs +1 -0
  5. package/dist/es/client/node_modules/.pnpm/@testing-library_react@16.3.1_@testing-library_dom@10.4.1_@types_react-dom@18.3.7_@types_reac_bxcobvx7qxffu3uqpqwx2mjx3m/node_modules/@testing-library/react/dist/@testing-library/react.esm.mjs +1 -0
  6. package/dist/es/client/node_modules/.pnpm/dom-accessibility-api@0.5.16/node_modules/dom-accessibility-api/dist/accessible-description.mjs +1 -0
  7. package/dist/es/client/node_modules/.pnpm/dom-accessibility-api@0.5.16/node_modules/dom-accessibility-api/dist/accessible-name-and-description.mjs +1 -0
  8. package/dist/es/client/node_modules/.pnpm/dom-accessibility-api@0.5.16/node_modules/dom-accessibility-api/dist/accessible-name.mjs +1 -0
  9. package/dist/es/client/node_modules/.pnpm/dom-accessibility-api@0.5.16/node_modules/dom-accessibility-api/dist/getRole.mjs +1 -0
  10. package/dist/es/client/node_modules/.pnpm/dom-accessibility-api@0.5.16/node_modules/dom-accessibility-api/dist/polyfills/SetLike.mjs +1 -0
  11. package/dist/es/client/node_modules/.pnpm/dom-accessibility-api@0.5.16/node_modules/dom-accessibility-api/dist/polyfills/array.from.mjs +1 -0
  12. package/dist/es/client/node_modules/.pnpm/dom-accessibility-api@0.5.16/node_modules/dom-accessibility-api/dist/util.mjs +1 -0
  13. package/dist/es/client/src/__tests__/index.mjs +1 -0
  14. package/dist/es/client/src/__tests__/utils/dev-utils.mjs +1 -0
  15. package/dist/es/client/src/components/DefaultNodeComponent.mjs +2 -0
  16. package/dist/es/client/src/components/componentMap.mjs +1 -1
  17. package/dist/es/client/src/components/defaultComponentFactory.mjs +1 -0
  18. package/dist/es/client/src/index.mjs +1 -1
  19. package/dist/es/client/src/react-wrapper/components/SduiLayoutRenderer.mjs +1 -1
  20. package/dist/es/client/src/react-wrapper/hooks/useRenderNode.mjs +1 -1
  21. package/dist/es/client/src/react-wrapper/hooks/useSduiNodeReference.mjs +2 -0
  22. package/dist/es/client/src/react-wrapper/hooks/useSduiNodeSubscription.mjs +1 -1
  23. package/dist/es/client/src/react-wrapper/hooks/useSduiNodeSubscriptionSync.mjs +2 -0
  24. package/dist/es/client/src/store/SduiLayoutStore.mjs +1 -1
  25. package/dist/es/client/src/store/managers/LayoutStateRepository.mjs +1 -1
  26. package/dist/es/client/src/store/managers/SubscriptionManager.mjs +1 -1
  27. package/dist/es/client/src/utils/normalize/denormalize.mjs +1 -1
  28. package/dist/es/client/src/utils/normalize/normalize.mjs +1 -1
  29. package/dist/es/client/src/utils/parentPath.mjs +1 -0
  30. package/dist/types/src/__tests__/index.d.ts +7 -0
  31. package/dist/types/src/__tests__/utils/dev-utils.d.ts +48 -0
  32. package/dist/types/src/components/DefaultNodeComponent.d.ts +15 -0
  33. package/dist/types/src/components/componentMap.d.ts +12 -9
  34. package/dist/types/src/components/defaultComponentFactory.d.ts +14 -0
  35. package/dist/types/src/components/index.d.ts +3 -2
  36. package/dist/types/src/components/types.d.ts +30 -8
  37. package/dist/types/src/index.d.ts +5 -3
  38. package/dist/types/src/react-wrapper/components/SduiLayoutRenderer.d.ts +9 -9
  39. package/dist/types/src/react-wrapper/context/SduiLayoutContext.d.ts +8 -8
  40. package/dist/types/src/react-wrapper/context/index.d.ts +1 -1
  41. package/dist/types/src/react-wrapper/hooks/index.d.ts +2 -0
  42. package/dist/types/src/react-wrapper/hooks/useRenderNode.d.ts +47 -6
  43. package/dist/types/src/react-wrapper/hooks/useSduiLayoutAction.d.ts +2 -2
  44. package/dist/types/src/react-wrapper/hooks/useSduiNodeReference.d.ts +67 -0
  45. package/dist/types/src/react-wrapper/hooks/useSduiNodeSubscription.d.ts +34 -17
  46. package/dist/types/src/react-wrapper/hooks/useSduiNodeSubscriptionSync.d.ts +37 -0
  47. package/dist/types/src/schema/base.d.ts +17 -15
  48. package/dist/types/src/schema/document.d.ts +5 -5
  49. package/dist/types/src/schema/index.d.ts +1 -1
  50. package/dist/types/src/schema/node.d.ts +11 -7
  51. package/dist/types/src/store/SduiLayoutStore.d.ts +126 -83
  52. package/dist/types/src/store/index.d.ts +1 -1
  53. package/dist/types/src/store/managers/DocumentManager.d.ts +22 -22
  54. package/dist/types/src/store/managers/LayoutStateRepository.d.ts +111 -37
  55. package/dist/types/src/store/managers/SubscriptionManager.d.ts +31 -17
  56. package/dist/types/src/store/managers/VariablesManager.d.ts +11 -11
  57. package/dist/types/src/store/managers/index.d.ts +1 -1
  58. package/dist/types/src/store/types.d.ts +26 -20
  59. package/dist/types/src/utils/normalize/denormalize.d.ts +10 -10
  60. package/dist/types/src/utils/normalize/normalize.d.ts +8 -8
  61. package/dist/types/src/utils/normalize/types.d.ts +4 -4
  62. package/dist/types/src/utils/parentPath.d.ts +58 -0
  63. package/package.json +54 -33
  64. package/src/__tests__/index.ts +7 -0
  65. package/src/__tests__/scenario/boundary-values.test.tsx +250 -0
  66. package/src/__tests__/scenario/component-override.test.tsx +68 -0
  67. package/src/__tests__/scenario/dynamic-document-merge.test.tsx +405 -0
  68. package/src/__tests__/scenario/error-handling.test.tsx +35 -0
  69. package/src/__tests__/scenario/node-not-found.test.tsx +236 -0
  70. package/src/__tests__/scenario/node-reference-subscription.test.tsx +461 -0
  71. package/src/__tests__/scenario/node-reference.test.tsx +670 -0
  72. package/src/__tests__/scenario/parent-path.test.tsx +198 -0
  73. package/src/__tests__/scenario/recursive-rendering.test.tsx +260 -0
  74. package/src/__tests__/scenario/render-document.test.tsx +98 -0
  75. package/src/__tests__/scenario/reset-to-initial.test.tsx +227 -0
  76. package/src/__tests__/scenario/store-reset.test.tsx +54 -0
  77. package/src/__tests__/scenario/subscription.test.tsx +146 -0
  78. package/src/__tests__/scenario/sync-external-store.test.tsx +392 -0
  79. package/src/__tests__/scenario/test-coverage.md +95 -0
  80. package/src/__tests__/scenario/timestamp-snapshot.test.tsx +420 -0
  81. package/src/__tests__/scenario/update-layout.test.tsx +103 -0
  82. package/src/__tests__/scenario/zod-validation.test.tsx +298 -0
  83. package/src/__tests__/utils/dev-utils.tsx +175 -0
@@ -0,0 +1,670 @@
1
+ /**
2
+ * Scenario Test: Node Reference
3
+ *
4
+ * Tests for node reference functionality
5
+ */
6
+
7
+ import { render, screen } from '@testing-library/react'
8
+ import React from 'react'
9
+
10
+ import type { ComponentFactory } from '../../components/types'
11
+ import { SduiLayoutRenderer } from '../../react-wrapper/components/SduiLayoutRenderer'
12
+ import { useSduiLayoutAction, useSduiNodeReference, useSduiNodeSubscription } from '../../react-wrapper/hooks'
13
+ import { createTestDocument, defaultTestComponentFactory, renderWithSduiLayout } from '../utils/dev-utils'
14
+
15
+ describe('Node Reference', () => {
16
+ describe('as is: document with node having single reference', () => {
17
+ describe('when: node has reference="target-node-id"', () => {
18
+ it('to be: reference accessible via useSduiNodeSubscription', () => {
19
+ const document = createTestDocument({
20
+ root: {
21
+ id: 'root',
22
+ type: 'Container',
23
+ children: [
24
+ {
25
+ id: 'source-node',
26
+ type: 'Card',
27
+ reference: 'target-node-id',
28
+ },
29
+ {
30
+ id: 'target-node-id',
31
+ type: 'Card',
32
+ state: {
33
+ title: 'Target Node',
34
+ },
35
+ },
36
+ ],
37
+ },
38
+ })
39
+
40
+ const ReferenceTestComponent: React.FC<{ nodeId: string }> = ({ nodeId }) => {
41
+ const { reference } = useSduiNodeSubscription({ nodeId })
42
+
43
+ return (
44
+ <div data-testid={`node-${nodeId}`}>
45
+ {reference && (
46
+ <div data-testid={`reference-${nodeId}`}>
47
+ Reference: {typeof reference === 'string' ? reference : reference.join(', ')}
48
+ </div>
49
+ )}
50
+ </div>
51
+ )
52
+ }
53
+
54
+ const componentFactory: ComponentFactory = (id) => {
55
+ if (id === 'source-node' || id === 'target-node-id') {
56
+ return <ReferenceTestComponent nodeId={id} />
57
+ }
58
+ return <div data-testid={`default-${id}`}>Default</div>
59
+ }
60
+
61
+ render(
62
+ <SduiLayoutRenderer
63
+ document={document}
64
+ components={{ Container: defaultTestComponentFactory, Card: componentFactory }}
65
+ />,
66
+ )
67
+
68
+ expect(screen.getByTestId('node-source-node')).toBeInTheDocument()
69
+ expect(screen.getByTestId('reference-source-node')).toBeInTheDocument()
70
+ expect(screen.getByText(/Reference: target-node-id/i)).toBeInTheDocument()
71
+ })
72
+
73
+ it('to be: reference accessible via store.getReferenceById', () => {
74
+ const document = createTestDocument({
75
+ root: {
76
+ id: 'root',
77
+ type: 'Container',
78
+ children: [
79
+ {
80
+ id: 'source-node',
81
+ type: 'Card',
82
+ reference: 'target-node-id',
83
+ },
84
+ ],
85
+ },
86
+ })
87
+
88
+ const StoreTestComponent: React.FC = () => {
89
+ const store = useSduiLayoutAction()
90
+ const [reference, setReference] = React.useState<string | string[] | undefined>()
91
+
92
+ React.useEffect(() => {
93
+ try {
94
+ const ref = store.getReferenceById('source-node')
95
+ setReference(ref)
96
+ } catch (e) {
97
+ // Ignore errors
98
+ }
99
+ }, [store])
100
+
101
+ return (
102
+ <div data-testid="store-test">
103
+ {reference && <div data-testid="reference-value">{String(reference)}</div>}
104
+ </div>
105
+ )
106
+ }
107
+
108
+ renderWithSduiLayout(document, {}, <StoreTestComponent />)
109
+
110
+ expect(screen.getByTestId('store-test')).toBeInTheDocument()
111
+ expect(screen.getByTestId('reference-value')).toBeInTheDocument()
112
+ expect(screen.getByText('target-node-id')).toBeInTheDocument()
113
+ })
114
+ })
115
+ })
116
+
117
+ describe('as is: document with node having multiple references', () => {
118
+ describe('when: node has reference=["ref-1", "ref-2"]', () => {
119
+ it('to be: multiple references accessible as array', () => {
120
+ const document = createTestDocument({
121
+ root: {
122
+ id: 'root',
123
+ type: 'Container',
124
+ children: [
125
+ {
126
+ id: 'source-node',
127
+ type: 'Card',
128
+ reference: ['ref-1', 'ref-2'],
129
+ },
130
+ ],
131
+ },
132
+ })
133
+
134
+ const ReferenceTestComponent: React.FC<{ nodeId: string }> = ({ nodeId }) => {
135
+ const { reference } = useSduiNodeSubscription({ nodeId })
136
+
137
+ return (
138
+ <div data-testid={`node-${nodeId}`}>
139
+ {reference && (
140
+ <div data-testid={`reference-${nodeId}`}>
141
+ {Array.isArray(reference) ? (
142
+ <div>
143
+ {reference.map((ref) => (
144
+ <span key={ref} data-testid={`ref-item-${ref}`}>
145
+ {ref}
146
+ </span>
147
+ ))}
148
+ </div>
149
+ ) : (
150
+ <div>{reference}</div>
151
+ )}
152
+ </div>
153
+ )}
154
+ </div>
155
+ )
156
+ }
157
+
158
+ const componentFactory: ComponentFactory = (id) => {
159
+ if (id === 'source-node') {
160
+ return <ReferenceTestComponent nodeId={id} />
161
+ }
162
+ return <div data-testid={`default-${id}`}>Default</div>
163
+ }
164
+
165
+ render(
166
+ <SduiLayoutRenderer
167
+ document={document}
168
+ components={{ Container: defaultTestComponentFactory, Card: componentFactory }}
169
+ />,
170
+ )
171
+
172
+ expect(screen.getByTestId('node-source-node')).toBeInTheDocument()
173
+ expect(screen.getByTestId('reference-source-node')).toBeInTheDocument()
174
+ expect(screen.getByTestId('ref-item-ref-1')).toHaveTextContent('ref-1')
175
+ expect(screen.getByTestId('ref-item-ref-2')).toHaveTextContent('ref-2')
176
+ })
177
+ })
178
+ })
179
+
180
+ describe('as is: document with node without reference', () => {
181
+ describe('when: node has no reference field', () => {
182
+ it('to be: reference is undefined', () => {
183
+ const document = createTestDocument({
184
+ root: {
185
+ id: 'root',
186
+ type: 'Container',
187
+ children: [
188
+ {
189
+ id: 'no-ref-node',
190
+ type: 'Card',
191
+ },
192
+ ],
193
+ },
194
+ })
195
+
196
+ const ReferenceTestComponent: React.FC<{ nodeId: string }> = ({ nodeId }) => {
197
+ const { reference } = useSduiNodeSubscription({ nodeId })
198
+
199
+ return (
200
+ <div data-testid={`node-${nodeId}`}>
201
+ {reference === undefined && <div data-testid={`no-reference-${nodeId}`}>No reference</div>}
202
+ </div>
203
+ )
204
+ }
205
+
206
+ const componentFactory: ComponentFactory = (id) => {
207
+ if (id === 'no-ref-node') {
208
+ return <ReferenceTestComponent nodeId={id} />
209
+ }
210
+ return <div data-testid={`default-${id}`}>Default</div>
211
+ }
212
+
213
+ render(
214
+ <SduiLayoutRenderer
215
+ document={document}
216
+ components={{ Container: defaultTestComponentFactory, Card: componentFactory }}
217
+ />,
218
+ )
219
+
220
+ expect(screen.getByTestId('node-no-ref-node')).toBeInTheDocument()
221
+ expect(screen.getByTestId('no-reference-no-ref-node')).toBeInTheDocument()
222
+ })
223
+ })
224
+ })
225
+
226
+ describe('as is: using reference to access other node state', () => {
227
+ describe('when: node references another node and accesses its state', () => {
228
+ it('to be: can access referenced node state via store', () => {
229
+ const document = createTestDocument({
230
+ root: {
231
+ id: 'root',
232
+ type: 'Container',
233
+ children: [
234
+ {
235
+ id: 'source-node',
236
+ type: 'Card',
237
+ reference: 'target-node',
238
+ },
239
+ {
240
+ id: 'target-node',
241
+ type: 'Card',
242
+ state: {
243
+ title: 'Target Title',
244
+ count: 42,
245
+ },
246
+ },
247
+ ],
248
+ },
249
+ })
250
+
251
+ const ReferenceAccessComponent: React.FC<{ nodeId: string }> = ({ nodeId }) => {
252
+ const { reference } = useSduiNodeSubscription({ nodeId })
253
+ const store = useSduiLayoutAction()
254
+ const [targetState, setTargetState] = React.useState<Record<string, unknown> | null>(null)
255
+
256
+ React.useEffect(() => {
257
+ if (reference) {
258
+ const refId = Array.isArray(reference) ? reference[0] : reference
259
+ try {
260
+ const state = store.getLayoutStateById(refId)
261
+ setTargetState(state)
262
+ } catch (e) {
263
+ // Ignore errors
264
+ }
265
+ }
266
+ }, [reference, store])
267
+
268
+ return (
269
+ <div data-testid={`node-${nodeId}`}>
270
+ {targetState && (
271
+ <div data-testid={`target-state-${nodeId}`}>
272
+ Title: {String(targetState.title)}, Count: {String(targetState.count)}
273
+ </div>
274
+ )}
275
+ </div>
276
+ )
277
+ }
278
+
279
+ const componentFactory: ComponentFactory = (id) => {
280
+ if (id === 'source-node') {
281
+ return <ReferenceAccessComponent nodeId={id} />
282
+ }
283
+ return <div data-testid={`default-${id}`}>Default</div>
284
+ }
285
+
286
+ render(
287
+ <SduiLayoutRenderer
288
+ document={document}
289
+ components={{ Container: defaultTestComponentFactory, Card: componentFactory }}
290
+ />,
291
+ )
292
+
293
+ expect(screen.getByTestId('node-source-node')).toBeInTheDocument()
294
+ expect(screen.getByTestId('target-state-source-node')).toBeInTheDocument()
295
+ expect(screen.getByText(/Title: Target Title/i)).toBeInTheDocument()
296
+ expect(screen.getByText(/Count: 42/i)).toBeInTheDocument()
297
+ })
298
+
299
+ it('to be: can access referenced node state via useSduiNodeReference hook', () => {
300
+ const document = createTestDocument({
301
+ root: {
302
+ id: 'root',
303
+ type: 'Container',
304
+ children: [
305
+ {
306
+ id: 'source-node',
307
+ type: 'Card',
308
+ reference: 'target-node',
309
+ },
310
+ {
311
+ id: 'target-node',
312
+ type: 'Card',
313
+ state: {
314
+ title: 'Target Title',
315
+ count: 42,
316
+ },
317
+ },
318
+ ],
319
+ },
320
+ })
321
+
322
+ const ReferenceHookComponent: React.FC<{ nodeId: string }> = ({ nodeId }) => {
323
+ const { referencedNodes, hasReference } = useSduiNodeReference({ nodeId })
324
+
325
+ return (
326
+ <div data-testid={`node-${nodeId}`}>
327
+ {hasReference && referencedNodes.length > 0 && (
328
+ <div data-testid={`referenced-info-${nodeId}`}>
329
+ {referencedNodes.map((refNode) => (
330
+ <div key={refNode.id} data-testid={`ref-node-${refNode.id}`}>
331
+ Title: {String(refNode.state.title)}, Count: {String(refNode.state.count)}
332
+ </div>
333
+ ))}
334
+ </div>
335
+ )}
336
+ </div>
337
+ )
338
+ }
339
+
340
+ const componentFactory: ComponentFactory = (id) => {
341
+ if (id === 'source-node') {
342
+ return <ReferenceHookComponent nodeId={id} />
343
+ }
344
+ return <div data-testid={`default-${id}`}>Default</div>
345
+ }
346
+
347
+ render(
348
+ <SduiLayoutRenderer
349
+ document={document}
350
+ components={{ Container: defaultTestComponentFactory, Card: componentFactory }}
351
+ />,
352
+ )
353
+
354
+ expect(screen.getByTestId('node-source-node')).toBeInTheDocument()
355
+ expect(screen.getByTestId('referenced-info-source-node')).toBeInTheDocument()
356
+ expect(screen.getByTestId('ref-node-target-node')).toBeInTheDocument()
357
+ expect(screen.getByText(/Title: Target Title/i)).toBeInTheDocument()
358
+ expect(screen.getByText(/Count: 42/i)).toBeInTheDocument()
359
+ })
360
+
361
+ it('to be: can access multiple referenced nodes via useSduiNodeReference hook', () => {
362
+ const document = createTestDocument({
363
+ root: {
364
+ id: 'root',
365
+ type: 'Container',
366
+ children: [
367
+ {
368
+ id: 'source-node',
369
+ type: 'Card',
370
+ reference: ['target-1', 'target-2'],
371
+ },
372
+ {
373
+ id: 'target-1',
374
+ type: 'Card',
375
+ state: {
376
+ title: 'Target 1',
377
+ count: 10,
378
+ },
379
+ },
380
+ {
381
+ id: 'target-2',
382
+ type: 'Card',
383
+ state: {
384
+ title: 'Target 2',
385
+ count: 20,
386
+ },
387
+ },
388
+ ],
389
+ },
390
+ })
391
+
392
+ const MultipleReferenceComponent: React.FC<{ nodeId: string }> = ({ nodeId }) => {
393
+ const { referencedNodes, hasReference } = useSduiNodeReference({ nodeId })
394
+
395
+ return (
396
+ <div data-testid={`node-${nodeId}`}>
397
+ {hasReference && (
398
+ <div data-testid={`referenced-list-${nodeId}`}>
399
+ {referencedNodes.map((refNode) => (
400
+ <div key={refNode.id} data-testid={`ref-item-${refNode.id}`}>
401
+ {String(refNode.state.title)}: {String(refNode.state.count)}
402
+ </div>
403
+ ))}
404
+ </div>
405
+ )}
406
+ </div>
407
+ )
408
+ }
409
+
410
+ const componentFactory: ComponentFactory = (id) => {
411
+ if (id === 'source-node') {
412
+ return <MultipleReferenceComponent nodeId={id} />
413
+ }
414
+ return <div data-testid={`default-${id}`}>Default</div>
415
+ }
416
+
417
+ render(
418
+ <SduiLayoutRenderer
419
+ document={document}
420
+ components={{ Container: defaultTestComponentFactory, Card: componentFactory }}
421
+ />,
422
+ )
423
+
424
+ expect(screen.getByTestId('node-source-node')).toBeInTheDocument()
425
+ expect(screen.getByTestId('referenced-list-source-node')).toBeInTheDocument()
426
+ expect(screen.getByTestId('ref-item-target-1')).toHaveTextContent('Target 1: 10')
427
+ expect(screen.getByTestId('ref-item-target-2')).toHaveTextContent('Target 2: 20')
428
+ })
429
+
430
+ it('to be: can access referenced nodes by ID using referencedNodesMap', () => {
431
+ const document = createTestDocument({
432
+ root: {
433
+ id: 'root',
434
+ type: 'Container',
435
+ children: [
436
+ {
437
+ id: 'source-node',
438
+ type: 'Card',
439
+ reference: ['target-1', 'target-2'],
440
+ },
441
+ {
442
+ id: 'target-1',
443
+ type: 'Card',
444
+ state: {
445
+ title: 'Target 1',
446
+ count: 10,
447
+ },
448
+ },
449
+ {
450
+ id: 'target-2',
451
+ type: 'Card',
452
+ state: {
453
+ title: 'Target 2',
454
+ count: 20,
455
+ },
456
+ },
457
+ ],
458
+ },
459
+ })
460
+
461
+ const MapAccessComponent: React.FC<{ nodeId: string }> = ({ nodeId }) => {
462
+ const { referencedNodesMap, hasReference } = useSduiNodeReference({ nodeId })
463
+
464
+ return (
465
+ <div data-testid={`node-${nodeId}`}>
466
+ {hasReference && (
467
+ <div data-testid={`map-access-${nodeId}`}>
468
+ {referencedNodesMap['target-1'] && (
469
+ <div data-testid="target-1-from-map">
470
+ {String(referencedNodesMap['target-1'].state.title)}:{' '}
471
+ {String(referencedNodesMap['target-1'].state.count)}
472
+ </div>
473
+ )}
474
+ {referencedNodesMap['target-2'] && (
475
+ <div data-testid="target-2-from-map">
476
+ {String(referencedNodesMap['target-2'].state.title)}:{' '}
477
+ {String(referencedNodesMap['target-2'].state.count)}
478
+ </div>
479
+ )}
480
+ </div>
481
+ )}
482
+ </div>
483
+ )
484
+ }
485
+
486
+ const componentFactory: ComponentFactory = (id) => {
487
+ if (id === 'source-node') {
488
+ return <MapAccessComponent nodeId={id} />
489
+ }
490
+ return <div data-testid={`default-${id}`}>Default</div>
491
+ }
492
+
493
+ render(
494
+ <SduiLayoutRenderer
495
+ document={document}
496
+ components={{ Container: defaultTestComponentFactory, Card: componentFactory }}
497
+ />,
498
+ )
499
+
500
+ expect(screen.getByTestId('node-source-node')).toBeInTheDocument()
501
+ expect(screen.getByTestId('map-access-source-node')).toBeInTheDocument()
502
+ expect(screen.getByTestId('target-1-from-map')).toHaveTextContent('Target 1: 10')
503
+ expect(screen.getByTestId('target-2-from-map')).toHaveTextContent('Target 2: 20')
504
+ })
505
+ })
506
+ })
507
+
508
+ describe('as is: updating node reference', () => {
509
+ describe('when: updating reference via store.updateNodeReference', () => {
510
+ it('to be: reference updated correctly', () => {
511
+ const document = createTestDocument({
512
+ root: {
513
+ id: 'root',
514
+ type: 'Container',
515
+ children: [
516
+ {
517
+ id: 'source-node',
518
+ type: 'Card',
519
+ reference: 'target-1',
520
+ },
521
+ {
522
+ id: 'target-1',
523
+ type: 'Card',
524
+ state: { count: 10 },
525
+ },
526
+ {
527
+ id: 'target-2',
528
+ type: 'Card',
529
+ state: { count: 20 },
530
+ },
531
+ ],
532
+ },
533
+ })
534
+
535
+ const UpdateReferenceComponent: React.FC = () => {
536
+ const store = useSduiLayoutAction()
537
+ const [reference, setReference] = React.useState<string | string[] | undefined>()
538
+
539
+ React.useEffect(() => {
540
+ // Get initial reference
541
+ const initialRef = store.getReferenceById('source-node')
542
+ setReference(initialRef)
543
+
544
+ // Update reference
545
+ store.updateNodeReference('source-node', 'target-2')
546
+ const updatedRef = store.getReferenceById('source-node')
547
+ setReference(updatedRef)
548
+ }, [store])
549
+
550
+ return (
551
+ <div data-testid="update-reference-test">
552
+ {reference && (
553
+ <div data-testid="reference-value">
554
+ {typeof reference === 'string' ? reference : reference.join(', ')}
555
+ </div>
556
+ )}
557
+ </div>
558
+ )
559
+ }
560
+
561
+ renderWithSduiLayout(document, {}, <UpdateReferenceComponent />)
562
+
563
+ expect(screen.getByTestId('update-reference-test')).toBeInTheDocument()
564
+ expect(screen.getByTestId('reference-value')).toBeInTheDocument()
565
+ expect(screen.getByText('target-2')).toBeInTheDocument()
566
+ })
567
+
568
+ it('to be: can update to multiple references', () => {
569
+ const document = createTestDocument({
570
+ root: {
571
+ id: 'root',
572
+ type: 'Container',
573
+ children: [
574
+ {
575
+ id: 'source-node',
576
+ type: 'Card',
577
+ },
578
+ {
579
+ id: 'target-1',
580
+ type: 'Card',
581
+ state: { count: 10 },
582
+ },
583
+ {
584
+ id: 'target-2',
585
+ type: 'Card',
586
+ state: { count: 20 },
587
+ },
588
+ ],
589
+ },
590
+ })
591
+
592
+ const UpdateMultipleReferenceComponent: React.FC = () => {
593
+ const store = useSduiLayoutAction()
594
+ const [reference, setReference] = React.useState<string | string[] | undefined>()
595
+
596
+ React.useEffect(() => {
597
+ // Update to multiple references
598
+ store.updateNodeReference('source-node', ['target-1', 'target-2'])
599
+ const updatedRef = store.getReferenceById('source-node')
600
+ setReference(updatedRef)
601
+ }, [store])
602
+
603
+ return (
604
+ <div data-testid="update-multiple-reference-test">
605
+ {reference && Array.isArray(reference) && (
606
+ <div data-testid="reference-list">
607
+ {reference.map((ref) => (
608
+ <span key={ref} data-testid={`ref-${ref}`}>
609
+ {ref}
610
+ </span>
611
+ ))}
612
+ </div>
613
+ )}
614
+ </div>
615
+ )
616
+ }
617
+
618
+ renderWithSduiLayout(document, {}, <UpdateMultipleReferenceComponent />)
619
+
620
+ expect(screen.getByTestId('update-multiple-reference-test')).toBeInTheDocument()
621
+ expect(screen.getByTestId('ref-target-1')).toBeInTheDocument()
622
+ expect(screen.getByTestId('ref-target-2')).toBeInTheDocument()
623
+ })
624
+
625
+ it('to be: can remove reference by setting to undefined', () => {
626
+ const document = createTestDocument({
627
+ root: {
628
+ id: 'root',
629
+ type: 'Container',
630
+ children: [
631
+ {
632
+ id: 'source-node',
633
+ type: 'Card',
634
+ reference: 'target-1',
635
+ },
636
+ {
637
+ id: 'target-1',
638
+ type: 'Card',
639
+ state: { count: 10 },
640
+ },
641
+ ],
642
+ },
643
+ })
644
+
645
+ const RemoveReferenceComponent: React.FC = () => {
646
+ const store = useSduiLayoutAction()
647
+ const [reference, setReference] = React.useState<string | string[] | undefined>()
648
+
649
+ React.useEffect(() => {
650
+ // Remove reference
651
+ store.updateNodeReference('source-node', undefined)
652
+ const updatedRef = store.getReferenceById('source-node')
653
+ setReference(updatedRef)
654
+ }, [store])
655
+
656
+ return (
657
+ <div data-testid="remove-reference-test">
658
+ {reference === undefined && <div data-testid="no-reference">No reference</div>}
659
+ </div>
660
+ )
661
+ }
662
+
663
+ renderWithSduiLayout(document, {}, <RemoveReferenceComponent />)
664
+
665
+ expect(screen.getByTestId('remove-reference-test')).toBeInTheDocument()
666
+ expect(screen.getByTestId('no-reference')).toBeInTheDocument()
667
+ })
668
+ })
669
+ })
670
+ })