@asteby/metacore-runtime-react 23.5.1 → 23.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.d.ts CHANGED
@@ -40,6 +40,13 @@ export interface TableMetadata {
40
40
  * keys as the kernel serves them.
41
41
  */
42
42
  stages?: StageMeta[];
43
+ /**
44
+ * Virtual "smart" lanes (ops #704): read-only board columns defined by a set
45
+ * of filters rather than a stored stage value. When present the kanban paints
46
+ * one lane per entry (querying the list with the lane's filters) after the
47
+ * real stages. Purely additive — absent on hosts without custom stages.
48
+ */
49
+ smart_lanes?: SmartLaneMeta[];
43
50
  /**
44
51
  * Allowed stage transitions (RFC §1.1). When present, the kanban only lets a
45
52
  * card drop into a lane reachable from its current stage; disallowed lanes
@@ -60,6 +67,28 @@ export interface StageMeta {
60
67
  color?: string;
61
68
  order?: number;
62
69
  is_final?: boolean;
70
+ /**
71
+ * True when the kernel merged a user-defined custom stage into `stages[]`
72
+ * (ops #704). Behaves as a normal droppable lane; the SDK just grows an
73
+ * Editar/Eliminar menu on it.
74
+ */
75
+ custom?: boolean;
76
+ }
77
+ /**
78
+ * A virtual "smart" lane (ops #704) served in `TableMetadata.smart_lanes`. It's
79
+ * defined by `filters` (never a stored stage value), so the board paints it by
80
+ * querying the list with those conditions. Read-only — not a drop target.
81
+ */
82
+ export interface SmartLaneMeta {
83
+ key: string;
84
+ label: string;
85
+ color?: string;
86
+ order?: number;
87
+ filters: {
88
+ field: string;
89
+ op: string;
90
+ value: string;
91
+ }[];
63
92
  }
64
93
  /** Allowed `from → to` stage transition (RFC §1.1). `'*'` is a wildcard. */
