@asteby/metacore-runtime-react 49.4.0 → 49.6.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/CHANGELOG.md +14 -0
- package/dist/api-context.d.ts +9 -1
- package/dist/api-context.d.ts.map +1 -1
- package/dist/api-context.js +6 -4
- package/dist/business/customer-picker.d.ts +8 -2
- package/dist/business/customer-picker.d.ts.map +1 -1
- package/dist/business/customer-picker.js +30 -27
- package/dist/business/index.d.ts +1 -0
- package/dist/business/index.d.ts.map +1 -1
- package/dist/business/index.js +1 -0
- package/dist/business/line-product-cell.d.ts +8 -10
- package/dist/business/line-product-cell.d.ts.map +1 -1
- package/dist/business/line-product-cell.js +13 -90
- package/dist/business/product-options.d.ts +41 -0
- package/dist/business/product-options.d.ts.map +1 -0
- package/dist/business/product-options.js +58 -0
- package/dist/business/product-picker.d.ts +5 -1
- package/dist/business/product-picker.d.ts.map +1 -1
- package/dist/business/product-picker.js +26 -35
- package/dist/business/relate-documents.d.ts +1 -1
- package/dist/business/relate-documents.d.ts.map +1 -1
- package/dist/business/relate-documents.js +6 -8
- package/dist/business/vehicle-picker.d.ts +8 -2
- package/dist/business/vehicle-picker.d.ts.map +1 -1
- package/dist/business/vehicle-picker.js +28 -28
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +28 -43
- package/dist/dynamic-multi-select-field.d.ts.map +1 -1
- package/dist/dynamic-multi-select-field.js +17 -5
- package/dist/dynamic-select-field.d.ts +7 -23
- package/dist/dynamic-select-field.d.ts.map +1 -1
- package/dist/dynamic-select-field.js +67 -161
- package/dist/dynamic-table.d.ts +31 -1
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +37 -4
- package/dist/entity-select.d.ts +5 -2
- package/dist/entity-select.d.ts.map +1 -1
- package/dist/entity-select.js +23 -116
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/query-batch.d.ts.map +1 -1
- package/dist/query-batch.js +17 -1
- package/dist/record-picker-actions.d.ts +40 -2
- package/dist/record-picker-actions.d.ts.map +1 -1
- package/dist/record-picker-actions.js +44 -4
- package/dist/record-picker-dialog.d.ts +23 -0
- package/dist/record-picker-dialog.d.ts.map +1 -0
- package/dist/record-picker-dialog.js +37 -0
- package/dist/record-picker-option.d.ts +25 -0
- package/dist/record-picker-option.d.ts.map +1 -0
- package/dist/record-picker-option.js +55 -0
- package/dist/record-picker.d.ts +149 -0
- package/dist/record-picker.d.ts.map +1 -0
- package/dist/record-picker.js +372 -0
- package/package.json +1 -1
- package/src/__tests__/api-batch-opt-out.test.tsx +83 -0
- package/src/__tests__/business-pickers.test.tsx +6 -3
- package/src/__tests__/document-editor-inline-selects.test.tsx +2 -1
- package/src/__tests__/document-form-dialog.test.tsx +4 -3
- package/src/__tests__/document-lines-grid.test.tsx +2 -1
- package/src/__tests__/dynamic-table-bulk-actions.test.tsx +140 -0
- package/src/__tests__/dynamic-table-load-error.test.tsx +97 -0
- package/src/__tests__/record-picker.test.tsx +469 -0
- package/src/api-context.tsx +19 -4
- package/src/business/customer-picker.tsx +87 -98
- package/src/business/index.ts +1 -0
- package/src/business/line-product-cell.tsx +55 -180
- package/src/business/product-options.tsx +119 -0
- package/src/business/product-picker.tsx +63 -126
- package/src/business/relate-documents.tsx +36 -45
- package/src/business/vehicle-picker.tsx +76 -99
- package/src/dialogs/dynamic-record.tsx +44 -97
- package/src/dynamic-multi-select-field.tsx +39 -13
- package/src/dynamic-select-field.tsx +143 -368
- package/src/dynamic-table.tsx +91 -4
- package/src/entity-select.tsx +53 -202
- package/src/index.ts +27 -2
- package/src/query-batch.ts +18 -1
- package/src/record-picker-actions.tsx +101 -8
- package/src/record-picker-dialog.tsx +80 -0
- package/src/record-picker-option.tsx +96 -0
- package/src/record-picker.tsx +971 -0
- package/dist/business/use-async-search.d.ts +0 -10
- package/dist/business/use-async-search.d.ts.map +0 -1
- package/dist/business/use-async-search.js +0 -38
- package/src/business/use-async-search.ts +0 -43
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
// #1025 VehiclePicker, #1026 RelateDocuments, #1027 PrintSendDialog.
|
|
3
3
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
-
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
4
|
+
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
|
5
5
|
|
|
6
6
|
vi.mock('react-i18next', () => ({
|
|
7
7
|
useTranslation: () => ({ t: (k: string, o?: any) => o?.defaultValue ?? k, i18n: { language: 'es' } }),
|
|
@@ -49,7 +49,9 @@ describe('VehiclePicker', () => {
|
|
|
49
49
|
fireEvent.change(screen.getByPlaceholderText('Placa o VIN'), { target: { value: 'ABC' } })
|
|
50
50
|
const opt = await screen.findByRole('option')
|
|
51
51
|
expect(search.mock.calls[0][0]).toBe('ABC')
|
|
52
|
-
|
|
52
|
+
// La opción misma es el elemento interactivo (RecordPicker: role=option
|
|
53
|
+
// sin <button> anidado, como pide ARIA para listbox).
|
|
54
|
+
fireEvent.click(opt)
|
|
53
55
|
expect(onChange).toHaveBeenCalledWith(veh)
|
|
54
56
|
})
|
|
55
57
|
|
|
@@ -102,7 +104,8 @@ describe('RelateDocuments', () => {
|
|
|
102
104
|
const { rerender } = render(<RelateDocuments value={value} onChange={onChange} relationTypes={types} search={search} />)
|
|
103
105
|
fireEvent.change(screen.getByPlaceholderText('Buscar por folio o UUID'), { target: { value: 'A-' } })
|
|
104
106
|
const list = await screen.findByRole('listbox')
|
|
105
|
-
|
|
107
|
+
// RecordPicker: se elige la opción (role=option), no un <button> interno.
|
|
108
|
+
fireEvent.click((await within(list).findAllByRole('option'))[0]!)
|
|
106
109
|
expect(onChange).toHaveBeenLastCalledWith([
|
|
107
110
|
expect.objectContaining({ related_document_id: 'd1', relation_type: '01', uuid: 'UUID-1', folio: 'A-1' }),
|
|
108
111
|
])
|
|
@@ -95,7 +95,8 @@ describe('DocumentLinesGrid — select de producto en la celda', () => {
|
|
|
95
95
|
expect(cell.closest('tr')!.getAttribute('data-line-key')).toBe(free.key)
|
|
96
96
|
fireEvent.change(cell, { target: { value: '205' } })
|
|
97
97
|
const option = await waitFor(() => screen.getByRole('option', { name: /Llanta 205/ }), { timeout: 2000 })
|
|
98
|
-
|
|
98
|
+
// La opción (role=option) es el elemento interactivo del RecordPicker.
|
|
99
|
+
fireEvent.click(option)
|
|
99
100
|
expect(state()).toHaveLength(1)
|
|
100
101
|
expect(state()[0]).toMatchObject({ key: free.key, product_id: 'p1', unit_price: 1500, sku: 'LL-205' })
|
|
101
102
|
})
|
|
@@ -169,7 +169,8 @@ describe('DocumentFormDialog: buscador de catálogo por defecto', () => {
|
|
|
169
169
|
fireEvent.change(document.querySelectorAll('input')[0], { target: { value: 'ACME' } })
|
|
170
170
|
fireEvent.click(screen.getByRole('button', { name: 'Siguiente' }))
|
|
171
171
|
fireEvent.change(await screen.findByLabelText('Buscar producto'), { target: { value: query } })
|
|
172
|
-
|
|
172
|
+
// RecordPicker: la opción del producto es role="option" clicable (sin <button> anidado).
|
|
173
|
+
fireEvent.click(await waitFor(() => screen.getByRole('option', { name }), { timeout: 2000 }))
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
it('sin searchProducts del host busca en el catálogo y el renglón entra con precio, cantidad 1, unidad, SKU e IVA de la org', async () => {
|
|
@@ -234,9 +235,9 @@ describe('DocumentFormDialog: IVA de la org con el buscador del host', () => {
|
|
|
234
235
|
fireEvent.click(screen.getByRole('button', { name: 'Siguiente' }))
|
|
235
236
|
const search = await screen.findByLabelText('Buscar producto')
|
|
236
237
|
fireEvent.change(search, { target: { value: 'host' } })
|
|
237
|
-
fireEvent.click(await waitFor(() => screen.getByRole('
|
|
238
|
+
fireEvent.click(await waitFor(() => screen.getByRole('option', { name: /Llanta host/ }), { timeout: 2000 }))
|
|
238
239
|
fireEvent.change(await screen.findByLabelText('Buscar producto'), { target: { value: 'fron' } })
|
|
239
|
-
fireEvent.click(await waitFor(() => screen.getByRole('
|
|
240
|
+
fireEvent.click(await waitFor(() => screen.getByRole('option', { name: /Frontera host/ }), { timeout: 2000 }))
|
|
240
241
|
fireEvent.click(screen.getByRole('button', { name: 'Crear' }))
|
|
241
242
|
await waitFor(() => expect(post).toHaveBeenCalled())
|
|
242
243
|
const body = post.mock.calls[0][1] as any
|
|
@@ -49,7 +49,8 @@ describe('DocumentLinesGrid', () => {
|
|
|
49
49
|
const search = vi.fn(async () => [tire])
|
|
50
50
|
render(<Harness search={search} priceSource="cost" />)
|
|
51
51
|
fireEvent.change(screen.getByLabelText('Buscar producto'), { target: { value: 'llan' } })
|
|
52
|
-
|
|
52
|
+
// RecordPicker: la opción del producto es role="option" clicable (sin <button> anidado).
|
|
53
|
+
const option = await waitFor(() => screen.getByRole('option', { name: /Llanta X/ }), { timeout: 2000 })
|
|
53
54
|
fireEvent.click(option)
|
|
54
55
|
expect(state()[0]).toMatchObject({ description: 'Llanta X', sku: 'LX', quantity: 1, unit_price: 800, catalog_price: 800 })
|
|
55
56
|
expect(screen.getByText(/Catálogo/)).toBeTruthy()
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
//
|
|
3
|
+
// DynamicTable `extraBulkActions` / `hideBulkDelete`:
|
|
4
|
+
// 1. Host actions render in the floating bar once rows are selected and
|
|
5
|
+
// receive the selected rows / ids.
|
|
6
|
+
// 2. `clearSelection()` empties the selection.
|
|
7
|
+
// 3. Changing page drops the selection (classic pagination) so a bulk
|
|
8
|
+
// action can never target rows that are no longer visible.
|
|
9
|
+
// 4. `hideBulkDelete` removes the built-in "Eliminar".
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
11
|
+
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
12
|
+
|
|
13
|
+
vi.mock('@tanstack/react-router', () => ({
|
|
14
|
+
useNavigate: () => () => {},
|
|
15
|
+
}))
|
|
16
|
+
const I18N = {
|
|
17
|
+
t: (_k: string, o?: { defaultValue?: string; count?: number; total?: number }) => {
|
|
18
|
+
if (o?.defaultValue && o.count != null && o.total != null) {
|
|
19
|
+
return o.defaultValue.replace('{{count}}', String(o.count)).replace('{{total}}', String(o.total))
|
|
20
|
+
}
|
|
21
|
+
return o?.defaultValue ?? _k
|
|
22
|
+
},
|
|
23
|
+
i18n: { language: 'es' },
|
|
24
|
+
}
|
|
25
|
+
vi.mock('react-i18next', () => ({ useTranslation: () => I18N }))
|
|
26
|
+
|
|
27
|
+
import { DynamicTable } from '../dynamic-table'
|
|
28
|
+
import { makeDefaultGetDynamicColumns } from '../dynamic-columns'
|
|
29
|
+
import { ApiProvider, type ApiClient } from '../api-context'
|
|
30
|
+
import { useMetadataCache } from '../metadata-cache'
|
|
31
|
+
import type { TableMetadata } from '../types'
|
|
32
|
+
|
|
33
|
+
afterEach(cleanup)
|
|
34
|
+
|
|
35
|
+
const getDynamicColumns = makeDefaultGetDynamicColumns()
|
|
36
|
+
|
|
37
|
+
function meta(): TableMetadata {
|
|
38
|
+
return {
|
|
39
|
+
title: 'Issues',
|
|
40
|
+
endpoint: '/data/issue',
|
|
41
|
+
columns: [
|
|
42
|
+
{ key: 'title', label: 'Title', type: 'text', sortable: true, filterable: false, searchable: true },
|
|
43
|
+
],
|
|
44
|
+
actions: [],
|
|
45
|
+
perPageOptions: [10, 20, 50],
|
|
46
|
+
defaultPerPage: 10,
|
|
47
|
+
searchPlaceholder: 'Buscar...',
|
|
48
|
+
enableCRUDActions: false,
|
|
49
|
+
hasActions: false,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function fakeApi(total: number): ApiClient {
|
|
54
|
+
const ok = (data: unknown) => ({ data: { success: true, data, meta: { total } } })
|
|
55
|
+
return {
|
|
56
|
+
get: vi.fn(async (url: string, cfg?: any) => {
|
|
57
|
+
if (url.startsWith('/metadata/table/')) return ok(meta())
|
|
58
|
+
if (url.endsWith('/facets')) return ok([])
|
|
59
|
+
const p = cfg?.params?.page ?? 1
|
|
60
|
+
const size = cfg?.params?.per_page ?? 10
|
|
61
|
+
const rows: Array<{ id: number; title: string }> = []
|
|
62
|
+
for (let i = (p - 1) * size; i < Math.min(p * size, total); i++) rows.push({ id: i + 1, title: `Issue ${i + 1}` })
|
|
63
|
+
return ok(rows)
|
|
64
|
+
}),
|
|
65
|
+
post: vi.fn(async () => ok(null)),
|
|
66
|
+
put: vi.fn(async () => ok(null)),
|
|
67
|
+
delete: vi.fn(async () => ok(null)),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function selectFirstRow() {
|
|
72
|
+
await screen.findAllByText('Issue 1')
|
|
73
|
+
const boxes = screen.getAllByRole('checkbox')
|
|
74
|
+
fireEvent.click(boxes[1]) // [0] is the select-all header checkbox
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describe('DynamicTable extraBulkActions', () => {
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
localStorage.clear()
|
|
80
|
+
sessionStorage.clear()
|
|
81
|
+
useMetadataCache.getState().setMetadata('issue', meta())
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('renders host actions with the selection and lets them clear it', async () => {
|
|
85
|
+
const seen: unknown[] = []
|
|
86
|
+
render(
|
|
87
|
+
<ApiProvider client={fakeApi(5)}>
|
|
88
|
+
<DynamicTable
|
|
89
|
+
model="issue"
|
|
90
|
+
enableUrlSync={false} getDynamicColumns={getDynamicColumns}
|
|
91
|
+
extraBulkActions={({ selectedRows, selectedIds, clearSelection }) => (
|
|
92
|
+
<button onClick={() => { seen.push({ rows: selectedRows.length, ids: selectedIds }); clearSelection() }}>
|
|
93
|
+
Aprobar seleccionados
|
|
94
|
+
</button>
|
|
95
|
+
)}
|
|
96
|
+
/>
|
|
97
|
+
</ApiProvider>,
|
|
98
|
+
)
|
|
99
|
+
expect(screen.queryByText('Aprobar seleccionados')).toBeNull()
|
|
100
|
+
await selectFirstRow()
|
|
101
|
+
fireEvent.click(await screen.findByText('Aprobar seleccionados'))
|
|
102
|
+
expect(seen).toEqual([{ rows: 1, ids: [1] }])
|
|
103
|
+
await waitFor(() => expect(screen.queryByText('Aprobar seleccionados')).toBeNull())
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('accepts a plain node and keeps the built-in delete by default', async () => {
|
|
107
|
+
render(
|
|
108
|
+
<ApiProvider client={fakeApi(5)}>
|
|
109
|
+
<DynamicTable model="issue" enableUrlSync={false} getDynamicColumns={getDynamicColumns} extraBulkActions={<button>Extra</button>} />
|
|
110
|
+
</ApiProvider>,
|
|
111
|
+
)
|
|
112
|
+
await selectFirstRow()
|
|
113
|
+
expect(await screen.findByText('Extra')).toBeTruthy()
|
|
114
|
+
expect(screen.getByText('Eliminar')).toBeTruthy()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('hideBulkDelete removes the built-in delete', async () => {
|
|
118
|
+
render(
|
|
119
|
+
<ApiProvider client={fakeApi(5)}>
|
|
120
|
+
<DynamicTable model="issue" enableUrlSync={false} getDynamicColumns={getDynamicColumns} hideBulkDelete extraBulkActions={<button>Extra</button>} />
|
|
121
|
+
</ApiProvider>,
|
|
122
|
+
)
|
|
123
|
+
await selectFirstRow()
|
|
124
|
+
expect(await screen.findByText('Extra')).toBeTruthy()
|
|
125
|
+
expect(screen.queryByText('Eliminar')).toBeNull()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('drops the selection when the page changes', async () => {
|
|
129
|
+
render(
|
|
130
|
+
<ApiProvider client={fakeApi(25)}>
|
|
131
|
+
<DynamicTable model="issue" enableUrlSync={false} getDynamicColumns={getDynamicColumns} extraBulkActions={<button>Extra</button>} />
|
|
132
|
+
</ApiProvider>,
|
|
133
|
+
)
|
|
134
|
+
await selectFirstRow()
|
|
135
|
+
expect(await screen.findByText('Extra')).toBeTruthy()
|
|
136
|
+
fireEvent.click(await screen.findByRole('button', { name: /siguiente|next/i }))
|
|
137
|
+
await screen.findAllByText('Issue 11')
|
|
138
|
+
await waitFor(() => expect(screen.queryByText('Extra')).toBeNull())
|
|
139
|
+
})
|
|
140
|
+
})
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
//
|
|
3
|
+
// Un fallo del endpoint de lista que NO es 403 (500, red, `success: false`)
|
|
4
|
+
// no debe verse como "sin resultados": se muestra "No se pudieron cargar los
|
|
5
|
+
// datos" con "Reintentar", que vuelve a pedir la lista. `emptyState` solo
|
|
6
|
+
// reemplaza el vacío cuando la lista cargó bien y no hay filtros activos.
|
|
7
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
8
|
+
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
9
|
+
|
|
10
|
+
vi.mock('@tanstack/react-router', () => ({ useNavigate: () => () => {} }))
|
|
11
|
+
|
|
12
|
+
import { ApiProvider, type ApiClient } from '../api-context'
|
|
13
|
+
import { useMetadataCache } from '../metadata-cache'
|
|
14
|
+
import { DynamicTable } from '../dynamic-table'
|
|
15
|
+
import type { TableMetadata } from '../types'
|
|
16
|
+
|
|
17
|
+
afterEach(cleanup)
|
|
18
|
+
|
|
19
|
+
const meta = {
|
|
20
|
+
title: 'Ajustes',
|
|
21
|
+
endpoint: '/data/load_err',
|
|
22
|
+
columns: [],
|
|
23
|
+
actions: [],
|
|
24
|
+
perPageOptions: [10],
|
|
25
|
+
defaultPerPage: 10,
|
|
26
|
+
searchPlaceholder: 'Buscar...',
|
|
27
|
+
enableCRUDActions: false,
|
|
28
|
+
hasActions: false,
|
|
29
|
+
} as unknown as TableMetadata
|
|
30
|
+
|
|
31
|
+
type ListResult = 'error500' | 'successFalse' | 'empty'
|
|
32
|
+
|
|
33
|
+
function mount(results: ListResult[], model: string, extra: Record<string, unknown> = {}) {
|
|
34
|
+
useMetadataCache.getState().setMetadata(model, meta)
|
|
35
|
+
let listCalls = 0
|
|
36
|
+
const client: ApiClient = {
|
|
37
|
+
get: vi.fn(async (url: string) => {
|
|
38
|
+
if (url.startsWith('/metadata/table/')) return { data: { success: true, data: meta } }
|
|
39
|
+
if (url.endsWith('/facets')) return { data: { success: true, data: [] } }
|
|
40
|
+
const r = results[Math.min(listCalls, results.length - 1)]
|
|
41
|
+
listCalls++
|
|
42
|
+
if (r === 'error500') throw Object.assign(new Error('500'), { response: { status: 500 } })
|
|
43
|
+
if (r === 'successFalse') return { data: { success: false, message: 'boom' } }
|
|
44
|
+
return { data: { success: true, data: [], meta: { total: 0 } } }
|
|
45
|
+
}),
|
|
46
|
+
post: vi.fn(async () => ({ data: { success: true, data: null } })),
|
|
47
|
+
put: vi.fn(async () => ({ data: { success: true, data: null } })),
|
|
48
|
+
delete: vi.fn(async () => ({ data: { success: true, data: null } })),
|
|
49
|
+
}
|
|
50
|
+
render(
|
|
51
|
+
<ApiProvider client={client}>
|
|
52
|
+
<DynamicTable model={model} enableUrlSync={false} {...extra} />
|
|
53
|
+
</ApiProvider>,
|
|
54
|
+
)
|
|
55
|
+
return () => listCalls
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe('DynamicTable — fallo de la lista (no 403)', () => {
|
|
59
|
+
it('un 500 muestra error con Reintentar, no "No se encontraron resultados"', async () => {
|
|
60
|
+
vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
61
|
+
mount(['error500'], 'lerr_a')
|
|
62
|
+
expect((await screen.findAllByText('No se pudieron cargar los datos')).length).toBeGreaterThan(0)
|
|
63
|
+
expect(screen.queryByText('No se encontraron resultados')).toBeNull()
|
|
64
|
+
expect(screen.queryByText('Sin permiso para ver este módulo')).toBeNull()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('`success: false` también es un error de carga', async () => {
|
|
68
|
+
mount(['successFalse'], 'lerr_b')
|
|
69
|
+
expect((await screen.findAllByText('No se pudieron cargar los datos')).length).toBeGreaterThan(0)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('Reintentar vuelve a pedir la lista y, si ahora carga, limpia el error', async () => {
|
|
73
|
+
vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
74
|
+
const calls = mount(['error500', 'empty'], 'lerr_c')
|
|
75
|
+
const retry = (await screen.findAllByRole('button', { name: 'Reintentar' }))[0]
|
|
76
|
+
const before = calls()
|
|
77
|
+
fireEvent.click(retry)
|
|
78
|
+
await waitFor(() => expect(calls()).toBeGreaterThan(before))
|
|
79
|
+
await waitFor(() => expect(screen.queryByText('No se pudieron cargar los datos')).toBeNull())
|
|
80
|
+
expect((await screen.findAllByText('No se encontraron resultados')).length).toBeGreaterThan(0)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('DynamicTable — emptyState', () => {
|
|
85
|
+
it('reemplaza el vacío cuando la lista cargó bien y no hay filtros', async () => {
|
|
86
|
+
mount(['empty'], 'lerr_d', { emptyState: <p>Aún no hay registros</p> })
|
|
87
|
+
expect((await screen.findAllByText('Aún no hay registros')).length).toBeGreaterThan(0)
|
|
88
|
+
expect(screen.queryByText('No se encontraron resultados')).toBeNull()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('no tapa un error de carga', async () => {
|
|
92
|
+
vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
93
|
+
mount(['error500'], 'lerr_e', { emptyState: <p>Aún no hay registros</p> })
|
|
94
|
+
expect((await screen.findAllByText('No se pudieron cargar los datos')).length).toBeGreaterThan(0)
|
|
95
|
+
expect(screen.queryByText('Aún no hay registros')).toBeNull()
|
|
96
|
+
})
|
|
97
|
+
})
|