playbook_ui 16.1.0.pre.rc.2 → 16.1.0.pre.rc.3
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_background/docs/_background_responsive.jsx +30 -0
- data/app/pb_kits/playbook/pb_background/docs/_background_responsive.md +1 -0
- data/app/pb_kits/playbook/pb_background/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_background/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +3 -1
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_input_display.html.erb +74 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_input_display.jsx +87 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_input_display.md +3 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +35 -33
- data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +33 -6
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +35 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.html.erb +10 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +21 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +3 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.rb +5 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.test.js +33 -18
- data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +29 -11
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_required_indicator.html.erb +5 -0
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_required_indicator.jsx +25 -0
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_textarea/docs/example.yml +3 -1
- data/app/pb_kits/playbook/pb_textarea/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_textarea/index.ts +12 -5
- data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +6 -0
- data/app/pb_kits/playbook/pb_textarea/textarea.rb +2 -0
- data/app/pb_kits/playbook/pb_textarea/textarea.test.js +18 -1
- data/app/pb_kits/playbook/utilities/test/globalProps/globalProps.integration.test.js +936 -0
- data/dist/chunks/{_pb_line_graph-hxi01lk7.js → _pb_line_graph-BgKF_zz1.js} +1 -1
- data/dist/chunks/{_typeahead-BgLnlhzP.js → _typeahead-B9a6ZsEP.js} +1 -1
- data/dist/chunks/{globalProps-DgYwLYNx.js → globalProps-BhVYCqRf.js} +1 -1
- data/dist/chunks/{lib-NLxTo8OB.js → lib-DD34ZrWL.js} +1 -1
- data/dist/chunks/vendor.js +2 -2
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +20 -6
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen } from '../../test-utils'
|
|
3
|
+
import Body from '../../../pb_body/_body'
|
|
4
|
+
import Button from '../../../pb_button/_button'
|
|
5
|
+
import Card from '../../../pb_card/_card'
|
|
6
|
+
import Flex from '../../../pb_flex/_flex'
|
|
7
|
+
import TextInput from '../../../pb_text_input/_text_input'
|
|
8
|
+
import Title from '../../../pb_title/_title'
|
|
9
|
+
import Link from '../../../pb_link/_link'
|
|
10
|
+
import Badge from '../../../pb_badge/_badge'
|
|
11
|
+
|
|
12
|
+
describe('Global Props Integration Tests', () => {
|
|
13
|
+
describe('All global props at once (comprehensive showcase)', () => {
|
|
14
|
+
test('All global props generate correct classes when used together', () => {
|
|
15
|
+
render(
|
|
16
|
+
<Body
|
|
17
|
+
alignContent="center"
|
|
18
|
+
alignItems="center"
|
|
19
|
+
alignSelf="center"
|
|
20
|
+
borderRadius="md"
|
|
21
|
+
cursor="pointer"
|
|
22
|
+
data={{ testid: 'all-props' }}
|
|
23
|
+
display="flex"
|
|
24
|
+
flex={1}
|
|
25
|
+
flexDirection="column"
|
|
26
|
+
flexGrow={1}
|
|
27
|
+
flexShrink={0}
|
|
28
|
+
flexWrap="wrap"
|
|
29
|
+
gap="md"
|
|
30
|
+
height="auto"
|
|
31
|
+
justifyContent="spaceBetween"
|
|
32
|
+
lineHeight="loose"
|
|
33
|
+
margin="lg"
|
|
34
|
+
maxHeight="xl"
|
|
35
|
+
maxWidth="lg"
|
|
36
|
+
minHeight="xs"
|
|
37
|
+
minWidth="sm"
|
|
38
|
+
order={1}
|
|
39
|
+
overflow="hidden"
|
|
40
|
+
padding="md"
|
|
41
|
+
position="relative"
|
|
42
|
+
shadow="deep"
|
|
43
|
+
text="Test"
|
|
44
|
+
textAlign="center"
|
|
45
|
+
truncate={1}
|
|
46
|
+
verticalAlign="middle"
|
|
47
|
+
width="100%"
|
|
48
|
+
zIndex={5}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
const body = screen.getByTestId('all-props')
|
|
52
|
+
|
|
53
|
+
// Verify all global prop classes are present
|
|
54
|
+
expect(body).toHaveClass('align_content_center')
|
|
55
|
+
expect(body).toHaveClass('align_items_center')
|
|
56
|
+
expect(body).toHaveClass('align_self_center')
|
|
57
|
+
expect(body).toHaveClass('border_radius_md')
|
|
58
|
+
expect(body).toHaveClass('cursor_pointer')
|
|
59
|
+
expect(body).toHaveClass('display_flex')
|
|
60
|
+
expect(body).toHaveClass('flex_1')
|
|
61
|
+
expect(body).toHaveClass('flex_direction_column')
|
|
62
|
+
expect(body).toHaveClass('flex_grow_1')
|
|
63
|
+
expect(body).toHaveClass('flex_shrink_0')
|
|
64
|
+
expect(body).toHaveClass('flex_wrap_wrap')
|
|
65
|
+
expect(body).toHaveClass('gap_md')
|
|
66
|
+
expect(body).toHaveClass('height_auto')
|
|
67
|
+
expect(body).toHaveClass('justify_content_space_between')
|
|
68
|
+
expect(body).toHaveClass('line_height_loose')
|
|
69
|
+
expect(body).toHaveClass('m_lg')
|
|
70
|
+
expect(body).toHaveClass('max_height_xl')
|
|
71
|
+
expect(body).toHaveClass('max_width_lg')
|
|
72
|
+
expect(body).toHaveClass('min_height_xs')
|
|
73
|
+
expect(body).toHaveClass('min_width_sm')
|
|
74
|
+
expect(body).toHaveClass('flex_order_1')
|
|
75
|
+
expect(body).toHaveClass('overflow_hidden')
|
|
76
|
+
expect(body).toHaveClass('p_md')
|
|
77
|
+
expect(body).toHaveClass('position_relative')
|
|
78
|
+
expect(body).toHaveClass('shadow_deep')
|
|
79
|
+
expect(body).toHaveClass('text_align_center')
|
|
80
|
+
expect(body).toHaveClass('truncate_1')
|
|
81
|
+
expect(body).toHaveClass('vertical_align_middle')
|
|
82
|
+
expect(body).toHaveClass('width_100_percent')
|
|
83
|
+
expect(body).toHaveClass('z_index_5')
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('Global props + other props on the same kit', () => {
|
|
88
|
+
test('Button: global props work with variant, size, and fullWidth', () => {
|
|
89
|
+
render(
|
|
90
|
+
<Button
|
|
91
|
+
data={{ testid: 'button-integration-1' }}
|
|
92
|
+
fullWidth
|
|
93
|
+
margin="md"
|
|
94
|
+
padding="lg"
|
|
95
|
+
size="lg"
|
|
96
|
+
textAlign="center"
|
|
97
|
+
variant="secondary"
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
const button = screen.getByTestId('button-integration-1')
|
|
101
|
+
|
|
102
|
+
// Kit-specific classes
|
|
103
|
+
expect(button).toHaveClass('pb_button_kit')
|
|
104
|
+
expect(button).toHaveClass('pb_button_secondary')
|
|
105
|
+
expect(button).toHaveClass('pb_button_block')
|
|
106
|
+
expect(button).toHaveClass('pb_button_size_lg')
|
|
107
|
+
|
|
108
|
+
// Global prop classes
|
|
109
|
+
expect(button).toHaveClass('m_md')
|
|
110
|
+
expect(button).toHaveClass('p_lg')
|
|
111
|
+
expect(button).toHaveClass('text_align_center')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('Title: global props work with size prop', () => {
|
|
115
|
+
render(
|
|
116
|
+
<Title
|
|
117
|
+
data={{ testid: 'title-integration-1' }}
|
|
118
|
+
margin="sm"
|
|
119
|
+
padding="xs"
|
|
120
|
+
size={3}
|
|
121
|
+
text="Test Title"
|
|
122
|
+
textAlign="right"
|
|
123
|
+
/>
|
|
124
|
+
)
|
|
125
|
+
const title = screen.getByTestId('title-integration-1')
|
|
126
|
+
|
|
127
|
+
// Kit-specific classes
|
|
128
|
+
expect(title).toHaveClass('pb_title_kit')
|
|
129
|
+
expect(title).toHaveClass('pb_title_3')
|
|
130
|
+
|
|
131
|
+
// Global prop classes
|
|
132
|
+
expect(title).toHaveClass('m_sm')
|
|
133
|
+
expect(title).toHaveClass('p_xs')
|
|
134
|
+
expect(title).toHaveClass('text_align_right')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('Card: global props work with borderRadius and padding props', () => {
|
|
138
|
+
render(
|
|
139
|
+
<Card
|
|
140
|
+
borderRadius="lg"
|
|
141
|
+
data={{ testid: 'card-integration-1' }}
|
|
142
|
+
margin="xl"
|
|
143
|
+
padding="md"
|
|
144
|
+
textAlign="center"
|
|
145
|
+
>
|
|
146
|
+
{"Card content"}
|
|
147
|
+
</Card>
|
|
148
|
+
)
|
|
149
|
+
const card = screen.getByTestId('card-integration-1')
|
|
150
|
+
|
|
151
|
+
// Kit-specific classes
|
|
152
|
+
expect(card).toHaveClass('pb_card_kit')
|
|
153
|
+
expect(card).toHaveClass('border_radius_lg')
|
|
154
|
+
|
|
155
|
+
// Global prop classes
|
|
156
|
+
expect(card).toHaveClass('m_xl')
|
|
157
|
+
expect(card).toHaveClass('p_md')
|
|
158
|
+
expect(card).toHaveClass('text_align_center')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('Flex: global props work with orientation, justify, and align props', () => {
|
|
162
|
+
render(
|
|
163
|
+
<Flex
|
|
164
|
+
align="center"
|
|
165
|
+
data={{ testid: 'flex-integration-1' }}
|
|
166
|
+
justify="spaceBetween"
|
|
167
|
+
margin="lg"
|
|
168
|
+
orientation="column"
|
|
169
|
+
padding="sm"
|
|
170
|
+
>
|
|
171
|
+
{"Flex content"}
|
|
172
|
+
</Flex>
|
|
173
|
+
)
|
|
174
|
+
const flex = screen.getByTestId('flex-integration-1')
|
|
175
|
+
|
|
176
|
+
// Kit-specific classes
|
|
177
|
+
expect(flex).toHaveClass('pb_flex_kit')
|
|
178
|
+
expect(flex).toHaveClass('pb_flex_kit_orientation_column')
|
|
179
|
+
expect(flex).toHaveClass('pb_flex_kit_justify_content_spaceBetween')
|
|
180
|
+
expect(flex).toHaveClass('pb_flex_kit_align_items_center')
|
|
181
|
+
|
|
182
|
+
// Global prop classes
|
|
183
|
+
expect(flex).toHaveClass('m_lg')
|
|
184
|
+
expect(flex).toHaveClass('p_sm')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('Link: global props work with link prop', () => {
|
|
188
|
+
render(
|
|
189
|
+
<Link
|
|
190
|
+
data={{ testid: 'link-integration-1' }}
|
|
191
|
+
link="https://example.com"
|
|
192
|
+
margin="xs"
|
|
193
|
+
text="Test Link"
|
|
194
|
+
textAlign="center"
|
|
195
|
+
/>
|
|
196
|
+
)
|
|
197
|
+
const link = screen.getByTestId('link-integration-1')
|
|
198
|
+
|
|
199
|
+
// Kit-specific classes
|
|
200
|
+
expect(link).toHaveClass('pb_link_kit')
|
|
201
|
+
|
|
202
|
+
// Global prop classes
|
|
203
|
+
expect(link).toHaveClass('m_xs')
|
|
204
|
+
expect(link).toHaveClass('text_align_center')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('Badge: global props work with variant prop', () => {
|
|
208
|
+
render(
|
|
209
|
+
<Badge
|
|
210
|
+
data={{ testid: 'badge-integration-1' }}
|
|
211
|
+
margin="md"
|
|
212
|
+
text="Badge"
|
|
213
|
+
variant="success"
|
|
214
|
+
/>
|
|
215
|
+
)
|
|
216
|
+
const badge = screen.getByTestId('badge-integration-1')
|
|
217
|
+
|
|
218
|
+
// Kit-specific classes (Badge includes size in classname)
|
|
219
|
+
expect(badge.className).toMatch(/pb_badge_kit_success/)
|
|
220
|
+
|
|
221
|
+
// Global prop classes
|
|
222
|
+
expect(badge).toHaveClass('m_md')
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
describe('Global props + other global props on the same kit', () => {
|
|
227
|
+
test('Multiple spacing global props work together', () => {
|
|
228
|
+
render(
|
|
229
|
+
<Body
|
|
230
|
+
data={{ testid: 'body-multiple-spacing' }}
|
|
231
|
+
margin="md"
|
|
232
|
+
marginBottom="lg"
|
|
233
|
+
marginTop="sm"
|
|
234
|
+
padding="lg"
|
|
235
|
+
paddingLeft="xs"
|
|
236
|
+
text="Test"
|
|
237
|
+
/>
|
|
238
|
+
)
|
|
239
|
+
const body = screen.getByTestId('body-multiple-spacing')
|
|
240
|
+
|
|
241
|
+
expect(body).toHaveClass('m_md')
|
|
242
|
+
expect(body).toHaveClass('mb_lg')
|
|
243
|
+
expect(body).toHaveClass('mt_sm')
|
|
244
|
+
expect(body).toHaveClass('p_lg')
|
|
245
|
+
expect(body).toHaveClass('pl_xs')
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
test('Spacing + layout + typography global props work together', () => {
|
|
249
|
+
render(
|
|
250
|
+
<Body
|
|
251
|
+
data={{ testid: 'body-layout-typography' }}
|
|
252
|
+
display="flex"
|
|
253
|
+
margin="md"
|
|
254
|
+
padding="sm"
|
|
255
|
+
text="Test"
|
|
256
|
+
textAlign="center"
|
|
257
|
+
/>
|
|
258
|
+
)
|
|
259
|
+
const body = screen.getByTestId('body-layout-typography')
|
|
260
|
+
|
|
261
|
+
expect(body).toHaveClass('m_md')
|
|
262
|
+
expect(body).toHaveClass('p_sm')
|
|
263
|
+
expect(body).toHaveClass('display_flex')
|
|
264
|
+
expect(body).toHaveClass('text_align_center')
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
test('Position + spacing + z-index global props work together', () => {
|
|
268
|
+
render(
|
|
269
|
+
<Body
|
|
270
|
+
data={{ testid: 'body-position-zindex' }}
|
|
271
|
+
margin="lg"
|
|
272
|
+
position="absolute"
|
|
273
|
+
text="Test"
|
|
274
|
+
top="md"
|
|
275
|
+
zIndex={5}
|
|
276
|
+
/>
|
|
277
|
+
)
|
|
278
|
+
const body = screen.getByTestId('body-position-zindex')
|
|
279
|
+
|
|
280
|
+
expect(body).toHaveClass('m_lg')
|
|
281
|
+
expect(body).toHaveClass('position_absolute')
|
|
282
|
+
expect(body).toHaveClass('top_md')
|
|
283
|
+
expect(body).toHaveClass('z_index_5')
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
test('Flexbox + spacing + typography global props work together', () => {
|
|
287
|
+
render(
|
|
288
|
+
<Body
|
|
289
|
+
alignItems="center"
|
|
290
|
+
data={{ testid: 'body-flexbox-combo' }}
|
|
291
|
+
flexDirection="column"
|
|
292
|
+
gap="md"
|
|
293
|
+
justifyContent="spaceBetween"
|
|
294
|
+
margin="sm"
|
|
295
|
+
padding="lg"
|
|
296
|
+
text="Test"
|
|
297
|
+
textAlign="right"
|
|
298
|
+
/>
|
|
299
|
+
)
|
|
300
|
+
const body = screen.getByTestId('body-flexbox-combo')
|
|
301
|
+
|
|
302
|
+
expect(body).toHaveClass('m_sm')
|
|
303
|
+
expect(body).toHaveClass('p_lg')
|
|
304
|
+
expect(body).toHaveClass('flex_direction_column')
|
|
305
|
+
expect(body).toHaveClass('justify_content_space_between')
|
|
306
|
+
expect(body).toHaveClass('align_items_center')
|
|
307
|
+
expect(body).toHaveClass('gap_md')
|
|
308
|
+
expect(body).toHaveClass('text_align_right')
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test('All major global prop categories work together', () => {
|
|
312
|
+
render(
|
|
313
|
+
<Body
|
|
314
|
+
borderRadius="md"
|
|
315
|
+
cursor="pointer"
|
|
316
|
+
data={{ testid: 'body-all-categories' }}
|
|
317
|
+
display="flex"
|
|
318
|
+
margin="lg"
|
|
319
|
+
padding="md"
|
|
320
|
+
position="relative"
|
|
321
|
+
shadow="lg"
|
|
322
|
+
text="Test"
|
|
323
|
+
textAlign="center"
|
|
324
|
+
zIndex={3}
|
|
325
|
+
/>
|
|
326
|
+
)
|
|
327
|
+
const body = screen.getByTestId('body-all-categories')
|
|
328
|
+
|
|
329
|
+
// Spacing
|
|
330
|
+
expect(body).toHaveClass('m_lg')
|
|
331
|
+
expect(body).toHaveClass('p_md')
|
|
332
|
+
|
|
333
|
+
// Layout
|
|
334
|
+
expect(body).toHaveClass('display_flex')
|
|
335
|
+
expect(body).toHaveClass('position_relative')
|
|
336
|
+
|
|
337
|
+
// Visual
|
|
338
|
+
expect(body).toHaveClass('border_radius_md')
|
|
339
|
+
expect(body).toHaveClass('shadow_lg')
|
|
340
|
+
expect(body).toHaveClass('cursor_pointer')
|
|
341
|
+
|
|
342
|
+
// Typography
|
|
343
|
+
expect(body).toHaveClass('text_align_center')
|
|
344
|
+
|
|
345
|
+
// Z-index
|
|
346
|
+
expect(body).toHaveClass('z_index_3')
|
|
347
|
+
})
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
describe('Global props in nested layouts / clickable wrappers', () => {
|
|
351
|
+
test('Button inside Card: both have global props', () => {
|
|
352
|
+
render(
|
|
353
|
+
<Card
|
|
354
|
+
data={{ testid: 'card-nested' }}
|
|
355
|
+
margin="lg"
|
|
356
|
+
padding="md"
|
|
357
|
+
>
|
|
358
|
+
<Button
|
|
359
|
+
data={{ testid: 'button-nested' }}
|
|
360
|
+
margin="sm"
|
|
361
|
+
text="Click me"
|
|
362
|
+
variant="primary"
|
|
363
|
+
/>
|
|
364
|
+
</Card>
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
const card = screen.getByTestId('card-nested')
|
|
368
|
+
const button = screen.getByTestId('button-nested')
|
|
369
|
+
|
|
370
|
+
// Card global props
|
|
371
|
+
expect(card).toHaveClass('m_lg')
|
|
372
|
+
expect(card).toHaveClass('p_md')
|
|
373
|
+
|
|
374
|
+
// Button global props
|
|
375
|
+
expect(button).toHaveClass('m_sm')
|
|
376
|
+
|
|
377
|
+
// Button kit-specific props
|
|
378
|
+
expect(button).toHaveClass('pb_button_kit')
|
|
379
|
+
expect(button).toHaveClass('pb_button_primary')
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
test('Multiple Body elements in Flex: each has different global props', () => {
|
|
383
|
+
render(
|
|
384
|
+
<Flex
|
|
385
|
+
data={{ testid: 'flex-nested' }}
|
|
386
|
+
gap="md"
|
|
387
|
+
margin="lg"
|
|
388
|
+
>
|
|
389
|
+
<Body
|
|
390
|
+
data={{ testid: 'body-nested-1' }}
|
|
391
|
+
margin="xs"
|
|
392
|
+
text="First"
|
|
393
|
+
textAlign="left"
|
|
394
|
+
/>
|
|
395
|
+
<Body
|
|
396
|
+
data={{ testid: 'body-nested-2' }}
|
|
397
|
+
margin="xs"
|
|
398
|
+
text="Second"
|
|
399
|
+
textAlign="center"
|
|
400
|
+
/>
|
|
401
|
+
<Body
|
|
402
|
+
data={{ testid: 'body-nested-3' }}
|
|
403
|
+
margin="xs"
|
|
404
|
+
text="Third"
|
|
405
|
+
textAlign="right"
|
|
406
|
+
/>
|
|
407
|
+
</Flex>
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
const flex = screen.getByTestId('flex-nested')
|
|
411
|
+
const body1 = screen.getByTestId('body-nested-1')
|
|
412
|
+
const body2 = screen.getByTestId('body-nested-2')
|
|
413
|
+
const body3 = screen.getByTestId('body-nested-3')
|
|
414
|
+
|
|
415
|
+
// Flex global props
|
|
416
|
+
expect(flex).toHaveClass('m_lg')
|
|
417
|
+
expect(flex).toHaveClass('gap_md')
|
|
418
|
+
|
|
419
|
+
// Each Body has its own global props
|
|
420
|
+
expect(body1).toHaveClass('m_xs')
|
|
421
|
+
expect(body1).toHaveClass('text_align_left')
|
|
422
|
+
|
|
423
|
+
expect(body2).toHaveClass('m_xs')
|
|
424
|
+
expect(body2).toHaveClass('text_align_center')
|
|
425
|
+
|
|
426
|
+
expect(body3).toHaveClass('m_xs')
|
|
427
|
+
expect(body3).toHaveClass('text_align_right')
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
test('Deep nesting: Card > Flex > Button with global props', () => {
|
|
431
|
+
render(
|
|
432
|
+
<Card
|
|
433
|
+
data={{ testid: 'card-deep' }}
|
|
434
|
+
margin="xl"
|
|
435
|
+
padding="lg"
|
|
436
|
+
>
|
|
437
|
+
<Flex
|
|
438
|
+
data={{ testid: 'flex-deep' }}
|
|
439
|
+
gap="sm"
|
|
440
|
+
justify="center"
|
|
441
|
+
>
|
|
442
|
+
<Button
|
|
443
|
+
data={{ testid: 'button-deep' }}
|
|
444
|
+
margin="xs"
|
|
445
|
+
text="Nested Button"
|
|
446
|
+
variant="secondary"
|
|
447
|
+
/>
|
|
448
|
+
</Flex>
|
|
449
|
+
</Card>
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
const card = screen.getByTestId('card-deep')
|
|
453
|
+
const flex = screen.getByTestId('flex-deep')
|
|
454
|
+
const button = screen.getByTestId('button-deep')
|
|
455
|
+
|
|
456
|
+
// Card global props
|
|
457
|
+
expect(card).toHaveClass('m_xl')
|
|
458
|
+
expect(card).toHaveClass('p_lg')
|
|
459
|
+
|
|
460
|
+
// Flex global props
|
|
461
|
+
expect(flex).toHaveClass('gap_sm')
|
|
462
|
+
expect(flex).toHaveClass('pb_flex_kit_justify_content_center')
|
|
463
|
+
|
|
464
|
+
// Button global props
|
|
465
|
+
expect(button).toHaveClass('m_xs')
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
test('Link wrapper with global props containing Body with global props', () => {
|
|
469
|
+
render(
|
|
470
|
+
<Link
|
|
471
|
+
data={{ testid: 'link-wrapper' }}
|
|
472
|
+
link="https://example.com"
|
|
473
|
+
margin="md"
|
|
474
|
+
textAlign="center"
|
|
475
|
+
>
|
|
476
|
+
<Body
|
|
477
|
+
data={{ testid: 'body-in-link' }}
|
|
478
|
+
margin="xs"
|
|
479
|
+
text="Link content"
|
|
480
|
+
textAlign="left"
|
|
481
|
+
/>
|
|
482
|
+
</Link>
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
const link = screen.getByTestId('link-wrapper')
|
|
486
|
+
const body = screen.getByTestId('body-in-link')
|
|
487
|
+
|
|
488
|
+
// Link global props
|
|
489
|
+
expect(link).toHaveClass('m_md')
|
|
490
|
+
expect(link).toHaveClass('text_align_center')
|
|
491
|
+
|
|
492
|
+
// Body global props (should still work even though inside Link)
|
|
493
|
+
expect(body).toHaveClass('m_xs')
|
|
494
|
+
expect(body).toHaveClass('text_align_left')
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
test('Button with onClick and global props in Card with global props', () => {
|
|
498
|
+
const handleClick = jest.fn()
|
|
499
|
+
|
|
500
|
+
render(
|
|
501
|
+
<Card
|
|
502
|
+
data={{ testid: 'card-clickable' }}
|
|
503
|
+
margin="lg"
|
|
504
|
+
onClick={handleClick}
|
|
505
|
+
padding="md"
|
|
506
|
+
>
|
|
507
|
+
<Button
|
|
508
|
+
data={{ testid: 'button-clickable' }}
|
|
509
|
+
margin="sm"
|
|
510
|
+
onClick={handleClick}
|
|
511
|
+
text="Click me"
|
|
512
|
+
variant="primary"
|
|
513
|
+
/>
|
|
514
|
+
</Card>
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
const card = screen.getByTestId('card-clickable')
|
|
518
|
+
const button = screen.getByTestId('button-clickable')
|
|
519
|
+
|
|
520
|
+
// Both should have their global props
|
|
521
|
+
expect(card).toHaveClass('m_lg')
|
|
522
|
+
expect(card).toHaveClass('p_md')
|
|
523
|
+
expect(button).toHaveClass('m_sm')
|
|
524
|
+
|
|
525
|
+
// Both should be clickable
|
|
526
|
+
button.click()
|
|
527
|
+
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
test('Responsive global props in nested layout', () => {
|
|
531
|
+
render(
|
|
532
|
+
<Card
|
|
533
|
+
data={{ testid: 'card-responsive' }}
|
|
534
|
+
margin={{ default: 'md', sm: 'lg', md: 'xl' }}
|
|
535
|
+
padding="md"
|
|
536
|
+
>
|
|
537
|
+
<Flex
|
|
538
|
+
data={{ testid: 'flex-responsive' }}
|
|
539
|
+
gap={{ default: 'sm', md: 'lg' }}
|
|
540
|
+
>
|
|
541
|
+
<Body
|
|
542
|
+
data={{ testid: 'body-responsive' }}
|
|
543
|
+
margin="xs"
|
|
544
|
+
text="Content"
|
|
545
|
+
textAlign={{ default: 'left', sm: 'center', md: 'right' }}
|
|
546
|
+
/>
|
|
547
|
+
</Flex>
|
|
548
|
+
</Card>
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
const card = screen.getByTestId('card-responsive')
|
|
552
|
+
const flex = screen.getByTestId('flex-responsive')
|
|
553
|
+
const body = screen.getByTestId('body-responsive')
|
|
554
|
+
|
|
555
|
+
// Card responsive margin (uses break_on format)
|
|
556
|
+
expect(card).toHaveClass('m_md')
|
|
557
|
+
expect(card.className).toMatch(/break_on_sm:m_lg/)
|
|
558
|
+
expect(card.className).toMatch(/break_on_md:m_xl/)
|
|
559
|
+
|
|
560
|
+
// Flex responsive gap
|
|
561
|
+
expect(flex).toHaveClass('gap_sm')
|
|
562
|
+
expect(flex).toHaveClass('gap_md_lg')
|
|
563
|
+
|
|
564
|
+
// Body responsive textAlign
|
|
565
|
+
expect(body).toHaveClass('text_align_left')
|
|
566
|
+
expect(body).toHaveClass('text_align_sm_center')
|
|
567
|
+
expect(body).toHaveClass('text_align_md_right')
|
|
568
|
+
})
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
describe('Prop precedence and conflicts', () => {
|
|
572
|
+
test('className prop overrides global prop classes', () => {
|
|
573
|
+
render(
|
|
574
|
+
<Body
|
|
575
|
+
className="custom-class override-margin"
|
|
576
|
+
data={{ testid: 'precedence-1' }}
|
|
577
|
+
margin="lg"
|
|
578
|
+
text="Test"
|
|
579
|
+
/>
|
|
580
|
+
)
|
|
581
|
+
const body = screen.getByTestId('precedence-1')
|
|
582
|
+
|
|
583
|
+
// Should have global prop class
|
|
584
|
+
expect(body).toHaveClass('m_lg')
|
|
585
|
+
// Should have custom className
|
|
586
|
+
expect(body).toHaveClass('custom-class')
|
|
587
|
+
expect(body).toHaveClass('override-margin')
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
test('htmlOptions overrides aria and data props', () => {
|
|
591
|
+
render(
|
|
592
|
+
<Body
|
|
593
|
+
aria={{ label: 'Aria label' }}
|
|
594
|
+
data={{ testid: 'precedence-2' }}
|
|
595
|
+
htmlOptions={{
|
|
596
|
+
'aria-label': 'HTML options label',
|
|
597
|
+
'data-testid': 'html-testid'
|
|
598
|
+
}}
|
|
599
|
+
text="Test"
|
|
600
|
+
/>
|
|
601
|
+
)
|
|
602
|
+
const body = screen.getByTestId('html-testid')
|
|
603
|
+
|
|
604
|
+
// htmlOptions should override aria prop
|
|
605
|
+
expect(body).toHaveAttribute('aria-label', 'HTML options label')
|
|
606
|
+
// htmlOptions should override data prop
|
|
607
|
+
expect(body).toHaveAttribute('data-testid', 'html-testid')
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
test('Multiple conflicting spacing props work together', () => {
|
|
611
|
+
render(
|
|
612
|
+
<Body
|
|
613
|
+
data={{ testid: 'precedence-3' }}
|
|
614
|
+
margin="md"
|
|
615
|
+
marginBottom="lg"
|
|
616
|
+
marginTop="sm"
|
|
617
|
+
text="Test"
|
|
618
|
+
/>
|
|
619
|
+
)
|
|
620
|
+
const body = screen.getByTestId('precedence-3')
|
|
621
|
+
|
|
622
|
+
// All should be present
|
|
623
|
+
expect(body).toHaveClass('m_md')
|
|
624
|
+
expect(body).toHaveClass('mb_lg')
|
|
625
|
+
expect(body).toHaveClass('mt_sm')
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
test('Global props do not interfere with kit-specific props', () => {
|
|
629
|
+
render(
|
|
630
|
+
<Button
|
|
631
|
+
data={{ testid: 'precedence-4' }}
|
|
632
|
+
margin="md"
|
|
633
|
+
size="lg"
|
|
634
|
+
text="Click me"
|
|
635
|
+
variant="secondary"
|
|
636
|
+
/>
|
|
637
|
+
)
|
|
638
|
+
const button = screen.getByTestId('precedence-4')
|
|
639
|
+
|
|
640
|
+
// Kit-specific classes should still be present
|
|
641
|
+
expect(button).toHaveClass('pb_button_kit')
|
|
642
|
+
expect(button).toHaveClass('pb_button_secondary')
|
|
643
|
+
expect(button).toHaveClass('pb_button_size_lg')
|
|
644
|
+
// Global prop classes should also be present
|
|
645
|
+
expect(button).toHaveClass('m_md')
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
test('id prop takes precedence over htmlOptions.id', () => {
|
|
649
|
+
render(
|
|
650
|
+
<Body
|
|
651
|
+
data={{ testid: 'precedence-5' }}
|
|
652
|
+
htmlOptions={{ id: 'html-id' }}
|
|
653
|
+
id="prop-id"
|
|
654
|
+
text="Test"
|
|
655
|
+
/>
|
|
656
|
+
)
|
|
657
|
+
const body = screen.getByTestId('precedence-5')
|
|
658
|
+
|
|
659
|
+
// id prop should win
|
|
660
|
+
expect(body).toHaveAttribute('id', 'prop-id')
|
|
661
|
+
expect(body).not.toHaveAttribute('id', 'html-id')
|
|
662
|
+
})
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
describe('Accessibility integration', () => {
|
|
666
|
+
test('Global props work with aria props', () => {
|
|
667
|
+
render(
|
|
668
|
+
<Body
|
|
669
|
+
aria={{
|
|
670
|
+
label: 'Test label',
|
|
671
|
+
describedby: 'desc-id',
|
|
672
|
+
hidden: 'false'
|
|
673
|
+
}}
|
|
674
|
+
data={{ testid: 'a11y-1' }}
|
|
675
|
+
margin="md"
|
|
676
|
+
text="Test"
|
|
677
|
+
textAlign="center"
|
|
678
|
+
/>
|
|
679
|
+
)
|
|
680
|
+
const body = screen.getByTestId('a11y-1')
|
|
681
|
+
|
|
682
|
+
// Aria attributes should be present
|
|
683
|
+
expect(body).toHaveAttribute('aria-label', 'Test label')
|
|
684
|
+
expect(body).toHaveAttribute('aria-describedby', 'desc-id')
|
|
685
|
+
expect(body).toHaveAttribute('aria-hidden', 'false')
|
|
686
|
+
// Global props should still work
|
|
687
|
+
expect(body).toHaveClass('m_md')
|
|
688
|
+
expect(body).toHaveClass('text_align_center')
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
test('Global props work with data props', () => {
|
|
692
|
+
render(
|
|
693
|
+
<Body
|
|
694
|
+
data={{
|
|
695
|
+
testid: 'a11y-2',
|
|
696
|
+
'custom-attr': 'custom-value',
|
|
697
|
+
'analytics-id': 'analytics-123'
|
|
698
|
+
}}
|
|
699
|
+
margin="sm"
|
|
700
|
+
padding="md"
|
|
701
|
+
text="Test"
|
|
702
|
+
/>
|
|
703
|
+
)
|
|
704
|
+
const body = screen.getByTestId('a11y-2')
|
|
705
|
+
|
|
706
|
+
// Data attributes should be present
|
|
707
|
+
expect(body).toHaveAttribute('data-custom-attr', 'custom-value')
|
|
708
|
+
expect(body).toHaveAttribute('data-analytics-id', 'analytics-123')
|
|
709
|
+
// Global props should still work
|
|
710
|
+
expect(body).toHaveClass('m_sm')
|
|
711
|
+
expect(body).toHaveClass('p_md')
|
|
712
|
+
})
|
|
713
|
+
|
|
714
|
+
test('htmlOptions aria attributes override aria prop', () => {
|
|
715
|
+
render(
|
|
716
|
+
<Body
|
|
717
|
+
aria={{ label: 'Aria prop label' }}
|
|
718
|
+
data={{ testid: 'a11y-3' }}
|
|
719
|
+
htmlOptions={{
|
|
720
|
+
'aria-label': 'HTML options label',
|
|
721
|
+
'aria-live': 'polite'
|
|
722
|
+
}}
|
|
723
|
+
text="Test"
|
|
724
|
+
/>
|
|
725
|
+
)
|
|
726
|
+
const body = screen.getByTestId('a11y-3')
|
|
727
|
+
|
|
728
|
+
// htmlOptions should override aria prop
|
|
729
|
+
expect(body).toHaveAttribute('aria-label', 'HTML options label')
|
|
730
|
+
expect(body).toHaveAttribute('aria-live', 'polite')
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
test('Global props with role and tabIndex', () => {
|
|
734
|
+
render(
|
|
735
|
+
<Body
|
|
736
|
+
data={{ testid: 'a11y-4' }}
|
|
737
|
+
htmlOptions={{
|
|
738
|
+
role: 'button',
|
|
739
|
+
tabIndex: 0
|
|
740
|
+
}}
|
|
741
|
+
margin="lg"
|
|
742
|
+
text="Test"
|
|
743
|
+
/>
|
|
744
|
+
)
|
|
745
|
+
const body = screen.getByTestId('a11y-4')
|
|
746
|
+
|
|
747
|
+
// Accessibility attributes should be present
|
|
748
|
+
expect(body).toHaveAttribute('role', 'button')
|
|
749
|
+
expect(body).toHaveAttribute('tabIndex', '0')
|
|
750
|
+
// Global props should still work
|
|
751
|
+
expect(body).toHaveClass('m_lg')
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
test('Screen reader compatibility with global props', () => {
|
|
755
|
+
render(
|
|
756
|
+
<Button
|
|
757
|
+
aria={{
|
|
758
|
+
label: 'Submit form',
|
|
759
|
+
describedby: 'submit-help'
|
|
760
|
+
}}
|
|
761
|
+
data={{ testid: 'a11y-5' }}
|
|
762
|
+
margin="md"
|
|
763
|
+
text="Submit"
|
|
764
|
+
/>
|
|
765
|
+
)
|
|
766
|
+
const button = screen.getByTestId('a11y-5')
|
|
767
|
+
|
|
768
|
+
// Should be accessible
|
|
769
|
+
expect(button).toHaveAttribute('aria-label', 'Submit form')
|
|
770
|
+
expect(button).toHaveAttribute('aria-describedby', 'submit-help')
|
|
771
|
+
expect(button).toHaveAttribute('role', 'button')
|
|
772
|
+
// Global props should not interfere
|
|
773
|
+
expect(button).toHaveClass('m_md')
|
|
774
|
+
})
|
|
775
|
+
})
|
|
776
|
+
|
|
777
|
+
describe('Form elements', () => {
|
|
778
|
+
test('Global props work with TextInput', () => {
|
|
779
|
+
const handleChange = jest.fn()
|
|
780
|
+
|
|
781
|
+
render(
|
|
782
|
+
<TextInput
|
|
783
|
+
data={{ testid: 'form-1' }}
|
|
784
|
+
label="Test Input"
|
|
785
|
+
margin="md"
|
|
786
|
+
name="test-input"
|
|
787
|
+
onChange={handleChange}
|
|
788
|
+
padding="sm"
|
|
789
|
+
placeholder="Enter text"
|
|
790
|
+
textAlign="right"
|
|
791
|
+
type="text"
|
|
792
|
+
value=""
|
|
793
|
+
>
|
|
794
|
+
<input />
|
|
795
|
+
</TextInput>
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
const wrapper = screen.getByTestId('form-1')
|
|
799
|
+
|
|
800
|
+
// Global props should work on TextInput wrapper
|
|
801
|
+
expect(wrapper).toHaveClass('m_md')
|
|
802
|
+
expect(wrapper).toHaveClass('p_sm')
|
|
803
|
+
expect(wrapper).toHaveClass('text_align_right')
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
test('Global props with TextInput error state', () => {
|
|
807
|
+
const handleChange = jest.fn()
|
|
808
|
+
|
|
809
|
+
render(
|
|
810
|
+
<TextInput
|
|
811
|
+
data={{ testid: 'form-2' }}
|
|
812
|
+
error="This field is required"
|
|
813
|
+
label="Test Input"
|
|
814
|
+
margin="sm"
|
|
815
|
+
name="test-input"
|
|
816
|
+
onChange={handleChange}
|
|
817
|
+
placeholder="Enter text"
|
|
818
|
+
type="text"
|
|
819
|
+
value=""
|
|
820
|
+
>
|
|
821
|
+
<input />
|
|
822
|
+
</TextInput>
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
const wrapper = screen.getByTestId('form-2')
|
|
826
|
+
|
|
827
|
+
// Error state should work
|
|
828
|
+
expect(wrapper).toHaveClass('error')
|
|
829
|
+
// Global props should still work
|
|
830
|
+
expect(wrapper).toHaveClass('m_sm')
|
|
831
|
+
})
|
|
832
|
+
|
|
833
|
+
test('Global props with disabled TextInput', () => {
|
|
834
|
+
const handleChange = jest.fn()
|
|
835
|
+
|
|
836
|
+
render(
|
|
837
|
+
<TextInput
|
|
838
|
+
data={{ testid: 'form-3' }}
|
|
839
|
+
disabled
|
|
840
|
+
label="Test Input"
|
|
841
|
+
margin="lg"
|
|
842
|
+
name="test-input"
|
|
843
|
+
onChange={handleChange}
|
|
844
|
+
placeholder="Enter text"
|
|
845
|
+
type="text"
|
|
846
|
+
value=""
|
|
847
|
+
>
|
|
848
|
+
<input disabled />
|
|
849
|
+
</TextInput>
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
const wrapper = screen.getByTestId('form-3')
|
|
853
|
+
const input = wrapper.querySelector('input')
|
|
854
|
+
|
|
855
|
+
// Input should be disabled (check attribute directly)
|
|
856
|
+
expect(input).toHaveAttribute('disabled')
|
|
857
|
+
// Global props should still work
|
|
858
|
+
expect(wrapper).toHaveClass('m_lg')
|
|
859
|
+
})
|
|
860
|
+
|
|
861
|
+
test('Global props with required TextInput', () => {
|
|
862
|
+
const handleChange = jest.fn()
|
|
863
|
+
|
|
864
|
+
render(
|
|
865
|
+
<TextInput
|
|
866
|
+
data={{ testid: 'form-4' }}
|
|
867
|
+
label="Test Input"
|
|
868
|
+
margin="md"
|
|
869
|
+
name="test-input"
|
|
870
|
+
onChange={handleChange}
|
|
871
|
+
placeholder="Enter text"
|
|
872
|
+
required
|
|
873
|
+
requiredIndicator
|
|
874
|
+
type="text"
|
|
875
|
+
value=""
|
|
876
|
+
>
|
|
877
|
+
<input />
|
|
878
|
+
</TextInput>
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
const wrapper = screen.getByTestId('form-4')
|
|
882
|
+
|
|
883
|
+
// Global props should work
|
|
884
|
+
expect(wrapper).toHaveClass('m_md')
|
|
885
|
+
// Required indicator should be present if requiredIndicator is true
|
|
886
|
+
const requiredIndicator = screen.queryByText('*')
|
|
887
|
+
if (requiredIndicator) {
|
|
888
|
+
expect(requiredIndicator).toBeInTheDocument()
|
|
889
|
+
}
|
|
890
|
+
})
|
|
891
|
+
})
|
|
892
|
+
|
|
893
|
+
describe('Edge cases and boundary conditions', () => {
|
|
894
|
+
test('Empty responsive object', () => {
|
|
895
|
+
render(
|
|
896
|
+
<Body
|
|
897
|
+
data={{ testid: 'edge-2' }}
|
|
898
|
+
margin={{}}
|
|
899
|
+
text="Test"
|
|
900
|
+
/>
|
|
901
|
+
)
|
|
902
|
+
const body = screen.getByTestId('edge-2')
|
|
903
|
+
|
|
904
|
+
// Should not crash with empty object
|
|
905
|
+
expect(body).toBeInTheDocument()
|
|
906
|
+
})
|
|
907
|
+
|
|
908
|
+
test('Only default value in responsive object', () => {
|
|
909
|
+
render(
|
|
910
|
+
<Body
|
|
911
|
+
data={{ testid: 'edge-3' }}
|
|
912
|
+
margin={{ default: 'md' }}
|
|
913
|
+
text="Test"
|
|
914
|
+
/>
|
|
915
|
+
)
|
|
916
|
+
const body = screen.getByTestId('edge-3')
|
|
917
|
+
|
|
918
|
+
// Should have default class
|
|
919
|
+
expect(body).toHaveClass('m_md')
|
|
920
|
+
})
|
|
921
|
+
|
|
922
|
+
test('Only breakpoints, no default in responsive object', () => {
|
|
923
|
+
render(
|
|
924
|
+
<Body
|
|
925
|
+
data={{ testid: 'edge-4' }}
|
|
926
|
+
margin={{ sm: 'md', md: 'lg' }}
|
|
927
|
+
text="Test"
|
|
928
|
+
/>
|
|
929
|
+
)
|
|
930
|
+
const body = screen.getByTestId('edge-4')
|
|
931
|
+
|
|
932
|
+
// Should handle breakpoints without default
|
|
933
|
+
expect(body.className).toBeTruthy()
|
|
934
|
+
})
|
|
935
|
+
})
|
|
936
|
+
})
|