@pie-lib/graphing 4.0.23 → 4.0.24-beta.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.
- package/CHANGELOG.md +0 -92
- package/lib/mark-label.js +7 -197
- package/lib/mark-label.js.map +1 -1
- package/lib/tools/circle/component.js +3 -34
- package/lib/tools/circle/component.js.map +1 -1
- package/lib/tools/point/component.js +8 -29
- package/lib/tools/point/component.js.map +1 -1
- package/lib/tools/polygon/component.js +6 -31
- package/lib/tools/polygon/component.js.map +1 -1
- package/lib/tools/shared/line/index.js +3 -34
- package/lib/tools/shared/line/index.js.map +1 -1
- package/package.json +17 -17
- package/src/mark-label.jsx +8 -209
- package/src/tools/circle/component.jsx +3 -26
- package/src/tools/point/component.jsx +8 -21
- package/src/tools/polygon/component.jsx +6 -24
- package/src/tools/shared/line/index.jsx +3 -26
- package/LICENSE.md +0 -5
- package/src/__tests__/bg.test.jsx +0 -250
- package/src/__tests__/coordinates-label.test.jsx +0 -243
- package/src/__tests__/graph-with-controls.test.jsx +0 -198
- package/src/__tests__/graph.test.jsx +0 -796
- package/src/__tests__/grid-setup.test.jsx +0 -645
- package/src/__tests__/grid.test.jsx +0 -22
- package/src/__tests__/key-legend.test.jsx +0 -260
- package/src/__tests__/label-svg-icon.test.jsx +0 -278
- package/src/__tests__/labels.test.jsx +0 -55
- package/src/__tests__/mark-label.test.jsx +0 -437
- package/src/__tests__/toggle-bar.test.jsx +0 -146
- package/src/__tests__/tool-menu.test.jsx +0 -115
- package/src/__tests__/undo-redo.test.jsx +0 -24
- package/src/__tests__/use-debounce.test.js +0 -21
- package/src/__tests__/utils.js +0 -41
- package/src/__tests__/utils.test.js +0 -105
- package/src/axis/__tests__/arrow.test.jsx +0 -38
- package/src/axis/__tests__/axes.test.jsx +0 -216
- package/src/container/__tests__/actions.test.js +0 -105
- package/src/container/__tests__/index.test.jsx +0 -319
- package/src/container/__tests__/marks.test.js +0 -172
- package/src/container/__tests__/middleware.test.js +0 -235
- package/src/container/__tests__/reducer.test.js +0 -324
- package/src/tools/absolute/__tests__/component.test.jsx +0 -53
- package/src/tools/circle/__tests__/bg-circle.test.jsx +0 -26
- package/src/tools/circle/__tests__/component.test.jsx +0 -494
- package/src/tools/circle/__tests__/index.test.js +0 -480
- package/src/tools/exponential/__tests__/component.test.jsx +0 -53
- package/src/tools/exponential/__tests__/index.test.js +0 -729
- package/src/tools/line/__tests__/component.test.jsx +0 -37
- package/src/tools/parabola/__tests__/component.test.jsx +0 -48
- package/src/tools/parabola/__tests__/index.test.js +0 -470
- package/src/tools/point/__tests__/component.test.jsx +0 -475
- package/src/tools/point/__tests__/index.test.js +0 -241
- package/src/tools/polygon/__tests__/component.test.jsx +0 -471
- package/src/tools/polygon/__tests__/index.test.js +0 -294
- package/src/tools/polygon/__tests__/line.test.jsx +0 -35
- package/src/tools/polygon/__tests__/polygon.test.jsx +0 -61
- package/src/tools/ray/__tests__/component.test.jsx +0 -29
- package/src/tools/segment/__tests__/component.test.jsx +0 -28
- package/src/tools/shared/__tests__/arrow-head.test.jsx +0 -31
- package/src/tools/shared/line/__tests__/index.test.jsx +0 -109
- package/src/tools/shared/line/__tests__/line-path.test.jsx +0 -53
- package/src/tools/shared/line/__tests__/with-root-edge.test.jsx +0 -73
- package/src/tools/shared/point/__tests__/arrow-point.test.jsx +0 -91
- package/src/tools/shared/point/__tests__/base-point.test.jsx +0 -87
- package/src/tools/sine/__tests__/component.test.jsx +0 -51
- package/src/tools/vector/__tests__/component.test.jsx +0 -25
|
@@ -1,437 +0,0 @@
|
|
|
1
|
-
import { act, fireEvent, render } from '@pie-lib/test-utils';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { coordinates, MarkLabel, position } from '../mark-label';
|
|
4
|
-
import { graphProps as getGraphProps } from './utils';
|
|
5
|
-
|
|
6
|
-
describe('MarkLabel', () => {
|
|
7
|
-
let onChange = jest.fn();
|
|
8
|
-
let inputRef = jest.fn();
|
|
9
|
-
const renderComponent = (extras) => {
|
|
10
|
-
const defaults = {
|
|
11
|
-
classes: {},
|
|
12
|
-
className: 'className',
|
|
13
|
-
onChange,
|
|
14
|
-
inputRef,
|
|
15
|
-
mark: { x: 1, y: 1 },
|
|
16
|
-
graphProps: getGraphProps(0, 10, 0, 10),
|
|
17
|
-
};
|
|
18
|
-
const props = { ...defaults, ...extras };
|
|
19
|
-
return render(<MarkLabel {...props} />);
|
|
20
|
-
};
|
|
21
|
-
describe('rendering', () => {
|
|
22
|
-
it('renders without crashing', () => {
|
|
23
|
-
const { container } = renderComponent();
|
|
24
|
-
expect(container.firstChild).toBeInTheDocument();
|
|
25
|
-
});
|
|
26
|
-
it('renders with different mark position', () => {
|
|
27
|
-
const { container } = renderComponent({ mark: { x: 10, y: 10 } });
|
|
28
|
-
expect(container.firstChild).toBeInTheDocument();
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('MarkLabel - editing', () => {
|
|
34
|
-
let onChange;
|
|
35
|
-
let onBlur;
|
|
36
|
-
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
onChange = jest.fn();
|
|
39
|
-
onBlur = jest.fn();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// the label input is the only input MarkLabel renders (react-input-autosize)
|
|
43
|
-
const input = (container) => container.querySelector('input');
|
|
44
|
-
|
|
45
|
-
const renderComponent = (extras) => {
|
|
46
|
-
const defaults = {
|
|
47
|
-
onChange,
|
|
48
|
-
onBlur,
|
|
49
|
-
inputRef: jest.fn(),
|
|
50
|
-
mark: { x: 1, y: 1, label: '' },
|
|
51
|
-
graphProps: getGraphProps(0, 10, 0, 10),
|
|
52
|
-
};
|
|
53
|
-
const props = { ...defaults, ...extras };
|
|
54
|
-
const result = render(<MarkLabel {...props} />);
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
...result,
|
|
58
|
-
input: input(result.container),
|
|
59
|
-
rerenderWith: (next) => result.rerender(<MarkLabel {...props} {...next} />),
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
describe('autoFocus', () => {
|
|
64
|
-
it('focuses the input so a new label can be typed into right away', () => {
|
|
65
|
-
const { input } = renderComponent({ autoFocus: true });
|
|
66
|
-
|
|
67
|
-
expect(document.activeElement).toBe(input);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('leaves the caret at the end of an existing label', () => {
|
|
71
|
-
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'AB' } });
|
|
72
|
-
|
|
73
|
-
expect(document.activeElement).toBe(input);
|
|
74
|
-
expect(input.selectionStart).toBe(2);
|
|
75
|
-
expect(input.selectionEnd).toBe(2);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('does not focus the input of an existing label when autoFocus is not set', () => {
|
|
79
|
-
const { input } = renderComponent({ mark: { x: 1, y: 1, label: 'A' } });
|
|
80
|
-
|
|
81
|
-
expect(document.activeElement).not.toBe(input);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('does not focus the input when disabled', () => {
|
|
85
|
-
const { input } = renderComponent({ autoFocus: true, disabled: true, mark: { x: 1, y: 1, label: 'A' } });
|
|
86
|
-
|
|
87
|
-
expect(document.activeElement).not.toBe(input);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('does not focus the input of a disabled mark', () => {
|
|
91
|
-
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A', disabled: true } });
|
|
92
|
-
|
|
93
|
-
expect(document.activeElement).not.toBe(input);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// the focus can go away without this component hearing about it - the input node gets replaced
|
|
97
|
-
// by a render higher up, so no blur is fired and autoFocus is still on
|
|
98
|
-
it('takes the focus back on the next render while it is still being edited', () => {
|
|
99
|
-
const { input, rerenderWith } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A' } });
|
|
100
|
-
|
|
101
|
-
expect(document.activeElement).toBe(input);
|
|
102
|
-
|
|
103
|
-
input.blur();
|
|
104
|
-
expect(document.activeElement).not.toBe(input);
|
|
105
|
-
|
|
106
|
-
rerenderWith({ mark: { x: 1, y: 1, label: 'A' } });
|
|
107
|
-
|
|
108
|
-
expect(document.activeElement).toBe(input);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('leaves the caret where the user put it while the input has the focus', () => {
|
|
112
|
-
const { input, rerenderWith } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'ABC' } });
|
|
113
|
-
|
|
114
|
-
input.setSelectionRange(1, 1);
|
|
115
|
-
|
|
116
|
-
rerenderWith({ mark: { x: 1, y: 1, label: 'ABC' } });
|
|
117
|
-
|
|
118
|
-
expect(document.activeElement).toBe(input);
|
|
119
|
-
expect(input.selectionStart).toBe(1);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('focuses the input once it is no longer disabled', () => {
|
|
123
|
-
const { rerenderWith, container } = renderComponent({ autoFocus: true, disabled: true });
|
|
124
|
-
|
|
125
|
-
rerenderWith({ disabled: false });
|
|
126
|
-
|
|
127
|
-
expect(document.activeElement).toBe(input(container));
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// the host saves the model with an api call and the model that comes back can replace the
|
|
131
|
-
// input node - the label has to keep the focus across that remount
|
|
132
|
-
it('re-focuses the input when it is remounted while being edited', () => {
|
|
133
|
-
const props = {
|
|
134
|
-
autoFocus: true,
|
|
135
|
-
onChange,
|
|
136
|
-
inputRef: jest.fn(),
|
|
137
|
-
mark: { x: 1, y: 1, label: 'A' },
|
|
138
|
-
graphProps: getGraphProps(0, 10, 0, 10),
|
|
139
|
-
};
|
|
140
|
-
const { container, rerender } = render(<MarkLabel key="first" {...props} />);
|
|
141
|
-
const first = input(container);
|
|
142
|
-
|
|
143
|
-
expect(document.activeElement).toBe(first);
|
|
144
|
-
|
|
145
|
-
rerender(<MarkLabel key="second" {...props} />);
|
|
146
|
-
const second = input(container);
|
|
147
|
-
|
|
148
|
-
expect(second).not.toBe(first);
|
|
149
|
-
expect(document.activeElement).toBe(second);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// the tool asks for the focus with autoFocus, but its state can be gone before the input renders:
|
|
154
|
-
// the marks come back from the host asynchronously and can remount the tool. An empty label in
|
|
155
|
-
// label mode is a label the user has just added, so the input can tell on its own.
|
|
156
|
-
describe('a label that was just added', () => {
|
|
157
|
-
it('focuses itself without the tool asking', () => {
|
|
158
|
-
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
159
|
-
|
|
160
|
-
expect(document.activeElement).toBe(input);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('focuses itself again when it is remounted before it was typed into', () => {
|
|
164
|
-
const props = {
|
|
165
|
-
onChange,
|
|
166
|
-
onBlur,
|
|
167
|
-
inputRef: jest.fn(),
|
|
168
|
-
mark: { x: 1, y: 1, label: '' },
|
|
169
|
-
graphProps: getGraphProps(0, 10, 0, 10),
|
|
170
|
-
};
|
|
171
|
-
const { container, rerender } = render(<MarkLabel key="first" {...props} />);
|
|
172
|
-
const first = input(container);
|
|
173
|
-
|
|
174
|
-
rerender(<MarkLabel key="second" {...props} />);
|
|
175
|
-
const second = input(container);
|
|
176
|
-
|
|
177
|
-
expect(second).not.toBe(first);
|
|
178
|
-
expect(document.activeElement).toBe(second);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('stops chasing the focus once it has been left', () => {
|
|
182
|
-
const { input, rerenderWith } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
183
|
-
|
|
184
|
-
expect(document.activeElement).toBe(input);
|
|
185
|
-
|
|
186
|
-
input.blur();
|
|
187
|
-
rerenderWith({ mark: { x: 1, y: 1, label: '' } });
|
|
188
|
-
|
|
189
|
-
expect(document.activeElement).not.toBe(input);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// outside of label mode the input is disabled, which is how an empty label that was loaded with
|
|
193
|
-
// the item is told apart from one the user has just added - label mode always starts off
|
|
194
|
-
// the config draws the background marks into the correct answer graphs as disabled marks
|
|
195
|
-
it('is not rendered at all while it cannot be edited', () => {
|
|
196
|
-
const { container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: '' } });
|
|
197
|
-
|
|
198
|
-
expect(container.querySelector('input')).toBe(null);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('is still rendered uneditable once it has text', () => {
|
|
202
|
-
const { container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: 'A' } });
|
|
203
|
-
|
|
204
|
-
expect(container.querySelector('input').value).toBe('A');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('is not focused when label mode is turned on afterwards', () => {
|
|
208
|
-
const { rerenderWith, container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: '' } });
|
|
209
|
-
|
|
210
|
-
rerenderWith({ disabled: false });
|
|
211
|
-
|
|
212
|
-
expect(input(container)).not.toBe(null);
|
|
213
|
-
expect(document.activeElement).not.toBe(input(container));
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('is not rendered for a disabled mark while it is empty', () => {
|
|
217
|
-
const { container } = renderComponent({ mark: { x: 1, y: 1, label: '', disabled: true } });
|
|
218
|
-
|
|
219
|
-
expect(container.querySelector('input')).toBe(null);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
// an input sized to its content is 2px wide while it is empty, and chrome paints the caret of
|
|
223
|
-
// an input inside an svg foreignObject as a hairline that never blinks - so an empty label that
|
|
224
|
-
// has the focus has to be wide enough to be seen as a box waiting for text
|
|
225
|
-
it('is wide enough to be seen while it is empty', () => {
|
|
226
|
-
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
227
|
-
|
|
228
|
-
expect(parseInt(input.style.width, 10)).toBeGreaterThanOrEqual(30);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('is only as wide as its text once it cannot be edited', () => {
|
|
232
|
-
const { container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: 'A' } });
|
|
233
|
-
|
|
234
|
-
expect(parseInt(container.querySelector('input').style.width, 10)).toBeLessThan(30);
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe('syncing mark.label into the input', () => {
|
|
239
|
-
it('picks up a new mark.label while the input does not have the focus', () => {
|
|
240
|
-
const { rerenderWith, container } = renderComponent({ mark: { x: 1, y: 1, label: 'A' } });
|
|
241
|
-
|
|
242
|
-
rerenderWith({ mark: { x: 1, y: 1, label: 'updated' } });
|
|
243
|
-
|
|
244
|
-
expect(input(container).value).toBe('updated');
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// a mark coming back from an (async) model save can carry an older label - applying it would
|
|
248
|
-
// wipe out the characters typed in the meantime
|
|
249
|
-
it('keeps what the user typed while the input has the focus', () => {
|
|
250
|
-
const { input, rerenderWith } = renderComponent({ autoFocus: true });
|
|
251
|
-
|
|
252
|
-
expect(document.activeElement).toBe(input);
|
|
253
|
-
|
|
254
|
-
fireEvent.change(input, { target: { value: 'AB' } });
|
|
255
|
-
expect(input.value).toBe('AB');
|
|
256
|
-
|
|
257
|
-
// the host echoes back the label as it was one keystroke ago
|
|
258
|
-
rerenderWith({ mark: { x: 1, y: 1, label: 'A' } });
|
|
259
|
-
|
|
260
|
-
expect(input.value).toBe('AB');
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// the characters land at the document's caret, not at the focused node. In the authoring
|
|
265
|
-
// environment the input ends up focused with no caret anywhere: keypress fires and nothing is
|
|
266
|
-
// inserted. jsdom never reports a caret either, so this is that state - the focus cycle that
|
|
267
|
-
// repairs it must not hand the label over to the blur handling.
|
|
268
|
-
describe('a focused input with no caret', () => {
|
|
269
|
-
it('is repaired without reporting the label as removed', () => {
|
|
270
|
-
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: '' } });
|
|
271
|
-
|
|
272
|
-
expect(document.activeElement).toBe(input);
|
|
273
|
-
expect(onChange).not.toHaveBeenCalled();
|
|
274
|
-
expect(onBlur).not.toHaveBeenCalled();
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('leaves the caret at the end of a label that already has text', () => {
|
|
278
|
-
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'AB' } });
|
|
279
|
-
|
|
280
|
-
expect(document.activeElement).toBe(input);
|
|
281
|
-
expect(input.selectionStart).toBe(2);
|
|
282
|
-
expect(onChange).not.toHaveBeenCalled();
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// clicking another window, the devtools, or the host moving the focus while it saves the model:
|
|
287
|
-
// the label is not being left, so it must survive and take the focus back
|
|
288
|
-
describe('while the window is away', () => {
|
|
289
|
-
const windowGoesAway = () => window.dispatchEvent(new Event('blur'));
|
|
290
|
-
const windowComesBack = () => window.dispatchEvent(new Event('focus'));
|
|
291
|
-
|
|
292
|
-
it('keeps an empty label instead of reporting it as removed', () => {
|
|
293
|
-
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
294
|
-
|
|
295
|
-
windowGoesAway();
|
|
296
|
-
fireEvent.blur(input);
|
|
297
|
-
|
|
298
|
-
expect(onChange).not.toHaveBeenCalled();
|
|
299
|
-
expect(onBlur).not.toHaveBeenCalled();
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('takes the focus back when the window comes back', () => {
|
|
303
|
-
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: '' } });
|
|
304
|
-
|
|
305
|
-
// the focus is moved to another input rather than with input.blur(): jsdom fires a window
|
|
306
|
-
// focus event as part of blur(), which is the very thing being tested here
|
|
307
|
-
const elsewhere = document.createElement('input');
|
|
308
|
-
|
|
309
|
-
document.body.appendChild(elsewhere);
|
|
310
|
-
|
|
311
|
-
windowGoesAway();
|
|
312
|
-
elsewhere.focus();
|
|
313
|
-
expect(document.activeElement).toBe(elsewhere);
|
|
314
|
-
|
|
315
|
-
windowComesBack();
|
|
316
|
-
|
|
317
|
-
expect(document.activeElement).toBe(input);
|
|
318
|
-
|
|
319
|
-
elsewhere.remove();
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it('still reports a label the user leaves once the window is back', () => {
|
|
323
|
-
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
324
|
-
|
|
325
|
-
windowGoesAway();
|
|
326
|
-
windowComesBack();
|
|
327
|
-
fireEvent.blur(input);
|
|
328
|
-
|
|
329
|
-
expect(onChange).toHaveBeenCalledWith('');
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
describe('onBlur', () => {
|
|
334
|
-
it('is called so the tool stops auto focusing the label', () => {
|
|
335
|
-
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A' } });
|
|
336
|
-
|
|
337
|
-
fireEvent.blur(input);
|
|
338
|
-
|
|
339
|
-
expect(onBlur).toHaveBeenCalled();
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
// clicking a button or anything else interactive blurs the label while the debounce is pending
|
|
343
|
-
it('saves what was typed straight away', () => {
|
|
344
|
-
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
345
|
-
|
|
346
|
-
fireEvent.change(input, { target: { value: 'AB' } });
|
|
347
|
-
fireEvent.blur(input);
|
|
348
|
-
|
|
349
|
-
expect(onChange).toHaveBeenCalledWith('AB');
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('does not save it twice when the debounce catches up', () => {
|
|
353
|
-
jest.useFakeTimers();
|
|
354
|
-
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
355
|
-
|
|
356
|
-
fireEvent.change(input, { target: { value: 'AB' } });
|
|
357
|
-
fireEvent.blur(input);
|
|
358
|
-
act(() => jest.advanceTimersByTime(1000));
|
|
359
|
-
|
|
360
|
-
expect(onChange).toHaveBeenCalledTimes(1);
|
|
361
|
-
expect(onChange).toHaveBeenCalledWith('AB');
|
|
362
|
-
jest.useRealTimers();
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
it('reports an empty label on blur', () => {
|
|
366
|
-
const { input } = renderComponent({ mark: { x: 1, y: 1, label: 'A' } });
|
|
367
|
-
|
|
368
|
-
fireEvent.change(input, { target: { value: '' } });
|
|
369
|
-
fireEvent.blur(input);
|
|
370
|
-
|
|
371
|
-
expect(onChange).toHaveBeenCalledWith('');
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
describe('debounce', () => {
|
|
376
|
-
beforeEach(() => jest.useFakeTimers());
|
|
377
|
-
afterEach(() => jest.useRealTimers());
|
|
378
|
-
|
|
379
|
-
it('does not report the typed label before the debounce delay elapses', () => {
|
|
380
|
-
const { input } = renderComponent({ autoFocus: true });
|
|
381
|
-
|
|
382
|
-
fireEvent.change(input, { target: { value: 'A' } });
|
|
383
|
-
|
|
384
|
-
act(() => jest.advanceTimersByTime(499));
|
|
385
|
-
expect(onChange).not.toHaveBeenCalled();
|
|
386
|
-
|
|
387
|
-
act(() => jest.advanceTimersByTime(1));
|
|
388
|
-
expect(onChange).toHaveBeenCalledWith('A');
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
it('reports the label once for a burst of keystrokes', () => {
|
|
392
|
-
const { input } = renderComponent({ autoFocus: true });
|
|
393
|
-
|
|
394
|
-
fireEvent.change(input, { target: { value: 'A' } });
|
|
395
|
-
act(() => jest.advanceTimersByTime(300));
|
|
396
|
-
fireEvent.change(input, { target: { value: 'AB' } });
|
|
397
|
-
act(() => jest.advanceTimersByTime(300));
|
|
398
|
-
fireEvent.change(input, { target: { value: 'ABC' } });
|
|
399
|
-
act(() => jest.advanceTimersByTime(500));
|
|
400
|
-
|
|
401
|
-
expect(onChange).toHaveBeenCalledTimes(1);
|
|
402
|
-
expect(onChange).toHaveBeenCalledWith('ABC');
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
describe('position', () => {
|
|
408
|
-
const assertPosition = (mark, rect, expected) => {
|
|
409
|
-
it(`${mark.x},${mark.y} + ${rect.width},${rect.height} => ${expected}`, () => {
|
|
410
|
-
// we set range.min to a value because in pixels - the greater the Y the lower down on the screen.
|
|
411
|
-
const graphProps = getGraphProps(0, 12, 12, 0);
|
|
412
|
-
const result = position(graphProps, mark, rect);
|
|
413
|
-
expect(result).toEqual(expected);
|
|
414
|
-
});
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
assertPosition({ x: 0, y: 0 }, { width: 10, height: 10 }, 'top-left');
|
|
418
|
-
assertPosition({ x: 0, y: 0 }, { width: 1, height: 1 }, 'bottom-right');
|
|
419
|
-
assertPosition({ x: 0, y: 0 }, { width: 10, height: 0 }, 'bottom-left');
|
|
420
|
-
assertPosition({ x: 0, y: 0 }, { width: 0, height: 10 }, 'top-right');
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
describe('coordinates', () => {
|
|
424
|
-
const assertCoordinates = (mark, rect, pos, expected) => {
|
|
425
|
-
it(`${mark.x}, ${mark.y} -> ${pos} = ${expected.left}, ${expected.top}`, () => {
|
|
426
|
-
const result = coordinates(getGraphProps(), mark, rect, pos);
|
|
427
|
-
expect(result).toEqual(expected);
|
|
428
|
-
});
|
|
429
|
-
};
|
|
430
|
-
assertCoordinates({ x: 0, y: 0 }, { width: 0, height: 0 }, 'top-left', { left: -5, top: -5 });
|
|
431
|
-
assertCoordinates({ x: 0, y: 0 }, { width: 0, height: 0 }, 'bottom-left', { left: -5, top: 5 });
|
|
432
|
-
assertCoordinates({ x: 0, y: 0 }, { width: 0, height: 0 }, 'top-right', { left: 5, top: -5 });
|
|
433
|
-
assertCoordinates({ x: 0, y: 0 }, { width: 0, height: 0 }, 'bottom-right', {
|
|
434
|
-
left: 5,
|
|
435
|
-
top: 5,
|
|
436
|
-
});
|
|
437
|
-
});
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@pie-lib/test-utils';
|
|
2
|
-
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
|
|
5
|
-
import { ToggleBar } from '../toggle-bar';
|
|
6
|
-
|
|
7
|
-
jest.mock('@pie-lib/drag', () => ({
|
|
8
|
-
DragProvider: ({ children }) => <div data-testid="drag-provider">{children}</div>,
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
jest.mock('@dnd-kit/core', () => ({
|
|
12
|
-
useDraggable: jest.fn(() => ({
|
|
13
|
-
attributes: {
|
|
14
|
-
role: 'button',
|
|
15
|
-
tabIndex: 0,
|
|
16
|
-
},
|
|
17
|
-
listeners: {
|
|
18
|
-
onPointerDown: jest.fn(),
|
|
19
|
-
},
|
|
20
|
-
setNodeRef: jest.fn(),
|
|
21
|
-
transform: null,
|
|
22
|
-
transition: null,
|
|
23
|
-
isDragging: false,
|
|
24
|
-
})),
|
|
25
|
-
useDroppable: jest.fn(() => ({
|
|
26
|
-
setNodeRef: jest.fn(),
|
|
27
|
-
isOver: false,
|
|
28
|
-
active: null,
|
|
29
|
-
})),
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
jest.mock('@dnd-kit/utilities', () => ({
|
|
33
|
-
CSS: {
|
|
34
|
-
Transform: {
|
|
35
|
-
toString: jest.fn((transform) => (transform ? 'translate3d(0, 0, 0)' : '')),
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
}));
|
|
39
|
-
|
|
40
|
-
jest.mock('@dnd-kit/sortable', () => ({
|
|
41
|
-
arrayMove: jest.fn((array, from, to) => {
|
|
42
|
-
const newArray = [...array];
|
|
43
|
-
const [removed] = newArray.splice(from, 1);
|
|
44
|
-
newArray.splice(to, 0, removed);
|
|
45
|
-
return newArray;
|
|
46
|
-
}),
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
|
-
jest.mock('@pie-lib/translator', () => ({
|
|
50
|
-
translator: {
|
|
51
|
-
t: (key) => {
|
|
52
|
-
const parts = key.split('.');
|
|
53
|
-
return parts[parts.length - 1];
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
describe('ToggleBar', () => {
|
|
59
|
-
let onChange = jest.fn();
|
|
60
|
-
let onChangeToolsOrder = jest.fn();
|
|
61
|
-
|
|
62
|
-
beforeEach(() => {
|
|
63
|
-
onChange.mockClear();
|
|
64
|
-
onChangeToolsOrder.mockClear();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const renderComponent = (extras) => {
|
|
68
|
-
const defaults = {
|
|
69
|
-
className: 'className',
|
|
70
|
-
onChange,
|
|
71
|
-
onChangeToolsOrder,
|
|
72
|
-
options: ['point', 'line'],
|
|
73
|
-
language: 'en',
|
|
74
|
-
};
|
|
75
|
-
const props = { ...defaults, ...extras };
|
|
76
|
-
return render(<ToggleBar {...props} />);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
describe('rendering', () => {
|
|
80
|
-
it('renders without crashing', () => {
|
|
81
|
-
const { container } = renderComponent();
|
|
82
|
-
expect(container.firstChild).toBeInTheDocument();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('renders tool buttons for valid tools', () => {
|
|
86
|
-
renderComponent({ options: ['point', 'line', 'circle'] });
|
|
87
|
-
expect(screen.getByText(/point/i)).toBeInTheDocument();
|
|
88
|
-
expect(screen.getByText(/line/i)).toBeInTheDocument();
|
|
89
|
-
expect(screen.getByText(/circle/i)).toBeInTheDocument();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('does not render invalid tool options', () => {
|
|
93
|
-
renderComponent({ options: ['point', 'invalid-tool', 'line'] });
|
|
94
|
-
expect(screen.getByText(/point/i)).toBeInTheDocument();
|
|
95
|
-
expect(screen.getByText(/line/i)).toBeInTheDocument();
|
|
96
|
-
expect(screen.queryByText(/invalid-tool/i)).not.toBeInTheDocument();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('highlights selected tool', () => {
|
|
100
|
-
const { container } = renderComponent({ selectedToolType: 'line' });
|
|
101
|
-
const selectedButton = screen.getByText(/line/i).closest('button');
|
|
102
|
-
expect(selectedButton).toHaveAttribute('value', 'line');
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe('interactions', () => {
|
|
107
|
-
it('calls onChange when tool button is clicked', async () => {
|
|
108
|
-
const user = userEvent.setup();
|
|
109
|
-
renderComponent({ options: ['point', 'line'] });
|
|
110
|
-
|
|
111
|
-
const pointButton = screen.getByText(/point/i).closest('button');
|
|
112
|
-
await user.click(pointButton);
|
|
113
|
-
|
|
114
|
-
expect(onChange).toHaveBeenCalledWith('point');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('calls onChange with selected tool type', async () => {
|
|
118
|
-
const user = userEvent.setup();
|
|
119
|
-
renderComponent({ options: ['point', 'line'], selectedToolType: 'point' });
|
|
120
|
-
|
|
121
|
-
const lineButton = screen.getByText(/line/i).closest('button');
|
|
122
|
-
await user.click(lineButton);
|
|
123
|
-
|
|
124
|
-
expect(onChange).toHaveBeenCalledWith('line');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('disables buttons when disabled prop is true', () => {
|
|
128
|
-
renderComponent({ disabled: true, options: ['point', 'line'] });
|
|
129
|
-
|
|
130
|
-
const pointButton = screen.getByText(/point/i).closest('button');
|
|
131
|
-
const lineButton = screen.getByText(/line/i).closest('button');
|
|
132
|
-
|
|
133
|
-
expect(pointButton).toBeDisabled();
|
|
134
|
-
expect(lineButton).toBeDisabled();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('does not call onChange when disabled', () => {
|
|
138
|
-
renderComponent({ disabled: true, options: ['point'] });
|
|
139
|
-
|
|
140
|
-
const pointButton = screen.getByText(/point/i).closest('button');
|
|
141
|
-
expect(pointButton).toBeDisabled();
|
|
142
|
-
// Note: Cannot test click on disabled button with pointer-events: none
|
|
143
|
-
// The disabled state is verified above
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@pie-lib/test-utils';
|
|
2
|
-
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
|
|
5
|
-
import ToolMenu from '../tool-menu';
|
|
6
|
-
|
|
7
|
-
jest.mock('@pie-lib/drag', () => ({
|
|
8
|
-
DragProvider: ({ children }) => <div data-testid="drag-provider">{children}</div>,
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
jest.mock('@dnd-kit/core', () => ({
|
|
12
|
-
useDraggable: jest.fn(() => ({
|
|
13
|
-
attributes: {
|
|
14
|
-
role: 'button',
|
|
15
|
-
tabIndex: 0,
|
|
16
|
-
},
|
|
17
|
-
listeners: {
|
|
18
|
-
onPointerDown: jest.fn(),
|
|
19
|
-
},
|
|
20
|
-
setNodeRef: jest.fn(),
|
|
21
|
-
transform: null,
|
|
22
|
-
transition: null,
|
|
23
|
-
isDragging: false,
|
|
24
|
-
})),
|
|
25
|
-
useDroppable: jest.fn(() => ({
|
|
26
|
-
setNodeRef: jest.fn(),
|
|
27
|
-
isOver: false,
|
|
28
|
-
active: null,
|
|
29
|
-
})),
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
jest.mock('@dnd-kit/utilities', () => ({
|
|
33
|
-
CSS: {
|
|
34
|
-
Transform: {
|
|
35
|
-
toString: jest.fn((transform) => (transform ? 'translate3d(0, 0, 0)' : '')),
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
}));
|
|
39
|
-
|
|
40
|
-
jest.mock('@dnd-kit/sortable', () => ({
|
|
41
|
-
arrayMove: jest.fn((array, from, to) => {
|
|
42
|
-
const newArray = [...array];
|
|
43
|
-
const [removed] = newArray.splice(from, 1);
|
|
44
|
-
newArray.splice(to, 0, removed);
|
|
45
|
-
return newArray;
|
|
46
|
-
}),
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
|
-
describe('ToolMenu', () => {
|
|
50
|
-
let onChange = jest.fn();
|
|
51
|
-
let onChangeTools = jest.fn();
|
|
52
|
-
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
onChange.mockClear();
|
|
55
|
-
onChangeTools.mockClear();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const renderComponent = (extras) => {
|
|
59
|
-
const defaults = {
|
|
60
|
-
className: 'className',
|
|
61
|
-
onChange,
|
|
62
|
-
onChangeTools,
|
|
63
|
-
currentToolType: 'point',
|
|
64
|
-
toolbarTools: ['point', 'line'],
|
|
65
|
-
language: 'en',
|
|
66
|
-
};
|
|
67
|
-
const props = { ...defaults, ...extras };
|
|
68
|
-
return render(<ToolMenu {...props} />);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
describe('rendering', () => {
|
|
72
|
-
it('renders without crashing', () => {
|
|
73
|
-
const { container } = renderComponent();
|
|
74
|
-
expect(container.firstChild).toBeInTheDocument();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('renders ToggleBar with correct props', () => {
|
|
78
|
-
renderComponent({ toolbarTools: ['point', 'line', 'circle'] });
|
|
79
|
-
expect(screen.getByText(/point/i)).toBeInTheDocument();
|
|
80
|
-
expect(screen.getByText(/line/i)).toBeInTheDocument();
|
|
81
|
-
expect(screen.getByText(/circle/i)).toBeInTheDocument();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('passes currentToolType to ToggleBar', () => {
|
|
85
|
-
renderComponent({ currentToolType: 'line' });
|
|
86
|
-
const selectedButton = screen.getByText(/line/i).closest('button');
|
|
87
|
-
expect(selectedButton).toBeInTheDocument();
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe('interactions', () => {
|
|
92
|
-
it('calls onChange when tool is selected', async () => {
|
|
93
|
-
const user = userEvent.setup();
|
|
94
|
-
renderComponent();
|
|
95
|
-
|
|
96
|
-
const lineButton = screen.getByText(/line/i).closest('button');
|
|
97
|
-
await user.click(lineButton);
|
|
98
|
-
|
|
99
|
-
expect(onChange).toHaveBeenCalledWith('line');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('calls onChangeTools when tools order changes', () => {
|
|
103
|
-
renderComponent({ draggableTools: true });
|
|
104
|
-
// Note: Drag-and-drop testing requires more complex setup with @dnd-kit/test-utils
|
|
105
|
-
// For now, we verify the component renders with draggableTools enabled
|
|
106
|
-
expect(screen.getByText(/point/i)).toBeInTheDocument();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('disables tools when disabled prop is true', () => {
|
|
110
|
-
renderComponent({ disabled: true });
|
|
111
|
-
const pointButton = screen.getByText(/point/i).closest('button');
|
|
112
|
-
expect(pointButton).toBeDisabled();
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
});
|