@khanacademy/wonder-blocks-clickable 4.2.6 → 4.2.8

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,20 +0,0 @@
1
- import * as React from "react";
2
-
3
- import ClickableBehavior from "../clickable-behavior";
4
-
5
- <ClickableBehavior>
6
- {(_, childrenProps) => <div {...childrenProps} />}
7
- </ClickableBehavior>;
8
-
9
- <ClickableBehavior target="_blank">
10
- {(_, childrenProps) => <div {...childrenProps} />}
11
- </ClickableBehavior>;
12
-
13
- <ClickableBehavior beforeNav={() => Promise.resolve()}>
14
- {(_, childrenProps) => <div {...childrenProps} />}
15
- </ClickableBehavior>;
16
-
17
- // @ts-expect-error `target` and `beforeNav` can't be used together.
18
- <ClickableBehavior target="_blank" beforeNav={() => Promise.resolve()}>
19
- {(_, childrenProps) => <div {...childrenProps} />}
20
- </ClickableBehavior>;
@@ -1,661 +0,0 @@
1
- import * as React from "react";
2
- import {MemoryRouter, Route, Switch} from "react-router-dom";
3
- import {render, screen, fireEvent, waitFor} from "@testing-library/react";
4
- import {userEvent} from "@testing-library/user-event";
5
-
6
- import {View} from "@khanacademy/wonder-blocks-core";
7
- import Clickable from "../clickable";
8
-
9
- describe("Clickable", () => {
10
- beforeEach(() => {
11
- // @ts-expect-error [FEI-5019] - TS2790 - The operand of a 'delete' operator must be optional.
12
- delete window.location;
13
- // @ts-expect-error [FEI-5019] - TS2740 - Type '{ assign: Mock<any, any, any>; }' is missing the following properties from type 'Location': ancestorOrigins, hash, host, hostname, and 8 more.
14
- window.location = {assign: jest.fn()};
15
- });
16
-
17
- test("client-side navigation", async () => {
18
- // Arrange
19
- render(
20
- <MemoryRouter>
21
- <View>
22
- <Clickable testId="button" href="/foo">
23
- {(eventState: any) => <h1>Click Me!</h1>}
24
- </Clickable>
25
- <Switch>
26
- <Route path="/foo">
27
- <View>Hello, world!</View>
28
- </Route>
29
- </Switch>
30
- </View>
31
- </MemoryRouter>,
32
- );
33
-
34
- // Act
35
- await userEvent.click(await screen.findByTestId("button"));
36
-
37
- // Assert
38
- expect(await screen.findByText("Hello, world!")).toBeInTheDocument();
39
- });
40
-
41
- test("client-side navigation with unknown URL fails", async () => {
42
- // Arrange
43
- render(
44
- <MemoryRouter>
45
- <View>
46
- <Clickable testId="button" href="/unknown">
47
- {(eventState: any) => <h1>Click Me!</h1>}
48
- </Clickable>
49
- <Switch>
50
- <Route path="/foo">
51
- <View>Hello, world!</View>
52
- </Route>
53
- </Switch>
54
- </View>
55
- </MemoryRouter>,
56
- );
57
-
58
- // Act
59
- await userEvent.click(await screen.findByTestId("button"));
60
-
61
- // Assert
62
- expect(screen.queryByText("Hello, world!")).not.toBeInTheDocument();
63
- });
64
-
65
- test("client-side navigation with `skipClientNav` set to `true` fails", async () => {
66
- // Arrange
67
- render(
68
- <MemoryRouter>
69
- <View>
70
- <Clickable testId="button" href="/foo" skipClientNav>
71
- {(eventState: any) => <h1>Click Me!</h1>}
72
- </Clickable>
73
- <Switch>
74
- <Route path="/foo">
75
- <View>Hello, world!</View>
76
- </Route>
77
- </Switch>
78
- </View>
79
- </MemoryRouter>,
80
- );
81
-
82
- // Act
83
- await userEvent.click(await screen.findByTestId("button"));
84
-
85
- // Assert
86
- expect(screen.queryByText("Hello, world!")).not.toBeInTheDocument();
87
- });
88
-
89
- test("disallow navigation when href and disabled are both set", async () => {
90
- // Arrange
91
- render(
92
- <MemoryRouter>
93
- <View>
94
- <Clickable testId="button" href="/foo" disabled={true}>
95
- {(eventState: any) => <h1>Click Me!</h1>}
96
- </Clickable>
97
- <Switch>
98
- <Route path="/foo">
99
- <View>Hello, world!</View>
100
- </Route>
101
- </Switch>
102
- </View>
103
- </MemoryRouter>,
104
- );
105
-
106
- // Act
107
- await userEvent.click(await screen.findByTestId("button"));
108
-
109
- // Assert
110
- expect(screen.queryByText("Hello, world!")).not.toBeInTheDocument();
111
- });
112
-
113
- test("a link is rendered with the given href", async () => {
114
- // Arrange, Act
115
- render(
116
- <Clickable testId="button" href="/foo" skipClientNav={true}>
117
- {(eventState: any) => <h1>Click Me!</h1>}
118
- </Clickable>,
119
- );
120
-
121
- // Assert
122
- const link = await screen.findByRole("link");
123
- expect(link).toBeInTheDocument();
124
- expect(link).toHaveAttribute("href", "/foo");
125
- });
126
-
127
- test("should navigate to a specific link using the keyboard", async () => {
128
- // Arrange
129
- render(
130
- <Clickable testId="button" href="/foo" skipClientNav={true}>
131
- {(eventState: any) => <h1>Click Me!</h1>}
132
- </Clickable>,
133
- );
134
-
135
- // Act
136
- const button = await screen.findByTestId("button");
137
- // simulate Enter
138
- // eslint-disable-next-line testing-library/prefer-user-event
139
- fireEvent.keyUp(button, {keyCode: 13});
140
-
141
- // Assert
142
- expect(window.location.assign).toHaveBeenCalledWith("/foo");
143
- });
144
-
145
- test("beforeNav rejection blocks client-side navigation", async () => {
146
- // Arrange
147
- render(
148
- <MemoryRouter>
149
- <div>
150
- <Clickable
151
- testId="button"
152
- href="/foo"
153
- beforeNav={() => Promise.reject()}
154
- >
155
- {() => <span>Click me!</span>}
156
- </Clickable>
157
- <Switch>
158
- <Route path="/foo">
159
- <div>Hello, world!</div>
160
- </Route>
161
- </Switch>
162
- </div>
163
- </MemoryRouter>,
164
- );
165
-
166
- // Act
167
- await userEvent.click(await screen.findByTestId("button"));
168
-
169
- // Assert
170
- expect(screen.queryByText("Hello, world!")).not.toBeInTheDocument();
171
- });
172
-
173
- test("beforeNav rejection blocks calling safeWithNav", async () => {
174
- // Arrange
175
- const safeWithNavMock = jest.fn();
176
- render(
177
- <MemoryRouter>
178
- <div>
179
- <Clickable
180
- testId="button"
181
- href="/foo"
182
- beforeNav={() => Promise.reject()}
183
- safeWithNav={safeWithNavMock}
184
- >
185
- {() => <span>Click me!</span>}
186
- </Clickable>
187
- <Switch>
188
- <Route path="/foo">
189
- <div>Hello, world!</div>
190
- </Route>
191
- </Switch>
192
- </div>
193
- </MemoryRouter>,
194
- );
195
-
196
- // Act
197
- await userEvent.click(await screen.findByTestId("button"));
198
-
199
- // Assert
200
- expect(safeWithNavMock).not.toHaveBeenCalled();
201
- });
202
-
203
- test("beforeNav resolution results in client-side navigation", async () => {
204
- // Arrange
205
- render(
206
- <MemoryRouter>
207
- <div>
208
- <Clickable
209
- testId="button"
210
- href="/foo"
211
- beforeNav={() => Promise.resolve()}
212
- >
213
- {() => <span>Click me!</span>}
214
- </Clickable>
215
- <Switch>
216
- <Route path="/foo">
217
- <div>Hello, world!</div>
218
- </Route>
219
- </Switch>
220
- </div>
221
- </MemoryRouter>,
222
- );
223
-
224
- // Act
225
- await userEvent.click(await screen.findByTestId("button"));
226
-
227
- // Assert
228
- await waitFor(async () => {
229
- expect(
230
- await screen.findByText("Hello, world!"),
231
- ).toBeInTheDocument();
232
- });
233
- });
234
-
235
- test("beforeNav resolution results in safeWithNav being called", async () => {
236
- // Arrange
237
- const safeWithNavMock = jest.fn();
238
- render(
239
- <MemoryRouter>
240
- <div>
241
- <Clickable
242
- testId="button"
243
- href="/foo"
244
- beforeNav={() => Promise.resolve()}
245
- safeWithNav={safeWithNavMock}
246
- >
247
- {() => <span>Click me!</span>}
248
- </Clickable>
249
- <Switch>
250
- <Route path="/foo">
251
- <div>Hello, world!</div>
252
- </Route>
253
- </Switch>
254
- </div>
255
- </MemoryRouter>,
256
- );
257
-
258
- // Act
259
- await userEvent.click(await screen.findByTestId("button"));
260
-
261
- // Assert
262
- await waitFor(() => {
263
- expect(safeWithNavMock).toHaveBeenCalled();
264
- });
265
- });
266
-
267
- test("safeWithNav with skipClientNav=true waits for promise resolution", async () => {
268
- // Arrange
269
- render(
270
- <MemoryRouter>
271
- <div>
272
- <Clickable
273
- testId="button"
274
- href="/foo"
275
- safeWithNav={() => Promise.resolve()}
276
- skipClientNav={true}
277
- >
278
- {() => <h1>Click me!</h1>}
279
- </Clickable>
280
- <Switch>
281
- <Route path="/foo">
282
- <div>Hello, world!</div>
283
- </Route>
284
- </Switch>
285
- </div>
286
- </MemoryRouter>,
287
- );
288
-
289
- // Act
290
- await userEvent.click(await screen.findByTestId("button"));
291
-
292
- // Assert
293
- await waitFor(() => {
294
- expect(window.location.assign).toHaveBeenCalledWith("/foo");
295
- });
296
- });
297
-
298
- test("beforeNav resolution and safeWithNav with skipClientNav=true waits for promise resolution", async () => {
299
- // Arrange
300
- render(
301
- <MemoryRouter>
302
- <div>
303
- <Clickable
304
- testId="button"
305
- href="/foo"
306
- beforeNav={() => Promise.resolve()}
307
- safeWithNav={() => Promise.resolve()}
308
- skipClientNav={true}
309
- >
310
- {() => <h1>Click me!</h1>}
311
- </Clickable>
312
- <Switch>
313
- <Route path="/foo">
314
- <div>Hello, world!</div>
315
- </Route>
316
- </Switch>
317
- </div>
318
- </MemoryRouter>,
319
- );
320
-
321
- // Act
322
- await userEvent.click(await screen.findByTestId("button"));
323
-
324
- // Assert
325
- await waitFor(() => {
326
- expect(window.location.assign).toHaveBeenCalledWith("/foo");
327
- });
328
- });
329
-
330
- test("safeWithNav with skipClientNav=true waits for promise rejection", async () => {
331
- // Arrange
332
- render(
333
- <MemoryRouter>
334
- <div>
335
- <Clickable
336
- testId="button"
337
- href="/foo"
338
- safeWithNav={() => Promise.reject()}
339
- skipClientNav={true}
340
- >
341
- {() => <h1>Click me!</h1>}
342
- </Clickable>
343
- <Switch>
344
- <Route path="/foo">
345
- <div>Hello, world!</div>
346
- </Route>
347
- </Switch>
348
- </div>
349
- </MemoryRouter>,
350
- );
351
-
352
- // Act
353
- await userEvent.click(await screen.findByTestId("button"));
354
-
355
- // Assert
356
- await waitFor(() => {
357
- expect(window.location.assign).toHaveBeenCalledWith("/foo");
358
- });
359
- });
360
-
361
- test("safeWithNav with skipClientNav=false calls safeWithNav but doesn't wait to navigate", async () => {
362
- // Arrange
363
- const safeWithNavMock = jest.fn();
364
- render(
365
- <MemoryRouter>
366
- <div>
367
- <Clickable
368
- testId="button"
369
- href="/foo"
370
- safeWithNav={safeWithNavMock}
371
- skipClientNav={false}
372
- >
373
- {() => <h1>Click me!</h1>}
374
- </Clickable>
375
- <Switch>
376
- <Route path="/foo">
377
- <div>Hello, world!</div>
378
- </Route>
379
- </Switch>
380
- </div>
381
- </MemoryRouter>,
382
- );
383
-
384
- // Act
385
- await userEvent.click(await screen.findByTestId("button"));
386
-
387
- // Assert
388
- expect(safeWithNavMock).toHaveBeenCalled();
389
- // client side nav to /foo
390
- expect(await screen.findByText("Hello, world!")).toBeInTheDocument();
391
- // not a full page nav
392
- expect(window.location.assign).not.toHaveBeenCalledWith("/foo");
393
- });
394
-
395
- test("safeWithNav with beforeNav resolution and skipClientNav=false calls safeWithNav but doesn't wait to navigate", async () => {
396
- // Arrange
397
- const safeWithNavMock = jest.fn();
398
- render(
399
- <MemoryRouter>
400
- <div>
401
- <Clickable
402
- testId="button"
403
- href="/foo"
404
- beforeNav={() => Promise.resolve()}
405
- safeWithNav={safeWithNavMock}
406
- skipClientNav={false}
407
- >
408
- {() => <h1>Click me!</h1>}
409
- </Clickable>
410
- <Switch>
411
- <Route path="/foo">
412
- <div>Hello, world!</div>
413
- </Route>
414
- </Switch>
415
- </div>
416
- </MemoryRouter>,
417
- );
418
-
419
- // Act
420
- await userEvent.click(await screen.findByTestId("button"));
421
-
422
- // Assert
423
- await waitFor(() => {
424
- expect(safeWithNavMock).toHaveBeenCalled();
425
- });
426
- // client side nav to /foo
427
- expect(await screen.findByText("Hello, world!")).toBeInTheDocument();
428
- // not a full page nav
429
- expect(window.location.assign).not.toHaveBeenCalledWith("/foo");
430
- });
431
-
432
- test("should add aria-disabled if disabled is set", async () => {
433
- // Arrange
434
-
435
- // Act
436
- render(
437
- <Clickable testId="clickable-button" disabled={true}>
438
- {(eventState: any) => <h1>Click Me!</h1>}
439
- </Clickable>,
440
- );
441
-
442
- const button = await screen.findByTestId("clickable-button");
443
-
444
- // Assert
445
- expect(button).toHaveAttribute("aria-disabled", "true");
446
- });
447
-
448
- test("should add aria-label if one is passed in", async () => {
449
- // Arrange
450
-
451
- // Act
452
- render(
453
- <Clickable
454
- testId="clickable-button"
455
- aria-label="clickable-button-aria-label"
456
- >
457
- {(eventState: any) => <h1>Click Me!</h1>}
458
- </Clickable>,
459
- );
460
-
461
- const button = await screen.findByTestId("clickable-button");
462
-
463
- // Assert
464
- expect(button).toHaveAttribute(
465
- "aria-label",
466
- "clickable-button-aria-label",
467
- );
468
- });
469
-
470
- test("should not have an aria-label if one is not passed in", async () => {
471
- // Arrange
472
-
473
- // Act
474
- render(
475
- <Clickable testId="clickable-button">
476
- {(eventState: any) => <h1>Click Me!</h1>}
477
- </Clickable>,
478
- );
479
-
480
- const button = await screen.findByTestId("clickable-button");
481
-
482
- // Assert
483
- expect(button).not.toHaveAttribute("aria-label");
484
- });
485
-
486
- test("allow keyboard navigation when disabled is set", async () => {
487
- // Arrange
488
- render(
489
- <div>
490
- <button>First focusable button</button>
491
- <Clickable testId="clickable-button" disabled={true}>
492
- {(eventState: any) => <h1>Click Me!</h1>}
493
- </Clickable>
494
- </div>,
495
- );
496
-
497
- // Act
498
- // RTL's focuses on `document.body` by default, so we need to focus on
499
- // the first button
500
- await userEvent.tab();
501
-
502
- // Then we focus on our Clickable button.
503
- await userEvent.tab();
504
-
505
- const button = await screen.findByTestId("clickable-button");
506
-
507
- // Assert
508
- expect(button).toHaveFocus();
509
- });
510
-
511
- test("should not have a tabIndex if one is not set", async () => {
512
- // Arrange
513
-
514
- // Act
515
- render(
516
- <Clickable testId="clickable-button">
517
- {(eventState: any) => <h1>Click Me!</h1>}
518
- </Clickable>,
519
- );
520
-
521
- const button = await screen.findByTestId("clickable-button");
522
-
523
- // Assert
524
- expect(button).not.toHaveAttribute("tabIndex");
525
- });
526
-
527
- test("should have the tabIndex that is passed in", async () => {
528
- // Arrange
529
-
530
- // Act
531
- render(
532
- <Clickable testId="clickable-button" tabIndex={1}>
533
- {(eventState: any) => <h1>Click Me!</h1>}
534
- </Clickable>,
535
- );
536
-
537
- const button = await screen.findByTestId("clickable-button");
538
-
539
- // Assert
540
- expect(button).toHaveAttribute("tabIndex", "1");
541
- });
542
-
543
- test("forwards the ref to the clickable button element", async () => {
544
- // Arrange
545
- const ref: React.RefObject<HTMLButtonElement> = React.createRef();
546
-
547
- // Act
548
- render(
549
- <Clickable testId="clickable-button" ref={ref}>
550
- {(eventState: any) => <h1>Click Me!</h1>}
551
- </Clickable>,
552
- );
553
-
554
- // Assert
555
- expect(ref.current).toBeInstanceOf(HTMLButtonElement);
556
- });
557
-
558
- test("forwards the ref to the clickable anchor element ", async () => {
559
- // Arrange
560
- const ref: React.RefObject<HTMLAnchorElement> = React.createRef();
561
-
562
- // Act
563
- render(
564
- <Clickable href="/test-url" testId="clickable-anchor" ref={ref}>
565
- {(eventState: any) => <h1>Click Me!</h1>}
566
- </Clickable>,
567
- );
568
-
569
- // Assert
570
- expect(ref.current).toBeInstanceOf(HTMLAnchorElement);
571
- });
572
-
573
- describe("raw events", () => {
574
- /**
575
- * Clickable expect a function as children so we create a simple wrapper to
576
- * allow a React.Node to be passed instead.
577
- */
578
- const ClickableWrapper = ({
579
- children,
580
- ...restProps
581
- }: {
582
- children: React.ReactNode;
583
- onKeyDown?: (e: React.KeyboardEvent) => unknown;
584
- onKeyUp?: (e: React.KeyboardEvent) => unknown;
585
- onMouseDown?: (e: React.MouseEvent) => unknown;
586
- onMouseUp?: (e: React.MouseEvent) => unknown;
587
- }) => {
588
- return <Clickable {...restProps}>{() => children}</Clickable>;
589
- };
590
-
591
- test("onKeyDown", async () => {
592
- // Arrange
593
- let keyCode: any;
594
- render(
595
- <ClickableWrapper onKeyDown={(e) => (keyCode = e.keyCode)}>
596
- Click me!
597
- </ClickableWrapper>,
598
- );
599
-
600
- // Act
601
- // eslint-disable-next-line testing-library/prefer-user-event
602
- fireEvent.keyDown(await screen.findByRole("button"), {keyCode: 32});
603
-
604
- // Assert
605
- expect(keyCode).toEqual(32);
606
- });
607
-
608
- test("onKeyUp", async () => {
609
- // Arrange
610
- let keyCode: any;
611
- render(
612
- <ClickableWrapper onKeyUp={(e) => (keyCode = e.keyCode)}>
613
- Click me!
614
- </ClickableWrapper>,
615
- );
616
-
617
- // Act
618
- // eslint-disable-next-line testing-library/prefer-user-event
619
- fireEvent.keyUp(await screen.findByRole("button"), {keyCode: 32});
620
-
621
- // Assert
622
- expect(keyCode).toEqual(32);
623
- });
624
-
625
- test("onMouseDown", async () => {
626
- // Arrange
627
- let clientX: any;
628
- render(
629
- <Clickable onMouseDown={(e) => (clientX = e.clientX)}>
630
- {() => "Click me!"}
631
- </Clickable>,
632
- );
633
-
634
- // Act
635
- // eslint-disable-next-line testing-library/prefer-user-event
636
- fireEvent.mouseDown(await screen.findByRole("button"), {
637
- clientX: 10,
638
- });
639
-
640
- // Assert
641
- expect(clientX).toEqual(10);
642
- });
643
-
644
- test("onMouseUp", async () => {
645
- // Arrange
646
- let clientX: any;
647
- render(
648
- <Clickable onMouseUp={(e) => (clientX = e.clientX)}>
649
- {() => "Click me!"}
650
- </Clickable>,
651
- );
652
-
653
- // Act
654
- // eslint-disable-next-line testing-library/prefer-user-event
655
- fireEvent.mouseUp(await screen.findByRole("button"), {clientX: 10});
656
-
657
- // Assert
658
- expect(clientX).toEqual(10);
659
- });
660
- });
661
- });