65
94
  export interface StageTransition {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "23.5.1",
3
+ "version": "23.7.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,86 @@
1
+ // @vitest-environment happy-dom
2
+ //
3
+ // GenericActionModal layout contract (the placement:create "Crear Issue" modal
4
+ // and every declarative action form): fields lay out in a shared responsive
5
+ // grid instead of a single cramped row with a horizontal scrollbar.
6
+ // - Scalar fields (input, select) each sit in a `min-w-0` cell so a long
7
+ // value can't blow the two columns past the dialog and spawn an x-scroll.
8
+ // - textareas span both columns (`sm:col-span-2`).
9
+ // - The grid itself declares one column on phones, two from `sm:`.
10
+ import { afterEach, describe, expect, it, vi } from 'vitest'
11
+ import { cleanup, render, screen } from '@testing-library/react'
12
+
13
+ vi.mock('react-i18next', () => ({
14
+ useTranslation: () => ({ t: (k: string) => k }),
15
+ }))
16
+
17
+ import { ActionModalDispatcher } from '../action-modal-dispatcher'
18
+ import { ApiProvider, type ApiClient } from '../api-context'
19
+ import type { ActionMetadata } from '@asteby/metacore-sdk'
20
+
21
+ afterEach(cleanup)
22
+
23
+ const noopApi: ApiClient = {
24
+ get: vi.fn().mockResolvedValue({ data: {} }),
25
+ post: vi.fn().mockResolvedValue({ data: { success: true } }),
26
+ } as unknown as ApiClient
27
+
28
+ const action: ActionMetadata = {
29
+ key: 'create_issue',
30
+ label: 'Crear Issue',
31
+ placement: 'create',
32
+ fields: [
33
+ { key: 'repo', label: 'Repositorio', type: 'select', required: true, options: [{ value: 'a', label: 'a' }] },
34
+ { key: 'title', label: 'Título', type: 'text', required: true },
35
+ { key: 'body', label: 'Descripción', type: 'textarea' },
36
+ ],
37
+ } as unknown as ActionMetadata
38
+
39
+ function renderModal() {
40
+ return render(
41
+ <ApiProvider client={noopApi}>
42
+ <ActionModalDispatcher
43
+ open
44
+ onOpenChange={() => {}}
45
+ action={action}
46
+ model="issue"
47
+ record={{} as any}
48
+ endpoint="/data/issue"
49
+ onSuccess={() => {}}
50
+ />
51
+ </ApiProvider>,
52
+ )
53
+ }
54
+
55
+ describe('GenericActionModal grid layout', () => {
56
+ it('renders a responsive two-column grid, not a single cramped row', () => {
57
+ renderModal()
58
+ const grid = document.body.querySelector('.sm\\:grid-cols-2')
59
+ expect(grid).not.toBeNull()
60
+ expect(grid!.className).toContain('grid')
61
+ expect(grid!.className).toContain('grid-cols-1')
62
+ })
63
+
64
+ it('gives every field cell min-w-0 so a long value cannot spawn a horizontal scrollbar', () => {
65
+ renderModal()
66
+ const repoLabel = screen.getByText('Repositorio')
67
+ // Label → FieldLabel → cell (FieldCell). The cell is the label's parent.
68
+ const cell = repoLabel.closest('div')!
69
+ expect(cell.className).toContain('min-w-0')
70
+ })
71
+
72
+ it('spans textareas across both columns and keeps scalars single-column', () => {
73
+ renderModal()
74
+ const bodyCell = screen.getByText('Descripción').closest('div')!
75
+ expect(bodyCell.className).toContain('sm:col-span-2')
76
+
77
+ const titleCell = screen.getByText('Título').closest('div')!
78
+ expect(titleCell.className).not.toContain('sm:col-span-2')
79
+ })
80
+
81
+ it('marks required fields with an asterisk', () => {
82
+ renderModal()
83
+ const label = screen.getByText('Repositorio').closest('label')!
84
+ expect(label.textContent).toContain('*')
85
+ })
86
+ })
@@ -0,0 +1,480 @@
1
+ // @vitest-environment happy-dom
2
+ //
3
+ // Custom stages coverage:
4
+ // 1. Pure helpers: splitCustomStages, mergeLaneStages, smartLaneParams,
5
+ // isCustomStageDraftValid, slugifyStageKey, customStageFilterFields.
6
+ // 2. AddStageColumn renders + fires onClick.
7
+ // 3. CustomStageDialog: create a NORMAL stage (mock onCreate), edit a SMART
8
+ // stage's condition value (mock onUpdate).
9
+ // 4. SmartLane queries the list endpoint with the lane's f_<field> params.
10
+ // 5. useCustomStages degrades to available=false when the endpoint 404s (the
11
+ // "+ Agregar etapa" affordance never renders → board intact).
12
+ import { afterEach, describe, expect, it, vi } from 'vitest'
13
+ import { cleanup, render, screen, fireEvent, waitFor } from '@testing-library/react'
14
+
15
+ const I18N_T = (k: string, opts?: Record<string, any>) => {
16
+ let s = (opts?.defaultValue as string) ?? k
17
+ if (opts) {
18
+ for (const [ok, ov] of Object.entries(opts)) {
19
+ if (ok === 'defaultValue') continue
20
+ s = s.replace(new RegExp(`{{${ok}}}`, 'g'), String(ov))
21
+ }
22
+ }
23
+ return s
24
+ }
25
+ const I18N = { language: 'es' }
26
+ const USE_TRANSLATION = { t: I18N_T, i18n: I18N }
27
+ vi.mock('react-i18next', () => ({
28
+ useTranslation: () => USE_TRANSLATION,
29
+ }))
30
+ vi.mock('sonner', () => ({
31
+ toast: { success: vi.fn(), error: vi.fn() },
32
+ }))
33
+ vi.mock('@tanstack/react-router', () => ({
34
+ useNavigate: () => () => {},
35
+ }))
36
+
37
+ import {
38
+ splitCustomStages,
39
+ mergeLaneStages,
40
+ resolveSmartLanes,
41
+ smartLaneParams,
42
+ isCustomStageDraftValid,
43
+ slugifyStageKey,
44
+ customStageFilterFields,
45
+ emptyCustomStageFilter,
46
+ AddStageColumn,
47
+ CustomStageDialog,
48
+ CustomStageDeleteDialog,
49
+ SmartLane,
50
+ useCustomStages,
51
+ type CustomStage,
52
+ } from '../custom-stages'
53
+ import type { SmartLaneMeta } from '../types'
54
+ import { ApiProvider, type ApiClient } from '../api-context'
55
+ import type { ColumnDefinition, StageMeta } from '../types'
56
+ import React from 'react'
57
+
58
+ afterEach(cleanup)
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Fixtures
62
+ // ---------------------------------------------------------------------------
63
+
64
+ const COLUMNS: ColumnDefinition[] = [
65
+ { key: 'title', label: 'Title', type: 'text', sortable: true, filterable: false },
66
+ { key: 'priority', label: 'Priority', type: 'text', sortable: false, filterable: false },
67
+ { key: 'id', label: 'ID', type: 'text', sortable: false, filterable: false },
68
+ { key: 'secret', label: 'Secret', type: 'text', sortable: false, filterable: false, hidden: true },
69
+ ]
70
+
71
+ const CUSTOM: CustomStage[] = [
72
+ {
73
+ id: 'c1',
74
+ model: 'issue',
75
+ key: 'review',
76
+ label: 'En revisión',
77
+ color: 'blue',
78
+ position: 5,
79
+ type: 'stage',
80
+ filters: [],
81
+ enabled: true,
82
+ },
83
+ {
84
+ id: 'c2',
85
+ model: 'issue',
86
+ key: 'urgent',
87
+ label: 'Urgentes',
88
+ color: 'red',
89
+ position: 6,
90
+ type: 'smart',
91
+ filters: [{ field: 'priority', op: 'eq', value: 'high' }],
92
+ enabled: true,
93
+ },
94
+ {
95
+ id: 'c3',
96
+ model: 'issue',
97
+ key: 'off',
98
+ label: 'Deshabilitada',
99
+ color: 'slate',
100
+ position: 7,
101
+ type: 'stage',
102
+ filters: [],
103
+ enabled: false,
104
+ },
105
+ ]
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // 1. Pure helpers
109
+ // ---------------------------------------------------------------------------
110
+
111
+ describe('splitCustomStages', () => {
112
+ it('splits enabled stages by flavor and drops disabled ones', () => {
113
+ const { laneStages, smartStages } = splitCustomStages(CUSTOM)
114
+ expect(laneStages.map((s) => s.key)).toEqual(['review'])
115
+ expect(smartStages.map((s) => s.key)).toEqual(['urgent'])
116
+ })
117
+ it('tolerates undefined', () => {
118
+ expect(splitCustomStages(undefined)).toEqual({ laneStages: [], smartStages: [] })
119
+ })
120
+ })
121
+
122
+ describe('mergeLaneStages', () => {
123
+ const declared: StageMeta[] = [
124
+ { key: 'todo', label: 'Todo', order: 0 },
125
+ { key: 'done', label: 'Done', order: 10 },
126
+ ]
127
+ it('appends unknown-key custom lanes, sorted by order/position', () => {
128
+ const custom: CustomStage[] = [
129
+ { ...CUSTOM[0], key: 'review', position: 5 },
130
+ ]
131
+ const { lanes, customByKey } = mergeLaneStages(declared, custom)
132
+ expect(lanes.map((s) => s.key)).toEqual(['todo', 'review', 'done'])
133
+ expect(customByKey.get('review')?.label).toBe('En revisión')
134
+ })
135
+ it('does not duplicate a custom lane the metadata already surfaced', () => {
136
+ const custom: CustomStage[] = [{ ...CUSTOM[0], key: 'done' }]
137
+ const { lanes, customByKey } = mergeLaneStages(declared, custom)
138
+ expect(lanes.map((s) => s.key)).toEqual(['todo', 'done'])
139
+ // Still tagged as custom so the lane gets its edit/delete menu.
140
+ expect(customByKey.has('done')).toBe(true)
141
+ })
142
+ })
143
+
144
+ describe('smartLaneParams', () => {
145
+ it('encodes the operator as an OP: prefix on a single f_<field> param', () => {
146
+ expect(
147
+ smartLaneParams([
148
+ { field: 'priority', op: 'eq', value: 'high' },
149
+ { field: 'title', op: 'contains', value: 'bug' },
150
+ { field: 'state', op: 'neq', value: 'closed' },
151
+ { field: 'label', op: 'in', value: 'a,b' },
152
+ ]),
153
+ ).toEqual({
154
+ f_priority: 'EQ:high',
155
+ f_title: 'HAS:bug',
156
+ f_state: 'NEQ:closed',
157
+ f_label: 'IN:a,b',
158
+ })
159
+ })
160
+ it('skips empty fields/values and tolerates undefined', () => {
161
+ expect(smartLaneParams(undefined)).toEqual({})
162
+ expect(
163
+ smartLaneParams([
164
+ { field: '', op: 'eq', value: 'x' },
165
+ { field: 'a', op: 'eq', value: ' ' },
166
+ ]),
167
+ ).toEqual({})
168
+ })
169
+ })
170
+
171
+ describe('isCustomStageDraftValid', () => {
172
+ it('requires a label', () => {
173
+ expect(isCustomStageDraftValid({ label: '', type: 'stage', filters: [] })).toBe(false)
174
+ expect(isCustomStageDraftValid({ label: 'A', type: 'stage', filters: [] })).toBe(true)
175
+ })
176
+ it('requires at least one complete condition for smart lanes', () => {
177
+ expect(
178
+ isCustomStageDraftValid({ label: 'A', type: 'smart', filters: [emptyCustomStageFilter('x')] }),
179
+ ).toBe(false)
180
+ expect(
181
+ isCustomStageDraftValid({
182
+ label: 'A',
183
+ type: 'smart',
184
+ filters: [{ field: 'x', op: 'eq', value: '1' }],
185
+ }),
186
+ ).toBe(true)
187
+ })
188
+ })
189
+
190
+ describe('slugifyStageKey / customStageFilterFields', () => {
191
+ it('slugifies labels (accents, spaces, symbols)', () => {
192
+ expect(slugifyStageKey('En Revisión!')).toBe('en_revision')
193
+ })
194
+ it('offers visible non-id columns for the condition builder', () => {
195
+ expect(customStageFilterFields(COLUMNS).map((c) => c.key)).toEqual(['title', 'priority'])
196
+ })
197
+ })
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // 2. AddStageColumn
201
+ // ---------------------------------------------------------------------------
202
+
203
+ describe('AddStageColumn', () => {
204
+ it('renders and fires onClick', () => {
205
+ const onClick = vi.fn()
206
+ render(<AddStageColumn onClick={onClick} />)
207
+ fireEvent.click(screen.getByTestId('kanban-add-stage'))
208
+ expect(onClick).toHaveBeenCalledTimes(1)
209
+ })
210
+ })
211
+
212
+ // ---------------------------------------------------------------------------
213
+ // 3. CustomStageDialog
214
+ // ---------------------------------------------------------------------------
215
+
216
+ describe('CustomStageDialog', () => {
217
+ it('creates a NORMAL stage with a slugified key and empty filters', async () => {
218
+ const onCreate = vi.fn(async () => {})
219
+ const onUpdate = vi.fn(async () => {})
220
+ render(
221
+ <CustomStageDialog
222
+ open
223
+ onOpenChange={() => {}}
224
+ model="issue"
225
+ columns={COLUMNS}
226
+ initial={null}
227
+ nextPosition={9}
228
+ onCreate={onCreate}
229
+ onUpdate={onUpdate}
230
+ />,
231
+ )
232
+ fireEvent.change(screen.getByTestId('custom-stage-name'), {
233
+ target: { value: 'En Progreso' },
234
+ })
235
+ fireEvent.click(screen.getByTestId('custom-stage-save'))
236
+ await waitFor(() => expect(onCreate).toHaveBeenCalledTimes(1))
237
+ expect(onCreate).toHaveBeenCalledWith({
238
+ model: 'issue',
239
+ key: 'en_progreso',
240
+ label: 'En Progreso',
241
+ color: 'slate',
242
+ position: 9,
243
+ type: 'stage',
244
+ filters: [],
245
+ enabled: true,
246
+ })
247
+ })
248
+
249
+ it('save is disabled until the stage has a name', () => {
250
+ render(
251
+ <CustomStageDialog
252
+ open
253
+ onOpenChange={() => {}}
254
+ model="issue"
255
+ columns={COLUMNS}
256
+ initial={null}
257
+ nextPosition={0}
258
+ onCreate={vi.fn()}
259
+ onUpdate={vi.fn()}
260
+ />,
261
+ )
262
+ expect((screen.getByTestId('custom-stage-save') as HTMLButtonElement).disabled).toBe(true)
263
+ })
264
+
265
+ it('edits a SMART stage: pre-fills conditions and updates the value', async () => {
266
+ const onUpdate = vi.fn(async () => {})
267
+ render(
268
+ <CustomStageDialog
269
+ open
270
+ onOpenChange={() => {}}
271
+ model="issue"
272
+ columns={COLUMNS}
273
+ initial={CUSTOM[1]}
274
+ nextPosition={0}
275
+ onCreate={vi.fn()}
276
+ onUpdate={onUpdate}
277
+ />,
278
+ )
279
+ // The smart lane's single condition renders pre-filled (value=high).
280
+ const valueInput = screen.getByTestId('custom-stage-condition-value-0') as HTMLInputElement
281
+ expect(valueInput.value).toBe('high')
282
+ fireEvent.change(valueInput, { target: { value: 'critical' } })
283
+ fireEvent.click(screen.getByTestId('custom-stage-save'))
284
+ await waitFor(() => expect(onUpdate).toHaveBeenCalledTimes(1))
285
+ // type/model/key are immutable → the patch omits them (ops #704).
286
+ expect(onUpdate).toHaveBeenCalledWith('c2', {
287
+ label: 'Urgentes',
288
+ color: 'red',
289
+ filters: [{ field: 'priority', op: 'eq', value: 'critical' }],
290
+ })
291
+ })
292
+
293
+ it('disables the type field when editing (type is immutable)', () => {
294
+ render(
295
+ <CustomStageDialog
296
+ open
297
+ onOpenChange={() => {}}
298
+ model="issue"
299
+ columns={COLUMNS}
300
+ initial={CUSTOM[1]}
301
+ nextPosition={0}
302
+ onCreate={vi.fn()}
303
+ onUpdate={vi.fn()}
304
+ />,
305
+ )
306
+ expect(
307
+ (screen.getByTestId('custom-stage-type') as HTMLButtonElement).disabled,
308
+ ).toBe(true)
309
+ })
310
+
311
+ it('adds and removes condition rows for a smart lane', () => {
312
+ render(
313
+ <CustomStageDialog
314
+ open
315
+ onOpenChange={() => {}}
316
+ model="issue"
317
+ columns={COLUMNS}
318
+ initial={CUSTOM[1]}
319
+ nextPosition={0}
320
+ onCreate={vi.fn()}
321
+ onUpdate={vi.fn()}
322
+ />,
323
+ )
324
+ expect(screen.getByTestId('custom-stage-condition-0')).toBeTruthy()
325
+ fireEvent.click(screen.getByTestId('custom-stage-add-condition'))
326
+ expect(screen.getByTestId('custom-stage-condition-1')).toBeTruthy()
327
+ fireEvent.click(screen.getByTestId('custom-stage-condition-remove-1'))
328
+ expect(screen.queryByTestId('custom-stage-condition-1')).toBeNull()
329
+ })
330
+ })
331
+
332
+ // ---------------------------------------------------------------------------
333
+ // 4. SmartLane
334
+ // ---------------------------------------------------------------------------
335
+
336
+ function fakeApi(over: Partial<ApiClient> = {}): ApiClient {
337
+ return {
338
+ get: vi.fn(async () => ({ data: { success: true, data: [], meta: { total: 0 } } })),
339
+ post: vi.fn(async () => ({ data: { success: true, data: null } })),
340
+ put: vi.fn(async () => ({ data: { success: true, data: null } })),
341
+ delete: vi.fn(async () => ({ data: { success: true, data: null } })),
342
+ ...over,
343
+ }
344
+ }
345
+
346
+ describe('SmartLane', () => {
347
+ it('queries the list endpoint with the lane f_<field> params', async () => {
348
+ const get = vi.fn(async () => ({
349
+ data: { success: true, data: [{ id: 1, title: 'Bug' }], meta: { total: 1 } },
350
+ }))
351
+ render(
352
+ <ApiProvider client={fakeApi({ get })}>
353
+ <SmartLane
354
+ stage={CUSTOM[1]}
355
+ model="issue"
356
+ endpoint="/data/issue/me"
357
+ defaultFilters={{ archived: false }}
358
+ isDark={false}
359
+ onEdit={() => {}}
360
+ onDelete={() => {}}
361
+ renderCard={(c) => <div data-testid={`smart-card-${c.id}`}>{c.title}</div>}
362
+ />
363
+ </ApiProvider>,
364
+ )
365
+ await waitFor(() => expect(get).toHaveBeenCalled())
366
+ const [url, cfg] = get.mock.calls[0] as [string, any]
367
+ expect(url).toBe('/data/issue/me')
368
+ expect(cfg.params).toMatchObject({ archived: false, f_priority: 'EQ:high' })
369
+ await waitFor(() => expect(screen.getByTestId('smart-card-1')).toBeTruthy())
370
+ })
371
+ })
372
+
373
+ // ---------------------------------------------------------------------------
374
+ // 4b. resolveSmartLanes — metadata-first with CRUD id fold-in
375
+ // ---------------------------------------------------------------------------
376
+
377
+ describe('resolveSmartLanes', () => {
378
+ const metaLanes: SmartLaneMeta[] = [
379
+ {
380
+ key: 'urgent',
381
+ label: 'Urgentes',
382
+ color: 'red',
383
+ order: 3,
384
+ filters: [{ field: 'priority', op: 'eq', value: 'high' }],
385
+ },
386
+ ]
387
+ it('maps metadata smart lanes, folding in the CRUD id by key', () => {
388
+ const out = resolveSmartLanes(metaLanes, [CUSTOM[1]], 'issue')
389
+ expect(out).toHaveLength(1)
390
+ expect(out[0]).toMatchObject({
391
+ id: 'c2', // folded in from the CRUD entry (metadata omits it)
392
+ key: 'urgent',
393
+ type: 'smart',
394
+ filters: [{ field: 'priority', op: 'eq', value: 'high' }],
395
+ })
396
+ })
397
+ it('synthesizes an id from the key when no CRUD match exists', () => {
398
+ expect(resolveSmartLanes(metaLanes, [], 'issue')[0].id).toBe('urgent')
399
+ })
400
+ it('falls back to CRUD smart stages when metadata omits smart_lanes', () => {
401
+ expect(resolveSmartLanes(undefined, [CUSTOM[1]], 'issue')).toEqual([CUSTOM[1]])
402
+ expect(resolveSmartLanes([], [CUSTOM[1]], 'issue')).toEqual([CUSTOM[1]])
403
+ })
404
+ })
405
+
406
+ // ---------------------------------------------------------------------------
407
+ // 4c. Delete dialog — 409 surfaces the card count + reassignment target
408
+ // ---------------------------------------------------------------------------
409
+
410
+ describe('CustomStageDeleteDialog', () => {
411
+ it('on 409 shows the card count and retries with a reassign target', async () => {
412
+ const onConfirm = vi
413
+ .fn()
414
+ .mockRejectedValueOnce({ response: { status: 409, data: { meta: { cards: 4 } } } })
415
+ .mockResolvedValueOnce(undefined)
416
+ render(
417
+ <CustomStageDeleteDialog
418
+ open
419
+ onOpenChange={() => {}}
420
+ stage={CUSTOM[0]}
421
+ reassignTargets={[
422
+ { key: 'review', label: 'En revisión' }, // same key → filtered out
423
+ { key: 'done', label: 'Done' },
424
+ ]}
425
+ onConfirm={onConfirm}
426
+ />,
427
+ )
428
+ // First attempt: plain delete, no reassign target.
429
+ fireEvent.click(screen.getByTestId('custom-stage-delete-confirm'))
430
+ await waitFor(() => expect(onConfirm).toHaveBeenCalledTimes(1))
431
+ expect(onConfirm).toHaveBeenLastCalledWith(CUSTOM[0], undefined)
432
+ // Conflict mode: the reassign select appears; the count is surfaced.
433
+ await waitFor(() => expect(screen.getByTestId('custom-stage-reassign')).toBeTruthy())
434
+ expect(screen.getByText(/4/)).toBeTruthy()
435
+ // Without a chosen target the confirm button stays a no-op (guarded).
436
+ fireEvent.click(screen.getByTestId('custom-stage-delete-confirm'))
437
+ expect(onConfirm).toHaveBeenCalledTimes(1)
438
+ })
439
+ })
440
+
441
+ // ---------------------------------------------------------------------------
442
+ // 5. Hook degrades gracefully when the endpoint is missing
443
+ // ---------------------------------------------------------------------------
444
+
445
+ function HookProbe({ model }: { model: string }) {
446
+ const c = useCustomStages(model)
447
+ return (
448
+ <div>
449
+ <span data-testid="available">{String(c.available)}</span>
450
+ <span data-testid="loading">{String(c.loading)}</span>
451
+ <span data-testid="count">{c.stages.length}</span>
452
+ </div>
453
+ )
454
+ }
455
+
456
+ describe('useCustomStages', () => {
457
+ it('loads stages when the endpoint is present', async () => {
458
+ render(
459
+ <ApiProvider client={fakeApi({ get: vi.fn(async () => ({ data: { success: true, data: CUSTOM } })) })}>
460
+ <HookProbe model="issue" />
461
+ </ApiProvider>,
462
+ )
463
+ await waitFor(() => expect(screen.getByTestId('available').textContent).toBe('true'))
464
+ expect(screen.getByTestId('count').textContent).toBe('3')
465
+ })
466
+
467
+ it('reports available=false when the endpoint 404s (no board break)', async () => {
468
+ const get = vi.fn(async () => {
469
+ throw { response: { status: 404 } }
470
+ })
471
+ render(
472
+ <ApiProvider client={fakeApi({ get })}>
473
+ <HookProbe model="issue" />
474
+ </ApiProvider>,
475
+ )
476
+ await waitFor(() => expect(screen.getByTestId('loading').textContent).toBe('false'))
477
+ expect(screen.getByTestId('available').textContent).toBe('false')
478
+ expect(screen.getByTestId('count').textContent).toBe('0')
479
+ })
480
+ })