Skip to content

Commit 239d084

Browse files
committed
feat(tree): init e2e tests
1 parent 6784ab2 commit 239d084

File tree

8 files changed

+291
-12
lines changed

8 files changed

+291
-12
lines changed

Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ fmt: ##@0 global format code using prettier (js, css, md)
6262
"storybook/stories/**/*.{js,ts,tsx}" \
6363
"cypress/src/**/*.{js,ts,tsx}" \
6464
"scripts/*.{js,mjs}" \
65+
"cypress/src/**/*.{js,tsx}" \
6566
"README.md"
6667

6768
fmt-check: ##@0 global check if files were all formatted using prettier
@@ -76,6 +77,7 @@ fmt-check: ##@0 global check if files were all formatted using prettier
7677
"storybook/stories/**/*.{js,ts,tsx}" \
7778
"cypress/src/**/*.{js,ts,tsx}" \
7879
"scripts/*.{js,mjs}" \
80+
"cypress/src/**/*.{js,tsx}" \
7981
"README.md"
8082

8183
test: ##@0 global run all checks/tests (packages, website)

cypress/cypress.config.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { defineConfig } from 'cypress'
1+
import { defineConfig } from "cypress";
22

33
export default defineConfig({
4+
viewportWidth: 600,
5+
viewportHeight: 600,
46
component: {
57
devServer: {
6-
framework: 'create-react-app',
7-
bundler: 'webpack',
8+
framework: "create-react-app",
9+
bundler: "webpack",
810
},
911
video: false,
1012
},
11-
})
13+
});

