@@ -6,6 +6,7 @@ import { hydrateRoot } from 'react-dom/client';
66import { renderToString } from 'react-dom/server' ;
77import { afterEach , describe , expect , it , vi } from 'vitest' ;
88
9+ import { Checkbox } from '../checkbox' ;
910import { Input } from '../input' ;
1011import { Select } from '../select' ;
1112import { Field } from './field' ;
@@ -14,6 +15,7 @@ const overrides = stylex.create({
1415 root : { display : 'grid' } ,
1516 label : { fontWeight : 700 } ,
1617 description : { opacity : 0.8 } ,
18+ content : { gap : '4px' } ,
1719 error : { fontWeight : 600 } ,
1820} ) ;
1921
@@ -175,6 +177,63 @@ describe('Mosaic Field', () => {
175177 expect ( inside ) . not . toHaveClass ( 'cl-field-control' ) ;
176178 } ) ;
177179
180+ it ( 'associates a checkbox laid out beside its label' , async ( ) => {
181+ render (
182+ < Field . Root
183+ orientation = 'horizontal'
184+ data-testid = 'root'
185+ >
186+ < Checkbox />
187+ < Field . Content data-testid = 'content' >
188+ < Field . Label > Sign out of all devices</ Field . Label >
189+ < Field . Description > Recommended after changing your password.</ Field . Description >
190+ </ Field . Content >
191+ </ Field . Root > ,
192+ ) ;
193+
194+ const checkbox = screen . getByRole ( 'checkbox' , { name : 'Sign out of all devices' } ) ;
195+ expect ( screen . getByTestId ( 'root' ) ) . toHaveAttribute ( 'data-orientation' , 'horizontal' ) ;
196+ expect ( screen . getByTestId ( 'content' ) ) . toHaveClass ( 'cl-field-content' ) ;
197+ expect ( checkbox ) . toHaveAttribute (
198+ 'aria-describedby' ,
199+ screen . getByText ( 'Recommended after changing your password.' ) . id ,
200+ ) ;
201+ await userEvent . click ( screen . getByText ( 'Sign out of all devices' ) ) ;
202+ expect ( checkbox ) . toBeChecked ( ) ;
203+ } ) ;
204+
205+ it ( 'toggles a checkbox wrapped in its label' , async ( ) => {
206+ render (
207+ < Field . Root >
208+ < Field . Label >
209+ < Checkbox />
210+ Stay signed in
211+ </ Field . Label >
212+ </ Field . Root > ,
213+ ) ;
214+
215+ await userEvent . click ( screen . getByText ( 'Stay signed in' ) ) ;
216+ expect ( screen . getByRole ( 'checkbox' , { name : 'Stay signed in' } ) ) . toBeChecked ( ) ;
217+ } ) ;
218+
219+ it ( 'propagates semantic state to a checkbox' , ( ) => {
220+ render (
221+ < Field . Root
222+ disabled
223+ required
224+ invalid
225+ >
226+ < Checkbox />
227+ </ Field . Root > ,
228+ ) ;
229+
230+ const checkbox = screen . getByRole ( 'checkbox' ) ;
231+ expect ( checkbox ) . toBeDisabled ( ) ;
232+ expect ( checkbox ) . toBeRequired ( ) ;
233+ expect ( checkbox ) . toHaveAttribute ( 'aria-invalid' , 'true' ) ;
234+ expect ( checkbox . parentElement ) . toHaveAttribute ( 'data-disabled' ) ;
235+ } ) ;
236+
178237 it ( 'propagates semantic state while preserving explicit control props' , ( ) => {
179238 render (
180239 < >
@@ -285,6 +344,7 @@ describe('Mosaic Field', () => {
285344 const rootRef = React . createRef < HTMLDivElement > ( ) ;
286345 const labelRef = React . createRef < HTMLLabelElement > ( ) ;
287346 const descriptionRef = React . createRef < HTMLParagraphElement > ( ) ;
347+ const contentRef = React . createRef < HTMLDivElement > ( ) ;
288348 const errorRef = React . createRef < HTMLParagraphElement > ( ) ;
289349
290350 render (
@@ -304,6 +364,10 @@ describe('Mosaic Field', () => {
304364 >
305365 Description
306366 </ Field . Description >
367+ < Field . Content
368+ ref = { contentRef }
369+ data-content = 'field'
370+ />
307371 < Field . Error
308372 ref = { errorRef }
309373 role = 'status'
@@ -316,6 +380,7 @@ describe('Mosaic Field', () => {
316380 expect ( rootRef . current ) . toHaveAttribute ( 'data-root' , 'field' ) ;
317381 expect ( labelRef . current ) . toHaveAttribute ( 'for' , 'name' ) ;
318382 expect ( descriptionRef . current ) . toHaveAttribute ( 'title' , 'Help' ) ;
383+ expect ( contentRef . current ) . toHaveAttribute ( 'data-content' , 'field' ) ;
319384 expect ( errorRef . current ) . toHaveAttribute ( 'role' , 'status' ) ;
320385 } ) ;
321386
@@ -327,11 +392,16 @@ describe('Mosaic Field', () => {
327392 >
328393 < Field . Label xstyle = { overrides . label } > Email</ Field . Label >
329394 < Field . Description xstyle = { overrides . description } > Description</ Field . Description >
395+ < Field . Content
396+ xstyle = { overrides . content }
397+ data-testid = 'content'
398+ />
330399 < Field . Error xstyle = { overrides . error } > Error</ Field . Error >
331400 </ Field . Root > ,
332401 ) ;
333402
334403 expect ( screen . getByTestId ( 'root' ) ) . toHaveClass ( 'cl-field-root' , ...atoms ( overrides . root ) ) ;
404+ expect ( screen . getByTestId ( 'content' ) ) . toHaveClass ( 'cl-field-content' , ...atoms ( overrides . content ) ) ;
335405 expect ( screen . getByText ( 'Email' ) ) . toHaveClass ( 'cl-field-label' , ...atoms ( overrides . label ) ) ;
336406 expect ( screen . getByText ( 'Description' ) ) . toHaveClass ( 'cl-field-description' , ...atoms ( overrides . description ) ) ;
337407 expect ( screen . getByText ( 'Error' ) . closest ( 'p' ) ) . toHaveClass ( 'cl-field-error' , ...atoms ( overrides . error ) ) ;
@@ -365,12 +435,14 @@ describe('Mosaic Field', () => {
365435 < Field . Root render = { props => < section { ...props } /> } >
366436 < Field . Label render = { props => < label { ...props } /> } > Biography</ Field . Label >
367437 < Field . Description render = { props => < div { ...props } /> } > Description</ Field . Description >
438+ < Field . Content render = { props => < section { ...props } /> } > Content</ Field . Content >
368439 < Field . Error render = { props => < div { ...props } /> } > Error</ Field . Error >
369440 </ Field . Root > ,
370441 ) ;
371442
372443 expect ( screen . getByText ( 'Biography' ) . closest ( 'section' ) ) . not . toBeNull ( ) ;
373444 expect ( screen . getByText ( 'Description' ) . tagName ) . toBe ( 'DIV' ) ;
445+ expect ( screen . getByText ( 'Content' ) . tagName ) . toBe ( 'SECTION' ) ;
374446 expect ( screen . getByText ( 'Error' ) . closest ( 'div' ) ) . toHaveClass ( 'cl-field-error' ) ;
375447 } ) ;
376448
0 commit comments