@khanacademy/wonder-blocks-form 2.4.8 → 3.1.0
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 +18 -0
- package/dist/es/index.js +15 -13
- package/dist/index.js +75 -77
- package/docs.md +5 -1
- package/package.json +2 -2
- package/src/__docs__/_overview_.stories.mdx +15 -0
- package/src/components/__docs__/checkbox-group.stories.js +35 -1
- package/src/components/__docs__/labeled-text-field.argtypes.js +2 -2
- package/src/components/__docs__/labeled-text-field.stories.js +25 -0
- package/src/components/__docs__/radio-group.stories.js +35 -0
- package/src/components/__docs__/radio.stories.js +3 -2
- package/src/components/__tests__/checkbox-group.test.js +144 -67
- package/src/components/__tests__/field-heading.test.js +40 -0
- package/src/components/__tests__/radio-group.test.js +155 -58
- package/src/components/checkbox-group.js +9 -15
- package/src/components/checkbox.js +2 -2
- package/src/components/choice-internal.js +5 -3
- package/src/components/choice.js +2 -2
- package/src/components/field-heading.js +27 -43
- package/src/components/labeled-text-field.js +2 -3
- package/src/components/radio-group.js +6 -4
- package/src/components/radio.js +2 -2
- package/src/index.js +0 -2
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +0 -6126
- package/src/__tests__/generated-snapshot.test.js +0 -654
- package/src/components/checkbox-group.md +0 -200
- package/src/components/checkbox.md +0 -134
- package/src/components/field-heading.md +0 -43
- package/src/components/labeled-text-field.md +0 -535
- package/src/components/radio-group.md +0 -129
- package/src/components/radio.md +0 -26
- package/src/components/text-field.md +0 -770
|
@@ -166,6 +166,41 @@ MultipleChoiceStyling.parameters = {
|
|
|
166
166
|
},
|
|
167
167
|
};
|
|
168
168
|
|
|
169
|
+
export const FiltersOutFalsyChildren: StoryComponentType = () => {
|
|
170
|
+
return (
|
|
171
|
+
<RadioGroup
|
|
172
|
+
groupName="pokemon"
|
|
173
|
+
selectedValue="bulbasaur"
|
|
174
|
+
onChange={() => {}}
|
|
175
|
+
label="Pokemon"
|
|
176
|
+
description="Your first Pokemon."
|
|
177
|
+
>
|
|
178
|
+
<Choice label="Bulbasaur" value="bulbasaur" />
|
|
179
|
+
<Choice
|
|
180
|
+
label="Charmander"
|
|
181
|
+
value="charmander"
|
|
182
|
+
description="Oops, we ran out of Charmanders"
|
|
183
|
+
disabled
|
|
184
|
+
/>
|
|
185
|
+
<Choice label="Squirtle" value="squirtle" />
|
|
186
|
+
{false && <Choice label="Pikachu" value="pikachu" />}
|
|
187
|
+
</RadioGroup>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
FiltersOutFalsyChildren.parameters = {
|
|
192
|
+
docs: {
|
|
193
|
+
storyDescription: `This example shows that children can be falsy values and
|
|
194
|
+
that those falsy values are filtered out when rendering children. In this
|
|
195
|
+
case, one of the children is \`{false && <Choice .../>}\` which results in
|
|
196
|
+
that choice being filtered out.`,
|
|
197
|
+
},
|
|
198
|
+
chromatic: {
|
|
199
|
+
// The unit tests already verify that false-y children aren't rendered.
|
|
200
|
+
disableSnapshot: true,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
169
204
|
const styles = StyleSheet.create({
|
|
170
205
|
choice: {
|
|
171
206
|
margin: 0,
|
|
@@ -3,15 +3,16 @@ import * as React from "react";
|
|
|
3
3
|
import {StyleSheet} from "aphrodite";
|
|
4
4
|
|
|
5
5
|
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
-
import {Radio} from "@khanacademy/wonder-blocks-form";
|
|
7
6
|
import {LabelMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
|
|
8
7
|
import type {StoryComponentType} from "@storybook/react";
|
|
9
8
|
|
|
10
9
|
import ComponentInfo from "../../../../../.storybook/components/component-info.js";
|
|
11
10
|
import {name, version} from "../../../package.json";
|
|
12
11
|
|
|
12
|
+
import Radio from "../radio.js";
|
|
13
|
+
|
|
13
14
|
export default {
|
|
14
|
-
title: "Form / Radio",
|
|
15
|
+
title: "Form / Radio (internal)",
|
|
15
16
|
component: Radio,
|
|
16
17
|
parameters: {
|
|
17
18
|
componentSubtitle: ((
|
|
@@ -1,85 +1,162 @@
|
|
|
1
1
|
//@flow
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
4
|
-
import "
|
|
3
|
+
import {render, screen} from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
5
|
|
|
6
6
|
import CheckboxGroup from "../checkbox-group.js";
|
|
7
7
|
import Choice from "../choice.js";
|
|
8
8
|
|
|
9
9
|
describe("CheckboxGroup", () => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
10
|
+
describe("behavior", () => {
|
|
11
|
+
const TestComponent = ({errorMessage}: {|errorMessage?: string|}) => {
|
|
12
|
+
const [selectedValues, setSelectedValue] = React.useState([
|
|
13
|
+
"a",
|
|
14
|
+
"b",
|
|
15
|
+
]);
|
|
16
|
+
const handleChange = (selectedValues) => {
|
|
17
|
+
setSelectedValue(selectedValues);
|
|
18
|
+
};
|
|
19
|
+
return (
|
|
20
|
+
<CheckboxGroup
|
|
21
|
+
label="Test"
|
|
22
|
+
description="test description"
|
|
23
|
+
groupName="test"
|
|
24
|
+
onChange={handleChange}
|
|
25
|
+
selectedValues={selectedValues}
|
|
26
|
+
errorMessage={errorMessage}
|
|
27
|
+
>
|
|
28
|
+
<Choice label="a" value="a" aria-labelledby="test-a" />
|
|
29
|
+
<Choice label="b" value="b" aria-labelledby="test-b" />
|
|
30
|
+
<Choice label="c" value="c" aria-labelledby="test-c" />
|
|
31
|
+
</CheckboxGroup>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const c = group.find(Choice).at(2);
|
|
35
|
+
it("has the correct items checked", () => {
|
|
36
|
+
// Arrange, Act
|
|
37
|
+
render(<TestComponent />);
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
expect(a.prop("checked")).toEqual(true);
|
|
36
|
-
expect(b.prop("checked")).toEqual(true);
|
|
37
|
-
expect(c.prop("checked")).toEqual(false);
|
|
38
|
-
});
|
|
39
|
+
const checkboxes = screen.getAllByRole("checkbox");
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
// Assert
|
|
42
|
+
// a starts off checked
|
|
43
|
+
expect(checkboxes[0]).toBeChecked();
|
|
44
|
+
expect(checkboxes[1]).toBeChecked();
|
|
45
|
+
expect(checkboxes[2]).not.toBeChecked();
|
|
46
|
+
});
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
expect(c.prop("checked")).toEqual(false);
|
|
50
|
-
});
|
|
48
|
+
it("clicking a selected choice deselects it", () => {
|
|
49
|
+
// Arrange
|
|
50
|
+
render(<TestComponent />);
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
group.setProps({errorMessage: "there's an error"});
|
|
54
|
-
const a = group.find(Choice).at(0);
|
|
55
|
-
const b = group.find(Choice).at(1);
|
|
56
|
-
const c = group.find(Choice).at(2);
|
|
52
|
+
const checkboxes = screen.getAllByRole("checkbox");
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
// Act
|
|
55
|
+
userEvent.click(checkboxes[0]);
|
|
56
|
+
|
|
57
|
+
// Assert
|
|
58
|
+
expect(checkboxes[0]).not.toBeChecked();
|
|
59
|
+
expect(checkboxes[1]).toBeChecked();
|
|
60
|
+
expect(checkboxes[2]).not.toBeChecked();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should set aria-invalid on choices when there's an error message", () => {
|
|
64
|
+
// Arrange, Act
|
|
65
|
+
render(<TestComponent errorMessage="there's an error" />);
|
|
66
|
+
|
|
67
|
+
const checkboxes = screen.getAllByRole("checkbox");
|
|
68
|
+
|
|
69
|
+
// Assert
|
|
70
|
+
expect(checkboxes[0]).toHaveAttribute("aria-invalid", "true");
|
|
71
|
+
expect(checkboxes[1]).toHaveAttribute("aria-invalid", "true");
|
|
72
|
+
expect(checkboxes[2]).toHaveAttribute("aria-invalid", "true");
|
|
73
|
+
});
|
|
62
74
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
expect(onChange).toHaveBeenCalledTimes(2);
|
|
75
|
+
it("checks that aria attributes have been added correctly", () => {
|
|
76
|
+
// Arrange, Act
|
|
77
|
+
render(<TestComponent />);
|
|
78
|
+
|
|
79
|
+
const checkboxes = screen.getAllByRole("checkbox");
|
|
80
|
+
|
|
81
|
+
// Assert
|
|
82
|
+
expect(checkboxes[0]).toHaveAttribute("aria-labelledby", "test-a");
|
|
83
|
+
expect(checkboxes[1]).toHaveAttribute("aria-labelledby", "test-b");
|
|
84
|
+
expect(checkboxes[2]).toHaveAttribute("aria-labelledby", "test-c");
|
|
85
|
+
});
|
|
75
86
|
});
|
|
76
87
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
describe("flexible props", () => {
|
|
89
|
+
it("should render with a React.Node label", () => {
|
|
90
|
+
// Arrange, Act
|
|
91
|
+
render(
|
|
92
|
+
<CheckboxGroup
|
|
93
|
+
label={
|
|
94
|
+
<span>
|
|
95
|
+
label with <strong>strong</strong> text
|
|
96
|
+
</span>
|
|
97
|
+
}
|
|
98
|
+
groupName="test"
|
|
99
|
+
onChange={() => {}}
|
|
100
|
+
selectedValues={[]}
|
|
101
|
+
>
|
|
102
|
+
<Choice label="a" value="a" aria-labelledby="test-a" />
|
|
103
|
+
<Choice label="b" value="b" aria-labelledby="test-b" />
|
|
104
|
+
<Choice label="c" value="c" aria-labelledby="test-c" />
|
|
105
|
+
</CheckboxGroup>,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Assert
|
|
109
|
+
expect(screen.getByText("strong")).toBeInTheDocument();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should render with a React.Node description", () => {
|
|
113
|
+
// Arrange, Act
|
|
114
|
+
render(
|
|
115
|
+
<CheckboxGroup
|
|
116
|
+
label="label"
|
|
117
|
+
description={
|
|
118
|
+
<span>
|
|
119
|
+
description with <strong>strong</strong> text
|
|
120
|
+
</span>
|
|
121
|
+
}
|
|
122
|
+
groupName="test"
|
|
123
|
+
onChange={() => {}}
|
|
124
|
+
selectedValues={[]}
|
|
125
|
+
>
|
|
126
|
+
<Choice label="a" value="a" aria-labelledby="test-a" />
|
|
127
|
+
<Choice label="b" value="b" aria-labelledby="test-b" />
|
|
128
|
+
<Choice label="c" value="c" aria-labelledby="test-c" />
|
|
129
|
+
</CheckboxGroup>,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Assert
|
|
133
|
+
expect(screen.getByText("strong")).toBeInTheDocument();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should filter out false-y children when rendering", () => {
|
|
137
|
+
// Arrange, Act
|
|
138
|
+
render(
|
|
139
|
+
<CheckboxGroup
|
|
140
|
+
label="label"
|
|
141
|
+
description="description"
|
|
142
|
+
groupName="test"
|
|
143
|
+
onChange={() => {}}
|
|
144
|
+
selectedValues={[]}
|
|
145
|
+
>
|
|
146
|
+
<Choice label="a" value="a" aria-labelledby="test-a" />
|
|
147
|
+
{false && (
|
|
148
|
+
<Choice label="b" value="b" aria-labelledby="test-b" />
|
|
149
|
+
)}
|
|
150
|
+
<Choice label="c" value="c" aria-labelledby="test-c" />
|
|
151
|
+
{undefined}
|
|
152
|
+
{null}
|
|
153
|
+
</CheckboxGroup>,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Assert
|
|
157
|
+
const checkboxes = screen.getAllByRole("checkbox");
|
|
158
|
+
|
|
159
|
+
expect(checkboxes).toHaveLength(2);
|
|
160
|
+
});
|
|
84
161
|
});
|
|
85
162
|
});
|
|
@@ -4,6 +4,13 @@ import {mount} from "enzyme";
|
|
|
4
4
|
import "jest-enzyme";
|
|
5
5
|
import {StyleSheet} from "aphrodite";
|
|
6
6
|
|
|
7
|
+
import {I18nInlineMarkup} from "@khanacademy/wonder-blocks-i18n";
|
|
8
|
+
import {
|
|
9
|
+
Body,
|
|
10
|
+
LabelMedium,
|
|
11
|
+
LabelSmall,
|
|
12
|
+
} from "@khanacademy/wonder-blocks-typography";
|
|
13
|
+
|
|
7
14
|
import FieldHeading from "../field-heading.js";
|
|
8
15
|
import TextField from "../text-field.js";
|
|
9
16
|
|
|
@@ -180,4 +187,37 @@ describe("FieldHeading", () => {
|
|
|
180
187
|
const container = wrapper.find("View").at(0);
|
|
181
188
|
expect(container).toHaveStyle(styles.style1);
|
|
182
189
|
});
|
|
190
|
+
|
|
191
|
+
it("should render a LabelSmall when the 'label' prop is a I18nInlineMarkup", () => {
|
|
192
|
+
// Arrange
|
|
193
|
+
|
|
194
|
+
// Act
|
|
195
|
+
const wrapper = mount(
|
|
196
|
+
<FieldHeading
|
|
197
|
+
field={<TextField id="tf-1" value="" onChange={() => {}} />}
|
|
198
|
+
label={<I18nInlineMarkup>Hello, world!</I18nInlineMarkup>}
|
|
199
|
+
/>,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Assert
|
|
203
|
+
const label = wrapper.find(LabelMedium);
|
|
204
|
+
expect(label).toExist();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should render a LabelSmall when the 'description' prop is a I18nInlineMarkup", () => {
|
|
208
|
+
// Arrange
|
|
209
|
+
|
|
210
|
+
// Act
|
|
211
|
+
const wrapper = mount(
|
|
212
|
+
<FieldHeading
|
|
213
|
+
field={<TextField id="tf-1" value="" onChange={() => {}} />}
|
|
214
|
+
label={<Body>Hello, world</Body>}
|
|
215
|
+
description={<I18nInlineMarkup>description</I18nInlineMarkup>}
|
|
216
|
+
/>,
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Assert
|
|
220
|
+
const label = wrapper.find(LabelSmall);
|
|
221
|
+
expect(label).toExist();
|
|
222
|
+
});
|
|
183
223
|
});
|
|
@@ -1,85 +1,182 @@
|
|
|
1
1
|
//@flow
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
4
|
-
import "
|
|
3
|
+
import {render, screen} from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
5
|
|
|
6
6
|
import RadioGroup from "../radio-group.js";
|
|
7
7
|
import Choice from "../choice.js";
|
|
8
8
|
|
|
9
9
|
describe("RadioGroup", () => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const TestComponent = ({
|
|
11
|
+
errorMessage,
|
|
12
|
+
onChange,
|
|
13
|
+
}: {|
|
|
14
|
+
errorMessage?: string,
|
|
15
|
+
onChange?: () => mixed,
|
|
16
|
+
|}) => {
|
|
17
|
+
const [selectedValue, setSelectedValue] = React.useState("a");
|
|
18
|
+
const handleChange = (selectedValue) => {
|
|
19
|
+
setSelectedValue(selectedValue);
|
|
20
|
+
onChange?.();
|
|
21
|
+
};
|
|
22
|
+
return (
|
|
15
23
|
<RadioGroup
|
|
16
24
|
label="Test"
|
|
17
25
|
description="test description"
|
|
18
26
|
groupName="test"
|
|
19
|
-
onChange={
|
|
20
|
-
selectedValue=
|
|
27
|
+
onChange={handleChange}
|
|
28
|
+
selectedValue={selectedValue}
|
|
29
|
+
errorMessage={errorMessage}
|
|
21
30
|
>
|
|
22
31
|
<Choice label="a" value="a" aria-labelledby="test-a" />
|
|
23
32
|
<Choice label="b" value="b" aria-labelledby="test-b" />
|
|
24
33
|
<Choice label="c" value="c" aria-labelledby="test-c" />
|
|
25
|
-
</RadioGroup
|
|
34
|
+
</RadioGroup>
|
|
26
35
|
);
|
|
27
|
-
}
|
|
36
|
+
};
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
describe("behavior", () => {
|
|
39
|
+
it("selects only one item at a time", () => {
|
|
40
|
+
// Arrange, Act
|
|
41
|
+
render(<TestComponent />);
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
expect(a.prop("checked")).toEqual(true);
|
|
36
|
-
expect(b.prop("checked")).toEqual(false);
|
|
37
|
-
expect(c.prop("checked")).toEqual(false);
|
|
38
|
-
});
|
|
43
|
+
const radios = screen.getAllByRole("radio");
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
// Assert
|
|
46
|
+
// a starts off checked
|
|
47
|
+
expect(radios[0]).toBeChecked();
|
|
48
|
+
expect(radios[1]).not.toBeChecked();
|
|
49
|
+
expect(radios[2]).not.toBeChecked();
|
|
50
|
+
});
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
expect(c.prop("checked")).toEqual(false);
|
|
50
|
-
});
|
|
52
|
+
it("changes selection when selectedValue changes", () => {
|
|
53
|
+
// Arrange
|
|
54
|
+
render(<TestComponent />);
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
group.setProps({errorMessage: "there's an error"});
|
|
54
|
-
const a = group.find(Choice).at(0);
|
|
55
|
-
const b = group.find(Choice).at(1);
|
|
56
|
-
const c = group.find(Choice).at(2);
|
|
56
|
+
const radios = screen.getAllByRole("radio");
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
// Act
|
|
59
|
+
userEvent.click(radios[1]);
|
|
60
|
+
|
|
61
|
+
// Assert
|
|
62
|
+
// a starts off checked
|
|
63
|
+
expect(radios[0]).not.toBeChecked();
|
|
64
|
+
expect(radios[1]).toBeChecked();
|
|
65
|
+
expect(radios[2]).not.toBeChecked();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should set aria-invalid on choices when there's an error message", () => {
|
|
69
|
+
// Arrange, Act
|
|
70
|
+
render(<TestComponent errorMessage="there's an error" />);
|
|
71
|
+
|
|
72
|
+
const radios = screen.getAllByRole("radio");
|
|
73
|
+
|
|
74
|
+
// Assert
|
|
75
|
+
expect(radios[0]).toHaveAttribute("aria-invalid", "true");
|
|
76
|
+
expect(radios[1]).toHaveAttribute("aria-invalid", "true");
|
|
77
|
+
expect(radios[2]).toHaveAttribute("aria-invalid", "true");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("doesn't change when an already selected item is reselected", () => {
|
|
81
|
+
// Arrange
|
|
82
|
+
const handleChange = jest.fn();
|
|
83
|
+
render(<TestComponent onChange={handleChange} />);
|
|
84
|
+
|
|
85
|
+
const radios = screen.getAllByRole("radio");
|
|
62
86
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
// Act
|
|
88
|
+
// a is already selected, onChange shouldn't be called
|
|
89
|
+
userEvent.click(radios[0]);
|
|
90
|
+
|
|
91
|
+
// Assert
|
|
92
|
+
expect(handleChange).toHaveBeenCalledTimes(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("checks that aria attributes have been added correctly", () => {
|
|
96
|
+
// Arrange, Act
|
|
97
|
+
render(<TestComponent />);
|
|
98
|
+
|
|
99
|
+
const radios = screen.getAllByRole("radio");
|
|
100
|
+
|
|
101
|
+
// Assert
|
|
102
|
+
expect(radios[0]).toHaveAttribute("aria-labelledby", "test-a");
|
|
103
|
+
expect(radios[1]).toHaveAttribute("aria-labelledby", "test-b");
|
|
104
|
+
expect(radios[2]).toHaveAttribute("aria-labelledby", "test-c");
|
|
105
|
+
});
|
|
75
106
|
});
|
|
76
107
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
108
|
+
describe("flexible props", () => {
|
|
109
|
+
it("should render with a React.Node label", () => {
|
|
110
|
+
// Arrange, Act
|
|
111
|
+
render(
|
|
112
|
+
<RadioGroup
|
|
113
|
+
label={
|
|
114
|
+
<span>
|
|
115
|
+
label with <strong>strong</strong> text
|
|
116
|
+
</span>
|
|
117
|
+
}
|
|
118
|
+
groupName="test"
|
|
119
|
+
onChange={() => {}}
|
|
120
|
+
selectedValue={"a"}
|
|
121
|
+
>
|
|
122
|
+
<Choice label="a" value="a" aria-labelledby="test-a" />
|
|
123
|
+
<Choice label="b" value="b" aria-labelledby="test-b" />
|
|
124
|
+
<Choice label="c" value="c" aria-labelledby="test-c" />
|
|
125
|
+
</RadioGroup>,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Assert
|
|
129
|
+
expect(screen.getByText("strong")).toBeInTheDocument();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should render with a React.Node description", () => {
|
|
133
|
+
// Arrange, Act
|
|
134
|
+
render(
|
|
135
|
+
<RadioGroup
|
|
136
|
+
label="label"
|
|
137
|
+
description={
|
|
138
|
+
<span>
|
|
139
|
+
description with <strong>strong</strong> text
|
|
140
|
+
</span>
|
|
141
|
+
}
|
|
142
|
+
groupName="test"
|
|
143
|
+
onChange={() => {}}
|
|
144
|
+
selectedValue={"a"}
|
|
145
|
+
>
|
|
146
|
+
<Choice label="a" value="a" aria-labelledby="test-a" />
|
|
147
|
+
<Choice label="b" value="b" aria-labelledby="test-b" />
|
|
148
|
+
<Choice label="c" value="c" aria-labelledby="test-c" />
|
|
149
|
+
</RadioGroup>,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Assert
|
|
153
|
+
expect(screen.getByText("strong")).toBeInTheDocument();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should filter out false-y children when rendering", () => {
|
|
157
|
+
// Arrange, Act
|
|
158
|
+
render(
|
|
159
|
+
<RadioGroup
|
|
160
|
+
label="label"
|
|
161
|
+
description="description"
|
|
162
|
+
groupName="test"
|
|
163
|
+
onChange={() => {}}
|
|
164
|
+
selectedValue={"a"}
|
|
165
|
+
>
|
|
166
|
+
<Choice label="a" value="a" aria-labelledby="test-a" />
|
|
167
|
+
{false && (
|
|
168
|
+
<Choice label="b" value="b" aria-labelledby="test-b" />
|
|
169
|
+
)}
|
|
170
|
+
<Choice label="c" value="c" aria-labelledby="test-c" />
|
|
171
|
+
{undefined}
|
|
172
|
+
{null}
|
|
173
|
+
</RadioGroup>,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Assert
|
|
177
|
+
const radios = screen.getAllByRole("radio");
|
|
178
|
+
|
|
179
|
+
expect(radios).toHaveLength(2);
|
|
180
|
+
});
|
|
84
181
|
});
|
|
85
182
|
});
|
|
@@ -5,11 +5,7 @@ import * as React from "react";
|
|
|
5
5
|
import {View, addStyle} from "@khanacademy/wonder-blocks-core";
|
|
6
6
|
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
7
7
|
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
8
|
-
import {
|
|
9
|
-
type Typography,
|
|
10
|
-
LabelMedium,
|
|
11
|
-
LabelSmall,
|
|
12
|
-
} from "@khanacademy/wonder-blocks-typography";
|
|
8
|
+
import {LabelMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
|
|
13
9
|
import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
14
10
|
|
|
15
11
|
import styles from "./group-styles.js";
|
|
@@ -20,7 +16,7 @@ type CheckboxGroupProps = {|
|
|
|
20
16
|
/**
|
|
21
17
|
* Children should be Choice components.
|
|
22
18
|
*/
|
|
23
|
-
children: Array
|
|
19
|
+
children: Array<?(React.Element<Choice> | false)>,
|
|
24
20
|
|
|
25
21
|
/**
|
|
26
22
|
* Group name for this checkbox or radio group. Should be unique for all
|
|
@@ -32,12 +28,12 @@ type CheckboxGroupProps = {|
|
|
|
32
28
|
* Optional label for the group. This label is optional to allow for
|
|
33
29
|
* greater flexibility in implementing checkbox and radio groups.
|
|
34
30
|
*/
|
|
35
|
-
label?:
|
|
31
|
+
label?: React.Node,
|
|
36
32
|
|
|
37
33
|
/**
|
|
38
34
|
* Optional description for the group.
|
|
39
35
|
*/
|
|
40
|
-
description?:
|
|
36
|
+
description?: React.Node,
|
|
41
37
|
|
|
42
38
|
/**
|
|
43
39
|
* Optional error message. If supplied, the group will be displayed in an
|
|
@@ -132,23 +128,21 @@ export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
|
|
|
132
128
|
testId,
|
|
133
129
|
} = this.props;
|
|
134
130
|
|
|
131
|
+
const allChildren = React.Children.toArray(children).filter(Boolean);
|
|
132
|
+
|
|
135
133
|
return (
|
|
136
134
|
<StyledFieldset data-test-id={testId} style={styles.fieldset}>
|
|
137
135
|
{/* We have a View here because fieldset cannot be used with flexbox*/}
|
|
138
136
|
<View style={style}>
|
|
139
|
-
{
|
|
137
|
+
{label && (
|
|
140
138
|
<StyledLegend style={styles.legend}>
|
|
141
139
|
<LabelMedium>{label}</LabelMedium>
|
|
142
140
|
</StyledLegend>
|
|
143
|
-
) : (
|
|
144
|
-
label && label
|
|
145
141
|
)}
|
|
146
|
-
{
|
|
142
|
+
{description && (
|
|
147
143
|
<LabelSmall style={styles.description}>
|
|
148
144
|
{description}
|
|
149
145
|
</LabelSmall>
|
|
150
|
-
) : (
|
|
151
|
-
description && description
|
|
152
146
|
)}
|
|
153
147
|
{errorMessage && (
|
|
154
148
|
<LabelSmall style={styles.error}>
|
|
@@ -159,7 +153,7 @@ export default class CheckboxGroup extends React.Component<CheckboxGroupProps> {
|
|
|
159
153
|
<Strut size={Spacing.small_12} />
|
|
160
154
|
)}
|
|
161
155
|
|
|
162
|
-
{
|
|
156
|
+
{allChildren.map((child, index) => {
|
|
163
157
|
const {style, value} = child.props;
|
|
164
158
|
const checked = selectedValues.includes(value);
|
|
165
159
|
return (
|