@@ -22,14 +22,41 @@ import { afterEach, beforeAll, expect, it, vi } from "vitest";
2222
2323import { TaskInstanceService } from "openapi/requests" ;
2424
25+ import {
26+ CLEAR_KEEP_TASK_STATE_KEY ,
27+ CLEAR_PREVENT_RUNNING_TASK_KEY ,
28+ CLEAR_TASK_INSTANCE_DEFAULT_OPTIONS_KEY ,
29+ } from "src/constants/localStorage" ;
2530import i18n from "src/i18n/config" ;
2631import { Wrapper } from "src/utils/Wrapper" ;
2732
2833import dagTranslations from "../../../../public/i18n/locales/en/dag.json" ;
34+ import dagsTranslations from "../../../../public/i18n/locales/en/dags.json" ;
2935import { ClearExecutionDialog } from "./ClearExecutionDialog" ;
3036
31- beforeAll ( ( ) => i18n . addResourceBundle ( "en" , "dag" , dagTranslations , true , true ) ) ;
32- afterEach ( ( ) => vi . restoreAllMocks ( ) ) ;
37+ beforeAll ( ( ) => {
38+ i18n . addResourceBundle ( "en" , "dag" , dagTranslations , true , true ) ;
39+ i18n . addResourceBundle ( "en" , "dags" , dagsTranslations , true , true ) ;
40+ } ) ;
41+ afterEach ( ( ) => {
42+ vi . restoreAllMocks ( ) ;
43+ localStorage . clear ( ) ;
44+ } ) ;
45+
46+ const execution = {
47+ id : "selected-uuid" ,
48+ map_index : - 1 ,
49+ note : "existing note" ,
50+ region_id : "11111111-1111-1111-1111-111111111111" ,
51+ region_index : 1 ,
52+ task_display_name : "work" ,
53+ task_id : "body.work" ,
54+ } ;
55+
56+ const findClearRequest = ( clear : { mock : { calls : Array < Array < unknown > > } } ) =>
57+ ( clear . mock . calls as Array < [ { requestBody : { dry_run ?: boolean ; note ?: string | null } } ] > )
58+ . map ( ( [ request ] ) => request )
59+ . find ( ( request ) => request . requestBody . dry_run === false ) ;
3360
3461it ( "keeps stale UUID scope visible when the clear preview rejects an archived execution" , async ( ) => {
3562 const clear = vi
@@ -136,3 +163,118 @@ it("previews and clears UUID seeds with separate whole-expansion and later-loop
136163 } ) ,
137164 ) ;
138165} ) ;
166+
167+ it ( "sends the saved clear defaults in the dry run and the real request" , async ( ) => {
168+ localStorage . setItem ( CLEAR_PREVENT_RUNNING_TASK_KEY , JSON . stringify ( true ) ) ;
169+ localStorage . setItem ( CLEAR_KEEP_TASK_STATE_KEY , JSON . stringify ( true ) ) ;
170+ localStorage . setItem ( CLEAR_TASK_INSTANCE_DEFAULT_OPTIONS_KEY , JSON . stringify ( [ ] ) ) ;
171+ const clear = vi
172+ . spyOn ( TaskInstanceService , "postClearTaskInstances" )
173+ . mockResolvedValue ( { task_instances : [ ] , total_entries : 1 } ) ;
174+
175+ render ( < ClearExecutionDialog dagId = "dag" executions = { [ execution ] } onClose = { vi . fn ( ) } open runId = "run" /> , {
176+ wrapper : Wrapper ,
177+ } ) ;
178+
179+ await waitFor ( ( ) =>
180+ expect ( clear . mock . lastCall ?. [ 0 ] . requestBody ) . toMatchObject ( {
181+ dry_run : true ,
182+ include_downstream : false ,
183+ keep_task_state : true ,
184+ prevent_running_task : true ,
185+ } ) ,
186+ ) ;
187+ expect ( await screen . findByText ( "1 execution will be cleared." ) ) . toBeVisible ( ) ;
188+ expect ( screen . getByLabelText ( "Prevent rerun if task is running" ) ) . toBeChecked ( ) ;
189+ expect ( screen . getByLabelText ( "Keep task state and resume" ) ) . toBeChecked ( ) ;
190+ fireEvent . click ( screen . getByLabelText ( "Keep task state and resume" ) ) ;
191+ fireEvent . click ( screen . getByLabelText ( "Prevent rerun if task is running" ) ) ;
192+ await waitFor ( ( ) =>
193+ expect ( clear . mock . lastCall ?. [ 0 ] . requestBody ) . toMatchObject ( {
194+ dry_run : true ,
195+ keep_task_state : false ,
196+ prevent_running_task : false ,
197+ } ) ,
198+ ) ;
199+ await waitFor ( ( ) =>
200+ expect ( screen . getByRole ( "button" , { name : "Clear selected executions" } ) ) . toBeEnabled ( ) ,
201+ ) ;
202+ fireEvent . click ( screen . getByRole ( "button" , { name : "Clear selected executions" } ) ) ;
203+ await waitFor ( ( ) =>
204+ expect ( findClearRequest ( clear ) ?. requestBody ) . toMatchObject ( {
205+ keep_task_state : false ,
206+ prevent_running_task : false ,
207+ } ) ,
208+ ) ;
209+ } ) ;
210+
211+ it ( "hides the affected count while the dry run is pending" , async ( ) => {
212+ vi . spyOn ( TaskInstanceService , "postClearTaskInstances" ) . mockReturnValue (
213+ new Promise ( ( ) => undefined ) as ReturnType < typeof TaskInstanceService . postClearTaskInstances > ,
214+ ) ;
215+
216+ render ( < ClearExecutionDialog dagId = "dag" executions = { [ execution ] } onClose = { vi . fn ( ) } open runId = "run" /> , {
217+ wrapper : Wrapper ,
218+ } ) ;
219+
220+ expect ( await screen . findByLabelText ( "Clear downstream tasks" ) ) . toBeVisible ( ) ;
221+ expect ( screen . queryByText ( / e x e c u t i o n s ? w i l l b e c l e a r e d / u) ) . not . toBeInTheDocument ( ) ;
222+ } ) ;
223+
224+ it ( "seeds the note from the execution and only sends it when edited" , async ( ) => {
225+ const clear = vi
226+ . spyOn ( TaskInstanceService , "postClearTaskInstances" )
227+ . mockResolvedValue ( { task_instances : [ ] , total_entries : 1 } ) ;
228+
229+ render ( < ClearExecutionDialog dagId = "dag" executions = { [ execution ] } onClose = { vi . fn ( ) } open runId = "run" /> , {
230+ wrapper : Wrapper ,
231+ } ) ;
232+
233+ const note = await screen . findByLabelText ( "Reason for clearing (optional)" ) ;
234+
235+ expect ( note ) . toHaveValue ( "existing note" ) ;
236+ await waitFor ( ( ) =>
237+ expect ( screen . getByRole ( "button" , { name : "Clear selected executions" } ) ) . toBeEnabled ( ) ,
238+ ) ;
239+ fireEvent . click ( screen . getByRole ( "button" , { name : "Clear selected executions" } ) ) ;
240+ await waitFor ( ( ) => expect ( findClearRequest ( clear ) ) . toBeDefined ( ) ) ;
241+ expect ( findClearRequest ( clear ) ?. requestBody . note ) . toBeUndefined ( ) ;
242+ } ) ;
243+
244+ it ( "does not send an empty note when the user erases the existing one" , async ( ) => {
245+ const clear = vi
246+ . spyOn ( TaskInstanceService , "postClearTaskInstances" )
247+ . mockResolvedValue ( { task_instances : [ ] , total_entries : 1 } ) ;
248+
249+ render ( < ClearExecutionDialog dagId = "dag" executions = { [ execution ] } onClose = { vi . fn ( ) } open runId = "run" /> , {
250+ wrapper : Wrapper ,
251+ } ) ;
252+
253+ fireEvent . change ( await screen . findByLabelText ( "Reason for clearing (optional)" ) , { target : { value : "" } } ) ;
254+ await waitFor ( ( ) =>
255+ expect ( screen . getByRole ( "button" , { name : "Clear selected executions" } ) ) . toBeEnabled ( ) ,
256+ ) ;
257+ fireEvent . click ( screen . getByRole ( "button" , { name : "Clear selected executions" } ) ) ;
258+ await waitFor ( ( ) => expect ( findClearRequest ( clear ) ) . toBeDefined ( ) ) ;
259+ expect ( findClearRequest ( clear ) ?. requestBody . note ) . toBeUndefined ( ) ;
260+ } ) ;
261+
262+ it ( "resets the dialog state once it closes" , async ( ) => {
263+ vi . spyOn ( TaskInstanceService , "postClearTaskInstances" ) . mockResolvedValue ( {
264+ task_instances : [ ] ,
265+ total_entries : 1 ,
266+ } ) ;
267+ const props = { dagId : "dag" , executions : [ execution ] , onClose : vi . fn ( ) , runId : "run" } ;
268+ const { rerender } = render ( < ClearExecutionDialog { ...props } open /> , { wrapper : Wrapper } ) ;
269+
270+ fireEvent . click ( await screen . findByLabelText ( "Clear later loop iterations" ) ) ;
271+ fireEvent . change ( screen . getByLabelText ( "Reason for clearing (optional)" ) , { target : { value : "typed" } } ) ;
272+ await waitFor ( ( ) => expect ( screen . getByLabelText ( "Clear later loop iterations" ) ) . not . toBeChecked ( ) ) ;
273+ await waitFor ( ( ) => expect ( screen . getByLabelText ( "Reason for clearing (optional)" ) ) . toHaveValue ( "typed" ) ) ;
274+ rerender ( < ClearExecutionDialog { ...props } open = { false } /> ) ;
275+ await waitFor ( ( ) => expect ( screen . queryByLabelText ( "Clear later loop iterations" ) ) . not . toBeInTheDocument ( ) ) ;
276+ rerender ( < ClearExecutionDialog { ...props } open /> ) ;
277+
278+ expect ( await screen . findByLabelText ( "Clear later loop iterations" ) ) . toBeChecked ( ) ;
279+ expect ( screen . getByLabelText ( "Reason for clearing (optional)" ) ) . toHaveValue ( "existing note" ) ;
280+ } ) ;
0 commit comments