@khanacademy/wonder-blocks-clickable 2.4.0 → 2.4.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # @khanacademy/wonder-blocks-clickable
2
2
 
3
+ ## 2.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [b561425a]
8
+ - Updated dependencies [a566e232]
9
+ - Updated dependencies [d2b21a6e]
10
+ - @khanacademy/wonder-blocks-core@4.6.0
11
+
12
+ ## 2.4.1
13
+
14
+ ### Patch Changes
15
+
16
+ - 4c682709: handleClick no longer redundantly triggers on mouseup
17
+
3
18
  ## 2.4.0
4
19
 
5
20
  ### Minor Changes
package/dist/es/index.js CHANGED
@@ -41,7 +41,6 @@ const disabledHandlers = {
41
41
  onMouseLeave: () => void 0,
42
42
  onMouseDown: () => void 0,
43
43
  onMouseUp: () => void 0,
44
- onDragStart: () => void 0,
45
44
  onTouchStart: () => void 0,
46
45
  onTouchEnd: () => void 0,
47
46
  onTouchCancel: () => void 0,
@@ -91,12 +90,7 @@ class ClickableBehavior extends React.Component {
91
90
  };
92
91
 
93
92
  this.handleMouseEnter = e => {
94
- if (e.buttons === 1) {
95
- this.dragging = true;
96
- this.setState({
97
- pressed: true
98
- });
99
- } else if (!this.waitingForClick) {
93
+ if (!this.waitingForClick) {
100
94
  this.setState({
101
95
  hovered: true
102
96
  });
@@ -105,7 +99,6 @@ class ClickableBehavior extends React.Component {
105
99
 
106
100
  this.handleMouseLeave = () => {
107
101
  if (!this.waitingForClick) {
108
- this.dragging = false;
109
102
  this.setState({
110
103
  hovered: false,
111
104
  pressed: false,
@@ -121,22 +114,12 @@ class ClickableBehavior extends React.Component {
121
114
  };
122
115
 
123
116
  this.handleMouseUp = e => {
124
- if (this.dragging) {
125
- this.dragging = false;
126
- this.handleClick(e);
127
- }
128
-
129
117
  this.setState({
130
118
  pressed: false,
131
119
  focused: false
132
120
  });
133
121
  };
134
122
 
135
- this.handleDragStart = e => {
136
- this.dragging = true;
137
- e.preventDefault();
138
- };
139
-
140
123
  this.handleTouchStart = () => {
141
124
  this.setState({
142
125
  pressed: true
@@ -226,7 +209,6 @@ class ClickableBehavior extends React.Component {
226
209
  this.state = startState;
227
210
  this.waitingForClick = false;
228
211
  this.enterClick = false;
229
- this.dragging = false;
230
212
  }
231
213
 
232
214
  navigateOrReset(shouldNavigate) {
@@ -358,7 +340,6 @@ class ClickableBehavior extends React.Component {
358
340
  onMouseLeave: this.handleMouseLeave,
359
341
  onMouseDown: this.handleMouseDown,
360
342
  onMouseUp: this.handleMouseUp,
361
- onDragStart: this.handleDragStart,
362
343
  onTouchStart: this.handleTouchStart,
363
344
  onTouchEnd: this.handleTouchEnd,
364
345
  onTouchCancel: this.handleTouchCancel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-clickable",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "design": "v1",
5
5
  "description": "Clickable component for Wonder-Blocks.",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.18.6",
19
- "@khanacademy/wonder-blocks-core": "^4.5.0"
19
+ "@khanacademy/wonder-blocks-core": "^4.6.0"
20
20
  },
21
21
  "peerDependencies": {
22
22
  "aphrodite": "^1.2.5",
@@ -26,6 +26,6 @@
26
26
  "react-router-dom": "5.3.0"
27
27
  },
28
28
  "devDependencies": {
29
- "wb-dev-build-settings": "^0.4.0"
29
+ "wb-dev-build-settings": "^0.5.0"
30
30
  }
31
31
  }
@@ -66,6 +66,13 @@ export const Default: StoryComponentType = (args) => (
66
66
  </Clickable>
67
67
  );
68
68
 
69
+ Default.args = {
70
+ onClick: () => {
71
+ // eslint-disable-next-line no-alert
72
+ alert("Click!");
73
+ },
74
+ };
75
+
69
76
  export const Basic: StoryComponentType = () => (
70
77
  <View style={styles.centerText}>
71
78
  <Clickable
@@ -2,7 +2,7 @@
2
2
  /* eslint-disable max-lines */
3
3
  // @flow
4
4
  import * as React from "react";
5
- import {render, screen, fireEvent} from "@testing-library/react";
5
+ import {render, screen, fireEvent, waitFor} from "@testing-library/react";
6
6
  import {MemoryRouter, Switch, Route} from "react-router-dom";
7
7
  import userEvent from "@testing-library/user-event";
8
8
 
@@ -16,12 +16,6 @@ const keyCodes = {
16
16
  space: 32,
17
17
  };
18
18
 
19
- const wait = (delay: number = 0) =>
20
- new Promise((resolve, reject) => {
21
- // eslint-disable-next-line no-restricted-syntax
22
- return setTimeout(resolve, delay);
23
- });
24
-
25
19
  const labelForState = (state: ClickableState): string => {
26
20
  const labels = [];
27
21
  if (state.hovered) {
@@ -64,7 +58,7 @@ describe("ClickableBehavior", () => {
64
58
  expect(onClick).toHaveBeenCalled();
65
59
  });
66
60
 
67
- it("changes only hovered state on mouse enter/leave", () => {
61
+ it("changes hovered state on mouse enter/leave", () => {
68
62
  const onClick = jest.fn();
69
63
  render(
70
64
  <ClickableBehavior disabled={false} onClick={(e) => onClick(e)}>
@@ -82,7 +76,7 @@ describe("ClickableBehavior", () => {
82
76
  expect(button).not.toHaveTextContent("hovered");
83
77
  });
84
78
 
85
- it("changes only pressed state on mouse enter/leave while dragging", () => {
79
+ it("changes hovered state on mouse enter while dragging", () => {
86
80
  const onClick = jest.fn();
87
81
  render(
88
82
  <ClickableBehavior disabled={false} onClick={(e) => onClick(e)}>
@@ -93,18 +87,34 @@ describe("ClickableBehavior", () => {
93
87
  </ClickableBehavior>,
94
88
  );
95
89
  const button = screen.getByRole("button");
90
+ expect(button).not.toHaveTextContent("hovered");
91
+ expect(button).not.toHaveTextContent("pressed");
92
+
93
+ fireEvent.mouseEnter(button, {buttons: 1});
94
+ expect(button).not.toHaveTextContent("pressed");
95
+ expect(button).toHaveTextContent("hovered");
96
+ });
97
+
98
+ it("changes pressed and hover states on mouse leave while dragging", () => {
99
+ const onClick = jest.fn();
100
+ render(
101
+ <ClickableBehavior disabled={false} onClick={(e) => onClick(e)}>
102
+ {(state, childrenProps) => {
103
+ const label = labelForState(state);
104
+ return <button {...childrenProps}>{label}</button>;
105
+ }}
106
+ </ClickableBehavior>,
107
+ );
108
+ const button = screen.getByRole("button");
109
+ expect(button).not.toHaveTextContent("hovered");
96
110
  expect(button).not.toHaveTextContent("pressed");
97
111
 
98
112
  fireEvent.mouseDown(button);
99
- fireEvent.dragStart(button);
100
- fireEvent.mouseMove(button);
101
113
  expect(button).toHaveTextContent("pressed");
102
114
 
103
115
  fireEvent.mouseLeave(button);
116
+ expect(button).not.toHaveTextContent("hovered");
104
117
  expect(button).not.toHaveTextContent("pressed");
105
-
106
- fireEvent.mouseEnter(button, {buttons: 1});
107
- expect(button).toHaveTextContent("pressed");
108
118
  });
109
119
 
110
120
  it("changes pressed state on mouse down/up", () => {
@@ -592,10 +602,11 @@ describe("ClickableBehavior", () => {
592
602
  // Act
593
603
  const link = screen.getByRole("link");
594
604
  userEvent.click(link);
595
- await wait(0);
596
605
 
597
606
  // Assert
598
- expect(window.location.assign).toHaveBeenCalledTimes(1);
607
+ await waitFor(() => {
608
+ expect(window.location.assign).toHaveBeenCalledTimes(1);
609
+ });
599
610
  });
600
611
 
601
612
  it("should show waiting UI before safeWithNav resolves", async () => {
@@ -763,7 +774,17 @@ describe("ClickableBehavior", () => {
763
774
  expect(onClick).toHaveBeenCalledTimes(expectedNumberTimesCalled);
764
775
  });
765
776
 
766
- it("calls onClick on mouseup when the mouse was dragging", () => {
777
+ // The following two tests involve click behavior when dragging.
778
+ // Here are some notable related actions that cannot be tested using
779
+ // existing jest/RTL events since these click types are handled
780
+ // by browsers but aren't registered as clicks by RTL/jest:
781
+ // 1. Mousedown in the button, drag within the button, and mouseup
782
+ // in the button (mouse doesn't leave the button at any point).
783
+ // This should result in a successful click.
784
+ // 2. Mouse down in the button, drag out of the button (don't let go),
785
+ // drag back into the button, and mouseup inside the button.
786
+ // This should result in a successful click.
787
+ it("does not call onClick on mouseup when the mouse presses inside and drags away", () => {
767
788
  const onClick = jest.fn();
768
789
  render(
769
790
  <ClickableBehavior disabled={false} onClick={(e) => onClick(e)}>
@@ -775,19 +796,25 @@ describe("ClickableBehavior", () => {
775
796
 
776
797
  const button = screen.getByRole("button");
777
798
  fireEvent.mouseDown(button);
778
- fireEvent.dragStart(button);
779
799
  fireEvent.mouseLeave(button);
780
800
  fireEvent.mouseUp(button);
781
801
  expect(onClick).toHaveBeenCalledTimes(0);
802
+ });
782
803
 
783
- fireEvent.mouseDown(button);
784
- fireEvent.dragStart(button);
785
- fireEvent.mouseUp(button);
786
- expect(onClick).toHaveBeenCalledTimes(1);
804
+ it("does not call onClick on mouseup when the mouse presses outside and drags in", () => {
805
+ const onClick = jest.fn();
806
+ render(
807
+ <ClickableBehavior disabled={false} onClick={(e) => onClick(e)}>
808
+ {(state, childrenProps) => {
809
+ return <button {...childrenProps}>Label</button>;
810
+ }}
811
+ </ClickableBehavior>,
812
+ );
787
813
 
814
+ const button = screen.getByRole("button");
788
815
  fireEvent.mouseEnter(button, {buttons: 1});
789
816
  fireEvent.mouseUp(button);
790
- expect(onClick).toHaveBeenCalledTimes(2);
817
+ expect(onClick).toHaveBeenCalledTimes(0);
791
818
  });
792
819
 
793
820
  it("doesn't trigger enter key when browser doesn't stop the click", () => {
@@ -891,10 +918,13 @@ describe("ClickableBehavior", () => {
891
918
 
892
919
  // Act
893
920
  userEvent.click(screen.getByRole("button"));
894
- await wait(0);
895
921
 
896
922
  // Assert
897
- expect(screen.getByText("Hello, world!")).toBeInTheDocument();
923
+ await waitFor(() => {
924
+ expect(
925
+ screen.getByText("Hello, world!"),
926
+ ).toBeInTheDocument();
927
+ });
898
928
  });
899
929
 
900
930
  it("shows waiting state before navigating", async () => {
@@ -970,7 +1000,6 @@ describe("ClickableBehavior", () => {
970
1000
 
971
1001
  // Act
972
1002
  userEvent.click(screen.getByRole("button"));
973
- await wait(0);
974
1003
 
975
1004
  // Assert
976
1005
  expect(
@@ -1015,10 +1044,11 @@ describe("ClickableBehavior", () => {
1015
1044
 
1016
1045
  // Act
1017
1046
  userEvent.click(screen.getByRole("button"));
1018
- await wait(0);
1019
1047
 
1020
1048
  // Assert
1021
- expect(safeWithNavMock).toHaveBeenCalled();
1049
+ await waitFor(() => {
1050
+ expect(safeWithNavMock).toHaveBeenCalled();
1051
+ });
1022
1052
  });
1023
1053
  });
1024
1054
 
@@ -1,18 +1,12 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import {MemoryRouter, Route, Switch} from "react-router-dom";
4
- import {render, screen, fireEvent} from "@testing-library/react";
4
+ import {render, screen, fireEvent, waitFor} from "@testing-library/react";
5
5
  import userEvent from "@testing-library/user-event";
6
6
 
7
7
  import {View} from "@khanacademy/wonder-blocks-core";
8
8
  import Clickable from "../clickable.js";
9
9
 
10
- const wait = (delay: number = 0) =>
11
- new Promise((resolve, reject) => {
12
- // eslint-disable-next-line no-restricted-syntax
13
- return setTimeout(resolve, delay);
14
- });
15
-
16
10
  describe("Clickable", () => {
17
11
  beforeEach(() => {
18
12
  delete window.location;
@@ -170,7 +164,6 @@ describe("Clickable", () => {
170
164
 
171
165
  // Act
172
166
  userEvent.click(screen.getByTestId("button"));
173
- await wait(0);
174
167
 
175
168
  // Assert
176
169
  expect(screen.queryByText("Hello, world!")).not.toBeInTheDocument();
@@ -201,7 +194,6 @@ describe("Clickable", () => {
201
194
 
202
195
  // Act
203
196
  userEvent.click(screen.getByTestId("button"));
204
- await wait(0);
205
197
 
206
198
  // Assert
207
199
  expect(safeWithNavMock).not.toHaveBeenCalled();
@@ -230,10 +222,11 @@ describe("Clickable", () => {
230
222
 
231
223
  // Act
232
224
  userEvent.click(screen.getByTestId("button"));
233
- await wait(0);
234
225
 
235
226
  // Assert
236
- expect(screen.getByText("Hello, world!")).toBeInTheDocument();
227
+ await waitFor(() => {
228
+ expect(screen.getByText("Hello, world!")).toBeInTheDocument();
229
+ });
237
230
  });
238
231
 
239
232
  test("beforeNav resolution results in safeWithNav being called", async () => {
@@ -261,10 +254,11 @@ describe("Clickable", () => {
261
254
 
262
255
  // Act
263
256
  userEvent.click(screen.getByTestId("button"));
264
- await wait(0);
265
257
 
266
258
  // Assert
267
- expect(safeWithNavMock).toHaveBeenCalled();
259
+ await waitFor(() => {
260
+ expect(safeWithNavMock).toHaveBeenCalled();
261
+ });
268
262
  });
269
263
 
270
264
  test("safeWithNav with skipClientNav=true waits for promise resolution", async () => {
@@ -291,10 +285,11 @@ describe("Clickable", () => {
291
285
 
292
286
  // Act
293
287
  userEvent.click(screen.getByTestId("button"));
294
- await wait(0);
295
288
 
296
289
  // Assert
297
- expect(window.location.assign).toHaveBeenCalledWith("/foo");
290
+ await waitFor(() => {
291
+ expect(window.location.assign).toHaveBeenCalledWith("/foo");
292
+ });
298
293
  });
299
294
 
300
295
  test("beforeNav resolution and safeWithNav with skipClientNav=true waits for promise resolution", async () => {
@@ -322,10 +317,11 @@ describe("Clickable", () => {
322
317
 
323
318
  // Act
324
319
  userEvent.click(screen.getByTestId("button"));
325
- await wait(0);
326
320
 
327
321
  // Assert
328
- expect(window.location.assign).toHaveBeenCalledWith("/foo");
322
+ await waitFor(() => {
323
+ expect(window.location.assign).toHaveBeenCalledWith("/foo");
324
+ });
329
325
  });
330
326
 
331
327
  test("safeWithNav with skipClientNav=true waits for promise rejection", async () => {
@@ -352,10 +348,11 @@ describe("Clickable", () => {
352
348
 
353
349
  // Act
354
350
  userEvent.click(screen.getByTestId("button"));
355
- await wait(0);
356
351
 
357
352
  // Assert
358
- expect(window.location.assign).toHaveBeenCalledWith("/foo");
353
+ await waitFor(() => {
354
+ expect(window.location.assign).toHaveBeenCalledWith("/foo");
355
+ });
359
356
  });
360
357
 
361
358
  test("safeWithNav with skipClientNav=false calls safeWithNav but doesn't wait to navigate", () => {
@@ -418,10 +415,11 @@ describe("Clickable", () => {
418
415
 
419
416
  // Act
420
417
  userEvent.click(screen.getByTestId("button"));
421
- await wait(0);
422
418
 
423
419
  // Assert
424
- expect(safeWithNavMock).toHaveBeenCalled();
420
+ await waitFor(() => {
421
+ expect(safeWithNavMock).toHaveBeenCalled();
422
+ });
425
423
  // client side nav to /foo
426
424
  expect(screen.getByText("Hello, world!")).toBeInTheDocument();
427
425
  // not a full page nav
@@ -205,7 +205,6 @@ export type ChildrenProps = {|
205
205
  onMouseLeave: () => mixed,
206
206
  onMouseDown: () => mixed,
207
207
  onMouseUp: (e: SyntheticMouseEvent<>) => mixed,
208
- onDragStart: (e: SyntheticMouseEvent<>) => mixed,
209
208
  onTouchStart: () => mixed,
210
209
  onTouchEnd: () => mixed,
211
210
  onTouchCancel: () => mixed,
@@ -223,7 +222,6 @@ const disabledHandlers = {
223
222
  onMouseLeave: () => void 0,
224
223
  onMouseDown: () => void 0,
225
224
  onMouseUp: () => void 0,
226
- onDragStart: () => void 0,
227
225
  onTouchStart: () => void 0,
228
226
  onTouchEnd: () => void 0,
229
227
  onTouchCancel: () => void 0,
@@ -265,7 +263,7 @@ const startState: ClickableState = {
265
263
  * 3. Keyup (spacebar/enter) -> focus state
266
264
  *
267
265
  * Warning: The event handlers returned (onClick, onMouseEnter, onMouseLeave,
268
- * onMouseDown, onMouseUp, onDragStart, onTouchStart, onTouchEnd, onTouchCancel,
266
+ * onMouseDown, onMouseUp, onTouchStart, onTouchEnd, onTouchCancel,
269
267
  * onKeyDown, onKeyUp, onFocus, onBlur, tabIndex) should be passed on to the
270
268
  * component that has the ClickableBehavior. You cannot override these handlers
271
269
  * without potentially breaking the functionality of ClickableBehavior.
@@ -330,7 +328,6 @@ export default class ClickableBehavior extends React.Component<
330
328
  > {
331
329
  waitingForClick: boolean;
332
330
  enterClick: boolean;
333
- dragging: boolean;
334
331
 
335
332
  static defaultProps: DefaultProps = {
336
333
  disabled: false,
@@ -356,7 +353,6 @@ export default class ClickableBehavior extends React.Component<
356
353
  this.state = startState;
357
354
  this.waitingForClick = false;
358
355
  this.enterClick = false;
359
- this.dragging = false;
360
356
  }
361
357
 
362
358
  navigateOrReset(shouldNavigate: boolean) {
@@ -513,18 +509,13 @@ export default class ClickableBehavior extends React.Component<
513
509
  };
514
510
 
515
511
  handleMouseEnter: (e: SyntheticMouseEvent<>) => void = (e) => {
516
- // When the left button is pressed already, we want it to be pressed
517
- if (e.buttons === 1) {
518
- this.dragging = true;
519
- this.setState({pressed: true});
520
- } else if (!this.waitingForClick) {
512
+ if (!this.waitingForClick) {
521
513
  this.setState({hovered: true});
522
514
  }
523
515
  };
524
516
 
525
517
  handleMouseLeave: () => void = () => {
526
518
  if (!this.waitingForClick) {
527
- this.dragging = false;
528
519
  this.setState({hovered: false, pressed: false, focused: false});
529
520
  }
530
521
  };
@@ -534,18 +525,9 @@ export default class ClickableBehavior extends React.Component<
534
525
  };
535
526
 
536
527
  handleMouseUp: (e: SyntheticMouseEvent<>) => void = (e) => {
537
- if (this.dragging) {
538
- this.dragging = false;
539
- this.handleClick(e);
540
- }
541
528
  this.setState({pressed: false, focused: false});
542
529
  };
543
530
 
544
- handleDragStart: (e: SyntheticMouseEvent<>) => void = (e) => {
545
- this.dragging = true;
546
- e.preventDefault();
547
- };
548
-
549
531
  handleTouchStart: () => void = () => {
550
532
  this.setState({pressed: true});
551
533
  };
@@ -630,7 +612,6 @@ export default class ClickableBehavior extends React.Component<
630
612
  onMouseLeave: this.handleMouseLeave,
631
613
  onMouseDown: this.handleMouseDown,
632
614
  onMouseUp: this.handleMouseUp,
633
- onDragStart: this.handleDragStart,
634
615
  onTouchStart: this.handleTouchStart,
635
616
  onTouchEnd: this.handleTouchEnd,
636
617
  onTouchCancel: this.handleTouchCancel,