@@ -6,23 +6,34 @@ import type { TaskItemImpl } from '@/stores/queueStore'
66import type { JobErrorDialogService } from '@/components/queue/job/useJobErrorReporting'
77import { useJobErrorReporting } from '@/components/queue/job/useJobErrorReporting'
88
9- const createTaskWithError = ( errorMessage ?: string ) : TaskItemImpl =>
10- ( { errorMessage } ) as unknown as TaskItemImpl
9+ const fetchJobDetailMock = vi . fn ( )
10+ vi . mock ( '@/platform/remote/comfyui/jobs' , ( ) => ( {
11+ fetchJobDetail : ( ...args : unknown [ ] ) => fetchJobDetailMock ( ...args )
12+ } ) )
13+
14+ const createTaskWithError = (
15+ promptId : string ,
16+ errorMessage ?: string
17+ ) : TaskItemImpl => ( { promptId, errorMessage } ) as unknown as TaskItemImpl
1118
1219describe ( 'useJobErrorReporting' , ( ) => {
1320 let taskState = ref < TaskItemImpl | null > ( null )
1421 let taskForJob : ComputedRef < TaskItemImpl | null >
1522 let copyToClipboard : ReturnType < typeof vi . fn >
1623 let showErrorDialog : ReturnType < typeof vi . fn >
24+ let showExecutionErrorDialog : ReturnType < typeof vi . fn >
1725 let dialog : JobErrorDialogService
1826 let composable : ReturnType < typeof useJobErrorReporting >
1927
2028 beforeEach ( ( ) => {
29+ vi . clearAllMocks ( )
2130 taskState = ref < TaskItemImpl | null > ( null )
2231 taskForJob = computed ( ( ) => taskState . value )
2332 copyToClipboard = vi . fn ( )
2433 showErrorDialog = vi . fn ( )
25- dialog = { showErrorDialog }
34+ showExecutionErrorDialog = vi . fn ( )
35+ dialog = { showErrorDialog, showExecutionErrorDialog }
36+ fetchJobDetailMock . mockResolvedValue ( undefined )
2637 composable = useJobErrorReporting ( {
2738 taskForJob,
2839 copyToClipboard,
@@ -35,44 +46,131 @@ describe('useJobErrorReporting', () => {
3546 } )
3647
3748 it ( 'exposes a computed message that reflects the current task error' , ( ) => {
38- taskState . value = createTaskWithError ( 'First failure' )
49+ taskState . value = createTaskWithError ( 'job-1' , ' First failure')
3950 expect ( composable . errorMessageValue . value ) . toBe ( 'First failure' )
4051
41- taskState . value = createTaskWithError ( 'Second failure' )
52+ taskState . value = createTaskWithError ( 'job-2' , ' Second failure')
4253 expect ( composable . errorMessageValue . value ) . toBe ( 'Second failure' )
4354 } )
4455
4556 it ( 'returns empty string when no error message' , ( ) => {
46- taskState . value = createTaskWithError ( )
57+ taskState . value = createTaskWithError ( 'job-1' )
4758 expect ( composable . errorMessageValue . value ) . toBe ( '' )
4859 } )
4960
5061 it ( 'only calls the copy handler when a message exists' , ( ) => {
51- taskState . value = createTaskWithError ( 'Clipboard failure' )
62+ taskState . value = createTaskWithError ( 'job-1' , ' Clipboard failure')
5263 composable . copyErrorMessage ( )
5364 expect ( copyToClipboard ) . toHaveBeenCalledTimes ( 1 )
5465 expect ( copyToClipboard ) . toHaveBeenCalledWith ( 'Clipboard failure' )
5566
5667 copyToClipboard . mockClear ( )
57- taskState . value = createTaskWithError ( )
68+ taskState . value = createTaskWithError ( 'job-2' )
5869 composable . copyErrorMessage ( )
5970 expect ( copyToClipboard ) . not . toHaveBeenCalled ( )
6071 } )
6172
62- it ( 'shows error dialog with the error message' , ( ) => {
63- taskState . value = createTaskWithError ( 'Queue job error' )
64- composable . reportJobError ( )
73+ it ( 'shows simple error dialog when no fetchApi provided' , async ( ) => {
74+ taskState . value = createTaskWithError ( 'job-1' , ' Queue job error')
75+ await composable . reportJobError ( )
6576
77+ expect ( fetchJobDetailMock ) . not . toHaveBeenCalled ( )
6678 expect ( showErrorDialog ) . toHaveBeenCalledTimes ( 1 )
6779 const [ errorArg , optionsArg ] = showErrorDialog . mock . calls [ 0 ]
6880 expect ( errorArg ) . toBeInstanceOf ( Error )
6981 expect ( errorArg . message ) . toBe ( 'Queue job error' )
7082 expect ( optionsArg ) . toEqual ( { reportType : 'queueJobError' } )
7183 } )
7284
73- it ( 'does nothing when no error message exists' , ( ) => {
74- taskState . value = createTaskWithError ( )
75- composable . reportJobError ( )
85+ it ( 'does nothing when no task exists' , async ( ) => {
86+ taskState . value = null
87+ await composable . reportJobError ( )
7688 expect ( showErrorDialog ) . not . toHaveBeenCalled ( )
89+ expect ( showExecutionErrorDialog ) . not . toHaveBeenCalled ( )
90+ } )
91+
92+ describe ( 'with fetchApi provided' , ( ) => {
93+ let fetchApi : ReturnType < typeof vi . fn >
94+
95+ beforeEach ( ( ) => {
96+ fetchApi = vi . fn ( )
97+ composable = useJobErrorReporting ( {
98+ taskForJob,
99+ copyToClipboard,
100+ dialog,
101+ fetchApi
102+ } )
103+ } )
104+
105+ it ( 'shows rich error dialog when execution_error available' , async ( ) => {
106+ const executionError = {
107+ node_id : '5' ,
108+ node_type : 'KSampler' ,
109+ executed : [ '1' , '2' ] ,
110+ exception_message : 'CUDA out of memory' ,
111+ exception_type : 'RuntimeError' ,
112+ traceback : [ 'line 1' , 'line 2' ] ,
113+ current_inputs : { } ,
114+ current_outputs : { }
115+ }
116+ fetchJobDetailMock . mockResolvedValue ( {
117+ id : 'job-1' ,
118+ create_time : 12345 ,
119+ execution_error : executionError
120+ } )
121+ taskState . value = createTaskWithError ( 'job-1' , 'CUDA out of memory' )
122+
123+ await composable . reportJobError ( )
124+
125+ expect ( fetchJobDetailMock ) . toHaveBeenCalledWith ( fetchApi , 'job-1' )
126+ expect ( showExecutionErrorDialog ) . toHaveBeenCalledTimes ( 1 )
127+ expect ( showExecutionErrorDialog ) . toHaveBeenCalledWith ( {
128+ prompt_id : 'job-1' ,
129+ timestamp : 12345 ,
130+ ...executionError
131+ } )
132+ expect ( showErrorDialog ) . not . toHaveBeenCalled ( )
133+ } )
134+
135+ it ( 'falls back to simple error dialog when no execution_error' , async ( ) => {
136+ fetchJobDetailMock . mockResolvedValue ( {
137+ id : 'job-1' ,
138+ execution_error : null
139+ } )
140+ taskState . value = createTaskWithError ( 'job-1' , 'Job failed' )
141+
142+ await composable . reportJobError ( )
143+
144+ expect ( fetchJobDetailMock ) . toHaveBeenCalledWith ( fetchApi , 'job-1' )
145+ expect ( showExecutionErrorDialog ) . not . toHaveBeenCalled ( )
146+ expect ( showErrorDialog ) . toHaveBeenCalledTimes ( 1 )
147+ const [ errorArg , optionsArg ] = showErrorDialog . mock . calls [ 0 ]
148+ expect ( errorArg ) . toBeInstanceOf ( Error )
149+ expect ( errorArg . message ) . toBe ( 'Job failed' )
150+ expect ( optionsArg ) . toEqual ( { reportType : 'queueJobError' } )
151+ } )
152+
153+ it ( 'falls back to simple error dialog when fetch fails' , async ( ) => {
154+ fetchJobDetailMock . mockResolvedValue ( undefined )
155+ taskState . value = createTaskWithError ( 'job-1' , 'Job failed' )
156+
157+ await composable . reportJobError ( )
158+
159+ expect ( showExecutionErrorDialog ) . not . toHaveBeenCalled ( )
160+ expect ( showErrorDialog ) . toHaveBeenCalledTimes ( 1 )
161+ } )
162+
163+ it ( 'does nothing when no error message and no execution_error' , async ( ) => {
164+ fetchJobDetailMock . mockResolvedValue ( {
165+ id : 'job-1' ,
166+ execution_error : null
167+ } )
168+ taskState . value = createTaskWithError ( 'job-1' )
169+
170+ await composable . reportJobError ( )
171+
172+ expect ( showErrorDialog ) . not . toHaveBeenCalled ( )
173+ expect ( showExecutionErrorDialog ) . not . toHaveBeenCalled ( )
174+ } )
77175 } )
78176} )
0 commit comments