@pie-lib/plot 2.41.0-mui-update.0 → 3.1.0-next.0

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.
@@ -1,8 +1,13 @@
1
- import { shallow } from 'enzyme';
1
+ import { render, cleanup } from '@testing-library/react';
2
2
  import React from 'react';
3
3
  import { Root } from '../root';
4
4
  import { select, mouse } from 'd3-selection';
5
5
 
6
+ jest.mock('d3-selection', () => ({
7
+ select: jest.fn(),
8
+ mouse: jest.fn(),
9
+ }));
10
+
6
11
  const scaleMock = () => {
7
12
  const fn = jest.fn((n) => n);
8
13
  fn.invert = jest.fn((n) => n);
@@ -34,83 +39,234 @@ const graphProps = () => ({
34
39
  },
35
40
  });
36
41
 
37
- const wrapper = (props) => {
38
- props = {
39
- classes: {},
40
- graphProps: graphProps(),
41
- ...props,
42
- };
42
+ describe('root', () => {
43
+ let mockOn;
44
+ let defaultProps;
43
45
 
44
- return shallow(<Root {...props}>hi</Root>, { disableLifecycleMethods: true });
45
- };
46
+ beforeEach(() => {
47
+ mockOn = jest.fn();
48
+ select.mockReturnValue({
49
+ on: mockOn,
50
+ });
51
+ mouse.mockReturnValue([0, 0]);
46
52
 
47
- jest.mock('d3-selection', () => ({
48
- select: jest.fn(),
49
- mouse: jest.fn(),
50
- }));
53
+ defaultProps = {
54
+ classes: {},
55
+ graphProps: graphProps(),
56
+ };
57
+ });
51
58
 
52
- describe('root', () => {
53
- describe('snapshot', () => {
54
- it('matches', () => {
55
- const w = wrapper();
56
- expect(w).toMatchSnapshot();
57
- });
59
+ afterEach(() => {
60
+ cleanup();
61
+ jest.clearAllMocks();
62
+ });
63
+
64
+ it('renders with children', () => {
65
+ const { container, getByText } = render(
66
+ <Root {...defaultProps}>hi</Root>
67
+ );
68
+ expect(container.firstChild).toBeInTheDocument();
69
+ expect(getByText('hi')).toBeInTheDocument();
58
70
  });
59
71
 
60
72
  describe('logic', () => {
61
73
  describe('mousemove', () => {
62
74
  describe('mount/unmount', () => {
63
- it('adds mousemove listener on compenentDidMount', () => {
64
- const w = wrapper();
65
- const g = {
66
- on: jest.fn(),
67
- };
68
- select.mockReturnValue(g);
69
- w.instance().componentDidMount();
70
- expect(g.on).toHaveBeenCalledWith('mousemove', expect.any(Function));
75
+ it('adds mousemove listener on componentDidMount', () => {
76
+ render(<Root {...defaultProps}>hi</Root>);
77
+
78
+ // Verify that select was called with the g element
79
+ expect(select).toHaveBeenCalled();
80
+
81
+ // Verify that on() was called with 'mousemove' and a function
82
+ expect(mockOn).toHaveBeenCalledWith('mousemove', expect.any(Function));
71
83
  });
72
- it('unsets mousemove listener on componentWillUnmount', () => {
73
- const w = wrapper();
74
- const g = {
75
- on: jest.fn(),
76
- };
77
- select.mockReturnValue(g);
78
- w.instance().componentWillUnmount();
79
- expect(g.on).toHaveBeenCalledWith('mousemove', null);
84
+
85
+ it('removes mousemove listener on componentWillUnmount', () => {
86
+ const { unmount } = render(<Root {...defaultProps}>hi</Root>);
87
+
88
+ // Clear previous calls to isolate unmount behavior
89
+ mockOn.mockClear();
90
+ select.mockClear();
91
+
92
+ unmount();
93
+
94
+ // Verify that select was called during unmount
95
+ expect(select).toHaveBeenCalled();
96
+
97
+ // Verify that on() was called with 'mousemove' and null to remove the listener
98
+ expect(mockOn).toHaveBeenCalledWith('mousemove', null);
80
99
  });
81
100
  });
82
101
 
83
102
  describe('mouseMove function', () => {
84
- let onMouseMove, w, gp;
85
- beforeEach(() => {
86
- onMouseMove = jest.fn();
87
- gp = graphProps();
88
- w = wrapper({
103
+ it('calls mouse with correct arguments', () => {
104
+ const onMouseMove = jest.fn();
105
+ const gp = graphProps();
106
+ const props = {
107
+ ...defaultProps,
89
108
  onMouseMove,
90
109
  graphProps: gp,
110
+ };
111
+
112
+ const mockNode = document.createElement('div');
113
+ const mockSelection = {
114
+ _groups: [[mockNode]],
115
+ node: () => mockNode,
116
+ };
117
+
118
+ // Mock select to return our mockSelection
119
+ select.mockReturnValue({
120
+ ...mockSelection,
121
+ on: (event, handler) => {
122
+ mockOn(event, handler);
123
+ // When 'mousemove' is registered, immediately test it
124
+ if (event === 'mousemove' && handler) {
125
+ mouse.mockReturnValue([10, 20]);
126
+ // Handler is bound with mockSelection as first arg, so call with no args
127
+ handler();
128
+ }
129
+ },
91
130
  });
92
- mouse.mockReturnValue([0, 0]);
93
- const g = { _groups: [[[0, 0]]] };
94
- w.instance().mouseMove(g);
95
- });
96
- it('calls mouse', () => {
97
- expect(mouse).toHaveBeenCalledWith([0, 0]);
98
- });
99
- it('calls, scale.x.invert', () => {
100
- expect(gp.scale.x.invert).toHaveBeenCalledWith(0);
131
+
132
+ render(<Root {...props}>hi</Root>);
133
+
134
+ // Verify mouse was called with the correct node
135
+ expect(mouse).toHaveBeenCalledWith(mockNode);
101
136
  });
102
- it('calls, scale.y.invert', () => {
103
- expect(gp.scale.y.invert).toHaveBeenCalledWith(0);
137
+
138
+ it('calls scale.x.invert and scale.y.invert', () => {
139
+ const onMouseMove = jest.fn();
140
+ const gp = graphProps();
141
+ const props = {
142
+ ...defaultProps,
143
+ onMouseMove,
144
+ graphProps: gp,
145
+ };
146
+
147
+ const mockNode = document.createElement('div');
148
+ const mockSelection = {
149
+ _groups: [[mockNode]],
150
+ node: () => mockNode,
151
+ };
152
+
153
+ select.mockReturnValue({
154
+ ...mockSelection,
155
+ on: (event, handler) => {
156
+ mockOn(event, handler);
157
+ if (event === 'mousemove' && handler) {
158
+ mouse.mockReturnValue([15, 25]);
159
+ handler();
160
+ }
161
+ },
162
+ });
163
+
164
+ render(<Root {...props}>hi</Root>);
165
+
166
+ expect(gp.scale.x.invert).toHaveBeenCalledWith(15);
167
+ expect(gp.scale.y.invert).toHaveBeenCalledWith(25);
104
168
  });
105
- it('calls, snap.x', () => {
106
- expect(gp.snap.x).toHaveBeenCalledWith(0);
169
+
170
+ it('calls snap.x and snap.y with inverted coordinates', () => {
171
+ const onMouseMove = jest.fn();
172
+ const gp = graphProps();
173
+ gp.scale.x.invert = jest.fn().mockReturnValue(5);
174
+ gp.scale.y.invert = jest.fn().mockReturnValue(10);
175
+ const props = {
176
+ ...defaultProps,
177
+ onMouseMove,
178
+ graphProps: gp,
179
+ };
180
+
181
+ const mockNode = document.createElement('div');
182
+ const mockSelection = {
183
+ _groups: [[mockNode]],
184
+ node: () => mockNode,
185
+ };
186
+
187
+ select.mockReturnValue({
188
+ ...mockSelection,
189
+ on: (event, handler) => {
190
+ mockOn(event, handler);
191
+ if (event === 'mousemove' && handler) {
192
+ mouse.mockReturnValue([15, 25]);
193
+ handler();
194
+ }
195
+ },
196
+ });
197
+
198
+ render(<Root {...props}>hi</Root>);
199
+
200
+ expect(gp.snap.x).toHaveBeenCalledWith(5);
201
+ expect(gp.snap.y).toHaveBeenCalledWith(10);
107
202
  });
108
- it('calls, snap.y', () => {
109
- expect(gp.snap.y).toHaveBeenCalledWith(0);
203
+
204
+ it('calls onMouseMove handler with snapped coordinates', () => {
205
+ const onMouseMove = jest.fn();
206
+ const gp = graphProps();
207
+ gp.scale.x.invert = jest.fn().mockReturnValue(7);
208
+ gp.scale.y.invert = jest.fn().mockReturnValue(14);
209
+ gp.snap.x = jest.fn().mockReturnValue(10);
210
+ gp.snap.y = jest.fn().mockReturnValue(15);
211
+
212
+ const props = {
213
+ ...defaultProps,
214
+ onMouseMove,
215
+ graphProps: gp,
216
+ };
217
+
218
+ const mockNode = document.createElement('div');
219
+ const mockSelection = {
220
+ _groups: [[mockNode]],
221
+ node: () => mockNode,
222
+ };
223
+
224
+ select.mockReturnValue({
225
+ ...mockSelection,
226
+ on: (event, handler) => {
227
+ mockOn(event, handler);
228
+ if (event === 'mousemove' && handler) {
229
+ mouse.mockReturnValue([100, 200]);
230
+ handler();
231
+ }
232
+ },
233
+ });
234
+
235
+ render(<Root {...props}>hi</Root>);
236
+
237
+ expect(onMouseMove).toHaveBeenCalledWith({ x: 10, y: 15 });
110
238
  });
111
239
 
112
- it('calls handler', () => {
113
- expect(onMouseMove).toHaveBeenCalledWith({ x: 0, y: 0 });
240
+ it('does not call onMouseMove when handler is not provided', () => {
241
+ const gp = graphProps();
242
+ const props = {
243
+ ...defaultProps,
244
+ graphProps: gp,
245
+ };
246
+
247
+ const mockNode = document.createElement('div');
248
+ const mockSelection = {
249
+ _groups: [[mockNode]],
250
+ node: () => mockNode,
251
+ };
252
+
253
+ select.mockReturnValue({
254
+ ...mockSelection,
255
+ on: (event, handler) => {
256
+ mockOn(event, handler);
257
+ if (event === 'mousemove' && handler) {
258
+ mouse.mockReturnValue([100, 200]);
259
+ // Should not throw error when onMouseMove is not provided
260
+ expect(() => handler()).not.toThrow();
261
+ }
262
+ },
263
+ });
264
+
265
+ render(<Root {...props}>hi</Root>);
266
+
267
+ // Verify scale methods were not called (early return in mouseMove)
268
+ expect(gp.scale.x.invert).not.toHaveBeenCalled();
269
+ expect(gp.scale.y.invert).not.toHaveBeenCalled();
114
270
  });
115
271
  });
116
272
  });
@@ -38,6 +38,15 @@ export const gridDraggable = (opts) => (Comp) => {
38
38
  onMove: PropTypes.func,
39
39
  graphProps: GraphPropsType.isRequired,
40
40
  };
41
+
42
+ constructor(props) {
43
+ super(props);
44
+ this.state = {
45
+ startX: null,
46
+ startY: null,
47
+ };
48
+ }
49
+
41
50
  grid = () => {
42
51
  const { graphProps } = this.props;
43
52
  const { scale, domain, range } = graphProps;
@@ -87,10 +96,10 @@ export const gridDraggable = (opts) => (Comp) => {
87
96
  const grid = this.grid();
88
97
 
89
98
  const scaled = {
90
- left: (bounds.left / grid.interval) * grid.x,
91
- right: (bounds.right / grid.interval) * grid.x,
92
- top: (bounds.top / grid.interval) * grid.y,
93
- bottom: (bounds.bottom / grid.interval) * grid.y,
99
+ left: bounds.left * grid.x,
100
+ right: bounds.right * grid.x,
101
+ top: bounds.top * grid.y,
102
+ bottom: bounds.bottom * grid.y,
94
103
  };
95
104
  log('[getScaledBounds]: ', scaled);
96
105
  return scaled;
package/src/label.jsx CHANGED
@@ -1,6 +1,6 @@
1
- import React, { useState } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import { Readable } from '@pie-lib/render-ui';
3
- import EditableHtml from '@pie-lib/editable-html';
3
+ import EditableHtml from '@pie-lib/editable-html-tip-tap';
4
4
  import PropTypes from 'prop-types';
5
5
  import { extractTextFromHTML, isEmptyString } from './utils';
6
6
 
@@ -66,29 +66,14 @@ const LabelComponent = (props) => {
66
66
 
67
67
  const [rotatedToHorizontal, setRotatedToHorizontal] = useState(false);
68
68
 
69
- const activePlugins = [
70
- 'bold',
71
- 'italic',
72
- 'underline',
73
- 'strikethrough',
74
- 'math',
75
- ];
69
+ const activePlugins = ['bold', 'italic', 'underline', 'strikethrough', 'math'];
76
70
 
77
- const isChart =
78
- isChartBottomLabel ||
79
- isChartLeftLabel ||
80
- isDefineChartBottomLabel ||
81
- isDefineChartLeftLabel;
71
+ const isChart = isChartBottomLabel || isChartLeftLabel || isDefineChartBottomLabel || isDefineChartLeftLabel;
82
72
 
83
- const chartValue =
84
- side === 'left' && isDefineChartLeftLabel && graphHeight - 220;
73
+ const chartValue = side === 'left' && isDefineChartLeftLabel && graphHeight - 220;
85
74
 
86
75
  const defaultStyle = {
87
- width:
88
- chartValue ||
89
- (side === 'left' || side === 'right'
90
- ? graphHeight - 8
91
- : graphWidth - 8),
76
+ width: chartValue || (side === 'left' || side === 'right' ? graphHeight - 8 : graphWidth - 8),
92
77
  top:
93
78
  chartValue ||
94
79
  (isChartLeftLabel && `${graphHeight - 70}px`) ||
@@ -115,6 +100,15 @@ const LabelComponent = (props) => {
115
100
  }
116
101
  };
117
102
 
103
+ const exitEditMode = () => {
104
+ setRotatedToHorizontal(false);
105
+
106
+ // blur active element because rotation is causing editing issues on exit
107
+ requestAnimationFrame(() => {
108
+ document.activeElement?.blur?.();
109
+ });
110
+ };
111
+
118
112
  return (
119
113
  <Readable false>
120
114
  <div
@@ -122,27 +116,15 @@ const LabelComponent = (props) => {
122
116
  style={{
123
117
  ...(rotatedToHorizontal ? rotatedStyle : defaultStyle),
124
118
  ...(isChart ? styles.chartLabel : styles.axisLabel),
125
- ...(side === 'left' && !rotatedToHorizontal
126
- ? styles.rotateLeftLabel
127
- : {}),
128
- ...(side === 'right' && !rotatedToHorizontal
129
- ? styles.rotateRightLabel
130
- : {}),
119
+ ...(side === 'left' && !rotatedToHorizontal ? styles.rotateLeftLabel : {}),
120
+ ...(side === 'right' && !rotatedToHorizontal ? styles.rotateRightLabel : {}),
131
121
  ...(rotatedToHorizontal ? styles.editLabel : {}),
132
- ...((isChartBottomLabel || isDefineChartBottomLabel)
133
- ? styles.customBottom
134
- : {}),
135
- ...((disabledLabel &&
136
- !isChart &&
137
- isEmptyString(extractTextFromHTML(text))) &&
138
- styles.displayNone),
122
+ ...(isChartBottomLabel || isDefineChartBottomLabel ? styles.customBottom : {}),
123
+ ...(disabledLabel && !isChart && isEmptyString(extractTextFromHTML(text)) && styles.displayNone),
139
124
  }}
140
125
  >
141
126
  {disabledLabel ? (
142
- <div
143
- style={styles.disabledLabel}
144
- dangerouslySetInnerHTML={{ __html: text || '' }}
145
- />
127
+ <div style={styles.disabledLabel} dangerouslySetInnerHTML={{ __html: text || '' }} />
146
128
  ) : (
147
129
  <EditableHtml
148
130
  markup={text || ''}
@@ -155,7 +137,7 @@ const LabelComponent = (props) => {
155
137
  }}
156
138
  disableScrollbar
157
139
  activePlugins={activePlugins}
158
- onDone={() => setRotatedToHorizontal(false)}
140
+ onDone={exitEditMode}
159
141
  mathMlOptions={mathMlOptions}
160
142
  charactersLimit={charactersLimit}
161
143
  />
package/src/root.jsx CHANGED
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
4
4
  import { select, mouse } from 'd3-selection';
5
5
 
6
6
  import { color, Readable } from '@pie-lib/render-ui';
7
- import EditableHtml from '@pie-lib/editable-html';
7
+ import EditableHtml from '@pie-lib/editable-html-tip-tap';
8
8
  import { ChildrenType } from './types';
9
9
  import { GraphPropsType } from './types';
10
10
  import Label from './label';
@@ -134,7 +134,7 @@ export class Root extends React.Component {
134
134
  }
135
135
 
136
136
  const { scale, snap } = graphProps;
137
- const coords = mouse(g._groups[0][0]);
137
+ const coords = mouse(g.node());
138
138
  const x = scale.x.invert(coords[0]);
139
139
  const y = scale.y.invert(coords[1]);
140
140
 
@@ -282,7 +282,7 @@ export class Root extends React.Component {
282
282
  const nbOfHorizontalLines = parseInt(actualHeight / 100);
283
283
  const sideGridlinesPadding = parseInt(actualHeight % 100);
284
284
  const { titleHeight } = this.state;
285
-
285
+
286
286
  return (
287
287
  <StyledRoot>
288
288
  {showPixelGuides && (
@@ -325,7 +325,8 @@ export class Root extends React.Component {
325
325
  markup={title || ''}
326
326
  onChange={onChangeTitle}
327
327
  placeholder={
328
- (defineChart && titlePlaceholder) || (!disabledTitle && 'Click here to add a title for this graph')
328
+ (defineChart && titlePlaceholder) ||
329
+ (!disabledTitle && 'Click here to add a title for this graph')
329
330
  }
330
331
  toolbarOpts={{ noPadding: true, noBorder: true }}
331
332
  activePlugins={activeTitlePlugins}
@@ -344,7 +345,8 @@ export class Root extends React.Component {
344
345
  markup={title || ''}
345
346
  onChange={onChangeTitle}
346
347
  placeholder={
347
- (defineChart && titlePlaceholder) || (!disabledTitle && 'Click here to add a title for this graph')
348
+ (defineChart && titlePlaceholder) ||
349
+ (!disabledTitle && 'Click here to add a title for this graph')
348
350
  }
349
351
  toolbarOpts={{ noPadding: true, noBorder: true }}
350
352
  activePlugins={activeTitlePlugins}
@@ -1,185 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`gridDraggable snapshot render with decimals 1`] = `
4
- <mockConstructor
5
- axis="both"
6
- grid={
7
- Array [
8
- 1,
9
- 1,
10
- ]
11
- }
12
- onDrag={[Function]}
13
- onMouseDown={[Function]}
14
- onStart={[Function]}
15
- onStop={[Function]}
16
- >
17
- <Component
18
- domain={
19
- Object {
20
- "max": 1.6,
21
- "min": -1.5,
22
- "step": 0.3,
23
- }
24
- }
25
- graphProps={
26
- Object {
27
- "domain": Object {
28
- "max": 1,
29
- "min": 0,
30
- "step": 1,
31
- },
32
- "getRootNode": [Function],
33
- "range": Object {
34
- "max": 1,
35
- "min": 0,
36
- "step": 1,
37
- },
38
- "scale": Object {
39
- "x": [MockFunction] {
40
- "calls": Array [
41
- Array [
42
- 1,
43
- ],
44
- Array [
45
- 0,
46
- ],
47
- ],
48
- "results": Array [
49
- Object {
50
- "type": "return",
51
- "value": 1,
52
- },
53
- Object {
54
- "type": "return",
55
- "value": 0,
56
- },
57
- ],
58
- },
59
- "y": [MockFunction] {
60
- "calls": Array [
61
- Array [
62
- 1,
63
- ],
64
- Array [
65
- 0,
66
- ],
67
- ],
68
- "results": Array [
69
- Object {
70
- "type": "return",
71
- "value": 1,
72
- },
73
- Object {
74
- "type": "return",
75
- "value": 0,
76
- },
77
- ],
78
- },
79
- },
80
- "size": Object {
81
- "height": 500,
82
- "width": 500,
83
- },
84
- "snap": Object {
85
- "x": [MockFunction],
86
- "y": [MockFunction],
87
- },
88
- }
89
- }
90
- isDragging={false}
91
- range={
92
- Object {
93
- "max": 3,
94
- "min": -2,
95
- "step": 0.2,
96
- }
97
- }
98
- />
99
- </mockConstructor>
100
- `;
101
-
102
- exports[`gridDraggable snapshot reqular 1`] = `
103
- <mockConstructor
104
- axis="both"
105
- grid={
106
- Array [
107
- 1,
108
- 1,
109
- ]
110
- }
111
- onDrag={[Function]}
112
- onMouseDown={[Function]}
113
- onStart={[Function]}
114
- onStop={[Function]}
115
- >
116
- <Component
117
- graphProps={
118
- Object {
119
- "domain": Object {
120
- "max": 1,
121
- "min": 0,
122
- "step": 1,
123
- },
124
- "getRootNode": [Function],
125
- "range": Object {
126
- "max": 1,
127
- "min": 0,
128
- "step": 1,
129
- },
130
- "scale": Object {
131
- "x": [MockFunction] {
132
- "calls": Array [
133
- Array [
134
- 1,
135
- ],
136
- Array [
137
- 0,
138
- ],
139
- ],
140
- "results": Array [
141
- Object {
142
- "type": "return",
143
- "value": 1,
144
- },
145
- Object {
146
- "type": "return",
147
- "value": 0,
148
- },
149
- ],
150
- },
151
- "y": [MockFunction] {
152
- "calls": Array [
153
- Array [
154
- 1,
155
- ],
156
- Array [
157
- 0,
158
- ],
159
- ],
160
- "results": Array [
161
- Object {
162
- "type": "return",
163
- "value": 1,
164
- },
165
- Object {
166
- "type": "return",
167
- "value": 0,
168
- },
169
- ],
170
- },
171
- },
172
- "size": Object {
173
- "height": 500,
174
- "width": 500,
175
- },
176
- "snap": Object {
177
- "x": [MockFunction],
178
- "y": [MockFunction],
179
- },
180
- }
181
- }
182
- isDragging={false}
183
- />
184
- </mockConstructor>
185
- `;
@@ -1,18 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`root snapshot matches 1`] = `
4
- <div>
5
- <div>
6
- <svg
7
- height={480}
8
- width={540}
9
- >
10
- <g
11
- transform="translate(70, 40)"
12
- >
13
- hi
14
- </g>
15
- </svg>
16
- </div>
17
- </div>
18
- `;