@truedat/dd 8.12.10 → 8.12.12
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/package.json +3 -3
- package/src/components/LineageGraph.js +106 -79
- package/src/components/LineageGraphNode.js +53 -0
- package/src/components/LineageGraphToolbar.js +45 -33
- package/src/components/__tests__/LineageGraph.spec.js +16 -0
- package/src/components/__tests__/LineageGraphInteraction.spec.js +1 -1
- package/src/components/__tests__/LineageGraphLayout.spec.js +21 -0
- package/src/components/__tests__/LineageGraphNode.spec.js +56 -0
- package/src/components/__tests__/LineageGraphPin.spec.js +338 -0
- package/src/components/__tests__/LineageGraphToolbar.spec.js +54 -2
- package/src/components/__tests__/LineageInspectorPanel.spec.js +1 -1
- package/src/styles/LineageGraph.less +164 -32
- package/src/utils/__tests__/lineageLayout.spec.js +68 -0
- package/src/utils/lineageGraph.js +5 -3
|
@@ -455,6 +455,27 @@ describe("layoutVisibleLineageGraph", () => {
|
|
|
455
455
|
});
|
|
456
456
|
|
|
457
457
|
describe("positioned lineage layout", () => {
|
|
458
|
+
it("drops a route from a previous layout when its endpoint is unavailable", () => {
|
|
459
|
+
const source = groupNode("source", 0, 0, 288, 120);
|
|
460
|
+
const [edge] = planLineageEdgeRoutes(
|
|
461
|
+
[
|
|
462
|
+
{
|
|
463
|
+
id: "e1",
|
|
464
|
+
source: "source",
|
|
465
|
+
target: "missing",
|
|
466
|
+
data: {
|
|
467
|
+
edgeOffset: 12,
|
|
468
|
+
routeGuide: { kind: "corridor", laneX: 400 },
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
[source],
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
expect(edge.data.routeGuide).toBeUndefined();
|
|
476
|
+
expect(edge.data.edgeOffset).toBeUndefined();
|
|
477
|
+
});
|
|
478
|
+
|
|
458
479
|
it("routes stacked siblings through the gap between their boxes", () => {
|
|
459
480
|
const group = groupNode("group", 0, 0, 360, 280);
|
|
460
481
|
const source = resourceNode("source", group, 24, 52, 288, 80);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { fireEvent, screen } from "@testing-library/react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import LineageGraphNode from "../LineageGraphNode";
|
|
4
|
+
|
|
5
|
+
jest.mock("../LineageResourceNode", () => () => <div>Resource</div>);
|
|
6
|
+
jest.mock("@truedat/core/components/graph/LineageGroupNode", () => () => (
|
|
7
|
+
<div>Group</div>
|
|
8
|
+
));
|
|
9
|
+
|
|
10
|
+
it.each(["lineageResource", "lineageGroup"])(
|
|
11
|
+
"unpins %s without selecting or expanding the node",
|
|
12
|
+
(type) => {
|
|
13
|
+
const onUnpin = jest.fn();
|
|
14
|
+
const onClick = jest.fn();
|
|
15
|
+
const onDoubleClick = jest.fn();
|
|
16
|
+
const view = render(
|
|
17
|
+
<div
|
|
18
|
+
className="react-flow__node"
|
|
19
|
+
tabIndex={0}
|
|
20
|
+
onClick={onClick}
|
|
21
|
+
onDoubleClick={onDoubleClick}
|
|
22
|
+
role="button"
|
|
23
|
+
>
|
|
24
|
+
<LineageGraphNode type={type} data={{ onUnpin }} />
|
|
25
|
+
</div>,
|
|
26
|
+
);
|
|
27
|
+
const button = screen.getByRole("button", { name: "Unpin focus" });
|
|
28
|
+
fireEvent.click(button);
|
|
29
|
+
fireEvent.doubleClick(button);
|
|
30
|
+
expect(onUnpin).toHaveBeenCalledTimes(1);
|
|
31
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
32
|
+
expect(onDoubleClick).not.toHaveBeenCalled();
|
|
33
|
+
expect(view.container.querySelector(".react-flow__node")).toHaveFocus();
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
it("renders no pin control for an unpinned node", () => {
|
|
38
|
+
render(<LineageGraphNode type="lineageResource" data={{}} />);
|
|
39
|
+
expect(
|
|
40
|
+
screen.queryByRole("button", { name: "Unpin focus" }),
|
|
41
|
+
).not.toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("keeps Space keyup away from the canvas pan shortcut", () => {
|
|
45
|
+
const onKeyUp = jest.fn();
|
|
46
|
+
render(
|
|
47
|
+
<div onKeyUp={onKeyUp} role="presentation">
|
|
48
|
+
<LineageGraphNode type="lineageResource" data={{ onUnpin: jest.fn() }} />
|
|
49
|
+
</div>,
|
|
50
|
+
);
|
|
51
|
+
fireEvent.keyUp(screen.getByRole("button", { name: "Unpin focus" }), {
|
|
52
|
+
key: " ",
|
|
53
|
+
code: "Space",
|
|
54
|
+
});
|
|
55
|
+
expect(onKeyUp).not.toHaveBeenCalled();
|
|
56
|
+
});
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { act, fireEvent, screen, within } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import Graph from "@truedat/core/components/Graph";
|
|
4
|
+
import { render } from "@truedat/test/render";
|
|
5
|
+
import { LineageGraph } from "../LineageGraph";
|
|
6
|
+
|
|
7
|
+
jest.mock("@truedat/core/components/Graph", () =>
|
|
8
|
+
jest.fn(
|
|
9
|
+
({
|
|
10
|
+
nodes,
|
|
11
|
+
edges,
|
|
12
|
+
topControls,
|
|
13
|
+
onNodeClick,
|
|
14
|
+
onNodeDoubleClick,
|
|
15
|
+
onPaneClick,
|
|
16
|
+
}) => (
|
|
17
|
+
<div>
|
|
18
|
+
{topControls}
|
|
19
|
+
{nodes.map((node) => (
|
|
20
|
+
<div key={node.id}>
|
|
21
|
+
<button
|
|
22
|
+
className={`react-flow__node ${node.className || ""}`}
|
|
23
|
+
data-id={node.id}
|
|
24
|
+
data-selected={node.data.focusSelected}
|
|
25
|
+
data-dimmed={node.data.focusDimmed}
|
|
26
|
+
onClick={(event) => onNodeClick(event, node)}
|
|
27
|
+
onDoubleClick={(event) => onNodeDoubleClick(event, node)}
|
|
28
|
+
type="button"
|
|
29
|
+
>
|
|
30
|
+
{`Node ${node.id}`}
|
|
31
|
+
</button>
|
|
32
|
+
{node.data.onUnpin ? (
|
|
33
|
+
<button
|
|
34
|
+
type="button"
|
|
35
|
+
onClick={node.data.onUnpin}
|
|
36
|
+
>{`Unpin node ${node.id}`}</button>
|
|
37
|
+
) : null}
|
|
38
|
+
</div>
|
|
39
|
+
))}
|
|
40
|
+
{edges.map((edge) => (
|
|
41
|
+
<button
|
|
42
|
+
key={edge.id}
|
|
43
|
+
data-focused={edge.data.focusFocused}
|
|
44
|
+
onClick={edge.data.onClick}
|
|
45
|
+
type="button"
|
|
46
|
+
>
|
|
47
|
+
{`Edge ${edge.id}`}
|
|
48
|
+
</button>
|
|
49
|
+
))}
|
|
50
|
+
<button onClick={onPaneClick} type="button">
|
|
51
|
+
Canvas
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const resources = ["A", "B", "C"].map((id) => ({ id, name: id }));
|
|
59
|
+
const defaultProps = {
|
|
60
|
+
resources,
|
|
61
|
+
edges: [],
|
|
62
|
+
groups: [],
|
|
63
|
+
cancelPoll: jest.fn(),
|
|
64
|
+
graphId: "graph-1",
|
|
65
|
+
};
|
|
66
|
+
const nodeButton = (id) => screen.getByRole("button", { name: `Node ${id}` });
|
|
67
|
+
const pinA = () => {
|
|
68
|
+
fireEvent.click(nodeButton("A"));
|
|
69
|
+
fireEvent.click(screen.getByRole("button", { name: "Pin focus" }));
|
|
70
|
+
};
|
|
71
|
+
const dock = (container) =>
|
|
72
|
+
container.querySelector(".lineage-graph__dock-context-bar");
|
|
73
|
+
const pinChip = (container) =>
|
|
74
|
+
container.querySelector(".lineage-graph__dock-pin");
|
|
75
|
+
|
|
76
|
+
describe("LineageGraph pinned focus", () => {
|
|
77
|
+
it("shows the new selection without an extra pin chip", () => {
|
|
78
|
+
const { container } = render(<LineageGraph {...defaultProps} />);
|
|
79
|
+
pinA();
|
|
80
|
+
fireEvent.click(nodeButton("B"));
|
|
81
|
+
expect(nodeButton("A")).toHaveClass("lineage-node--pinned");
|
|
82
|
+
expect(nodeButton("B")).toHaveAttribute("data-selected", "true");
|
|
83
|
+
expect(nodeButton("B")).toHaveAttribute("data-dimmed", "false");
|
|
84
|
+
expect(dock(container)).toHaveTextContent("B");
|
|
85
|
+
expect(pinChip(container)).not.toBeInTheDocument();
|
|
86
|
+
expect(
|
|
87
|
+
screen.getByRole("button", { name: "Clear selection for B" }),
|
|
88
|
+
).toBeInTheDocument();
|
|
89
|
+
fireEvent.mouseDown(document.body);
|
|
90
|
+
fireEvent.click(screen.getByRole("button", { name: "Canvas" }));
|
|
91
|
+
expect(nodeButton("A")).toHaveClass("lineage-node--pinned");
|
|
92
|
+
expect(dock(container)).toHaveTextContent("A");
|
|
93
|
+
expect(pinChip(container)).not.toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it.each(["Enter", " "])(
|
|
97
|
+
"selects with %s and restores default focus when unpinning the node",
|
|
98
|
+
(key) => {
|
|
99
|
+
const { container } = render(<LineageGraph {...defaultProps} />);
|
|
100
|
+
pinA();
|
|
101
|
+
fireEvent.keyDown(nodeButton("B"), { key });
|
|
102
|
+
expect(nodeButton("B")).toHaveAttribute("data-selected", "true");
|
|
103
|
+
fireEvent.click(screen.getByRole("button", { name: "Unpin node A" }));
|
|
104
|
+
expect(nodeButton("A")).not.toHaveClass("lineage-node--pinned");
|
|
105
|
+
expect(nodeButton("B")).not.toHaveAttribute("data-selected", "true");
|
|
106
|
+
expect(nodeButton("C")).not.toHaveAttribute("data-dimmed", "true");
|
|
107
|
+
expect(dock(container)).not.toBeInTheDocument();
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
it("reopens the pinned context, replaces pin explicitly and clears only selection", () => {
|
|
112
|
+
const { container } = render(<LineageGraph {...defaultProps} />);
|
|
113
|
+
pinA();
|
|
114
|
+
fireEvent.click(nodeButton("B"));
|
|
115
|
+
fireEvent.click(screen.getByRole("button", { name: "Pin focus" }));
|
|
116
|
+
expect(nodeButton("A")).not.toHaveClass("lineage-node--pinned");
|
|
117
|
+
expect(nodeButton("B")).toHaveClass("lineage-node--pinned");
|
|
118
|
+
expect(pinChip(container)).not.toBeInTheDocument();
|
|
119
|
+
fireEvent.click(
|
|
120
|
+
screen.getByRole("button", { name: "Clear selection for B" }),
|
|
121
|
+
);
|
|
122
|
+
expect(dock(container)).toHaveTextContent("B");
|
|
123
|
+
fireEvent.click(
|
|
124
|
+
within(dock(container)).getByRole("button", { name: "B Table" }),
|
|
125
|
+
);
|
|
126
|
+
expect(screen.getByLabelText("Lineage inspector")).toBeInTheDocument();
|
|
127
|
+
expect(nodeButton("B")).toHaveAttribute("data-selected", "true");
|
|
128
|
+
act(() => jest.runOnlyPendingTimers());
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("retains pin through loading but clears it when the graph changes or the node is removed", () => {
|
|
132
|
+
const view = render(<LineageGraph {...defaultProps} />);
|
|
133
|
+
pinA();
|
|
134
|
+
view.rerender(<LineageGraph {...defaultProps} loading resources={[]} />);
|
|
135
|
+
view.rerender(<LineageGraph {...defaultProps} />);
|
|
136
|
+
expect(nodeButton("A")).toHaveClass("lineage-node--pinned");
|
|
137
|
+
view.rerender(<LineageGraph {...defaultProps} graphId="graph-2" />);
|
|
138
|
+
expect(nodeButton("A")).not.toHaveClass("lineage-node--pinned");
|
|
139
|
+
pinA();
|
|
140
|
+
view.rerender(
|
|
141
|
+
<LineageGraph
|
|
142
|
+
{...defaultProps}
|
|
143
|
+
graphId="graph-2"
|
|
144
|
+
resources={resources.slice(1)}
|
|
145
|
+
/>,
|
|
146
|
+
);
|
|
147
|
+
expect(
|
|
148
|
+
screen.queryByRole("button", { name: "Unpin focus" }),
|
|
149
|
+
).not.toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("inspects an unrelated edge without replacing the pinned focus", () => {
|
|
154
|
+
render(
|
|
155
|
+
<LineageGraph
|
|
156
|
+
{...defaultProps}
|
|
157
|
+
edges={[{ id: "BC", source: "B", target: "C" }]}
|
|
158
|
+
/>,
|
|
159
|
+
);
|
|
160
|
+
pinA();
|
|
161
|
+
fireEvent.click(screen.getByRole("button", { name: "Edge BC" }));
|
|
162
|
+
expect(nodeButton("A")).toHaveClass("lineage-node--pinned");
|
|
163
|
+
expect(screen.getByRole("button", { name: "Edge BC" })).toHaveAttribute(
|
|
164
|
+
"data-focused",
|
|
165
|
+
"false",
|
|
166
|
+
);
|
|
167
|
+
expect(screen.getByLabelText("Lineage inspector")).toBeInTheDocument();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("keeps pin when selecting a search result or double-clicking another node", () => {
|
|
171
|
+
const { container } = render(<LineageGraph {...defaultProps} />);
|
|
172
|
+
pinA();
|
|
173
|
+
fireEvent.click(screen.getByRole("button", { name: "Search nodes" }));
|
|
174
|
+
fireEvent.change(
|
|
175
|
+
screen.getByRole("textbox", { name: "Search graph nodes" }),
|
|
176
|
+
{ target: { value: "B" } },
|
|
177
|
+
);
|
|
178
|
+
act(() => jest.advanceTimersByTime(300));
|
|
179
|
+
fireEvent.click(container.querySelector(".lineage-graph__search-result"));
|
|
180
|
+
expect(nodeButton("B")).toHaveAttribute("data-selected", "true");
|
|
181
|
+
expect(nodeButton("A")).toHaveClass("lineage-node--pinned");
|
|
182
|
+
fireEvent.doubleClick(nodeButton("C"));
|
|
183
|
+
expect(nodeButton("C")).toHaveAttribute("data-selected", "true");
|
|
184
|
+
expect(nodeButton("A")).toHaveClass("lineage-node--pinned");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("keeps a hidden pinned child through collapse and expansion", () => {
|
|
188
|
+
const topology = {
|
|
189
|
+
nodes: [{ id: "G", name: "Group", class: "Group" }, ...resources],
|
|
190
|
+
contains: [{ source: "G", target: "A" }],
|
|
191
|
+
depends: [],
|
|
192
|
+
};
|
|
193
|
+
const { container } = render(
|
|
194
|
+
<LineageGraph
|
|
195
|
+
{...defaultProps}
|
|
196
|
+
topology={topology}
|
|
197
|
+
defaultExpansionDepth={10}
|
|
198
|
+
/>,
|
|
199
|
+
);
|
|
200
|
+
pinA();
|
|
201
|
+
fireEvent.click(screen.getByRole("button", { name: "Collapse all" }));
|
|
202
|
+
expect(
|
|
203
|
+
screen.queryByRole("button", { name: "Node A" }),
|
|
204
|
+
).not.toBeInTheDocument();
|
|
205
|
+
expect(dock(container)).toHaveTextContent("A");
|
|
206
|
+
expect(nodeButton("G")).not.toHaveClass("lineage-node--pinned");
|
|
207
|
+
fireEvent.click(screen.getByRole("button", { name: "Expand all" }));
|
|
208
|
+
expect(nodeButton("A")).toHaveClass("lineage-node--pinned");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it.each([
|
|
212
|
+
[false, "A"],
|
|
213
|
+
[true, "A"],
|
|
214
|
+
[true, "nested"],
|
|
215
|
+
])(
|
|
216
|
+
"opens only the ancestors when centering a hidden node (pinned: %s, node: %s)",
|
|
217
|
+
async (pinned, targetId) => {
|
|
218
|
+
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
|
219
|
+
const topology = {
|
|
220
|
+
nodes: [
|
|
221
|
+
{ id: "G", name: "Group", class: "Group" },
|
|
222
|
+
{ id: "nested", name: "Nested", class: "Group" },
|
|
223
|
+
{ id: "other", name: "Other", class: "Group" },
|
|
224
|
+
...resources,
|
|
225
|
+
],
|
|
226
|
+
contains: [
|
|
227
|
+
{ source: "G", target: "nested" },
|
|
228
|
+
{ source: "nested", target: "A" },
|
|
229
|
+
{ source: "other", target: "B" },
|
|
230
|
+
],
|
|
231
|
+
depends: [],
|
|
232
|
+
};
|
|
233
|
+
const { container } = render(
|
|
234
|
+
<LineageGraph
|
|
235
|
+
{...defaultProps}
|
|
236
|
+
topology={topology}
|
|
237
|
+
defaultExpansionDepth={10}
|
|
238
|
+
/>,
|
|
239
|
+
);
|
|
240
|
+
fireEvent.keyDown(nodeButton(targetId), { key: "Enter" });
|
|
241
|
+
if (pinned) {
|
|
242
|
+
fireEvent.click(screen.getByRole("button", { name: "Pin focus" }));
|
|
243
|
+
}
|
|
244
|
+
fireEvent.click(screen.getByRole("button", { name: "Collapse all" }));
|
|
245
|
+
expect(screen.queryByRole("button", { name: "Node A" })).toBeNull();
|
|
246
|
+
expect(screen.queryByRole("button", { name: "Node nested" })).toBeNull();
|
|
247
|
+
const requestKey =
|
|
248
|
+
Graph.mock.calls.at(-1)[0].graphOptions.focusNodeRequestKey;
|
|
249
|
+
if (pinned) {
|
|
250
|
+
fireEvent.click(screen.getByRole("button", { name: "Canvas" }));
|
|
251
|
+
fireEvent.click(
|
|
252
|
+
within(dock(container)).getByRole("button", { name: /^(A|Nested)/ }),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
screen.getByRole("button", { name: "Center in graph" }).focus();
|
|
256
|
+
await user.keyboard("{Enter}");
|
|
257
|
+
expect(nodeButton("nested")).toBeInTheDocument();
|
|
258
|
+
expect(nodeButton(targetId)).toHaveAttribute("data-selected", "true");
|
|
259
|
+
if (pinned)
|
|
260
|
+
expect(nodeButton(targetId)).toHaveClass("lineage-node--pinned");
|
|
261
|
+
if (targetId === "nested") {
|
|
262
|
+
expect(screen.queryByRole("button", { name: "Node A" })).toBeNull();
|
|
263
|
+
}
|
|
264
|
+
expect(nodeButton("other")).toBeInTheDocument();
|
|
265
|
+
expect(screen.queryByRole("button", { name: "Node B" })).toBeNull();
|
|
266
|
+
expect(Graph.mock.calls.at(-1)[0].graphOptions).toMatchObject({
|
|
267
|
+
focusNodeId: targetId,
|
|
268
|
+
focusNodeRequestKey: requestKey + 1,
|
|
269
|
+
});
|
|
270
|
+
fireEvent.click(screen.getByRole("button", { name: "Center in graph" }));
|
|
271
|
+
expect(Graph.mock.calls.at(-1)[0].graphOptions.focusNodeRequestKey).toBe(
|
|
272
|
+
requestKey + 2,
|
|
273
|
+
);
|
|
274
|
+
},
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
it("preserves the logical pin when duplicate fields acquire a new visible representative", () => {
|
|
278
|
+
const originalId = "https://glue.eu.amazonaws.com/#/table/field";
|
|
279
|
+
const aliasId = "glue:/Glue/table/field";
|
|
280
|
+
const topology = {
|
|
281
|
+
nodes: [
|
|
282
|
+
{ id: "table", name: "Table", class: "Resource", type: "table" },
|
|
283
|
+
{ id: originalId, name: "Field", class: "Resource", type: "column" },
|
|
284
|
+
],
|
|
285
|
+
contains: [{ source: "table", target: originalId }],
|
|
286
|
+
depends: [],
|
|
287
|
+
};
|
|
288
|
+
const view = render(
|
|
289
|
+
<LineageGraph
|
|
290
|
+
{...defaultProps}
|
|
291
|
+
topology={topology}
|
|
292
|
+
defaultExpansionDepth={10}
|
|
293
|
+
/>,
|
|
294
|
+
);
|
|
295
|
+
fireEvent.click(nodeButton(originalId));
|
|
296
|
+
fireEvent.click(screen.getByRole("button", { name: "Pin focus" }));
|
|
297
|
+
view.rerender(
|
|
298
|
+
<LineageGraph
|
|
299
|
+
{...defaultProps}
|
|
300
|
+
defaultExpansionDepth={10}
|
|
301
|
+
topology={{
|
|
302
|
+
...topology,
|
|
303
|
+
nodes: [
|
|
304
|
+
...topology.nodes,
|
|
305
|
+
{ id: aliasId, name: "Field", class: "Resource", type: "column" },
|
|
306
|
+
],
|
|
307
|
+
contains: [...topology.contains, { source: "table", target: aliasId }],
|
|
308
|
+
}}
|
|
309
|
+
/>,
|
|
310
|
+
);
|
|
311
|
+
expect(nodeButton(aliasId)).toHaveClass("lineage-node--pinned");
|
|
312
|
+
expect(dock(view.container)).toHaveTextContent("Field");
|
|
313
|
+
fireEvent.click(
|
|
314
|
+
within(dock(view.container)).getByRole("button", { name: "Unpin focus" }),
|
|
315
|
+
);
|
|
316
|
+
expect(nodeButton(aliasId)).not.toHaveClass("lineage-node--pinned");
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("minimizes to the pinned context on an outside click without a pane click", () => {
|
|
320
|
+
const { container } = render(<LineageGraph {...defaultProps} />);
|
|
321
|
+
pinA();
|
|
322
|
+
expect(
|
|
323
|
+
within(dock(container)).queryByRole("button", { name: "Unpin focus" }),
|
|
324
|
+
).not.toBeInTheDocument();
|
|
325
|
+
expect(dock(container).closest(".lineage-graph__dock-context")).toHaveClass(
|
|
326
|
+
"lineage-graph__dock-context--pinned",
|
|
327
|
+
);
|
|
328
|
+
fireEvent.click(nodeButton("B"));
|
|
329
|
+
fireEvent.mouseDown(document.body);
|
|
330
|
+
expect(dock(container)).toHaveTextContent("A");
|
|
331
|
+
expect(screen.queryByLabelText("Lineage inspector")).not.toBeInTheDocument();
|
|
332
|
+
fireEvent.click(
|
|
333
|
+
within(dock(container)).getByRole("button", { name: "Unpin focus" }),
|
|
334
|
+
);
|
|
335
|
+
expect(dock(container)).not.toBeInTheDocument();
|
|
336
|
+
expect(nodeButton("A")).not.toHaveClass("lineage-node--pinned");
|
|
337
|
+
expect(nodeButton("B")).not.toHaveAttribute("data-selected", "true");
|
|
338
|
+
});
|
|
@@ -233,13 +233,13 @@ describe("<LineageGraphToolbar />", () => {
|
|
|
233
233
|
});
|
|
234
234
|
|
|
235
235
|
it("renders the pinned minimized bar with unpin and accent state", () => {
|
|
236
|
-
renderToolbar({
|
|
236
|
+
renderToolbar({ context: null, pinnedContext: defaultProps.context });
|
|
237
237
|
|
|
238
238
|
const unpin = screen.getByRole("button", { name: "Unpin focus" });
|
|
239
239
|
|
|
240
240
|
expect(unpin).toHaveAttribute("aria-pressed", "true");
|
|
241
241
|
expect(unpin.querySelector(".lucide-pin")).toBeInTheDocument();
|
|
242
|
-
expect(screen.getByRole("button", { name: "Search nodes" })).
|
|
242
|
+
expect(screen.getByRole("button", { name: "Search nodes" })).toBeEnabled();
|
|
243
243
|
|
|
244
244
|
fireEvent.click(unpin);
|
|
245
245
|
expect(defaultProps.onUnpin).toHaveBeenCalledTimes(1);
|
|
@@ -362,3 +362,55 @@ describe("visibleLegendKeys", () => {
|
|
|
362
362
|
).not.toBeInTheDocument();
|
|
363
363
|
});
|
|
364
364
|
});
|
|
365
|
+
|
|
366
|
+
it("shows the current selection without an additional pinned chip", () => {
|
|
367
|
+
const onPinnedContextSelect = jest.fn();
|
|
368
|
+
const rendered = renderToolbar({
|
|
369
|
+
pinnedContext: defaultProps.context,
|
|
370
|
+
context: {
|
|
371
|
+
type: "node",
|
|
372
|
+
node: {
|
|
373
|
+
...defaultProps.context.node,
|
|
374
|
+
name: "Customers",
|
|
375
|
+
graphNodeId: "customers",
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
activePanel: "inspector",
|
|
379
|
+
onPinnedContextSelect,
|
|
380
|
+
});
|
|
381
|
+
expect(screen.getByLabelText("Lineage inspector")).toHaveTextContent(
|
|
382
|
+
"Customers",
|
|
383
|
+
);
|
|
384
|
+
expect(
|
|
385
|
+
rendered.container.querySelector(".lineage-graph__dock-context-bar"),
|
|
386
|
+
).toHaveTextContent("Customers");
|
|
387
|
+
expect(
|
|
388
|
+
rendered.container.querySelector(".lineage-graph__dock-pin"),
|
|
389
|
+
).not.toBeInTheDocument();
|
|
390
|
+
expect(
|
|
391
|
+
screen.queryByRole("button", { name: "Unpin focus" }),
|
|
392
|
+
).not.toBeInTheDocument();
|
|
393
|
+
expect(
|
|
394
|
+
screen.getByRole("button", { name: "Clear selection for Customers" }),
|
|
395
|
+
).toBeInTheDocument();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("preserves the structure icon in the pinned header without an unpin button", () => {
|
|
399
|
+
const { container } = renderToolbar({
|
|
400
|
+
pinned: true,
|
|
401
|
+
pinnedContext: defaultProps.context,
|
|
402
|
+
activePanel: "inspector",
|
|
403
|
+
});
|
|
404
|
+
const bar = container.querySelector(".lineage-graph__dock-context-bar");
|
|
405
|
+
expect(bar.closest(".lineage-graph__dock-context")).toHaveClass(
|
|
406
|
+
"lineage-graph__dock-context--pinned",
|
|
407
|
+
);
|
|
408
|
+
expect(
|
|
409
|
+
bar.querySelector(
|
|
410
|
+
".lineage-graph__dock-context-summary-icon .lucide-table-2",
|
|
411
|
+
),
|
|
412
|
+
).toBeInTheDocument();
|
|
413
|
+
expect(
|
|
414
|
+
bar.querySelector(".lineage-graph__dock-context-unpin"),
|
|
415
|
+
).not.toBeInTheDocument();
|
|
416
|
+
});
|
|
@@ -344,7 +344,7 @@ describe("<LineageInspectorPanel />", () => {
|
|
|
344
344
|
"utf8",
|
|
345
345
|
);
|
|
346
346
|
|
|
347
|
-
expect(styles).toContain("--td-lineage-inspector-motion-duration:
|
|
347
|
+
expect(styles).toContain("--td-lineage-inspector-motion-duration: 220ms;");
|
|
348
348
|
expect(styles).toContain("@media (prefers-reduced-motion: reduce)");
|
|
349
349
|
expect(styles).toContain(
|
|
350
350
|
".lineage-view__header-icon > .icon,\n.lineage-inspector__header-icon > .icon",
|