@khanacademy/wonder-blocks-card 0.0.0-PR2904-20251211224227 → 0.0.0-PR2905-20251212160058

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,6 +1,6 @@
1
1
  # @khanacademy/wonder-blocks-card
2
2
 
3
- ## 0.0.0-PR2904-20251211224227
3
+ ## 0.0.0-PR2905-20251212160058
4
4
 
5
5
  ### Minor Changes
6
6
 
@@ -8,10 +8,9 @@
8
8
 
9
9
  ### Patch Changes
10
10
 
11
- - Updated dependencies [19e4c20]
12
- - @khanacademy/wonder-blocks-core@0.0.0-PR2904-20251211224227
13
- - @khanacademy/wonder-blocks-icon-button@0.0.0-PR2904-20251211224227
14
- - @khanacademy/wonder-blocks-tokens@14.1.3
11
+ - Updated dependencies [2a46e5e]
12
+ - Updated dependencies [01198de]
13
+ - @khanacademy/wonder-blocks-icon-button@0.0.0-PR2905-20251212160058
15
14
 
16
15
  ## 1.3.2
17
16
 
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Card component for Wonder Blocks.",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "0.0.0-PR2904-20251211224227",
6
+ "version": "0.0.0-PR2905-20251212160058",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -21,8 +21,8 @@
21
21
  "types": "dist/index.d.ts",
22
22
  "source": "src/index.js",
23
23
  "dependencies": {
24
- "@khanacademy/wonder-blocks-core": "0.0.0-PR2904-20251211224227",
25
- "@khanacademy/wonder-blocks-icon-button": "0.0.0-PR2904-20251211224227",
24
+ "@khanacademy/wonder-blocks-core": "12.4.2",
25
+ "@khanacademy/wonder-blocks-icon-button": "0.0.0-PR2905-20251212160058",
26
26
  "@khanacademy/wonder-blocks-tokens": "14.1.3"
27
27
  },
28
28
  "peerDependencies": {
@@ -31,7 +31,7 @@
31
31
  "@phosphor-icons/core": "^2.0.2"
32
32
  },
33
33
  "devDependencies": {
34
- "@khanacademy/wb-dev-build-settings": "0.0.0-PR2904-20251211224227"
34
+ "@khanacademy/wb-dev-build-settings": "3.2.0"
35
35
  },
