@khanacademy/wonder-blocks-clickable 2.1.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.
@@ -0,0 +1,500 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {MemoryRouter, Route, Switch} from "react-router-dom";
4
+ import {mount} from "enzyme";
5
+
6
+ import {View} from "@khanacademy/wonder-blocks-core";
7
+ import Clickable from "../clickable.js";
8
+
9
+ const wait = (delay: number = 0) =>
10
+ new Promise((resolve, reject) => {
11
+ // eslint-disable-next-line no-restricted-syntax
12
+ return setTimeout(resolve, delay);
13
+ });
14
+
15
+ describe("Clickable", () => {
16
+ test("client-side navigation", () => {
17
+ // Arrange
18
+ const wrapper = mount(
19
+ <MemoryRouter>
20
+ <View>
21
+ <Clickable testId="button" href="/foo">
22
+ {(eventState) => <h1>Click Me!</h1>}
23
+ </Clickable>
24
+ <Switch>
25
+ <Route path="/foo">
26
+ <View id="foo">Hello, world!</View>
27
+ </Route>
28
+ </Switch>
29
+ </View>
30
+ </MemoryRouter>,
31
+ );
32
+
33
+ // Act
34
+ const clickableWrapper = wrapper
35
+ .find(`[data-test-id="button"]`)
36
+ .first();
37
+ clickableWrapper.simulate("click", {button: 0});
38
+
39
+ // Assert
40
+ expect(wrapper.find("#foo").exists()).toBe(true);
41
+ });
42
+
43
+ test("client-side navigation with unknown URL fails", () => {
44
+ // Arrange
45
+ const wrapper = mount(
46
+ <MemoryRouter>
47
+ <View>
48
+ <Clickable testId="button" href="/unknown">
49
+ {(eventState) => <h1>Click Me!</h1>}
50
+ </Clickable>
51
+ <Switch>
52
+ <Route path="/foo">
53
+ <View id="foo">Hello, world!</View>
54
+ </Route>
55
+ </Switch>
56
+ </View>
57
+ </MemoryRouter>,
58
+ );
59
+
60
+ // Act
61
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
62
+ buttonWrapper.simulate("click", {button: 0});
63
+
64
+ // Assert
65
+ expect(wrapper.find("#foo").exists()).toBe(false);
66
+ });
67
+
68
+ test("client-side navigation with `skipClientNav` set to `true` fails", () => {
69
+ // Arrange
70
+ const wrapper = mount(
71
+ <MemoryRouter>
72
+ <View>
73
+ <Clickable testId="button" href="/foo" skipClientNav>
74
+ {(eventState) => <h1>Click Me!</h1>}
75
+ </Clickable>
76
+ <Switch>
77
+ <Route path="/foo">
78
+ <View id="foo">Hello, world!</View>
79
+ </Route>
80
+ </Switch>
81
+ </View>
82
+ </MemoryRouter>,
83
+ );
84
+
85
+ // Act
86
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
87
+ buttonWrapper.simulate("click", {button: 0});
88
+
89
+ // Assert
90
+ expect(wrapper.find("#foo").exists()).toBe(false);
91
+ });
92
+
93
+ test("disallow navigation when href and disabled are both set", () => {
94
+ // Arrange
95
+ const wrapper = mount(
96
+ <MemoryRouter>
97
+ <View>
98
+ <Clickable testId="button" href="/foo" disabled={true}>
99
+ {(eventState) => <h1>Click Me!</h1>}
100
+ </Clickable>
101
+ <Switch>
102
+ <Route path="/foo">
103
+ <View id="foo">Hello, world!</View>
104
+ </Route>
105
+ </Switch>
106
+ </View>
107
+ </MemoryRouter>,
108
+ );
109
+
110
+ // Act
111
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
112
+ buttonWrapper.simulate("click", {button: 0});
113
+
114
+ // Assert
115
+ expect(wrapper.find("#foo").exists()).toBe(false);
116
+ });
117
+
118
+ test("should verify if href is passed to Clickablebehavior", () => {
119
+ // Arrange, Act
120
+ const wrapper = mount(
121
+ <Clickable testId="button" href="/foo" skipClientNav={true}>
122
+ {(eventState) => <h1>Click Me!</h1>}
123
+ </Clickable>,
124
+ );
125
+
126
+ // Assert
127
+ expect(wrapper.find("ClickableBehavior")).toHaveProp({href: "/foo"});
128
+ });
129
+
130
+ test("should navigate to a specific link using the keyboard", () => {
131
+ // Arrange
132
+ window.location.assign = jest.fn();
133
+
134
+ const wrapper = mount(
135
+ <Clickable testId="button" href="/foo" skipClientNav={true}>
136
+ {(eventState) => <h1>Click Me!</h1>}
137
+ </Clickable>,
138
+ );
139
+
140
+ // Act
141
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
142
+ // simulate Enter
143
+ buttonWrapper.simulate("keyup", {keyCode: 13});
144
+
145
+ // Assert
146
+ expect(window.location.assign).toHaveBeenCalledWith("/foo");
147
+ });
148
+
149
+ test("beforeNav rejection blocks client-side navigation", async () => {
150
+ // Arrange
151
+ const wrapper = mount(
152
+ <MemoryRouter>
153
+ <div>
154
+ <Clickable
155
+ testId="button"
156
+ href="/foo"
157
+ beforeNav={(e) => Promise.reject()}
158
+ >
159
+ {() => <span>Click me!</span>}
160
+ </Clickable>
161
+ <Switch>
162
+ <Route path="/foo">
163
+ <div id="foo">Hello, world!</div>
164
+ </Route>
165
+ </Switch>
166
+ </div>
167
+ </MemoryRouter>,
168
+ );
169
+
170
+ // Act
171
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
172
+ buttonWrapper.simulate("click", {button: 0});
173
+ await wait(0);
174
+ buttonWrapper.update();
175
+
176
+ // Assert
177
+ expect(wrapper.find("#foo")).not.toExist();
178
+ });
179
+
180
+ test("beforeNav rejection blocks calling safeWithNav", async () => {
181
+ // Arrange
182
+ const safeWithNavMock = jest.fn();
183
+ const wrapper = mount(
184
+ <MemoryRouter>
185
+ <div>
186
+ <Clickable
187
+ testId="button"
188
+ href="/foo"
189
+ beforeNav={(e) => Promise.reject()}
190
+ safeWithNav={safeWithNavMock}
191
+ >
192
+ {() => <span>Click me!</span>}
193
+ </Clickable>
194
+ <Switch>
195
+ <Route path="/foo">
196
+ <div id="foo">Hello, world!</div>
197
+ </Route>
198
+ </Switch>
199
+ </div>
200
+ </MemoryRouter>,
201
+ );
202
+
203
+ // Act
204
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
205
+ buttonWrapper.simulate("click", {button: 0});
206
+ await wait(0);
207
+ buttonWrapper.update();
208
+
209
+ // Assert
210
+ expect(safeWithNavMock).not.toHaveBeenCalled();
211
+ });
212
+
213
+ test("beforeNav resolution results in client-side navigation", async () => {
214
+ // Arrange
215
+ const wrapper = mount(
216
+ <MemoryRouter>
217
+ <div>
218
+ <Clickable
219
+ testId="button"
220
+ href="/foo"
221
+ beforeNav={(e) => Promise.resolve()}
222
+ >
223
+ {() => <span>Click me!</span>}
224
+ </Clickable>
225
+ <Switch>
226
+ <Route path="/foo">
227
+ <div id="foo">Hello, world!</div>
228
+ </Route>
229
+ </Switch>
230
+ </div>
231
+ </MemoryRouter>,
232
+ );
233
+
234
+ // Act
235
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
236
+ buttonWrapper.simulate("click", {button: 0});
237
+ await wait(0);
238
+ buttonWrapper.update();
239
+
240
+ // Assert
241
+ expect(wrapper.find("#foo")).toExist();
242
+ });
243
+
244
+ test("beforeNav resolution results in safeWithNav being called", async () => {
245
+ // Arrange
246
+ const safeWithNavMock = jest.fn();
247
+ const wrapper = mount(
248
+ <MemoryRouter>
249
+ <div>
250
+ <Clickable
251
+ testId="button"
252
+ href="/foo"
253
+ beforeNav={(e) => Promise.resolve()}
254
+ safeWithNav={safeWithNavMock}
255
+ >
256
+ {() => <span>Click me!</span>}
257
+ </Clickable>
258
+ <Switch>
259
+ <Route path="/foo">
260
+ <div id="foo">Hello, world!</div>
261
+ </Route>
262
+ </Switch>
263
+ </div>
264
+ </MemoryRouter>,
265
+ );
266
+
267
+ // Act
268
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
269
+ buttonWrapper.simulate("click", {button: 0});
270
+ await wait(0);
271
+ buttonWrapper.update();
272
+
273
+ // Assert
274
+ expect(safeWithNavMock).toHaveBeenCalled();
275
+ });
276
+
277
+ test("safeWithNav with skipClientNav=true waits for promise resolution", async () => {
278
+ // Arrange
279
+ jest.spyOn(window.location, "assign");
280
+ const wrapper = mount(
281
+ <MemoryRouter>
282
+ <div>
283
+ <Clickable
284
+ testId="button"
285
+ href="/foo"
286
+ safeWithNav={(e) => Promise.resolve()}
287
+ skipClientNav={true}
288
+ >
289
+ {() => <h1>Click me!</h1>}
290
+ </Clickable>
291
+ <Switch>
292
+ <Route path="/foo">
293
+ <div id="foo">Hello, world!</div>
294
+ </Route>
295
+ </Switch>
296
+ </div>
297
+ </MemoryRouter>,
298
+ );
299
+
300
+ // Act
301
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
302
+ buttonWrapper.simulate("click", {button: 0});
303
+ await wait(0);
304
+ buttonWrapper.update();
305
+
306
+ // Assert
307
+ expect(window.location.assign).toHaveBeenCalledWith("/foo");
308
+ });
309
+
310
+ test("beforeNav resolution and safeWithNav with skipClientNav=true waits for promise resolution", async () => {
311
+ // Arrange
312
+ jest.spyOn(window.location, "assign");
313
+ const wrapper = mount(
314
+ <MemoryRouter>
315
+ <div>
316
+ <Clickable
317
+ testId="button"
318
+ href="/foo"
319
+ beforeNav={(e) => Promise.resolve()}
320
+ safeWithNav={(e) => Promise.resolve()}
321
+ skipClientNav={true}
322
+ >
323
+ {() => <h1>Click me!</h1>}
324
+ </Clickable>
325
+ <Switch>
326
+ <Route path="/foo">
327
+ <div id="foo">Hello, world!</div>
328
+ </Route>
329
+ </Switch>
330
+ </div>
331
+ </MemoryRouter>,
332
+ );
333
+
334
+ // Act
335
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
336
+ buttonWrapper.simulate("click", {button: 0});
337
+ await wait(0);
338
+ buttonWrapper.update();
339
+ await wait(0);
340
+ buttonWrapper.update();
341
+
342
+ // Assert
343
+ expect(window.location.assign).toHaveBeenCalledWith("/foo");
344
+ });
345
+
346
+ test("safeWithNav with skipClientNav=true waits for promise rejection", async () => {
347
+ // Arrange
348
+ jest.spyOn(window.location, "assign");
349
+ const wrapper = mount(
350
+ <MemoryRouter>
351
+ <div>
352
+ <Clickable
353
+ testId="button"
354
+ href="/foo"
355
+ safeWithNav={(e) => Promise.reject()}
356
+ skipClientNav={true}
357
+ >
358
+ {() => <h1>Click me!</h1>}
359
+ </Clickable>
360
+ <Switch>
361
+ <Route path="/foo">
362
+ <div id="foo">Hello, world!</div>
363
+ </Route>
364
+ </Switch>
365
+ </div>
366
+ </MemoryRouter>,
367
+ );
368
+
369
+ // Act
370
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
371
+ buttonWrapper.simulate("click", {button: 0});
372
+ await wait(0);
373
+ buttonWrapper.update();
374
+
375
+ // Assert
376
+ expect(window.location.assign).toHaveBeenCalledWith("/foo");
377
+ });
378
+
379
+ test("safeWithNav with skipClientNav=false calls safeWithNav but doesn't wait to navigate", async () => {
380
+ // Arrange
381
+ jest.spyOn(window.location, "assign");
382
+ const safeWithNavMock = jest.fn();
383
+ const wrapper = mount(
384
+ <MemoryRouter>
385
+ <div>
386
+ <Clickable
387
+ testId="button"
388
+ href="/foo"
389
+ safeWithNav={safeWithNavMock}
390
+ skipClientNav={false}
391
+ >
392
+ {() => <h1>Click me!</h1>}
393
+ </Clickable>
394
+ <Switch>
395
+ <Route path="/foo">
396
+ <div id="foo">Hello, world!</div>
397
+ </Route>
398
+ </Switch>
399
+ </div>
400
+ </MemoryRouter>,
401
+ );
402
+
403
+ // Act
404
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
405
+ buttonWrapper.simulate("click", {button: 0});
406
+
407
+ // Assert
408
+ expect(safeWithNavMock).toHaveBeenCalled();
409
+ expect(window.location.assign).toHaveBeenCalledWith("/foo");
410
+ });
411
+
412
+ test("safeWithNav with beforeNav resolution and skipClientNav=false calls safeWithNav but doesn't wait to navigate", async () => {
413
+ // Arrange
414
+ jest.spyOn(window.location, "assign");
415
+ const safeWithNavMock = jest.fn();
416
+ const wrapper = mount(
417
+ <MemoryRouter>
418
+ <div>
419
+ <Clickable
420
+ testId="button"
421
+ href="/foo"
422
+ beforeNav={() => Promise.resolve()}
423
+ safeWithNav={safeWithNavMock}
424
+ skipClientNav={false}
425
+ >
426
+ {() => <h1>Click me!</h1>}
427
+ </Clickable>
428
+ <Switch>
429
+ <Route path="/foo">
430
+ <div id="foo">Hello, world!</div>
431
+ </Route>
432
+ </Switch>
433
+ </div>
434
+ </MemoryRouter>,
435
+ );
436
+
437
+ // Act
438
+ const buttonWrapper = wrapper.find(`[data-test-id="button"]`).first();
439
+ buttonWrapper.simulate("click", {button: 0});
440
+ await wait(0);
441
+ buttonWrapper.update();
442
+
443
+ // Assert
444
+ expect(safeWithNavMock).toHaveBeenCalled();
445
+ expect(window.location.assign).toHaveBeenCalledWith("/foo");
446
+ });
447
+
448
+ describe("raw events", () => {
449
+ /**
450
+ * Clickable expect a function as children so we create a simple wrapper to
451
+ * allow a React.Node to be passed instead.
452
+ */
453
+ const ClickableWrapper = ({
454
+ children,
455
+ ...restProps
456
+ }: {|
457
+ children: React.Node,
458
+ onKeyDown?: (e: SyntheticKeyboardEvent<>) => mixed,
459
+ onKeyUp?: (e: SyntheticKeyboardEvent<>) => mixed,
460
+ |}) => {
461
+ return <Clickable {...restProps}>{() => children}</Clickable>;
462
+ };
463
+
464
+ test("onKeyDown", () => {
465
+ // Arrange
466
+ const keyMock = jest.fn();
467
+ const wrapper = mount(
468
+ <ClickableWrapper onKeyDown={keyMock}>
469
+ Click me!
470
+ </ClickableWrapper>,
471
+ );
472
+
473
+ // Act
474
+ wrapper.find("Clickable").simulate("keydown", {keyCode: 32});
475
+
476
+ // Assert
477
+ expect(keyMock).toHaveBeenCalledWith(
478
+ expect.objectContaining({keyCode: 32}),
479
+ );
480
+ });
481
+
482
+ test("onKeyUp", () => {
483
+ // Arrange
484
+ const keyMock = jest.fn();
485
+ const wrapper = mount(
486
+ <ClickableWrapper onKeyDown={keyMock}>
487
+ Click me!
488
+ </ClickableWrapper>,
489
+ );
490
+
491
+ // Act
492
+ wrapper.find("Clickable").simulate("keydown", {keyCode: 32});
493
+
494
+ // Assert
495
+ expect(keyMock).toHaveBeenCalledWith(
496
+ expect.objectContaining({keyCode: 32}),
497
+ );
498
+ });
499
+ });
500
+ });