@@ -87,14 +87,130 @@ describe('TaskPerformers', () => {
8787 const getSkipCheckboxCall = ( ) => {
8888 const calls = ( Checkbox as jest . Mock ) . mock . calls ;
8989 return calls . find (
90- ( c : any [ ] ) => c [ 0 ] . checkboxId === 'skipForStarter' ,
90+ ( c : any [ ] ) => c [ 0 ] . checkboxId === 'skipForStarter-task-1 ' ,
9191 ) ;
9292 } ;
9393
94+ const getCompleteByAllCall = ( ) => {
95+ const calls = ( Checkbox as jest . Mock ) . mock . calls ;
96+ return calls . find (
97+ ( c : any [ ] ) => c [ 0 ] . checkboxId === 'completeByAll-task-1' ,
98+ ) ;
99+ } ;
100+
101+ const renderTwoTasks = ( ) => {
102+ const task1 = makeTask ( { apiName : 'task-1' } ) ;
103+ const task2 = makeTask ( { apiName : 'task-2' } ) ;
104+ const setCurrentTask1 = jest . fn ( ) ;
105+ const setCurrentTask2 = jest . fn ( ) ;
106+
107+ render (
108+ React . createElement ( 'div' , null ,
109+ React . createElement ( TaskPerformers , {
110+ task : task1 ,
111+ tasks : [ task1 , task2 ] ,
112+ users : [ ] ,
113+ variables : [ ] ,
114+ isTeamInvitesModalOpen : false ,
115+ setCurrentTask : setCurrentTask1 ,
116+ } ) ,
117+ React . createElement ( TaskPerformers , {
118+ task : task2 ,
119+ tasks : [ task1 , task2 ] ,
120+ users : [ ] ,
121+ variables : [ ] ,
122+ isTeamInvitesModalOpen : false ,
123+ setCurrentTask : setCurrentTask2 ,
124+ } ) ,
125+ ) ,
126+ ) ;
127+
128+ return { setCurrentTask1, setCurrentTask2 } ;
129+ } ;
130+
94131 beforeEach ( ( ) => {
95132 jest . clearAllMocks ( ) ;
96133 } ) ;
97134
135+ describe ( 'checkbox identifiers' , ( ) => {
136+ it ( 'generates unique checkboxId using task.apiName to prevent HTML id collisions' , ( ) => {
137+ renderComponent ( { apiName : 'custom-task-123' } ) ;
138+
139+ const calls = ( Checkbox as jest . Mock ) . mock . calls ;
140+ const completeByAllCall = calls . find ( ( c : any [ ] ) => c [ 0 ] . checkboxId ?. startsWith ( 'completeByAll-' ) ) ;
141+ const skipForStarterCall = calls . find ( ( c : any [ ] ) => c [ 0 ] . checkboxId ?. startsWith ( 'skipForStarter-' ) ) ;
142+
143+ expect ( completeByAllCall ) . toBeDefined ( ) ;
144+ expect ( skipForStarterCall ) . toBeDefined ( ) ;
145+
146+ expect ( completeByAllCall ! [ 0 ] . checkboxId ) . toBe ( 'completeByAll-custom-task-123' ) ;
147+ expect ( skipForStarterCall ! [ 0 ] . checkboxId ) . toBe ( 'skipForStarter-custom-task-123' ) ;
148+ } ) ;
149+
150+ it ( 'two tasks produce different checkboxIds (no DOM id collisions)' , ( ) => {
151+ renderTwoTasks ( ) ;
152+
153+ const calls = ( Checkbox as jest . Mock ) . mock . calls ;
154+ const checkboxIds = calls . map ( ( c : any [ ] ) => c [ 0 ] . checkboxId ) ;
155+
156+ const completeByAllIds = checkboxIds . filter ( ( id : string ) => id ?. startsWith ( 'completeByAll-' ) ) ;
157+ const skipForStarterIds = checkboxIds . filter ( ( id : string ) => id ?. startsWith ( 'skipForStarter-' ) ) ;
158+
159+ expect ( completeByAllIds ) . toEqual ( [ 'completeByAll-task-1' , 'completeByAll-task-2' ] ) ;
160+ expect ( skipForStarterIds ) . toEqual ( [ 'skipForStarter-task-1' , 'skipForStarter-task-2' ] ) ;
161+ } ) ;
162+
163+ it ( 'uses checkboxId prop (not id) to ensure proper label-input binding' , ( ) => {
164+ renderComponent ( ) ;
165+
166+ const calls = ( Checkbox as jest . Mock ) . mock . calls ;
167+
168+ calls . forEach ( ( c : any [ ] ) => {
169+ expect ( c [ 0 ] . checkboxId ) . toBeDefined ( ) ;
170+ expect ( c [ 0 ] . id ) . toBeUndefined ( ) ;
171+ } ) ;
172+ } ) ;
173+ } ) ;
174+
175+ describe ( 'requireCompletionByAll checkbox' , ( ) => {
176+ it ( 'passes title with correct localization text' , ( ) => {
177+ renderComponent ( ) ;
178+
179+ const call = getCompleteByAllCall ( ) ;
180+
181+ expect ( call ) . toBeDefined ( ) ;
182+ expect ( call ! [ 0 ] . title ) . toBe ( t ( 'templates.task-require-completion-by-all' ) ) ;
183+ } ) ;
184+
185+ it ( 'passes checked=true when requireCompletionByAll=true' , ( ) => {
186+ renderComponent ( { requireCompletionByAll : true } ) ;
187+
188+ const call = getCompleteByAllCall ( ) ;
189+
190+ expect ( call ! [ 0 ] . checked ) . toBe ( true ) ;
191+ } ) ;
192+
193+ it ( 'passes checked=false when requireCompletionByAll=false' , ( ) => {
194+ renderComponent ( { requireCompletionByAll : false } ) ;
195+
196+ const call = getCompleteByAllCall ( ) ;
197+
198+ expect ( call ! [ 0 ] . checked ) . toBe ( false ) ;
199+ } ) ;
200+
201+ it ( 'calls setCurrentTask with requireCompletionByAll on onChange' , ( ) => {
202+ renderComponent ( { requireCompletionByAll : false } ) ;
203+
204+ const call = getCompleteByAllCall ( ) ;
205+ const onChangeFn = call ! [ 0 ] . onChange ;
206+ onChangeFn ( { currentTarget : { checked : true } } ) ;
207+
208+ expect ( mockSetCurrentTask ) . toHaveBeenCalledWith (
209+ { requireCompletionByAll : true } ,
210+ ) ;
211+ } ) ;
212+ } ) ;
213+
98214 describe ( 'skipForStarter checkbox' , ( ) => {
99215 it ( 'passes title with correct localization text' , ( ) => {
100216 renderComponent ( ) ;
0 commit comments