cypress/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"@nivo/stream": "workspace:*",
3434
"@nivo/sunburst": "workspace:*",
3535
"@nivo/swarmplot": "workspace:*",
36+
"@nivo/tree": "workspace:*",
3637
"@nivo/treemap": "workspace:*",
3738
"@nivo/voronoi": "workspace:*",
3839
"@nivo/waffle": "workspace:*"
@@ -45,9 +46,9 @@
4546
"node": ">=18"
4647
},
4748
"devDependencies": {
48-
"cypress": "^12.11.0",
49-
"react": "^18.2.0",
50-
"react-dom": "^18.2.0",
49+
"cypress": "^13.8.1",
50+
"react": "^18.3.1",
51+
"react-dom": "^18.3.1",
5152
"react-scripts": "^5.0.1",
5253
"typescript": "^4.9.5"
5354
},
+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { Tree, TreeSvgProps } from '@nivo/tree'
2+
import { before } from 'lodash'
3+
4+
interface Datum {
5+
id: string
6+
children?: Datum[]
7+
}
8+
9+
const sampleData: Datum = {
10+
id: 'A',
11+
children: [
12+
{ id: '0' },
13+
{
14+
id: '1',
15+
children: [{ id: 'A' }, { id: 'B' }],
16+
},
17+
{ id: '2' },
18+
],
19+
}
20+
21+
const defaultProps: Pick<
22+
TreeSvgProps<Datum>,
23+
| 'data'
24+
| 'width'
25+
| 'height'
26+
| 'margin'
27+
| 'nodeSize'
28+
| 'activeNodeSize'
29+
| 'inactiveNodeSize'
30+
| 'linkThickness'
31+
| 'activeLinkThickness'
32+
| 'inactiveLinkThickness'
33+
| 'animate'
34+
> = {
35+
data: sampleData,
36+
width: 640,
37+
height: 640,
38+
margin: {
39+
top: 20,
40+
right: 20,
41+
bottom: 20,
42+
left: 20,
43+
},
44+
nodeSize: 12,
45+
activeNodeSize: 24,
46+
inactiveNodeSize: 8,
47+
linkThickness: 2,
48+
activeLinkThickness: 12,
49+
inactiveLinkThickness: 1,
50+
animate: false,
51+
}
52+
53+
describe('<Tree />', () => {
54+
beforeEach(() => {
55+
cy.viewport(
56+
defaultProps.margin.left + defaultProps.width + defaultProps.margin.right,
57+
defaultProps.margin.top + defaultProps.height + defaultProps.margin.bottom
58+
)
59+
})
60+
61+
it('should render a tree graph', () => {
62+
cy.mount(<Tree<Datum> {...defaultProps} />)
63+
64+
cy.get('[data-testid="node.A"]').should('exist')
65+
cy.get('[data-testid="node.A.0"]').should('exist')
66+
cy.get('[data-testid="node.A.1"]').should('exist')
67+
cy.get('[data-testid="node.A.1.A"]').should('exist')
68+
cy.get('[data-testid="node.A.1.B"]').should('exist')
69+
cy.get('[data-testid="node.A.2"]').should('exist')
70+
})
71+
72+
it('should highlight ancestor nodes and links', () => {
73+
cy.mount(
74+
<Tree<Datum>
75+
{...defaultProps}
76+
useMesh={false}
77+
highlightAncestorNodes={true}
78+
highlightAncestorLinks={true}
79+
/>
80+
)
81+
82+
const expectations = [
83+
{ uid: 'node.A', nodes: ['node.A'], links: [] },
84+
{ uid: 'node.A.0', nodes: ['node.A', 'node.A.0'], links: ['link.A:A.0'] },
85+
{ uid: 'node.A.1', nodes: ['node.A', 'node.A.1'], links: ['link.A:A.1'] },
86+
{
87+
uid: 'node.A.1.A',
88+
nodes: ['node.A', 'node.A.1', 'node.A.1.A'],
89+
links: ['link.A:A.1', 'link.A.1:A.1.A'],
90+
},
91+
{
92+
uid: 'node.A.1.B',
93+
nodes: ['node.A', 'node.A.1', 'node.A.1.B'],
94+
links: ['link.A:A.1', 'link.A.1:A.1.B'],
95+
},
96+
{ uid: 'node.A.2', nodes: ['node.A', 'node.A.2'], links: ['link.A:A.2'] },
97+
]
98+
99+
for (const expectation of expectations) {
100+
cy.get(`[data-testid="${expectation.uid}"]`).trigger('mouseover')
101+
cy.wait(100)
102+
103+
cy.get('[data-testid^="node."]').each($node => {
104+
cy.wrap($node)
105+
.invoke('attr', 'data-testid')
106+
.then(testId => {
107+
const size = expectation.nodes.includes(testId!)
108+
? defaultProps.activeNodeSize
109+
: defaultProps.inactiveNodeSize
110+
cy.wrap($node)
111+
.invoke('attr', 'r')
112+
.should('equal', `${size / 2}`)
113+
})
114+
})
115+
116+
cy.get('[data-testid^="link."]').each($link => {
117+
cy.wrap($link)
118+
.invoke('attr', 'data-testid')
119+
.then(testId => {
120+
const thickness = expectation.links.includes(testId!)
121+
? defaultProps.activeLinkThickness
122+
: defaultProps.inactiveLinkThickness
123+
cy.wrap($link)
124+
.invoke('attr', 'stroke-width')
125+
.should('equal', `${thickness}`)
126+
})
127+
})
128+
}
129+
})
130+
131+
it('should highlight descendant nodes and links', () => {
132+
cy.mount(
133+
<Tree<Datum>
134+
{...defaultProps}
135+
useMesh={false}
136+
highlightAncestorNodes={false}
137+
highlightAncestorLinks={false}
138+
highlightDescendantNodes={true}
139+
highlightDescendantLinks={true}
140+
/>
141+
)
142+
143+
const expectations = [
144+
{
145+
uid: 'node.A',
146+
nodes: ['node.A', 'node.A.0', 'node.A.1', 'node.A.1.A', 'node.A.1.B', 'node.A.2'],
147+
links: [],
148+
},
149+
{ uid: 'node.A.0', nodes: ['node.A.0'], links: [] },
150+
{ uid: 'node.A.1', nodes: ['node.A.1', 'node.A.1.A', 'node.A.1.B'], links: [] },
151+
{ uid: 'node.A.1.A', nodes: ['node.A.1.A'], links: [] },
152+
{ uid: 'node.A.1.B', nodes: ['node.A.1.B'], links: [] },
153+
{ uid: 'node.A.2', nodes: ['node.A.2'], links: [] },
154+
]
155+
156+
for (const expectation of expectations) {
157+
cy.get(`[data-testid="${expectation.uid}"]`).trigger('mouseover')
158+
cy.wait(100)
159+
160+
cy.get('[data-testid^="node."]').each($node => {
161+
cy.wrap($node)
162+
.invoke('attr', 'data-testid')
163+
.then(testId => {
164+
const size = expectation.nodes.includes(testId!)
165+
? defaultProps.activeNodeSize
166+
: defaultProps.inactiveNodeSize
167+
cy.wrap($node)
168+
.invoke('attr', 'r')
169+
.should('equal', `${size / 2}`)
170+
})
171+
})
172+
}
173+
})
174+
})

