su2a / frontend /src /views /admin /__tests__ /RedeemView.batchUpdate.spec.ts
cjovs's picture
PG local + bucket backup/restore, DB: su2adb
126ef31 verified
Raw
History Blame Contribute Delete
5.21 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { flushPromises, mount } from '@vue/test-utils'
import RedeemView from '../RedeemView.vue'
const { listRedeemCodes, batchUpdateRedeemCodes, getAllGroups, showSuccess, showError, showInfo } =
vi.hoisted(() => ({
listRedeemCodes: vi.fn(),
batchUpdateRedeemCodes: vi.fn(),
getAllGroups: vi.fn(),
showSuccess: vi.fn(),
showError: vi.fn(),
showInfo: vi.fn()
}))
vi.mock('@/api/admin', () => ({
adminAPI: {
redeem: {
list: listRedeemCodes,
generate: vi.fn(),
delete: vi.fn(),
batchDelete: vi.fn(),
batchUpdate: batchUpdateRedeemCodes,
exportCodes: vi.fn()
},
groups: {
getAll: getAllGroups
}
}
}))
vi.mock('@/stores/app', () => ({
useAppStore: () => ({
showSuccess,
showError,
showInfo
})
}))
vi.mock('@/composables/useClipboard', () => ({
useClipboard: () => ({
copyToClipboard: vi.fn()
})
}))
vi.mock('vue-i18n', async () => {
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
return {
...actual,
useI18n: () => ({
t: (key: string) => key
})
}
})
const DataTableStub = {
props: ['columns', 'data'],
template: `
<table>
<thead>
<tr>
<th v-for="column in columns" :key="column.key">
<slot :name="'header-' + column.key" :column="column">{{ column.label }}</slot>
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in data" :key="row.id">
<td v-for="column in columns" :key="column.key">
<slot :name="'cell-' + column.key" :row="row" :value="row[column.key]">
{{ row[column.key] }}
</slot>
</td>
</tr>
</tbody>
</table>
`
}
const SelectStub = {
props: ['modelValue', 'options'],
emits: ['update:modelValue', 'change'],
setup(props: { options: Array<{ value: unknown; label: string }> }, { emit }: { emit: (event: string, ...args: unknown[]) => void }) {
const onChange = (event: Event) => {
const raw = (event.target as HTMLSelectElement).value
const option = props.options.find((item) => String(item.value ?? '') === raw)
const value = option ? option.value : raw
emit('update:modelValue', value)
emit('change', value, option ?? null)
}
return { onChange }
},
template: `
<select v-bind="$attrs" :value="modelValue ?? ''" @change="onChange">
<option v-for="option in options" :key="String(option.value ?? '')" :value="option.value ?? ''">
{{ option.label }}
</option>
</select>
`
}
describe('admin RedeemView batch update', () => {
beforeEach(() => {
localStorage.clear()
document.body.innerHTML = ''
listRedeemCodes.mockReset()
batchUpdateRedeemCodes.mockReset()
getAllGroups.mockReset()
showSuccess.mockReset()
showError.mockReset()
showInfo.mockReset()
listRedeemCodes.mockResolvedValue({
items: [
{
id: 1,
code: 'CODE-1',
type: 'balance',
value: 10,
status: 'unused',
used_by: null,
used_at: null,
created_at: '2026-01-01T00:00:00Z',
expires_at: null
},
{
id: 2,
code: 'CODE-2',
type: 'balance',
value: 20,
status: 'unused',
used_by: null,
used_at: null,
created_at: '2026-01-01T00:00:00Z',
expires_at: null
}
],
total: 2,
page: 1,
page_size: 20,
pages: 1
})
batchUpdateRedeemCodes.mockResolvedValue({ updated: 1, message: 'ok' })
getAllGroups.mockResolvedValue([])
})
it('submits only checked fields for selected redeem codes', async () => {
const wrapper = mount(RedeemView, {
attachTo: document.body,
global: {
stubs: {
AppLayout: { template: '<div><slot /></div>' },
TablePageLayout: {
template: '<div><slot name="filters" /><slot name="table" /><slot name="pagination" /></div>'
},
DataTable: DataTableStub,
Pagination: true,
ConfirmDialog: true,
Select: SelectStub,
GroupBadge: true,
GroupOptionItem: true,
Icon: true,
Teleport: true
}
}
})
await flushPromises()
await wrapper.findAll('[data-test="select-code"]')[0].setValue(true)
await wrapper.get('[data-test="batch-update-open"]').trigger('click')
await flushPromises()
await wrapper.get('[data-test="batch-field-status"]').setValue(true)
await wrapper.get('[data-test="batch-status-select"]').setValue('disabled')
await wrapper.get('[data-test="batch-field-notes"]').setValue(true)
await wrapper.get('[data-test="batch-notes-input"]').setValue('maintenance')
await wrapper.get('[data-test="batch-update-form"]').trigger('submit')
await flushPromises()
expect(batchUpdateRedeemCodes).toHaveBeenCalledWith([1], {
status: 'disabled',
notes: 'maintenance'
})
expect(showSuccess).toHaveBeenCalledWith('admin.redeem.batchUpdateSuccess')
})
})