@khanacademy/wonder-blocks-link 6.1.7 → 6.1.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.
- package/CHANGELOG.md +11 -0
- package/package.json +5 -5
- package/src/__tests__/__snapshots__/custom-snapshot.test.tsx.snap +0 -820
- package/src/__tests__/custom-snapshot.test.tsx +0 -79
- package/src/components/__tests__/link.test.tsx +0 -559
- package/src/components/__tests__/link.typestest.tsx +0 -40
- package/src/components/link-core.tsx +0 -289
- package/src/components/link.tsx +0 -291
- package/src/index.ts +0 -3
- package/tsconfig-build.json +0 -15
- package/tsconfig-build.tsbuildinfo +0 -1
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import {render} from "@testing-library/react";
|
|
4
|
-
import LinkCore from "../components/link-core";
|
|
5
|
-
import Link from "../components/link";
|
|
6
|
-
|
|
7
|
-
const defaultHandlers = {
|
|
8
|
-
onClick: () => void 0,
|
|
9
|
-
onMouseEnter: () => void 0,
|
|
10
|
-
onMouseLeave: () => void 0,
|
|
11
|
-
onMouseDown: () => void 0,
|
|
12
|
-
onMouseUp: () => void 0,
|
|
13
|
-
onTouchStart: () => void 0,
|
|
14
|
-
onTouchEnd: () => void 0,
|
|
15
|
-
onTouchCancel: () => void 0,
|
|
16
|
-
onKeyDown: () => void 0,
|
|
17
|
-
onKeyUp: () => void 0,
|
|
18
|
-
onFocus: () => void 0,
|
|
19
|
-
onBlur: () => void 0,
|
|
20
|
-
tabIndex: 0,
|
|
21
|
-
} as const;
|
|
22
|
-
|
|
23
|
-
describe("Link", () => {
|
|
24
|
-
test.each`
|
|
25
|
-
tabIndex
|
|
26
|
-
${-1}
|
|
27
|
-
${0}
|
|
28
|
-
${1}
|
|
29
|
-
`("<Link tabIndex={$tabIndex}>", ({tabIndex}: any) => {
|
|
30
|
-
const {container} = render(
|
|
31
|
-
<Link href="#" tabIndex={tabIndex}>
|
|
32
|
-
Click me
|
|
33
|
-
</Link>,
|
|
34
|
-
);
|
|
35
|
-
expect(container).toMatchSnapshot();
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe("LinkCore", () => {
|
|
40
|
-
for (const kind of ["primary", "secondary"] as const) {
|
|
41
|
-
for (const href of ["#", "#non-existent-link"]) {
|
|
42
|
-
for (const light of kind === "primary" ? [true, false] : [false]) {
|
|
43
|
-
for (const visitable of kind === "primary"
|
|
44
|
-
? [true, false]
|
|
45
|
-
: [false]) {
|
|
46
|
-
for (const inline of [true, false]) {
|
|
47
|
-
for (const state of ["focused", "hovered", "pressed"]) {
|
|
48
|
-
const stateProps = {
|
|
49
|
-
focused: state === "focused",
|
|
50
|
-
hovered: state === "hovered",
|
|
51
|
-
pressed: state === "pressed",
|
|
52
|
-
waiting: false,
|
|
53
|
-
} as const;
|
|
54
|
-
test(`kind:${kind} href:${href} light:${String(
|
|
55
|
-
light,
|
|
56
|
-
)} visitable:${String(visitable)} ${state}`, () => {
|
|
57
|
-
const {container} = render(
|
|
58
|
-
<LinkCore
|
|
59
|
-
href="#"
|
|
60
|
-
inline={inline}
|
|
61
|
-
kind={kind}
|
|
62
|
-
light={light}
|
|
63
|
-
visitable={visitable}
|
|
64
|
-
{...stateProps}
|
|
65
|
-
{...defaultHandlers}
|
|
66
|
-
>
|
|
67
|
-
Click me
|
|
68
|
-
</LinkCore>,
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
expect(container).toMatchSnapshot();
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
});
|
|
@@ -1,559 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {MemoryRouter, Route, Switch} from "react-router-dom";
|
|
3
|
-
import {fireEvent, render, screen, waitFor} from "@testing-library/react";
|
|
4
|
-
import {userEvent} from "@testing-library/user-event";
|
|
5
|
-
|
|
6
|
-
import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
|
|
7
|
-
import plusIcon from "@phosphor-icons/core/bold/plus-bold.svg";
|
|
8
|
-
|
|
9
|
-
import Link from "../link";
|
|
10
|
-
|
|
11
|
-
describe("Link", () => {
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
// Note: window.location.assign needs a mock function in the testing
|
|
14
|
-
// environment.
|
|
15
|
-
// @ts-expect-error [FEI-5019] - TS2790 - The operand of a 'delete' operator must be optional.
|
|
16
|
-
delete window.location;
|
|
17
|
-
// @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.
|
|
18
|
-
window.location = {assign: jest.fn(), origin: "http://localhost"};
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
// @ts-expect-error [FEI-5019] - TS2339 - Property 'mockClear' does not exist on type '(url: string | URL) => void'.
|
|
23
|
-
window.location.assign.mockClear();
|
|
24
|
-
jest.clearAllMocks();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe("client-side navigation", () => {
|
|
28
|
-
test("works for known URLs", async () => {
|
|
29
|
-
// Arrange
|
|
30
|
-
render(
|
|
31
|
-
<MemoryRouter>
|
|
32
|
-
<div>
|
|
33
|
-
<Link href="/foo">Click me!</Link>
|
|
34
|
-
<Switch>
|
|
35
|
-
<Route path="/foo">
|
|
36
|
-
<div id="foo">Hello, world!</div>
|
|
37
|
-
</Route>
|
|
38
|
-
</Switch>
|
|
39
|
-
</div>
|
|
40
|
-
</MemoryRouter>,
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
// Act
|
|
44
|
-
const link = await screen.findByText("Click me!");
|
|
45
|
-
await userEvent.click(link);
|
|
46
|
-
|
|
47
|
-
// Assert
|
|
48
|
-
expect(
|
|
49
|
-
await screen.findByText("Hello, world!"),
|
|
50
|
-
).toBeInTheDocument();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("navigation to without route does not render", async () => {
|
|
54
|
-
// Arrange
|
|
55
|
-
render(
|
|
56
|
-
<MemoryRouter>
|
|
57
|
-
<div>
|
|
58
|
-
<Link href="/">Click me!</Link>
|
|
59
|
-
<Switch>
|
|
60
|
-
<Route path="/foo">
|
|
61
|
-
<div id="foo">Hello, world!</div>
|
|
62
|
-
</Route>
|
|
63
|
-
</Switch>
|
|
64
|
-
</div>
|
|
65
|
-
</MemoryRouter>,
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
// Act
|
|
69
|
-
const link = await screen.findByText("Click me!");
|
|
70
|
-
await userEvent.click(link);
|
|
71
|
-
|
|
72
|
-
// Assert
|
|
73
|
-
expect(screen.queryByText("Hello, world!")).not.toBeInTheDocument();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// NOTE(john): This fails after upgrading to user-event v14.
|
|
77
|
-
// I believe that the act() call is resolving the promise, so this
|
|
78
|
-
// test is no longer doing what it expects.
|
|
79
|
-
test.skip("waits until beforeNav resolves before navigating", async () => {
|
|
80
|
-
// Arrange
|
|
81
|
-
render(
|
|
82
|
-
<MemoryRouter>
|
|
83
|
-
<div>
|
|
84
|
-
<Link href="/foo" beforeNav={() => Promise.resolve()}>
|
|
85
|
-
Click me!
|
|
86
|
-
</Link>
|
|
87
|
-
<Switch>
|
|
88
|
-
<Route path="/foo">
|
|
89
|
-
<div id="foo">Hello, world!</div>
|
|
90
|
-
</Route>
|
|
91
|
-
</Switch>
|
|
92
|
-
</div>
|
|
93
|
-
</MemoryRouter>,
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
// Act
|
|
97
|
-
const link = await screen.findByText("Click me!");
|
|
98
|
-
await userEvent.click(link);
|
|
99
|
-
|
|
100
|
-
// Assert
|
|
101
|
-
expect(screen.queryByText("Hello, world!")).not.toBeInTheDocument();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// NOTE(john): This fails after upgrading to user-event v14.
|
|
105
|
-
// I believe that the act() call is resolving the promise, so this
|
|
106
|
-
// test is no longer doing what it expects.
|
|
107
|
-
test.skip("doesn't navigate before beforeNav resolves", async () => {
|
|
108
|
-
// Arrange
|
|
109
|
-
render(
|
|
110
|
-
<MemoryRouter>
|
|
111
|
-
<div>
|
|
112
|
-
<Link href="/foo" beforeNav={() => Promise.resolve()}>
|
|
113
|
-
Click me!
|
|
114
|
-
</Link>
|
|
115
|
-
<Switch>
|
|
116
|
-
<Route path="/foo">
|
|
117
|
-
<div id="foo">Hello, world!</div>
|
|
118
|
-
</Route>
|
|
119
|
-
</Switch>
|
|
120
|
-
</div>
|
|
121
|
-
</MemoryRouter>,
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
// Act
|
|
125
|
-
const link = await screen.findByText("Click me!");
|
|
126
|
-
await userEvent.click(link);
|
|
127
|
-
|
|
128
|
-
// Assert
|
|
129
|
-
expect(screen.queryByText("Hello, world!")).not.toBeInTheDocument();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("does not navigate if beforeNav rejects", async () => {
|
|
133
|
-
// Arrange
|
|
134
|
-
render(
|
|
135
|
-
<MemoryRouter>
|
|
136
|
-
<div>
|
|
137
|
-
<Link href="/foo" beforeNav={() => Promise.reject()}>
|
|
138
|
-
Click me!
|
|
139
|
-
</Link>
|
|
140
|
-
<Switch>
|
|
141
|
-
<Route path="/foo">
|
|
142
|
-
<div id="foo">Hello, world!</div>
|
|
143
|
-
</Route>
|
|
144
|
-
</Switch>
|
|
145
|
-
</div>
|
|
146
|
-
</MemoryRouter>,
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
// Act
|
|
150
|
-
const link = await screen.findByText("Click me!");
|
|
151
|
-
await userEvent.click(link);
|
|
152
|
-
|
|
153
|
-
// Assert
|
|
154
|
-
expect(screen.queryByText("Hello, world!")).not.toBeInTheDocument();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("runs safeWithNav if set", async () => {
|
|
158
|
-
// Arrange
|
|
159
|
-
const safeWithNavMock = jest.fn();
|
|
160
|
-
render(
|
|
161
|
-
<MemoryRouter>
|
|
162
|
-
<div>
|
|
163
|
-
<Link
|
|
164
|
-
href="/foo"
|
|
165
|
-
beforeNav={() => Promise.resolve()}
|
|
166
|
-
safeWithNav={safeWithNavMock}
|
|
167
|
-
>
|
|
168
|
-
Click me!
|
|
169
|
-
</Link>
|
|
170
|
-
<Switch>
|
|
171
|
-
<Route path="/foo">
|
|
172
|
-
<div id="foo">Hello, world!</div>
|
|
173
|
-
</Route>
|
|
174
|
-
</Switch>
|
|
175
|
-
</div>
|
|
176
|
-
</MemoryRouter>,
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
// Act
|
|
180
|
-
const link = await screen.findByText("Click me!");
|
|
181
|
-
await userEvent.click(link);
|
|
182
|
-
|
|
183
|
-
// Assert
|
|
184
|
-
await waitFor(() => {
|
|
185
|
-
expect(safeWithNavMock).toHaveBeenCalled();
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// NOTE(john): This fails after upgrading to user-event v14.
|
|
190
|
-
// I believe that the act() call is resolving the promise, so this
|
|
191
|
-
// test is no longer doing what it expects.
|
|
192
|
-
test.skip("doesn't run safeWithNav until beforeNav resolves", async () => {
|
|
193
|
-
// Arrange
|
|
194
|
-
const safeWithNavMock = jest.fn();
|
|
195
|
-
render(
|
|
196
|
-
<MemoryRouter>
|
|
197
|
-
<div>
|
|
198
|
-
<Link
|
|
199
|
-
href="/foo"
|
|
200
|
-
beforeNav={() => Promise.resolve()}
|
|
201
|
-
safeWithNav={safeWithNavMock}
|
|
202
|
-
>
|
|
203
|
-
Click me!
|
|
204
|
-
</Link>
|
|
205
|
-
<Switch>
|
|
206
|
-
<Route path="/foo">
|
|
207
|
-
<div id="foo">Hello, world!</div>
|
|
208
|
-
</Route>
|
|
209
|
-
</Switch>
|
|
210
|
-
</div>
|
|
211
|
-
</MemoryRouter>,
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
// Act
|
|
215
|
-
const link = await screen.findByText("Click me!");
|
|
216
|
-
await userEvent.click(link);
|
|
217
|
-
|
|
218
|
-
// Assert
|
|
219
|
-
expect(safeWithNavMock).not.toHaveBeenCalled();
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
describe("full page load navigation", () => {
|
|
224
|
-
// NOTE(john): This fails after upgrading to user-event v14.
|
|
225
|
-
// I believe that the act() call is resolving the promise, so this
|
|
226
|
-
// test is no longer doing what it expects.
|
|
227
|
-
test.skip("doesn't redirect if safeWithNav hasn't resolved yet when skipClientNav=true", async () => {
|
|
228
|
-
// Arrange
|
|
229
|
-
jest.spyOn(window.location, "assign").mockImplementation(() => {});
|
|
230
|
-
render(
|
|
231
|
-
<Link
|
|
232
|
-
href="/foo"
|
|
233
|
-
safeWithNav={() => Promise.resolve()}
|
|
234
|
-
skipClientNav={true}
|
|
235
|
-
>
|
|
236
|
-
Click me!
|
|
237
|
-
</Link>,
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
// Act
|
|
241
|
-
const link = await screen.findByText("Click me!");
|
|
242
|
-
await userEvent.click(link);
|
|
243
|
-
|
|
244
|
-
// Assert
|
|
245
|
-
expect(window.location.assign).not.toHaveBeenCalled();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test("redirects after safeWithNav resolves when skipClientNav=true", async () => {
|
|
249
|
-
// Arrange
|
|
250
|
-
jest.spyOn(window.location, "assign").mockImplementation(() => {});
|
|
251
|
-
render(
|
|
252
|
-
<Link
|
|
253
|
-
href="/foo"
|
|
254
|
-
safeWithNav={() => Promise.resolve()}
|
|
255
|
-
skipClientNav={true}
|
|
256
|
-
>
|
|
257
|
-
Click me!
|
|
258
|
-
</Link>,
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
// Act
|
|
262
|
-
const link = await screen.findByText("Click me!");
|
|
263
|
-
await userEvent.click(link);
|
|
264
|
-
|
|
265
|
-
// Assert
|
|
266
|
-
await waitFor(() => {
|
|
267
|
-
expect(window.location.assign).toHaveBeenCalledWith("/foo");
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
test("redirects after beforeNav and safeWithNav resolve when skipClientNav=true", async () => {
|
|
272
|
-
// Arrange
|
|
273
|
-
jest.spyOn(window.location, "assign").mockImplementation(() => {});
|
|
274
|
-
render(
|
|
275
|
-
<Link
|
|
276
|
-
href="/foo"
|
|
277
|
-
beforeNav={() => Promise.resolve()}
|
|
278
|
-
safeWithNav={() => Promise.resolve()}
|
|
279
|
-
skipClientNav={true}
|
|
280
|
-
>
|
|
281
|
-
Click me!
|
|
282
|
-
</Link>,
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
// Act
|
|
286
|
-
const link = await screen.findByText("Click me!");
|
|
287
|
-
await userEvent.click(link);
|
|
288
|
-
|
|
289
|
-
// Assert
|
|
290
|
-
await waitFor(() => {
|
|
291
|
-
expect(window.location.assign).toHaveBeenCalledWith("/foo");
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// NOTE(john): This fails after upgrading to user-event v14.
|
|
296
|
-
// I believe that the act() call is resolving the promise, so this
|
|
297
|
-
// test is no longer doing what it expects.
|
|
298
|
-
test.skip("doesn't redirect before beforeNav resolves when skipClientNav=true", async () => {
|
|
299
|
-
// Arrange
|
|
300
|
-
jest.spyOn(window.location, "assign").mockImplementation(() => {});
|
|
301
|
-
render(
|
|
302
|
-
<Link
|
|
303
|
-
href="/foo"
|
|
304
|
-
beforeNav={() => Promise.resolve()}
|
|
305
|
-
skipClientNav={true}
|
|
306
|
-
>
|
|
307
|
-
Click me!
|
|
308
|
-
</Link>,
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
// Act
|
|
312
|
-
const link = await screen.findByText("Click me!");
|
|
313
|
-
await userEvent.click(link);
|
|
314
|
-
|
|
315
|
-
// Assert
|
|
316
|
-
expect(window.location.assign).not.toHaveBeenCalled();
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
describe("raw events", () => {
|
|
321
|
-
test("onKeyDown", async () => {
|
|
322
|
-
// Arrange
|
|
323
|
-
let keyCode: any;
|
|
324
|
-
render(
|
|
325
|
-
<Link href="/foo" onKeyDown={(e: any) => (keyCode = e.keyCode)}>
|
|
326
|
-
Click me!
|
|
327
|
-
</Link>,
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
// Act
|
|
331
|
-
const link = await screen.findByText("Click me!");
|
|
332
|
-
// eslint-disable-next-line testing-library/prefer-user-event
|
|
333
|
-
fireEvent.keyDown(link, {keyCode: 32});
|
|
334
|
-
|
|
335
|
-
// Assert
|
|
336
|
-
expect(keyCode).toEqual(32);
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
test("onKeyUp", async () => {
|
|
340
|
-
// Arrange
|
|
341
|
-
let keyCode: any;
|
|
342
|
-
render(
|
|
343
|
-
<Link href="/foo" onKeyUp={(e: any) => (keyCode = e.keyCode)}>
|
|
344
|
-
Click me!
|
|
345
|
-
</Link>,
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
// Act
|
|
349
|
-
const link = await screen.findByText("Click me!");
|
|
350
|
-
// eslint-disable-next-line testing-library/prefer-user-event
|
|
351
|
-
fireEvent.keyUp(link, {keyCode: 32});
|
|
352
|
-
|
|
353
|
-
// Assert
|
|
354
|
-
expect(keyCode).toEqual(32);
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
describe("external link that opens in a new tab", () => {
|
|
359
|
-
test("target attribute passed down correctly", async () => {
|
|
360
|
-
// Arrange
|
|
361
|
-
render(
|
|
362
|
-
<Link href="https://www.google.com/" target="_blank">
|
|
363
|
-
Click me!
|
|
364
|
-
</Link>,
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
// Act
|
|
368
|
-
const link = await screen.findByRole("link");
|
|
369
|
-
|
|
370
|
-
// Assert
|
|
371
|
-
expect(link).toHaveAttribute("target", "_blank");
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
test("render external icon when `target=_blank` and link is external", async () => {
|
|
375
|
-
// Arrange
|
|
376
|
-
render(
|
|
377
|
-
<Link href="https://www.google.com/" target="_blank">
|
|
378
|
-
Click me!
|
|
379
|
-
</Link>,
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
// Act
|
|
383
|
-
const icon = await screen.findByTestId("external-icon");
|
|
384
|
-
|
|
385
|
-
// Assert
|
|
386
|
-
expect(icon).toHaveStyle({
|
|
387
|
-
maskImage: "url(arrow-square-out-bold.svg)",
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test("does not render external icon when `target=_blank` and link is relative", async () => {
|
|
392
|
-
// Arrange
|
|
393
|
-
render(
|
|
394
|
-
<Link href="/" target="_blank">
|
|
395
|
-
Click me!
|
|
396
|
-
</Link>,
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
// Act
|
|
400
|
-
const icon = screen.queryByTestId("external-icon");
|
|
401
|
-
|
|
402
|
-
// Assert
|
|
403
|
-
expect(icon).not.toBeInTheDocument();
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
test("does not render external icon when there is no target and link is external", async () => {
|
|
407
|
-
// Arrange
|
|
408
|
-
render(<Link href="https://www.google.com/">Click me!</Link>);
|
|
409
|
-
|
|
410
|
-
// Act
|
|
411
|
-
const icon = screen.queryByTestId("external-icon");
|
|
412
|
-
|
|
413
|
-
// Assert
|
|
414
|
-
expect(icon).not.toBeInTheDocument();
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
test("does not render external icon when there is no target and link is relative", async () => {
|
|
418
|
-
// Arrange
|
|
419
|
-
render(<Link href="/">Click me!</Link>);
|
|
420
|
-
|
|
421
|
-
// Act
|
|
422
|
-
const icon = screen.queryByTestId("external-icon");
|
|
423
|
-
|
|
424
|
-
// Assert
|
|
425
|
-
expect(icon).not.toBeInTheDocument();
|
|
426
|
-
});
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
describe("start and end icons", () => {
|
|
430
|
-
test("render icon with link when startIcon prop is passed in", async () => {
|
|
431
|
-
// Arrange
|
|
432
|
-
render(
|
|
433
|
-
<Link
|
|
434
|
-
href="https://www.khanacademy.org/"
|
|
435
|
-
startIcon={<PhosphorIcon icon={plusIcon} />}
|
|
436
|
-
>
|
|
437
|
-
Add new item
|
|
438
|
-
</Link>,
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
// Act
|
|
442
|
-
const icon = await screen.findByTestId("start-icon");
|
|
443
|
-
|
|
444
|
-
// Assert
|
|
445
|
-
expect(icon).toHaveStyle({maskImage: "url(plus-bold.svg)"});
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
test("does not render icon when startIcon prop is not passed in", async () => {
|
|
449
|
-
// Arrange
|
|
450
|
-
render(<Link href="https://www.khanacademy.org/">Click me!</Link>);
|
|
451
|
-
|
|
452
|
-
// Act
|
|
453
|
-
const icon = screen.queryByTestId("start-icon");
|
|
454
|
-
|
|
455
|
-
// Assert
|
|
456
|
-
expect(icon).not.toBeInTheDocument();
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
test("startIcon prop passed down correctly", async () => {
|
|
460
|
-
// Arrange
|
|
461
|
-
render(
|
|
462
|
-
<Link
|
|
463
|
-
href="https://www.khanacademy.org/"
|
|
464
|
-
startIcon={<PhosphorIcon icon={plusIcon} />}
|
|
465
|
-
>
|
|
466
|
-
Add new item
|
|
467
|
-
</Link>,
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
// Act
|
|
471
|
-
const icon = await screen.findByTestId("start-icon");
|
|
472
|
-
|
|
473
|
-
// Assert
|
|
474
|
-
expect(icon).toHaveStyle({maskImage: "url(plus-bold.svg)"});
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
test("render icon with link when endIcon prop is passed in", async () => {
|
|
478
|
-
// Arrange
|
|
479
|
-
render(
|
|
480
|
-
<Link
|
|
481
|
-
href="https://www.khanacademy.org/"
|
|
482
|
-
endIcon={<PhosphorIcon icon={plusIcon} />}
|
|
483
|
-
>
|
|
484
|
-
Click to go back
|
|
485
|
-
</Link>,
|
|
486
|
-
);
|
|
487
|
-
|
|
488
|
-
// Act
|
|
489
|
-
const icon = await screen.findByTestId("end-icon");
|
|
490
|
-
|
|
491
|
-
// Assert
|
|
492
|
-
expect(icon).toHaveStyle({maskImage: "url(plus-bold.svg)"});
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
test("does not render icon when endIcon prop is not passed in", async () => {
|
|
496
|
-
// Arrange
|
|
497
|
-
render(<Link href="https://www.khanacademy.org/">Click me!</Link>);
|
|
498
|
-
|
|
499
|
-
// Act
|
|
500
|
-
const icon = screen.queryByTestId("end-icon");
|
|
501
|
-
|
|
502
|
-
// Assert
|
|
503
|
-
expect(icon).not.toBeInTheDocument();
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
test("does not render externalIcon when endIcon is passed in and `target='_blank'`", async () => {
|
|
507
|
-
// Arrange
|
|
508
|
-
render(
|
|
509
|
-
<Link
|
|
510
|
-
href="https://www.google.com/"
|
|
511
|
-
endIcon={<PhosphorIcon icon={plusIcon} />}
|
|
512
|
-
target="_blank"
|
|
513
|
-
>
|
|
514
|
-
Open a new tab
|
|
515
|
-
</Link>,
|
|
516
|
-
);
|
|
517
|
-
|
|
518
|
-
// Act
|
|
519
|
-
const externalIcon = screen.queryByTestId("external-icon");
|
|
520
|
-
|
|
521
|
-
// Assert
|
|
522
|
-
expect(externalIcon).not.toBeInTheDocument();
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
test("render endIcon instead of default externalIcon when `target='_blank' and link is external`", async () => {
|
|
526
|
-
// Arrange
|
|
527
|
-
render(
|
|
528
|
-
<Link
|
|
529
|
-
href="https://www.google.com/"
|
|
530
|
-
endIcon={<PhosphorIcon icon={plusIcon} />}
|
|
531
|
-
target="_blank"
|
|
532
|
-
>
|
|
533
|
-
Open a new tab
|
|
534
|
-
</Link>,
|
|
535
|
-
);
|
|
536
|
-
|
|
537
|
-
// Act
|
|
538
|
-
const icon = await screen.findByTestId("end-icon");
|
|
539
|
-
|
|
540
|
-
// Assert
|
|
541
|
-
expect(icon).toHaveStyle({maskImage: "url(plus-bold.svg)"});
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
test("endIcon prop passed down correctly", async () => {
|
|
545
|
-
// Arrange
|
|
546
|
-
render(
|
|
547
|
-
<Link href="/" endIcon={<PhosphorIcon icon={plusIcon} />}>
|
|
548
|
-
Click to go back
|
|
549
|
-
</Link>,
|
|
550
|
-
);
|
|
551
|
-
|
|
552
|
-
// Act
|
|
553
|
-
const icon = await screen.findByTestId("end-icon");
|
|
554
|
-
|
|
555
|
-
// Assert
|
|
556
|
-
expect(icon).toHaveStyle({maskImage: "url(plus-bold.svg)"});
|
|
557
|
-
});
|
|
558
|
-
});
|
|
559
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import Link from "../link";
|
|
4
|
-
|
|
5
|
-
// @ts-expect-error - href must be used with safeWithNav
|
|
6
|
-
<Link beforeNav={() => Promise.resolve()}>Hello, world!</Link>;
|
|
7
|
-
|
|
8
|
-
// @ts-expect-error - href must be used with safeWithNav
|
|
9
|
-
<Link safeWithNav={() => Promise.resolve()}>Hello, world!</Link>;
|
|
10
|
-
|
|
11
|
-
// It's okay to use onClick with href
|
|
12
|
-
<Link href="/foo" onClick={() => {}}>
|
|
13
|
-
Hello, world!
|
|
14
|
-
</Link>;
|
|
15
|
-
|
|
16
|
-
<Link href="/foo" beforeNav={() => Promise.resolve()}>
|
|
17
|
-
Hello, world!
|
|
18
|
-
</Link>;
|
|
19
|
-
|
|
20
|
-
<Link href="/foo" safeWithNav={() => Promise.resolve()}>
|
|
21
|
-
Hello, world!
|
|
22
|
-
</Link>;
|
|
23
|
-
|
|
24
|
-
// @ts-expect-error - `target="_blank"` cannot beused with `beforeNav`
|
|
25
|
-
<Link href="/foo" target="_blank" beforeNav={() => Promise.resolve()}>
|
|
26
|
-
Hello, world!
|
|
27
|
-
</Link>;
|
|
28
|
-
|
|
29
|
-
// All three of these props can be used together
|
|
30
|
-
<Link
|
|
31
|
-
href="/foo"
|
|
32
|
-
beforeNav={() => Promise.resolve()}
|
|
33
|
-
safeWithNav={() => Promise.resolve()}
|
|
34
|
-
onClick={() => {}}
|
|
35
|
-
>
|
|
36
|
-
Hello, world!
|
|
37
|
-
</Link>;
|
|
38
|
-
|
|
39
|
-
// It's also fine to use href by itself
|
|
40
|
-
<Link href="/foo">Hello, world!</Link>;
|