@tldiagram/core-ui 2.0.6 → 2.0.8
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/api/transport-vscode.d.ts +0 -1
- package/dist/components/ZUI/ZUICanvas.d.ts +2 -0
- package/dist/components/ZUI/renderer.d.ts +1 -1
- package/dist/components/ZUI/types.d.ts +1 -0
- package/dist/index.js +8229 -7834
- package/dist/lib/vscodeBridge.d.ts +9 -2
- package/dist/utils/exploreDiffLens.d.ts +47 -0
- package/dist/utils/exploreDiffLens.test.d.ts +1 -0
- package/dist/utils/watchDiffSummary.d.ts +4 -0
- package/dist/utils/watchDiffSummary.test.d.ts +1 -0
- package/package.json +2 -1
- package/src/App.tsx +13 -8
- package/src/api/transport-vscode.ts +3 -11
- package/src/components/ElementLibrary.tsx +1 -0
- package/src/components/ElementNode.tsx +15 -3
- package/src/components/WorkspacePanel.tsx +34 -8
- package/src/components/ZUI/ZUICanvas.tsx +103 -2
- package/src/components/ZUI/renderer.ts +40 -4
- package/src/components/ZUI/types.ts +1 -0
- package/src/components/ZUI/useZUIInteraction.ts +3 -0
- package/src/config/runtime-vscode.ts +2 -4
- package/src/context/WorkspaceVersionContext.tsx +4 -4
- package/src/lib/vscodeBridge.ts +22 -3
- package/src/pages/InfiniteZoom.tsx +210 -0
- package/src/types/offline-ambient.d.ts +1 -0
- package/src/utils/exploreDiffLens.test.ts +185 -0
- package/src/utils/exploreDiffLens.ts +262 -0
- package/src/utils/technologyCatalog.ts +5 -0
- package/src/utils/url.ts +7 -0
- package/src/utils/watchDiffSummary.test.ts +95 -0
- package/src/utils/watchDiffSummary.ts +35 -2
- package/src/vscode-entry.tsx +56 -0
|
@@ -3,6 +3,7 @@ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRe
|
|
|
3
3
|
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
|
4
4
|
import {
|
|
5
5
|
Box,
|
|
6
|
+
Badge,
|
|
6
7
|
Button,
|
|
7
8
|
Center,
|
|
8
9
|
HStack,
|
|
@@ -30,6 +31,14 @@ import CrossBranchControls from '../components/CrossBranchControls'
|
|
|
30
31
|
import { primeWorkspaceGraphSnapshot } from '../crossBranch/store'
|
|
31
32
|
import { WATCH_REPRESENTATION_UPDATED_EVENT } from '../components/WorkspacePanel'
|
|
32
33
|
import { useWorkspaceVersionPreview } from '../context/WorkspaceVersionContext'
|
|
34
|
+
import {
|
|
35
|
+
buildExploreDiffLens,
|
|
36
|
+
type ExploreDiffDetail,
|
|
37
|
+
type ExploreDiffLens,
|
|
38
|
+
type ExploreDiffTarget,
|
|
39
|
+
} from '../utils/exploreDiffLens'
|
|
40
|
+
import { getSourceEditor } from '../utils/sourceEditor'
|
|
41
|
+
import { toast } from '../utils/toast'
|
|
33
42
|
|
|
34
43
|
// ── Types ──────────────────────────────────────────────────────────
|
|
35
44
|
interface Props {
|
|
@@ -71,8 +80,16 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
71
80
|
} = useCrossBranchContextSettings(crossBranchSurface)
|
|
72
81
|
const { preview: versionPreview, followTarget: versionFollowTarget } = useWorkspaceVersionPreview()
|
|
73
82
|
|
|
83
|
+
const diffVersionId = useMemo(() => {
|
|
84
|
+
if (sharedToken) return 0
|
|
85
|
+
const value = Number(new URLSearchParams(location.search).get('diffVersion') ?? 0)
|
|
86
|
+
return Number.isFinite(value) && value > 0 ? value : 0
|
|
87
|
+
}, [location.search, sharedToken])
|
|
74
88
|
const cameraProfile = useMemo(() => new URLSearchParams(location.search).get('profile'), [location.search])
|
|
75
89
|
const isDetailToOverviewProfile = sharedToken && cameraProfile === 'detail-to-overview'
|
|
90
|
+
const [diffLens, setDiffLens] = useState<ExploreDiffLens | null>(null)
|
|
91
|
+
const [diffLoading, setDiffLoading] = useState(false)
|
|
92
|
+
const [activeDiffTargetIndex, setActiveDiffTargetIndex] = useState(0)
|
|
76
93
|
|
|
77
94
|
const initialCameraFrame = useMemo<ZUICameraFrame | undefined>(() => {
|
|
78
95
|
return isDetailToOverviewProfile
|
|
@@ -248,6 +265,85 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
248
265
|
return () => window.removeEventListener('message', handleMessage)
|
|
249
266
|
}, [sharedToken])
|
|
250
267
|
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
if (!data || !diffVersionId) {
|
|
270
|
+
setDiffLens(null)
|
|
271
|
+
setDiffLoading(false)
|
|
272
|
+
setActiveDiffTargetIndex(0)
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
let cancelled = false
|
|
276
|
+
setDiffLoading(true)
|
|
277
|
+
api.watch.diffs(diffVersionId)
|
|
278
|
+
.then((diffs) => {
|
|
279
|
+
if (cancelled) return
|
|
280
|
+
setDiffLens(buildExploreDiffLens(data, diffs, diffVersionId))
|
|
281
|
+
setActiveDiffTargetIndex(0)
|
|
282
|
+
})
|
|
283
|
+
.catch((error: unknown) => {
|
|
284
|
+
if (cancelled) return
|
|
285
|
+
setDiffLens(null)
|
|
286
|
+
toast({
|
|
287
|
+
title: 'Could not load diff map',
|
|
288
|
+
description: error instanceof Error ? error.message : 'The selected watch diff could not be loaded.',
|
|
289
|
+
status: 'error',
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
.finally(() => {
|
|
293
|
+
if (!cancelled) setDiffLoading(false)
|
|
294
|
+
})
|
|
295
|
+
return () => { cancelled = true }
|
|
296
|
+
}, [data, diffVersionId])
|
|
297
|
+
|
|
298
|
+
const activeDiffTarget = diffLens?.orderedTargets[activeDiffTargetIndex] ?? null
|
|
299
|
+
|
|
300
|
+
const focusDiffTarget = useCallback((target: ExploreDiffTarget | null | undefined) => {
|
|
301
|
+
if (!target?.viewId) return false
|
|
302
|
+
if (target.resourceType === 'element' && target.resourceId) {
|
|
303
|
+
return zuiRef.current?.focusElement(target.viewId, target.resourceId) ?? false
|
|
304
|
+
}
|
|
305
|
+
return zuiRef.current?.focusDiagram(target.viewId) ?? false
|
|
306
|
+
}, [])
|
|
307
|
+
|
|
308
|
+
useEffect(() => {
|
|
309
|
+
if (!canvasReady || !activeDiffTarget) return
|
|
310
|
+
const timer = window.setTimeout(() => {
|
|
311
|
+
focusDiffTarget(activeDiffTarget)
|
|
312
|
+
}, 80)
|
|
313
|
+
return () => window.clearTimeout(timer)
|
|
314
|
+
}, [activeDiffTarget, canvasReady, focusDiffTarget])
|
|
315
|
+
|
|
316
|
+
const navigateDiffTarget = useCallback((offset: number) => {
|
|
317
|
+
const count = diffLens?.orderedTargets.length ?? 0
|
|
318
|
+
if (count === 0) return
|
|
319
|
+
setActiveDiffTargetIndex((index) => (index + offset + count) % count)
|
|
320
|
+
}, [diffLens])
|
|
321
|
+
|
|
322
|
+
const exitDiffMode = useCallback(() => {
|
|
323
|
+
const params = new URLSearchParams(location.search)
|
|
324
|
+
params.set('view', 'explore')
|
|
325
|
+
params.delete('diffVersion')
|
|
326
|
+
params.delete('focus')
|
|
327
|
+
params.delete('element')
|
|
328
|
+
const suffix = params.toString()
|
|
329
|
+
navigate(`${location.pathname}${suffix ? `?${suffix}` : ''}`, { replace: true })
|
|
330
|
+
}, [location.pathname, location.search, navigate])
|
|
331
|
+
|
|
332
|
+
const openDiffSource = useCallback((detail: ExploreDiffDetail) => {
|
|
333
|
+
if (!detail.sourcePath) return
|
|
334
|
+
api.editor.open({
|
|
335
|
+
editor: getSourceEditor(),
|
|
336
|
+
file_path: detail.sourcePath,
|
|
337
|
+
line: detail.line ?? null,
|
|
338
|
+
}).catch((error: unknown) => {
|
|
339
|
+
toast({
|
|
340
|
+
title: 'Could not open source',
|
|
341
|
+
description: error instanceof Error ? error.message : 'The source editor command failed.',
|
|
342
|
+
status: 'error',
|
|
343
|
+
})
|
|
344
|
+
})
|
|
345
|
+
}, [])
|
|
346
|
+
|
|
251
347
|
if (!loading && (!data || (data.tree ?? []).length === 0 || !hasPlacements)) {
|
|
252
348
|
const noDiagrams = !data || (data.tree ?? []).length === 0
|
|
253
349
|
return (
|
|
@@ -304,6 +400,7 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
304
400
|
hiddenTags={hiddenTags}
|
|
305
401
|
versionPreview={versionPreview}
|
|
306
402
|
versionFollowTarget={versionFollowTarget}
|
|
403
|
+
diffLens={diffLens}
|
|
307
404
|
crossBranchSettings={crossBranchSettings}
|
|
308
405
|
hoverLocked={isTagsOpen}
|
|
309
406
|
/>
|
|
@@ -312,6 +409,119 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
312
409
|
{data && !sharedToken && <ExploreOnboarding hasLinkedNodes={!!(data.navigations?.length > 0)} />}
|
|
313
410
|
<MiniZoomOnboarding isVisible={showMiniOnboarding} onClose={dismissMiniOnboarding} />
|
|
314
411
|
|
|
412
|
+
{diffVersionId > 0 && (
|
|
413
|
+
<Box
|
|
414
|
+
position="absolute"
|
|
415
|
+
top={4}
|
|
416
|
+
right={4}
|
|
417
|
+
zIndex={14}
|
|
418
|
+
className="glass"
|
|
419
|
+
borderRadius="lg"
|
|
420
|
+
px={3}
|
|
421
|
+
py={2.5}
|
|
422
|
+
w={{ base: 'calc(100vw - 32px)', md: '340px' }}
|
|
423
|
+
maxW="calc(100vw - 32px)"
|
|
424
|
+
pointerEvents="auto"
|
|
425
|
+
opacity={showContent ? 1 : 0}
|
|
426
|
+
transition="opacity 0.3s"
|
|
427
|
+
>
|
|
428
|
+
<VStack align="stretch" spacing={2}>
|
|
429
|
+
<HStack justify="space-between" spacing={3}>
|
|
430
|
+
<HStack spacing={2} minW={0}>
|
|
431
|
+
<Badge colorScheme="blue" variant="subtle">Diff map</Badge>
|
|
432
|
+
<Text fontSize="xs" color="gray.400" fontFamily="mono" flexShrink={0}>
|
|
433
|
+
+{diffLens?.totalAddedLines ?? 0} -{diffLens?.totalRemovedLines ?? 0}
|
|
434
|
+
</Text>
|
|
435
|
+
</HStack>
|
|
436
|
+
<Button size="xs" variant="ghost" color="gray.300" onClick={exitDiffMode}>
|
|
437
|
+
Exit
|
|
438
|
+
</Button>
|
|
439
|
+
</HStack>
|
|
440
|
+
<Text fontSize="xs" color="gray.200" noOfLines={1} minH="18px">
|
|
441
|
+
{diffLoading
|
|
442
|
+
? 'Loading changed resources...'
|
|
443
|
+
: activeDiffTarget
|
|
444
|
+
? `${activeDiffTargetIndex + 1} of ${diffLens?.orderedTargets.length ?? 0}: ${activeDiffTarget.label}`
|
|
445
|
+
: 'No placed changed resources'}
|
|
446
|
+
</Text>
|
|
447
|
+
<HStack spacing={2}>
|
|
448
|
+
<Button
|
|
449
|
+
size="xs"
|
|
450
|
+
variant="solid"
|
|
451
|
+
bg="whiteAlpha.200"
|
|
452
|
+
_hover={{ bg: 'whiteAlpha.300' }}
|
|
453
|
+
flex={1}
|
|
454
|
+
isDisabled={!diffLens?.orderedTargets.length}
|
|
455
|
+
onClick={() => navigateDiffTarget(-1)}
|
|
456
|
+
>
|
|
457
|
+
Previous
|
|
458
|
+
</Button>
|
|
459
|
+
<Button
|
|
460
|
+
size="xs"
|
|
461
|
+
variant="solid"
|
|
462
|
+
bg="whiteAlpha.200"
|
|
463
|
+
_hover={{ bg: 'whiteAlpha.300' }}
|
|
464
|
+
flex={1}
|
|
465
|
+
isDisabled={!diffLens?.orderedTargets.length}
|
|
466
|
+
onClick={() => navigateDiffTarget(1)}
|
|
467
|
+
>
|
|
468
|
+
Next
|
|
469
|
+
</Button>
|
|
470
|
+
</HStack>
|
|
471
|
+
</VStack>
|
|
472
|
+
</Box>
|
|
473
|
+
)}
|
|
474
|
+
|
|
475
|
+
{diffLens && diffLens.unplacedTargets.length > 0 && (
|
|
476
|
+
<Box
|
|
477
|
+
position="absolute"
|
|
478
|
+
top={{ base: '150px', md: '132px' }}
|
|
479
|
+
right={4}
|
|
480
|
+
zIndex={13}
|
|
481
|
+
className="glass"
|
|
482
|
+
borderRadius="lg"
|
|
483
|
+
px={3}
|
|
484
|
+
py={3}
|
|
485
|
+
w={{ base: 'calc(100vw - 32px)', md: '340px' }}
|
|
486
|
+
maxH="260px"
|
|
487
|
+
overflowY="auto"
|
|
488
|
+
pointerEvents="auto"
|
|
489
|
+
data-zui-native-wheel="true"
|
|
490
|
+
sx={{ overscrollBehavior: 'contain', WebkitOverflowScrolling: 'touch', touchAction: 'pan-y' }}
|
|
491
|
+
>
|
|
492
|
+
<VStack align="stretch" spacing={2}>
|
|
493
|
+
<Text fontSize="11px" color="gray.400" fontWeight="700" textTransform="uppercase">
|
|
494
|
+
Deleted or unplaced
|
|
495
|
+
</Text>
|
|
496
|
+
{diffLens.unplacedTargets.slice(0, 8).map((target) => (
|
|
497
|
+
<Box key={target.key} borderTop="1px solid" borderColor="whiteAlpha.100" pt={2}>
|
|
498
|
+
<HStack spacing={2} align="start">
|
|
499
|
+
<Badge colorScheme={target.changeType === 'deleted' ? 'red' : 'yellow'} variant="subtle" fontSize="9px">
|
|
500
|
+
{target.changeType}
|
|
501
|
+
</Badge>
|
|
502
|
+
<Box minW={0} flex={1}>
|
|
503
|
+
<Text fontSize="xs" color="gray.100" noOfLines={1}>{target.label}</Text>
|
|
504
|
+
{target.sourcePath && (
|
|
505
|
+
<Text fontSize="10px" color="gray.500" fontFamily="mono" noOfLines={1}>{target.sourcePath}</Text>
|
|
506
|
+
)}
|
|
507
|
+
</Box>
|
|
508
|
+
{target.sourcePath && (
|
|
509
|
+
<Button size="xs" variant="ghost" color="var(--accent)" onClick={() => openDiffSource(target)}>
|
|
510
|
+
Open
|
|
511
|
+
</Button>
|
|
512
|
+
)}
|
|
513
|
+
</HStack>
|
|
514
|
+
</Box>
|
|
515
|
+
))}
|
|
516
|
+
{diffLens.unplacedTargets.length > 8 && (
|
|
517
|
+
<Text fontSize="xs" color="gray.500">
|
|
518
|
+
+{diffLens.unplacedTargets.length - 8} more
|
|
519
|
+
</Text>
|
|
520
|
+
)}
|
|
521
|
+
</VStack>
|
|
522
|
+
</Box>
|
|
523
|
+
)}
|
|
524
|
+
|
|
315
525
|
{/* Bottom toolbar */}
|
|
316
526
|
<Box
|
|
317
527
|
position="absolute"
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import type { WatchDiff } from '../api/client'
|
|
3
|
+
import type { Connector, ExploreData, PlacedElement, ViewTreeNode } from '../types'
|
|
4
|
+
import { buildExploreDiffLens, sourcePathFromDiff } from './exploreDiffLens'
|
|
5
|
+
|
|
6
|
+
function viewNode(id: number, name: string, parentViewId: number | null = null, ownerElementId: number | null = null, children: ViewTreeNode[] = []): ViewTreeNode {
|
|
7
|
+
return {
|
|
8
|
+
id,
|
|
9
|
+
owner_element_id: ownerElementId,
|
|
10
|
+
name,
|
|
11
|
+
description: null,
|
|
12
|
+
level_label: null,
|
|
13
|
+
level: parentViewId ? 2 : 1,
|
|
14
|
+
depth: parentViewId ? 1 : 0,
|
|
15
|
+
created_at: '2024-01-01',
|
|
16
|
+
updated_at: '2024-01-01',
|
|
17
|
+
parent_view_id: parentViewId,
|
|
18
|
+
children,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function placed(viewId: number, elementId: number, name: string): PlacedElement {
|
|
23
|
+
return {
|
|
24
|
+
id: viewId * 1000 + elementId,
|
|
25
|
+
view_id: viewId,
|
|
26
|
+
element_id: elementId,
|
|
27
|
+
position_x: 0,
|
|
28
|
+
position_y: 0,
|
|
29
|
+
name,
|
|
30
|
+
description: null,
|
|
31
|
+
kind: 'service',
|
|
32
|
+
technology: null,
|
|
33
|
+
url: null,
|
|
34
|
+
logo_url: null,
|
|
35
|
+
technology_connectors: [],
|
|
36
|
+
tags: [],
|
|
37
|
+
has_view: false,
|
|
38
|
+
view_label: null,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function connector(id: number, viewId: number, source: number, target: number): Connector {
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
view_id: viewId,
|
|
46
|
+
source_element_id: source,
|
|
47
|
+
target_element_id: target,
|
|
48
|
+
label: 'calls',
|
|
49
|
+
description: null,
|
|
50
|
+
relationship: null,
|
|
51
|
+
direction: 'forward',
|
|
52
|
+
style: 'bezier',
|
|
53
|
+
url: null,
|
|
54
|
+
source_handle: null,
|
|
55
|
+
target_handle: null,
|
|
56
|
+
created_at: '2024-01-01',
|
|
57
|
+
updated_at: '2024-01-01',
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function diff(overrides: Partial<WatchDiff>): WatchDiff {
|
|
62
|
+
return {
|
|
63
|
+
id: 1,
|
|
64
|
+
version_id: 9,
|
|
65
|
+
owner_type: 'symbol',
|
|
66
|
+
owner_key: 'go:services/api/main.go:function:Serve',
|
|
67
|
+
change_type: 'updated',
|
|
68
|
+
resource_type: 'element',
|
|
69
|
+
resource_id: 301,
|
|
70
|
+
summary: 'Serve',
|
|
71
|
+
added_lines: 3,
|
|
72
|
+
removed_lines: 1,
|
|
73
|
+
...overrides,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describe('explore diff lens', () => {
|
|
78
|
+
const tree = [
|
|
79
|
+
viewNode(1, 'Root', null, null, [
|
|
80
|
+
viewNode(2, 'Payments', 1, 101),
|
|
81
|
+
]),
|
|
82
|
+
]
|
|
83
|
+
const data: ExploreData = {
|
|
84
|
+
tree,
|
|
85
|
+
navigations: [],
|
|
86
|
+
views: {
|
|
87
|
+
1: {
|
|
88
|
+
placements: [placed(1, 101, 'Payments'), placed(1, 102, 'Identity')],
|
|
89
|
+
connectors: [connector(501, 1, 101, 102)],
|
|
90
|
+
},
|
|
91
|
+
2: {
|
|
92
|
+
placements: [placed(2, 301, 'Checkout API'), placed(2, 302, 'Ledger')],
|
|
93
|
+
connectors: [connector(601, 2, 301, 302)],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
it('keeps changed nodes, ancestor path, and sibling context', () => {
|
|
99
|
+
const lens = buildExploreDiffLens(data, [diff({})], 9)
|
|
100
|
+
|
|
101
|
+
expect(lens.changedElementIds.has(301)).toBe(true)
|
|
102
|
+
expect(lens.ancestorElementIds.has(101)).toBe(true)
|
|
103
|
+
expect(lens.siblingElementIds.has(302)).toBe(true)
|
|
104
|
+
expect(lens.siblingElementIds.has(102)).toBe(true)
|
|
105
|
+
expect(lens.contextElementIds.has(101)).toBe(true)
|
|
106
|
+
expect(lens.contextElementIds.has(302)).toBe(true)
|
|
107
|
+
expect(lens.contextElementIds.has(301)).toBe(false)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('indexes connector changes and adjacent context connectors', () => {
|
|
111
|
+
const lens = buildExploreDiffLens(data, [diff({ resource_type: 'connector', resource_id: 601, summary: 'calls' })], 9)
|
|
112
|
+
|
|
113
|
+
expect(lens.connectorChanges.get(601)).toBe('updated')
|
|
114
|
+
expect(lens.contextElementIds.has(301)).toBe(true)
|
|
115
|
+
expect(lens.contextElementIds.has(302)).toBe(true)
|
|
116
|
+
expect(lens.contextConnectorIds.has(501)).toBe(true)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('places missing resources into the unplaced tray', () => {
|
|
120
|
+
const lens = buildExploreDiffLens(data, [diff({ resource_id: 999, summary: 'Removed service', change_type: 'deleted' })], 9)
|
|
121
|
+
|
|
122
|
+
expect(lens.orderedTargets).toHaveLength(0)
|
|
123
|
+
expect(lens.unplacedTargets).toEqual([
|
|
124
|
+
expect.objectContaining({
|
|
125
|
+
resourceId: 999,
|
|
126
|
+
changeType: 'deleted',
|
|
127
|
+
label: 'Removed service',
|
|
128
|
+
unplaced: true,
|
|
129
|
+
}),
|
|
130
|
+
])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('keeps file-only changes available in the unplaced tray', () => {
|
|
134
|
+
const lens = buildExploreDiffLens(data, [
|
|
135
|
+
diff({
|
|
136
|
+
owner_type: 'file',
|
|
137
|
+
owner_key: 'services/api/main.go',
|
|
138
|
+
resource_type: 'file',
|
|
139
|
+
resource_id: undefined,
|
|
140
|
+
summary: 'services/api/main.go',
|
|
141
|
+
}),
|
|
142
|
+
], 9)
|
|
143
|
+
|
|
144
|
+
expect(lens.unplacedTargets).toEqual([
|
|
145
|
+
expect.objectContaining({
|
|
146
|
+
resourceType: 'file',
|
|
147
|
+
label: 'services/api/main.go',
|
|
148
|
+
sourcePath: 'services/api/main.go',
|
|
149
|
+
}),
|
|
150
|
+
])
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('preserves summaries, line deltas, and source paths', () => {
|
|
154
|
+
const lens = buildExploreDiffLens(data, [diff({})], 9)
|
|
155
|
+
|
|
156
|
+
expect(lens.elementLineDeltas.get(301)).toEqual({ added: 3, removed: 1 })
|
|
157
|
+
expect(lens.diffDetailsByResource.get('element:301')).toEqual(expect.objectContaining({
|
|
158
|
+
summary: 'Serve',
|
|
159
|
+
addedLines: 3,
|
|
160
|
+
removedLines: 1,
|
|
161
|
+
sourcePath: 'services/api/main.go',
|
|
162
|
+
}))
|
|
163
|
+
expect(lens.totalAddedLines).toBe(3)
|
|
164
|
+
expect(lens.totalRemovedLines).toBe(1)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('ignores initialized resources as diff targets', () => {
|
|
168
|
+
const lens = buildExploreDiffLens(data, [
|
|
169
|
+
diff({ change_type: 'initialized', resource_type: 'element', resource_id: 301 }),
|
|
170
|
+
diff({ change_type: 'initialized', resource_type: 'connector', resource_id: 601 }),
|
|
171
|
+
], 9)
|
|
172
|
+
|
|
173
|
+
expect(lens.orderedTargets).toHaveLength(0)
|
|
174
|
+
expect(lens.unplacedTargets).toHaveLength(0)
|
|
175
|
+
expect(lens.changedElementIds.size).toBe(0)
|
|
176
|
+
expect(lens.changedConnectorIds.size).toBe(0)
|
|
177
|
+
expect(lens.diffDetailsByResource.size).toBe(0)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('extracts source paths from common owner key shapes', () => {
|
|
181
|
+
expect(sourcePathFromDiff({ owner_type: 'file', owner_key: 'cmd/root.go' })).toBe('cmd/root.go')
|
|
182
|
+
expect(sourcePathFromDiff({ owner_type: 'symbol', owner_key: 'go:internal/app/app.go:function:Run' })).toBe('internal/app/app.go')
|
|
183
|
+
expect(sourcePathFromDiff({ owner_type: 'reference', owner_key: 'symbol:go:a.go:function:A:go:b.go:function:B:call' })).toBe('a.go')
|
|
184
|
+
})
|
|
185
|
+
})
|