Skip to content

Commit bd61b46

Browse files
jho406claude
andcommitted
Add React Aria v1 wrapper
Headless wrapper using react-aria-components with DateFieldProps for date transforms, value/defaultValue for Select, and min/max on RangeField. Includes transform functions for checkbox, date, time, and number field prop mapping. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ba63926 commit bd61b46

24 files changed

Lines changed: 2392 additions & 0 deletions

‎wrappers/js/react-aria/v1/index.jsx‎

Lines changed: 426 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import React from 'react'
2+
import { render } from '@testing-library/react'
3+
import { Checkbox, ValidationContext } from '.'
4+
5+
const buildPayload = () => {
6+
return {
7+
type: 'checkbox',
8+
value: '1',
9+
uncheckedValue: '0',
10+
defaultChecked: true,
11+
name: 'post[admin]',
12+
id: 'post_admin',
13+
}
14+
}
15+
16+
describe('Checkbox', () => {
17+
it('renders', () => {
18+
const payload = buildPayload()
19+
20+
const { getByRole } = render(<Checkbox label="Is admin" {...payload} />)
21+
22+
const checkbox = getByRole('checkbox', { name: 'Is admin' })
23+
expect(checkbox).not.toBeNull()
24+
})
25+
26+
it('renders defaultSelected', () => {
27+
const payload = buildPayload()
28+
payload.defaultChecked = true
29+
30+
const { getByRole } = render(
31+
<Checkbox {...payload} label={'Is admin'} errorKey={'is_admin'} />
32+
)
33+
34+
const checkbox = getByRole('checkbox', { name: 'Is admin' })
35+
expect(checkbox.checked).toEqual(true)
36+
})
37+
38+
it('renders defaultSelected false', () => {
39+
const payload = buildPayload()
40+
payload.defaultChecked = false
41+
42+
const { getByRole } = render(
43+
<Checkbox {...payload} label={'Is admin'} errorKey={'is_admin'} />
44+
)
45+
46+
const checkbox = getByRole('checkbox', { name: 'Is admin' })
47+
expect(checkbox.checked).toEqual(false)
48+
})
49+
50+
it('renders selected', () => {
51+
const payload = buildPayload()
52+
delete payload.defaultChecked
53+
payload.checked = true
54+
55+
const { getByRole } = render(
56+
<Checkbox {...payload} label={'Is admin'} errorKey={'is_admin'} />
57+
)
58+
59+
const checkbox = getByRole('checkbox', { name: 'Is admin' })
60+
expect(checkbox.checked).toEqual(true)
61+
})
62+
63+
it('renders unselected', () => {
64+
const payload = buildPayload()
65+
delete payload.defaultChecked
66+
payload.checked = false
67+
68+
const { getByRole } = render(
69+
<Checkbox {...payload} label={'Is admin'} errorKey={'is_admin'} />
70+
)
71+
72+
const checkbox = getByRole('checkbox', { name: 'Is admin' })
73+
expect(checkbox.checked).toEqual(false)
74+
})
75+
76+
it('adds a hidden input if includeHidden is true', () => {
77+
const payload = buildPayload()
78+
payload.includeHidden = true
79+
payload.uncheckedValue = '10'
80+
81+
const { container } = render(
82+
<Checkbox {...payload} label={'Is admin'} errorKey={'is_admin'} />
83+
)
84+
85+
const hiddenInput = container.querySelector('input[type=hidden]')
86+
expect(hiddenInput).not.toBe(null)
87+
expect(hiddenInput.name).toEqual('post[admin]')
88+
expect(hiddenInput.value).toEqual('10')
89+
})
90+
91+
it('renders with errors', async () => {
92+
const payload = buildPayload()
93+
94+
const validationErrors = {
95+
is_admin: 'Admin invalid',
96+
}
97+
98+
const { getByRole } = render(
99+
<ValidationContext.Provider value={validationErrors}>
100+
<Checkbox {...payload} label={'Is admin'} errorKey={'is_admin'} />
101+
</ValidationContext.Provider>
102+
)
103+
104+
const checkbox = getByRole('checkbox', { name: 'Is admin' })
105+
expect(checkbox).not.toBe(null)
106+
expect(checkbox.getAttribute('aria-invalid')).toEqual('true')
107+
})
108+
})
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React from 'react'
2+
import { render } from '@testing-library/react'
3+
import { CollectionCheckboxes, ValidationContext } from '.'
4+
5+
const buildCheckboxPayload = (value, label) => {
6+
return {
7+
type: 'checkbox',
8+
value,
9+
defaultChecked: true,
10+
label,
11+
name: 'post[author_ids][]',
12+
id: `post_author_ids_${value}`,
13+
}
14+
}
15+
16+
const buildPayload = () => {
17+
return {
18+
collection: [
19+
buildCheckboxPayload(1, 'one'),
20+
buildCheckboxPayload(2, 'two'),
21+
],
22+
includeHidden: true,
23+
label: 'authors',
24+
required: false,
25+
}
26+
}
27+
28+
describe('CollectionCheckboxes', () => {
29+
it('renders', () => {
30+
const payload = buildPayload()
31+
32+
const { getByRole, getAllByRole } = render(
33+
<CollectionCheckboxes
34+
{...payload}
35+
label={'Authors'}
36+
errorKey={'authors'}
37+
/>
38+
)
39+
40+
const group = getByRole('group', { name: 'Authors' })
41+
expect(group).not.toBeNull()
42+
43+
const checkboxes = getAllByRole('checkbox')
44+
expect(checkboxes.length).toEqual(2)
45+
})
46+
47+
it('renders nothing when the collection is blank', () => {
48+
const payload = buildPayload()
49+
payload.collection = []
50+
51+
const { container } = render(
52+
<CollectionCheckboxes
53+
{...payload}
54+
label={'Authors'}
55+
errorKey={'authors'}
56+
/>
57+
)
58+
59+
expect(container.innerHTML).toEqual('')
60+
})
61+
62+
it('renders hidden', () => {
63+
const payload = buildPayload()
64+
payload.includeHidden = true
65+
66+
const { container } = render(
67+
<CollectionCheckboxes
68+
{...payload}
69+
label={'Authors'}
70+
errorKey={'authors'}
71+
/>
72+
)
73+
74+
const hiddenInput = container.querySelector('input[type=hidden]')
75+
expect(hiddenInput.name).toEqual('post[author_ids][]')
76+
expect(hiddenInput.value).toEqual('')
77+
})
78+
79+
it('renders with error highlighting', async () => {
80+
const payload = buildPayload()
81+
82+
const validationErrors = {
83+
author_ids: 'id invalid',
84+
}
85+
86+
const { getByText } = render(
87+
<ValidationContext.Provider value={validationErrors}>
88+
<CollectionCheckboxes
89+
{...payload}
90+
label={'Authors'}
91+
errorKey={'author_ids'}
92+
/>
93+
</ValidationContext.Provider>
94+
)
95+
96+
const errorField = getByText('id invalid')
97+
expect(errorField).not.toBeNull()
98+
})
99+
})
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React from 'react'
2+
import { render } from '@testing-library/react'
3+
import { CollectionRadioButtons, ValidationContext } from '.'
4+
5+
const buildRadioButtonPayload = (value, label, rest) => {
6+
return {
7+
type: 'radio',
8+
value,
9+
label,
10+
name: 'post[subscribe]',
11+
id: `post_subscribe_${value}`,
12+
...rest,
13+
}
14+
}
15+
16+
const buildPayload = () => {
17+
return {
18+
collection: [
19+
buildRadioButtonPayload(1, 'one', { defaultChecked: true }),
20+
buildRadioButtonPayload(2, 'two'),
21+
],
22+
includeHidden: true,
23+
label: 'Subscribe',
24+
required: false,
25+
}
26+
}
27+
28+
describe('CollectionRadioButtons', () => {
29+
it('renders', () => {
30+
const payload = buildPayload()
31+
32+
const { getByRole, getAllByRole } = render(
33+
<CollectionRadioButtons
34+
{...payload}
35+
label={'Subscribe'}
36+
errorKey={'subscribe'}
37+
/>
38+
)
39+
40+
const radiogroup = getByRole('radiogroup', { name: 'Subscribe' })
41+
expect(radiogroup).not.toBeNull()
42+
43+
const radios = getAllByRole('radio')
44+
expect(radios.length).toEqual(2)
45+
})
46+
47+
it('renders nothing when the collection is blank', () => {
48+
const payload = buildPayload()
49+
payload.collection = []
50+
51+
const { container } = render(
52+
<CollectionRadioButtons
53+
{...payload}
54+
label={'Subscribe'}
55+
errorKey={'subscribe'}
56+
/>
57+
)
58+
59+
expect(container.innerHTML).toEqual('')
60+
})
61+
62+
it('renders hidden', () => {
63+
const payload = buildPayload()
64+
payload.includeHidden = true
65+
66+
const { container } = render(
67+
<CollectionRadioButtons
68+
{...payload}
69+
label={'Subscribe'}
70+
errorKey={'subscribe'}
71+
/>
72+
)
73+
74+
const hiddenInput = container.querySelector('input[type=hidden]')
75+
expect(hiddenInput.name).toEqual('post[subscribe]')
76+
expect(hiddenInput.value).toEqual('')
77+
})
78+
79+
it('renders with error highlighting', async () => {
80+
const payload = buildPayload()
81+
82+
const validationErrors = {
83+
subscribe: 'id invalid',
84+
}
85+
86+
const { getByText } = render(
87+
<ValidationContext.Provider value={validationErrors}>
88+
<CollectionRadioButtons
89+
{...payload}
90+
label={'Subscribe'}
91+
errorKey={'subscribe'}
92+
/>
93+
</ValidationContext.Provider>
94+
)
95+
96+
const errorField = getByText('id invalid')
97+
expect(errorField).not.toBeNull()
98+
})
99+
})
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react'
2+
import { render } from '@testing-library/react'
3+
import { ColorField, ValidationContext } from '.'
4+
5+
const buildPayload = () => {
6+
return {
7+
type: 'color',
8+
name: 'post[color]',
9+
id: 'post_color',
10+
required: false,
11+
defaultValue: '#000000',
12+
}
13+
}
14+
15+
describe('ColorField', () => {
16+
it('renders', () => {
17+
const payload = buildPayload()
18+
19+
const { container } = render(
20+
<ColorField {...payload} label={'color'} errorKey={'color'} />
21+
)
22+
23+
const input = container.querySelector('input')
24+
expect(input).not.toBeNull()
25+
expect(input.value).toEqual('#000000')
26+
})
27+
28+
it('renders with field errors', async () => {
29+
const payload = buildPayload()
30+
31+
const validationErrors = {
32+
color: 'color invalid',
33+
}
34+
35+
const { getByText } = render(
36+
<ValidationContext.Provider value={validationErrors}>
37+
<ColorField {...payload} label={'color'} errorKey={'color'} />
38+
</ValidationContext.Provider>
39+
)
40+
41+
const errorField = getByText('color invalid')
42+
expect(errorField).not.toBeNull()
43+
})
44+
})

0 commit comments

Comments
 (0)