36
36
  "scripts": {
37
37
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -0,0 +1,301 @@
1
+ import * as React from "react";
2
+ import {render, screen} from "@testing-library/react";
3
+ import {userEvent} from "@testing-library/user-event";
4
+
5
+ import Card from "../../components/card";
6
+
7
+ describe("Card", () => {
8
+ describe("Basic rendering", () => {
9
+ it("should render children correctly", () => {
10
+ // Arrange
11
+ render(
12
+ <Card>
13
+ <div data-testid="child-content">Card Content</div>
14
+ </Card>,
15
+ );
16
+
17
+ // Act
18
+ const childContent = screen.getByTestId("child-content");
19
+
20
+ // Assert
21
+ expect(childContent).toBeInTheDocument();
22
+ });
23
+
24
+ it("should not render dismiss button by default", () => {
25
+ // Arrange
26
+ render(
27
+ <Card>
28
+ <div>Content</div>
29
+ </Card>,
30
+ );
31
+
32
+ // Act
33
+ const dismissButton = screen.queryByRole("button");
34
+
35
+ // Assert
36
+ expect(dismissButton).not.toBeInTheDocument();
37
+ });
38
+ });
39
+
40
+ describe("Dismiss button functionality", () => {
41
+ it("should render dismiss button when onDismiss is present", () => {
42
+ // Arrange
43
+ render(
44
+ <Card
45
+ onDismiss={() => {}}
46
+ labels={{dismissButtonAriaLabel: "Close"}}
47
+ >
48
+ <div>Content</div>
49
+ </Card>,
50
+ );
51
+
52
+ // Act
53
+ const dismissButton = screen.getByRole("button");
54
+
55
+ // Assert
56
+ expect(dismissButton).toBeInTheDocument();
57
+ });
58
+
59
+ it("should not render dismiss button there is no onDismiss prop", () => {
60
+ // Arrange
61
+ render(
62
+ <Card>
63
+ <div>Content</div>
64
+ </Card>,
65
+ );
66
+
67
+ // Act
68
+ const dismissButton = screen.queryByRole("button");
69
+
70
+ // Assert
71
+ expect(dismissButton).not.toBeInTheDocument();
72
+ });
73
+
74
+ it("should call onDismiss when dismiss button is clicked", async () => {
75
+ // Arrange
76
+ const mockOnDismiss = jest.fn();
77
+ render(
78
+ <Card
79
+ labels={{dismissButtonAriaLabel: "Close it!"}}
80
+ onDismiss={mockOnDismiss}
81
+ >
82
+ <div>Content</div>
83
+ </Card>,
84
+ );
85
+
86
+ // Act
87
+ const dismissButton = screen.getByRole("button", {
88
+ name: "Close it!",
89
+ });
90
+ await userEvent.click(dismissButton);
91
+
92
+ // Assert
93
+ expect(mockOnDismiss).toHaveBeenCalledTimes(1);
94
+ });
95
+ });
96
+
97
+ describe("Test IDs", () => {
98
+ it("should apply a custom testId for card", () => {
99
+ // Arrange
100
+ render(
101
+ <Card testId="my-card">
102
+ <div>Content</div>
103
+ </Card>,
104
+ );
105
+
106
+ // Act
107
+ const card = screen.getByTestId("my-card");
108
+
109
+ // Assert
110
+ expect(card).toBeInTheDocument();
111
+ });
112
+
113
+ it("should append -dismiss-button to testId for dismiss button", () => {
114
+ // Arrange
115
+ render(
116
+ <Card
117
+ testId="my-card"
118
+ onDismiss={() => {}}
119
+ labels={{dismissButtonAriaLabel: "Close"}}
120
+ >
121
+ <div>Content</div>
122
+ </Card>,
123
+ );
124
+
125
+ // Act
126
+ const dismissButton = screen.getByTestId("my-card-dismiss-button");
127
+
128
+ // Assert
129
+ expect(dismissButton).toBeInTheDocument();
130
+ });
131
+ });
132
+
133
+ describe("Accessibility", () => {
134
+ it("should pass custom aria-label to dismiss button", () => {
135
+ // Arrange
136
+ render(
137
+ <Card
138
+ onDismiss={() => {}}
139
+ labels={{dismissButtonAriaLabel: "Custom Close"}}
140
+ >
141
+ <div>Content</div>
142
+ </Card>,
143
+ );
144
+
145
+ // Act
146
+ const dismissButton = screen.getByRole("button", {
147
+ name: "Custom Close",
148
+ });
149
+
150
+ // Assert
151
+ expect(dismissButton).toBeInTheDocument();
152
+ });
153
+
154
+ it("should pass custom aria-describedby to dismiss button", () => {
155
+ // Arrange
156
+ render(
157
+ <Card
158
+ onDismiss={() => {}}
159
+ labels={{
160
+ dismissButtonAriaLabel: "Custom Close",
161
+ dismissButtonAriaDescribedBy:
162
+ "dismiss-button-describedby",
163
+ }}
164
+ >
165
+ <div>Content</div>
166
+ </Card>,
167
+ );
168
+
169
+ // Act
170
+ const dismissButton = screen.getByRole("button", {
171
+ name: "Custom Close",
172
+ });
173
+
174
+ // Assert
175
+ expect(dismissButton).toHaveAttribute(
176
+ "aria-describedby",
177
+ "dismiss-button-describedby",
178
+ );
179
+ });
180
+
181
+ it("should render with a custom tag", () => {
182
+ // Arrange
183
+ render(
184
+ <Card tag="section" labels={{cardAriaLabel: "Card Section"}}>
185
+ <h2>Heading</h2>
186
+ <p>Description</p>
187
+ </Card>,
188
+ );
189
+
190
+ // Act
191
+ const section = screen.getByRole("region");
192
+
193
+ // Assert
194
+ expect(section).toBeInTheDocument();
195
+ });
196
+
197
+ it("should apply labels.cardAriaLabel for a custom tag (preferred)", () => {
198
+ // Arrange
199
+ render(
200
+ <Card
201
+ tag="section"
202
+ labels={{cardAriaLabel: "Custom section label"}}
203
+ >
204
+ <h2>Heading</h2>
205
+ <p>Description</p>
206
+ </Card>,
207
+ );
208
+
209
+ // Act
210
+ const section = screen.getByRole("region", {
211
+ name: "Custom section label",
212
+ });
213
+
214
+ // Assert
215
+ expect(section).toBeInTheDocument();
216
+ });
217
+
218
+ it("should apply aria-labelledby for a custom tag", () => {
219
+ // Arrange
220
+ render(
221
+ <div>
222
+ <h2 id="card-heading">My Card Title</h2>
223
+ <Card tag="section" aria-labelledby="card-heading">
224
+ <p>Description</p>
225
+ </Card>
226
+ </div>,
227
+ );
228
+
229
+ // Act
230
+ const section = screen.getByRole("region", {
231
+ name: "My Card Title",
232
+ });
233
+
234
+ // Assert
235
+ expect(section).toBeInTheDocument();
236
+ });
237
+
238
+ it("should only apply aria-labelledby", () => {
239
+ // Arrange
240
+ render(
241
+ <div>
242
+ <h2 id="card-heading">My Card Title</h2>
243
+ <Card
244
+ tag="section"
245
+ aria-labelledby="card-heading"
246
+ aria-label="Fallback label"
247
+ >
248
+ <p>Description</p>
249
+ </Card>
250
+ </div>,
251
+ );
252
+
253
+ // Act
254
+ const section = screen.getByRole("region", {
255
+ name: "My Card Title",
256
+ });
257
+
258
+ // Assert
259
+ expect(section).toBeInTheDocument();
260
+ });
261
+
262
+ it("should apply the inert attribute", () => {
263
+ // Arrange
264
+ render(
265
+ <Card inert testId="card">
266
+ <h2>Heading</h2>
267
+ <p>Description</p>
268
+ <button>Button</button>
269
+ </Card>,
270
+ );
271
+
272
+ // Act
273
+ const section = screen.getByTestId("card");
274
+
275
+ // Assert
276
+ expect(section).toHaveAttribute("inert");
277
+ });
278
+ });
279
+
280
+ describe("Complex content scenarios", () => {
281
+ it("should work with fragment children", () => {
282
+ // Arrange
283
+ render(
284
+ <Card>
285
+ <>
286
+ <span data-testid="fragment-child-1">First</span>
287
+ <span data-testid="fragment-child-2">Second</span>
288
+ </>
289
+ </Card>,
290
+ );
291
+
292
+ // Act
293
+ const firstChild = screen.getByTestId("fragment-child-1");
294
+ const secondChild = screen.getByTestId("fragment-child-2");
295
+
296
+ // Assert
297
+ expect(firstChild).toBeInTheDocument();
298
+ expect(secondChild).toBeInTheDocument();
299
+ });
300
+ });
301
+ });
@@ -0,0 +1,207 @@
1
+ import * as React from "react";
2
+
3
+ // Import Button and Link for proper usage examples
4
+ import Button from "@khanacademy/wonder-blocks-button";
5
+ import Link from "@khanacademy/wonder-blocks-link";
6
+ import Card from "../../components/card";
7
+
8
+ /**
9
+ * Basic Card usage
10
+ */
11
+
12
+ <Card>Hello, world!</Card>;
13
+
14
+ <Card>
15
+ <div>Some content</div>
16
+ </Card>;
17
+
18
+ /**
19
+ * Card with Button and Link components (correct usage)
20
+ */
21
+
22
+ <Card>
23
+ <Button onClick={() => {}}>Click me</Button>
24
+ </Card>;
25
+
26
+ <Card>
27
+ <Link href="/foo">Click me</Link>
28
+ </Card>;
29
+
30
+ /**
31
+ * Card with all style props
32
+ */
33
+
34
+ <Card
35
+ background="base-default"
36
+ borderRadius="small"
37
+ paddingSize="small"
38
+ elevation="none"
39
+ >
40
+ Content
41
+ </Card>;
42
+
43
+ <Card
44
+ background="base-subtle"
45
+ borderRadius="medium"
46
+ paddingSize="medium"
47
+ elevation="low"
48
+ >
49
+ Content
50
+ </Card>;
51
+
52
+ <Card paddingSize="none">Content</Card>;
53
+
54
+ // @ts-expect-error - invalid background value
55
+ <Card background="invalid-background">Content</Card>;
56
+
57
+ // @ts-expect-error - invalid borderRadius value
58
+ <Card borderRadius="invalid-radius">Content</Card>;
59
+
60
+ // @ts-expect-error - invalid paddingSize value
61
+ <Card paddingSize="invalid-padding">Content</Card>;
62
+
63
+ // @ts-expect-error - invalid elevation value
64
+ <Card elevation="invalid-elevation">Content</Card>;
65
+
66
+ /**
67
+ * Card with dismiss functionality
68
+ */
69
+
70
+ <Card onDismiss={() => {}} labels={{dismissButtonAriaLabel: "Close card"}}>
71
+ Content
72
+ </Card>;
73
+
74
+ // @ts-expect-error - onDismiss requires dismissButtonAriaLabel
75
+ <Card onDismiss={() => {}}>Content</Card>;
76
+
77
+ // @ts-expect-error - onDismiss requires dismissButtonAriaLabel
78
+ <Card onDismiss={() => {}} labels={{}}>
79
+ Content
80
+ </Card>;
81
+
82
+ // @ts-expect-error - onClick is not allowed on Card wrapper
83
+ <Card onClick={() => {}}>Content</Card>;
84
+
85
+ /**
86
+ * Card with different HTML tags
87
+ */
88
+
89
+ <Card tag="div">Content</Card>;
90
+
91
+ // @ts-expect-error - button tag not allowed, use Wonder Blocks Button component instead
92
+ <Card tag="button">Content</Card>;
93
+
94
+ // @ts-expect-error - anchor tag not allowed, use Wonder Blocks Link component instead
95
+ <Card tag="a">Content</Card>;
96
+
97
+ <Card tag="section" labels={{cardAriaLabel: "Card section"}}>
98
+ Content
99
+ </Card>;
100
+
101
+ <Card tag="figure" labels={{cardAriaLabel: "Card figure"}}>
102
+ Content
103
+ </Card>;
104
+
105
+ <Card tag="section" aria-label="Card section">
106
+ Content
107
+ </Card>;
108
+
109
+ <Card tag="figure" aria-label="Card figure">
110
+ Content
111
+ </Card>;
112
+
113
+ <Card tag="section" aria-labelledby="someId">
114
+ <h2 id="someId">Card title</h2>
115
+ </Card>;
116
+
117
+ <Card tag="figure" aria-labelledby="someId2">
118
+ <h2 id="someId2">Card title</h2>
119
+ </Card>;
120
+
121
+ // @ts-expect-error - aria-labelledby cannot be used with labels.cardAriaLabel
122
+ <Card
123
+ tag="figure"
124
+ aria-labelledby="someId2"
125
+ labels={{cardAriaLabel: "preferred label"}}
126
+ >
127
+ <h2 id="someId2">Card title</h2>
128
+ </Card>;
129
+
130
+ // @ts-expect-error - aria-labelledby cannot be used with labels.cardAriaLabel
131
+ <Card
132
+ tag="section"
133
+ aria-labelledby="someId2"
134
+ labels={{cardAriaLabel: "preferred label"}}
135
+ >
136
+ <h2 id="someId2">Card title</h2>
137
+ </Card>;
138
+
139
+ <Card tag="figure" aria-labelledby="someId2" aria-label="fallback label">
140
+ <h2 id="someId2">Card title</h2>
141
+ </Card>;
142
+
143
+ <Card tag="section">Content</Card>;
144
+
145
+ <Card tag="figure">Content</Card>;
146
+
147
+ <Card tag="section" labels={{}}>
148
+ Content
149
+ </Card>;
150
+
151
+ <Card tag="figure" labels={{}}>
152
+ Content
153
+ </Card>;
154
+
155
+ /**
156
+ * Card with additional props
157
+ */
158
+
159
+ <Card testId="my-card">Content</Card>;
160
+
161
+ <Card inert>Content</Card>;
162
+
163
+ <Card ref={React.createRef<HTMLDivElement>()}>Content</Card>;
164
+
165
+ <Card styles={{root: {width: 200}, dismissButton: {position: "absolute"}}}>
166
+ Content
167
+ </Card>;
168
+
169
+ <Card
170
+ tag="section"
171
+ onDismiss={() => {}}
172
+ labels={{
173
+ cardAriaLabel: "Card section",
174
+ dismissButtonAriaLabel: "Close card",
175
+ }}
176
+ >
177
+ Content
178
+ </Card>;
179
+
180
+ /**
181
+ * Card with all props
182
+ */
183
+
184
+ <Card
185
+ aria-busy={true}
186
+ aria-roledescription="A custom card"
187
+ background="base-subtle"
188
+ borderRadius="medium"
189
+ paddingSize="medium"
190
+ elevation="low"
191
+ tag="figure"
192
+ onDismiss={() => {}}
193
+ labels={{
194
+ cardAriaLabel: "Card figure",
195
+ dismissButtonAriaLabel: "Close card",
196
+ }}
197
+ testId="complex-card"
198
+ inert
199
+ styles={{
200
+ root: {width: 300},
201
+ dismissButton: {top: 10, right: 10},
202
+ }}
203
+ ref={React.createRef<HTMLElement>()}
204
+ role="status"
205
+ >
206
+ <div>Complex content</div>
207
+ </Card>;