packages/tree/src/Link.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const Link = <Datum,>({
2424

2525
return (
2626
<animated.path
27+
data-testid={`link.${link.id}`}
2728
d={to(
2829
[
2930
animatedProps.sourceX,

packages/tree/src/Node.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const Node = <Datum,>({
2525

2626
return (
2727
<animated.circle
28+
data-testid={`node.${node.uid}`}
2829
r={animatedProps.size.to(size => size / 2)}
2930
fill={animatedProps.color}
3031
cx={animatedProps.x}

packages/tree/src/hooks.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ const useNodes = <Datum>({
209209
activeNodeUids,
210210
])
211211

212-
return { ...computed, setActiveNodeUids }
212+
return { ...computed, activeNodeUids, setActiveNodeUids }
213213
}
214214

215215
const useLinkThicknessModifier = <Datum>(
@@ -224,13 +224,15 @@ const useLinkThicknessModifier = <Datum>(
224224
const useLinks = <Datum>({
225225
root,
226226
nodeByUid,
227+
activeNodeUids,
227228
linkThickness,
228229
activeLinkThickness,
229230
inactiveLinkThickness,
230231
linkColor,
231232
}: {
232233
root: HierarchyTreeNode<Datum>
233234
nodeByUid: Record<string, ComputedNode<Datum>>
235+
activeNodeUids: string[]
234236
linkThickness: Exclude<CommonProps<Datum>['linkThickness'], undefined>
235237
activeLinkThickness?: CommonProps<Datum>['activeLinkThickness']
236238
inactiveLinkThickness?: CommonProps<Datum>['inactiveLinkThickness']
@@ -268,7 +270,7 @@ const useLinks = <Datum>({
268270
isActive: null,
269271
}
270272

271-
if (activeLinkIds.length > 0) {
273+
if (activeNodeUids.length > 0) {
272274
computedLink.isActive = activeLinkIds.includes(computedLink.id)
273275
if (computedLink.isActive) {
274276
computedLink.thickness = getActiveLinkThickness(computedLink)
@@ -417,7 +419,7 @@ export const useTree = <Datum = DefaultDatum>({
417419
const root = useRoot<Datum>({ data, mode, getIdentity })
418420

419421
const { xScale, yScale } = useCartesianScales({ width, height, layout })
420-
const { nodes, nodeByUid, setActiveNodeUids } = useNodes<Datum>({
422+
const { nodes, nodeByUid, activeNodeUids, setActiveNodeUids } = useNodes<Datum>({
421423
root,
422424
xScale,
423425
yScale,
@@ -433,6 +435,7 @@ export const useTree = <Datum = DefaultDatum>({
433435
const { links, setActiveLinkIds } = useLinks<Datum>({
434436
root,
435437
nodeByUid,
438+
activeNodeUids,
436439
linkThickness,
437440
activeLinkThickness,
438441
inactiveLinkThickness,

0 commit comments

Comments
 (0)