@pie-lib/graphing 3.1.0-next.5 → 3.1.1-next.1
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/NEXT.CHANGELOG.json +16 -1
- package/lib/axis/axes.js.map +1 -1
- package/lib/axis/index.js.map +1 -1
- package/lib/bg.js +9 -6
- package/lib/bg.js.map +1 -1
- package/lib/container/index.js +4 -4
- package/lib/container/index.js.map +1 -1
- package/lib/coordinates-label.js.map +1 -1
- package/lib/graph-with-controls.js +3 -4
- package/lib/graph-with-controls.js.map +1 -1
- package/lib/graph.js +8 -9
- package/lib/graph.js.map +1 -1
- package/lib/grid-setup.js.map +1 -1
- package/lib/key-legend.js +1 -1
- package/lib/key-legend.js.map +1 -1
- package/lib/mark-label.js.map +1 -1
- package/lib/toggle-bar.js.map +1 -1
- package/lib/tools/absolute/component.js.map +1 -1
- package/lib/tools/circle/bg-circle.js.map +1 -1
- package/lib/tools/circle/component.js +2 -2
- package/lib/tools/circle/component.js.map +1 -1
- package/lib/tools/exponential/component.js.map +1 -1
- package/lib/tools/line/component.js.map +1 -1
- package/lib/tools/parabola/component.js.map +1 -1
- package/lib/tools/point/component.js +3 -4
- package/lib/tools/point/component.js.map +1 -1
- package/lib/tools/polygon/component.js +5 -7
- package/lib/tools/polygon/component.js.map +1 -1
- package/lib/tools/polygon/line.js.map +1 -1
- package/lib/tools/polygon/polygon.js.map +1 -1
- package/lib/tools/ray/component.js.map +1 -1
- package/lib/tools/segment/component.js.map +1 -1
- package/lib/tools/shared/arrow-head.js.map +1 -1
- package/lib/tools/shared/line/index.js +7 -9
- package/lib/tools/shared/line/index.js.map +1 -1
- package/lib/tools/shared/line/line-path.js.map +1 -1
- package/lib/tools/shared/point/arrow-point.js.map +1 -1
- package/lib/tools/sine/component.js.map +1 -1
- package/lib/tools/vector/component.js.map +1 -1
- package/lib/undo-redo.js.map +1 -1
- package/lib/use-debounce.js.map +1 -1
- package/lib/utils.js +9 -13
- package/lib/utils.js.map +1 -1
- package/package.json +14 -14
- package/src/__tests__/bg.test.jsx +250 -0
- package/src/__tests__/coordinates-label.test.jsx +243 -0
- package/src/__tests__/graph-with-controls.test.jsx +46 -12
- package/src/__tests__/graph.test.jsx +560 -5
- package/src/__tests__/grid-setup.test.jsx +645 -0
- package/src/__tests__/grid.test.jsx +1 -1
- package/src/__tests__/key-legend.test.jsx +260 -0
- package/src/__tests__/label-svg-icon.test.jsx +278 -0
- package/src/__tests__/mark-label.test.jsx +1 -1
- package/src/__tests__/toggle-bar.test.jsx +38 -3
- package/src/__tests__/tool-menu.test.jsx +38 -1
- package/src/__tests__/use-debounce.test.js +1 -1
- package/src/__tests__/utils.test.js +15 -61
- package/src/axis/__tests__/axes.test.jsx +1 -1
- package/src/axis/axes.jsx +7 -21
- package/src/axis/index.js +1 -0
- package/src/bg.jsx +5 -5
- package/src/container/__tests__/actions.test.js +105 -0
- package/src/container/__tests__/index.test.jsx +319 -0
- package/src/container/__tests__/marks.test.js +172 -0
- package/src/container/__tests__/middleware.test.js +235 -0
- package/src/container/__tests__/reducer.test.js +324 -0
- package/src/container/index.jsx +2 -3
- package/src/coordinates-label.jsx +1 -7
- package/src/graph-with-controls.jsx +8 -6
- package/src/graph.jsx +2 -3
- package/src/grid-setup.jsx +1 -1
- package/src/key-legend.jsx +2 -1
- package/src/mark-label.jsx +7 -24
- package/src/toggle-bar.jsx +8 -1
- package/src/tools/absolute/__tests__/component.test.jsx +1 -2
- package/src/tools/absolute/component.jsx +2 -2
- package/src/tools/circle/__tests__/component.test.jsx +438 -0
- package/src/tools/circle/__tests__/index.test.js +480 -0
- package/src/tools/circle/bg-circle.jsx +2 -2
- package/src/tools/circle/component.jsx +10 -12
- package/src/tools/exponential/__tests__/component.test.jsx +0 -1
- package/src/tools/exponential/__tests__/index.test.js +729 -0
- package/src/tools/exponential/component.jsx +1 -1
- package/src/tools/line/__tests__/component.test.jsx +1 -0
- package/src/tools/line/component.jsx +4 -11
- package/src/tools/parabola/__tests__/component.test.jsx +0 -1
- package/src/tools/parabola/__tests__/index.test.js +470 -0
- package/src/tools/parabola/component.jsx +1 -1
- package/src/tools/point/__tests__/component.test.jsx +310 -2
- package/src/tools/point/__tests__/index.test.js +241 -0
- package/src/tools/point/component.jsx +1 -2
- package/src/tools/polygon/__tests__/component.test.jsx +391 -2
- package/src/tools/polygon/__tests__/index.test.js +237 -8
- package/src/tools/polygon/__tests__/line.test.jsx +13 -0
- package/src/tools/polygon/__tests__/polygon.test.jsx +19 -1
- package/src/tools/polygon/component.jsx +4 -14
- package/src/tools/polygon/line.jsx +1 -1
- package/src/tools/polygon/polygon.jsx +1 -1
- package/src/tools/ray/__tests__/component.test.jsx +1 -0
- package/src/tools/ray/component.jsx +3 -5
- package/src/tools/segment/__tests__/component.test.jsx +1 -0
- package/src/tools/segment/component.jsx +1 -1
- package/src/tools/shared/arrow-head.jsx +11 -6
- package/src/tools/shared/line/__tests__/index.test.jsx +1 -1
- package/src/tools/shared/line/__tests__/line-path.test.jsx +3 -3
- package/src/tools/shared/line/__tests__/with-root-edge.test.jsx +2 -2
- package/src/tools/shared/line/index.jsx +4 -6
- package/src/tools/shared/line/line-path.jsx +2 -8
- package/src/tools/shared/point/arrow-point.jsx +2 -5
- package/src/tools/sine/component.jsx +2 -2
- package/src/tools/vector/component.jsx +1 -1
- package/src/undo-redo.jsx +3 -9
- package/src/use-debounce.js +1 -1
- package/src/utils.js +1 -5
|
@@ -63,67 +63,21 @@ describe('utils', () => {
|
|
|
63
63
|
assertGetTickValues({ min: -0.2, max: 2, step: 0.6 }, [0, 0.6, 1.2, 1.8]);
|
|
64
64
|
assertGetTickValues({ min: -3.4, max: 6.2, step: 1.2 }, [0, -1.2, -2.4, 1.2, 2.4, 3.6, 4.8, 6]);
|
|
65
65
|
|
|
66
|
-
assertGetTickValues(
|
|
67
|
-
0.6,
|
|
68
|
-
0.9,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
2.1,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
3.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
4.8,
|
|
82
|
-
]);
|
|
83
|
-
assertGetTickValues({ min: 0.5, max: 4.9, step: 0.3 }, [
|
|
84
|
-
0.6,
|
|
85
|
-
0.9,
|
|
86
|
-
1.2,
|
|
87
|
-
1.5,
|
|
88
|
-
1.8,
|
|
89
|
-
2.1,
|
|
90
|
-
2.4,
|
|
91
|
-
2.7,
|
|
92
|
-
3.0,
|
|
93
|
-
3.3,
|
|
94
|
-
3.6,
|
|
95
|
-
3.9,
|
|
96
|
-
4.2,
|
|
97
|
-
4.5,
|
|
98
|
-
4.8,
|
|
99
|
-
]);
|
|
100
|
-
assertGetTickValues({ min: 0, max: 3, step: 0.125 }, [
|
|
101
|
-
0,
|
|
102
|
-
0.125,
|
|
103
|
-
0.25,
|
|
104
|
-
0.375,
|
|
105
|
-
0.5,
|
|
106
|
-
0.625,
|
|
107
|
-
0.75,
|
|
108
|
-
0.875,
|
|
109
|
-
1,
|
|
110
|
-
1.125,
|
|
111
|
-
1.25,
|
|
112
|
-
1.375,
|
|
113
|
-
1.5,
|
|
114
|
-
1.625,
|
|
115
|
-
1.75,
|
|
116
|
-
1.875,
|
|
117
|
-
2,
|
|
118
|
-
2.125,
|
|
119
|
-
2.25,
|
|
120
|
-
2.375,
|
|
121
|
-
2.5,
|
|
122
|
-
2.625,
|
|
123
|
-
2.75,
|
|
124
|
-
2.875,
|
|
125
|
-
3,
|
|
126
|
-
]);
|
|
66
|
+
assertGetTickValues(
|
|
67
|
+
{ min: 0.6, max: 4.8, step: 0.3 },
|
|
68
|
+
[0.6, 0.9, 1.2, 1.5, 1.8, 2.1, 2.4, 2.7, 3.0, 3.3, 3.6, 3.9, 4.2, 4.5, 4.8],
|
|
69
|
+
);
|
|
70
|
+
assertGetTickValues(
|
|
71
|
+
{ min: 0.5, max: 4.9, step: 0.3 },
|
|
72
|
+
[0.6, 0.9, 1.2, 1.5, 1.8, 2.1, 2.4, 2.7, 3.0, 3.3, 3.6, 3.9, 4.2, 4.5, 4.8],
|
|
73
|
+
);
|
|
74
|
+
assertGetTickValues(
|
|
75
|
+
{ min: 0, max: 3, step: 0.125 },
|
|
76
|
+
[
|
|
77
|
+
0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1, 1.125, 1.25, 1.375, 1.5, 1.625, 1.75, 1.875, 2, 2.125, 2.25,
|
|
78
|
+
2.375, 2.5, 2.625, 2.75, 2.875, 3,
|
|
79
|
+
],
|
|
80
|
+
);
|
|
127
81
|
});
|
|
128
82
|
|
|
129
83
|
describe('countWords', () => {
|
|
@@ -2,7 +2,7 @@ import { render } from '@pie-lib/test-utils';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { graphProps } from '../../__tests__/utils';
|
|
4
4
|
|
|
5
|
-
import Axes, { RawXAxis, RawYAxis,
|
|
5
|
+
import Axes, { firstNegativeValue, RawXAxis, RawYAxis, sharedValues } from '../axes';
|
|
6
6
|
|
|
7
7
|
describe('RawXAxis', () => {
|
|
8
8
|
let onChange = jest.fn();
|
package/src/axis/axes.jsx
CHANGED
|
@@ -4,7 +4,7 @@ import { types } from '@pie-lib/plot';
|
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
5
|
import Arrow from './arrow';
|
|
6
6
|
import { styled } from '@mui/material/styles';
|
|
7
|
-
import { countWords, findLongestWord,
|
|
7
|
+
import { amountToIncreaseWidth, countWords, findLongestWord, getTickValues } from '../utils';
|
|
8
8
|
import { color, Readable } from '@pie-lib/render-ui';
|
|
9
9
|
|
|
10
10
|
export const AxisPropTypes = {
|
|
@@ -92,14 +92,8 @@ export class RawXAxis extends React.Component {
|
|
|
92
92
|
static defaultProps = AxisDefaultProps;
|
|
93
93
|
|
|
94
94
|
render() {
|
|
95
|
-
const {
|
|
96
|
-
|
|
97
|
-
graphProps,
|
|
98
|
-
columnTicksValues,
|
|
99
|
-
skipValues,
|
|
100
|
-
distanceFromOriginToFirstNegativeY,
|
|
101
|
-
dy,
|
|
102
|
-
} = this.props;
|
|
95
|
+
const { includeArrows, graphProps, columnTicksValues, skipValues, distanceFromOriginToFirstNegativeY, dy } =
|
|
96
|
+
this.props;
|
|
103
97
|
const { scale, domain, size, range } = graphProps || {};
|
|
104
98
|
|
|
105
99
|
const labelProps = (label) => {
|
|
@@ -131,12 +125,8 @@ export class RawXAxis extends React.Component {
|
|
|
131
125
|
tickValues={columnTicksValues}
|
|
132
126
|
hideZero={!(domain.labelStep || range.labelStep) && domain.min <= 0}
|
|
133
127
|
/>
|
|
134
|
-
{includeArrows && includeArrows.left &&
|
|
135
|
-
|
|
136
|
-
)}
|
|
137
|
-
{includeArrows && includeArrows.right && (
|
|
138
|
-
<StyledArrow direction="right" x={domain.max} y={0} scale={scale} />
|
|
139
|
-
)}
|
|
128
|
+
{includeArrows && includeArrows.left && <StyledArrow direction="left" x={domain.min} y={0} scale={scale} />}
|
|
129
|
+
{includeArrows && includeArrows.right && <StyledArrow direction="right" x={domain.max} y={0} scale={scale} />}
|
|
140
130
|
{domain.axisLabel && (
|
|
141
131
|
<foreignObject x={size.width + 17} y={scale.y(0) - 9} width={necessaryWidth} height={20 * necessaryRows}>
|
|
142
132
|
<StyledLabel dangerouslySetInnerHTML={{ __html: domain.axisLabel }} />
|
|
@@ -191,12 +181,8 @@ export class RawYAxis extends React.Component {
|
|
|
191
181
|
tickTextAnchor={'bottom'}
|
|
192
182
|
tickValues={rowTickValues}
|
|
193
183
|
/>
|
|
194
|
-
{includeArrows && includeArrows.down &&
|
|
195
|
-
|
|
196
|
-
)}
|
|
197
|
-
{includeArrows && includeArrows.up && (
|
|
198
|
-
<StyledArrow direction="up" x={0} y={range.max} scale={scale} />
|
|
199
|
-
)}
|
|
184
|
+
{includeArrows && includeArrows.down && <StyledArrow direction="down" x={0} y={range.min} scale={scale} />}
|
|
185
|
+
{includeArrows && includeArrows.up && <StyledArrow direction="up" x={0} y={range.max} scale={scale} />}
|
|
200
186
|
{range.axisLabel && (
|
|
201
187
|
<foreignObject x={scale.x(0) - necessaryWidth / 2} y={-33} width={necessaryWidth} height="20">
|
|
202
188
|
<Readable false>
|
package/src/axis/index.js
CHANGED
package/src/bg.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import {
|
|
3
|
+
import { pointer, select } from 'd3-selection';
|
|
4
4
|
import { types, utils } from '@pie-lib/plot';
|
|
5
5
|
import { getTickValues, thinnerShapesNeeded } from './utils';
|
|
6
6
|
|
|
@@ -17,7 +17,7 @@ export default class Bg extends React.Component {
|
|
|
17
17
|
componentDidMount() {
|
|
18
18
|
const rect = select(this.rect);
|
|
19
19
|
|
|
20
|
-
rect.on('click', this.onRectClick
|
|
20
|
+
rect.on('click', (event) => this.onRectClick(rect, event));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
shouldComponentUpdate(nextProps) {
|
|
@@ -35,15 +35,15 @@ export default class Bg extends React.Component {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* Note: we use d3 click +
|
|
38
|
+
* Note: we use d3 click + pointer to give us domain values directly.
|
|
39
39
|
* Saves us having to calculate them ourselves from a MouseEvent.
|
|
40
40
|
*/
|
|
41
|
-
onRectClick = (rect) => {
|
|
41
|
+
onRectClick = (rect, event) => {
|
|
42
42
|
const { onClick, graphProps } = this.props;
|
|
43
43
|
const { scale } = graphProps;
|
|
44
44
|
|
|
45
45
|
const padding = this.getRectPadding();
|
|
46
|
-
const coords =
|
|
46
|
+
const coords = pointer(event, rect.node());
|
|
47
47
|
|
|
48
48
|
// decrease the padding from coordinates to indicate the correct point clicked
|
|
49
49
|
const x = scale.x.invert(coords[0] - padding);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { addMark, changeMarks } from '../actions';
|
|
2
|
+
|
|
3
|
+
describe('container actions', () => {
|
|
4
|
+
describe('addMark', () => {
|
|
5
|
+
it('creates ADD_MARK action', () => {
|
|
6
|
+
const action = addMark();
|
|
7
|
+
expect(action).toEqual({
|
|
8
|
+
type: 'ADD_MARK',
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('creates action without parameters', () => {
|
|
13
|
+
const action = addMark();
|
|
14
|
+
expect(action.type).toBe('ADD_MARK');
|
|
15
|
+
expect(Object.keys(action)).toEqual(['type']);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('changeMarks', () => {
|
|
20
|
+
it('creates CHANGE_MARKS action with marks', () => {
|
|
21
|
+
const marks = [{ id: 1, type: 'point' }];
|
|
22
|
+
const action = changeMarks(marks);
|
|
23
|
+
|
|
24
|
+
expect(action).toEqual({
|
|
25
|
+
type: 'CHANGE_MARKS',
|
|
26
|
+
marks,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('creates action with empty marks array', () => {
|
|
31
|
+
const marks = [];
|
|
32
|
+
const action = changeMarks(marks);
|
|
33
|
+
|
|
34
|
+
expect(action).toEqual({
|
|
35
|
+
type: 'CHANGE_MARKS',
|
|
36
|
+
marks: [],
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('creates action with multiple marks', () => {
|
|
41
|
+
const marks = [
|
|
42
|
+
{ id: 1, type: 'point', x: 1, y: 2 },
|
|
43
|
+
{ id: 2, type: 'line', from: { x: 0, y: 0 }, to: { x: 5, y: 5 } },
|
|
44
|
+
];
|
|
45
|
+
const action = changeMarks(marks);
|
|
46
|
+
|
|
47
|
+
expect(action.marks).toEqual(marks);
|
|
48
|
+
expect(action.marks.length).toBe(2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('creates action with null marks', () => {
|
|
52
|
+
const marks = null;
|
|
53
|
+
const action = changeMarks(marks);
|
|
54
|
+
|
|
55
|
+
expect(action).toEqual({
|
|
56
|
+
type: 'CHANGE_MARKS',
|
|
57
|
+
marks: null,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('creates action with undefined marks', () => {
|
|
62
|
+
const marks = undefined;
|
|
63
|
+
const action = changeMarks(marks);
|
|
64
|
+
|
|
65
|
+
expect(action).toEqual({
|
|
66
|
+
type: 'CHANGE_MARKS',
|
|
67
|
+
marks: undefined,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('preserves mark properties', () => {
|
|
72
|
+
const marks = [
|
|
73
|
+
{
|
|
74
|
+
id: 1,
|
|
75
|
+
type: 'point',
|
|
76
|
+
x: 10,
|
|
77
|
+
y: 20,
|
|
78
|
+
label: 'A',
|
|
79
|
+
correctness: { value: 'correct' },
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
const action = changeMarks(marks);
|
|
83
|
+
|
|
84
|
+
expect(action.marks[0]).toEqual(marks[0]);
|
|
85
|
+
expect(action.marks[0].correctness).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('action types', () => {
|
|
90
|
+
it('has consistent action type strings', () => {
|
|
91
|
+
const addMarkAction = addMark();
|
|
92
|
+
const changeMarksAction = changeMarks([]);
|
|
93
|
+
|
|
94
|
+
expect(typeof addMarkAction.type).toBe('string');
|
|
95
|
+
expect(typeof changeMarksAction.type).toBe('string');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('has unique action types', () => {
|
|
99
|
+
const addMarkAction = addMark();
|
|
100
|
+
const changeMarksAction = changeMarks([]);
|
|
101
|
+
|
|
102
|
+
expect(addMarkAction.type).not.toBe(changeMarksAction.type);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, waitFor } from '@pie-lib/test-utils';
|
|
3
|
+
import Root, { GraphContainer } from '../index';
|
|
4
|
+
import { changeMarks } from '../actions';
|
|
5
|
+
|
|
6
|
+
jest.mock('../../graph-with-controls', () => {
|
|
7
|
+
return function GraphWithControls({ marks, onChangeMarks, onUndo, onRedo, onReset, disabled }) {
|
|
8
|
+
return (
|
|
9
|
+
<div data-testid="graph-with-controls">
|
|
10
|
+
<div data-testid="marks-count">{marks?.length || 0}</div>
|
|
11
|
+
{disabled && <div data-testid="disabled-indicator">Disabled</div>}
|
|
12
|
+
<button data-testid="change-marks" onClick={() => onChangeMarks && onChangeMarks([{ id: 1 }])}>
|
|
13
|
+
Change
|
|
14
|
+
</button>
|
|
15
|
+
<button data-testid="undo" onClick={onUndo}>
|
|
16
|
+
Undo
|
|
17
|
+
</button>
|
|
18
|
+
<button data-testid="redo" onClick={onRedo}>
|
|
19
|
+
Redo
|
|
20
|
+
</button>
|
|
21
|
+
<button data-testid="reset" onClick={onReset}>
|
|
22
|
+
Reset
|
|
23
|
+
</button>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
jest.mock('@pie-lib/drag', () => ({
|
|
30
|
+
DragProvider: ({ children }) => <div data-testid="drag-provider">{children}</div>,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
describe('Container Root Component', () => {
|
|
34
|
+
const defaultProps = {
|
|
35
|
+
marks: [],
|
|
36
|
+
onChangeMarks: jest.fn(),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const renderComponent = (props = {}) => {
|
|
40
|
+
return render(<Root {...defaultProps} {...props} />);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe('rendering', () => {
|
|
44
|
+
it('renders without crashing', () => {
|
|
45
|
+
const { container } = renderComponent();
|
|
46
|
+
expect(container).toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders GraphWithControls', () => {
|
|
50
|
+
const { getByTestId } = renderComponent();
|
|
51
|
+
expect(getByTestId('graph-with-controls')).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders with Provider when no correctness set', () => {
|
|
55
|
+
const { container } = renderComponent({ marks: [{ id: 1, type: 'point' }] });
|
|
56
|
+
expect(container.querySelector('[data-testid="graph-with-controls"]')).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('renders directly when correctness is set', () => {
|
|
60
|
+
const marks = [{ id: 1, type: 'point', correctness: { value: 'correct' } }];
|
|
61
|
+
const { getByTestId } = renderComponent({ marks });
|
|
62
|
+
|
|
63
|
+
expect(getByTestId('graph-with-controls')).toBeInTheDocument();
|
|
64
|
+
expect(getByTestId('disabled-indicator')).toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('passes marks to GraphWithControls', () => {
|
|
68
|
+
const marks = [{ id: 1 }, { id: 2 }];
|
|
69
|
+
const { getByTestId } = renderComponent({ marks });
|
|
70
|
+
expect(getByTestId('marks-count')).toHaveTextContent('2');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('Redux store', () => {
|
|
75
|
+
it('creates Redux store on mount', () => {
|
|
76
|
+
const component = new Root({ marks: [], onChangeMarks: jest.fn() });
|
|
77
|
+
expect(component.store).toBeDefined();
|
|
78
|
+
expect(typeof component.store.getState).toBe('function');
|
|
79
|
+
expect(typeof component.store.dispatch).toBe('function');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('initializes store with marks from props', () => {
|
|
83
|
+
const marks = [{ id: 1, type: 'point' }];
|
|
84
|
+
const component = new Root({ marks, onChangeMarks: jest.fn() });
|
|
85
|
+
|
|
86
|
+
const state = component.store.getState();
|
|
87
|
+
expect(state.marks.present).toEqual(marks);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('initializes store with empty marks', () => {
|
|
91
|
+
const component = new Root({ marks: [], onChangeMarks: jest.fn() });
|
|
92
|
+
|
|
93
|
+
const state = component.store.getState();
|
|
94
|
+
expect(state.marks.present).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('subscribes to store changes', async () => {
|
|
98
|
+
const onChangeMarks = jest.fn();
|
|
99
|
+
const component = new Root({ marks: [], onChangeMarks });
|
|
100
|
+
|
|
101
|
+
const newMarks = [{ id: 1 }];
|
|
102
|
+
component.store.dispatch(changeMarks(newMarks));
|
|
103
|
+
|
|
104
|
+
await waitFor(() => {
|
|
105
|
+
expect(onChangeMarks).toHaveBeenCalledWith(newMarks);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('componentDidUpdate', () => {
|
|
111
|
+
it('updates store when marks prop changes', () => {
|
|
112
|
+
const onChangeMarks = jest.fn();
|
|
113
|
+
const { rerender } = render(<Root marks={[]} onChangeMarks={onChangeMarks} />);
|
|
114
|
+
|
|
115
|
+
const newMarks = [{ id: 1, type: 'point' }];
|
|
116
|
+
rerender(<Root marks={newMarks} onChangeMarks={onChangeMarks} />);
|
|
117
|
+
|
|
118
|
+
expect(onChangeMarks).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('does not update when marks are equal', () => {
|
|
122
|
+
const marks = [{ id: 1, type: 'point' }];
|
|
123
|
+
const onChangeMarks = jest.fn();
|
|
124
|
+
const { rerender } = render(<Root marks={marks} onChangeMarks={onChangeMarks} />);
|
|
125
|
+
|
|
126
|
+
onChangeMarks.mockClear();
|
|
127
|
+
|
|
128
|
+
rerender(<Root marks={marks} onChangeMarks={onChangeMarks} />);
|
|
129
|
+
|
|
130
|
+
expect(onChangeMarks).not.toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('handles marks changing from empty to non-empty', () => {
|
|
134
|
+
const onChangeMarks = jest.fn();
|
|
135
|
+
const { rerender } = render(<Root marks={[]} onChangeMarks={onChangeMarks} />);
|
|
136
|
+
|
|
137
|
+
const newMarks = [{ id: 1 }];
|
|
138
|
+
|
|
139
|
+
expect(() => {
|
|
140
|
+
rerender(<Root marks={newMarks} onChangeMarks={onChangeMarks} />);
|
|
141
|
+
}).not.toThrow();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('handles marks changing from non-empty to empty', () => {
|
|
145
|
+
const onChangeMarks = jest.fn();
|
|
146
|
+
const { rerender } = render(<Root marks={[{ id: 1 }]} onChangeMarks={onChangeMarks} />);
|
|
147
|
+
|
|
148
|
+
expect(() => {
|
|
149
|
+
rerender(<Root marks={[]} onChangeMarks={onChangeMarks} />);
|
|
150
|
+
}).not.toThrow();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('onStoreChange', () => {
|
|
155
|
+
it('calls onChangeMarks when store state changes', async () => {
|
|
156
|
+
const onChangeMarks = jest.fn();
|
|
157
|
+
const component = new Root({ marks: [], onChangeMarks });
|
|
158
|
+
|
|
159
|
+
const newMarks = [{ id: 1, type: 'point' }];
|
|
160
|
+
component.store.dispatch(changeMarks(newMarks));
|
|
161
|
+
|
|
162
|
+
await waitFor(() => {
|
|
163
|
+
expect(onChangeMarks).toHaveBeenCalledWith(newMarks);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('does not call onChangeMarks when marks are equal', () => {
|
|
168
|
+
const marks = [{ id: 1, type: 'point' }];
|
|
169
|
+
const onChangeMarks = jest.fn();
|
|
170
|
+
const component = new Root({ marks, onChangeMarks });
|
|
171
|
+
|
|
172
|
+
onChangeMarks.mockClear();
|
|
173
|
+
|
|
174
|
+
component.store.dispatch(changeMarks(marks));
|
|
175
|
+
|
|
176
|
+
expect(onChangeMarks).not.toHaveBeenCalled();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('handles multiple store changes', async () => {
|
|
180
|
+
const onChangeMarks = jest.fn();
|
|
181
|
+
const component = new Root({ marks: [], onChangeMarks });
|
|
182
|
+
|
|
183
|
+
component.store.dispatch(changeMarks([{ id: 1 }]));
|
|
184
|
+
component.store.dispatch(changeMarks([{ id: 2 }]));
|
|
185
|
+
component.store.dispatch(changeMarks([{ id: 3 }]));
|
|
186
|
+
|
|
187
|
+
await waitFor(() => {
|
|
188
|
+
expect(onChangeMarks).toHaveBeenCalledTimes(3);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('correctness handling', () => {
|
|
194
|
+
it('disables when any mark has correctness', () => {
|
|
195
|
+
const marks = [
|
|
196
|
+
{ id: 1, type: 'point' },
|
|
197
|
+
{ id: 2, type: 'point', correctness: { value: 'correct' } },
|
|
198
|
+
];
|
|
199
|
+
const { getByTestId } = renderComponent({ marks });
|
|
200
|
+
|
|
201
|
+
expect(getByTestId('disabled-indicator')).toBeInTheDocument();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('renders without Provider when correctness is set', () => {
|
|
205
|
+
const marks = [{ id: 1, correctness: { value: 'correct' } }];
|
|
206
|
+
const { container } = renderComponent({ marks });
|
|
207
|
+
|
|
208
|
+
expect(container.querySelector('[data-testid="graph-with-controls"]')).toBeInTheDocument();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('does not disable when no correctness is set', () => {
|
|
212
|
+
const marks = [
|
|
213
|
+
{ id: 1, type: 'point' },
|
|
214
|
+
{ id: 2, type: 'line' },
|
|
215
|
+
];
|
|
216
|
+
const { queryByTestId } = renderComponent({ marks });
|
|
217
|
+
|
|
218
|
+
expect(queryByTestId('disabled-indicator')).not.toBeInTheDocument();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('handles correctness with different values', () => {
|
|
222
|
+
const marks = [{ id: 1, correctness: { value: 'incorrect' } }];
|
|
223
|
+
const { getByTestId } = renderComponent({ marks });
|
|
224
|
+
|
|
225
|
+
expect(getByTestId('disabled-indicator')).toBeInTheDocument();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('handles empty correctness object', () => {
|
|
229
|
+
const marks = [{ id: 1, correctness: {} }];
|
|
230
|
+
const { getByTestId } = renderComponent({ marks });
|
|
231
|
+
|
|
232
|
+
expect(getByTestId('disabled-indicator')).toBeInTheDocument();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('props forwarding', () => {
|
|
237
|
+
it('forwards additional props to GraphWithControls', () => {
|
|
238
|
+
const { container } = renderComponent({
|
|
239
|
+
className: 'custom-class',
|
|
240
|
+
size: { width: 500, height: 500 },
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
expect(container.querySelector('[data-testid="graph-with-controls"]')).toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('does not forward onChangeMarks and marks to GraphWithControls', () => {
|
|
247
|
+
const { getByTestId } = renderComponent({
|
|
248
|
+
marks: [{ id: 1 }],
|
|
249
|
+
onChangeMarks: jest.fn(),
|
|
250
|
+
otherProp: 'test',
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
expect(getByTestId('graph-with-controls')).toBeInTheDocument();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('edge cases', () => {
|
|
258
|
+
it('handles empty marks array', () => {
|
|
259
|
+
const { container } = renderComponent({ marks: [] });
|
|
260
|
+
expect(container).toBeInTheDocument();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('handles marks with complex structure', () => {
|
|
264
|
+
const marks = [
|
|
265
|
+
{
|
|
266
|
+
id: 1,
|
|
267
|
+
type: 'line',
|
|
268
|
+
from: { x: 0, y: 0 },
|
|
269
|
+
to: { x: 10, y: 10 },
|
|
270
|
+
label: 'Line A',
|
|
271
|
+
correctness: { value: 'correct', label: 'Correct!' },
|
|
272
|
+
},
|
|
273
|
+
];
|
|
274
|
+
const { getByTestId } = renderComponent({ marks });
|
|
275
|
+
expect(getByTestId('graph-with-controls')).toBeInTheDocument();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('handles switching between correctness states', () => {
|
|
279
|
+
const { rerender } = render(<Root marks={[{ id: 1 }]} onChangeMarks={jest.fn()} />);
|
|
280
|
+
|
|
281
|
+
rerender(<Root marks={[{ id: 1, correctness: { value: 'correct' } }]} onChangeMarks={jest.fn()} />);
|
|
282
|
+
|
|
283
|
+
rerender(<Root marks={[{ id: 1 }]} onChangeMarks={jest.fn()} />);
|
|
284
|
+
|
|
285
|
+
expect(document.querySelector('[data-testid="graph-with-controls"]')).toBeInTheDocument();
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('store synchronization', () => {
|
|
290
|
+
it('keeps store and props in sync', async () => {
|
|
291
|
+
const onChangeMarks = jest.fn();
|
|
292
|
+
const component = new Root({ marks: [], onChangeMarks });
|
|
293
|
+
|
|
294
|
+
const newMarks = [{ id: 1 }];
|
|
295
|
+
component.store.dispatch(changeMarks(newMarks));
|
|
296
|
+
|
|
297
|
+
await waitFor(() => {
|
|
298
|
+
const state = component.store.getState();
|
|
299
|
+
expect(state.marks.present).toEqual(newMarks);
|
|
300
|
+
expect(onChangeMarks).toHaveBeenCalledWith(newMarks);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('GraphContainer (connected component)', () => {
|
|
307
|
+
it('is a connected component', () => {
|
|
308
|
+
expect(GraphContainer).toBeDefined();
|
|
309
|
+
expect(GraphContainer.displayName).toContain('Connect');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('maps state to props', () => {
|
|
313
|
+
expect(GraphContainer).toBeDefined();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('maps dispatch to props', () => {
|
|
317
|
+
expect(GraphContainer).toBeDefined();
|
|
318
|
+
});
|
|
319
|
+
});
|