feat: media duplicate purge, part 3 of 3: the SPA (Settings "Media cleanup" card, PurgeDuplicatesDialog, Media Duplicates toolbar button)
This commit is contained in:
1 parent
1a55a39425
commit
2303b12377
26 files changed
+3856
-83
No files matched your search
@@ -0,0 +1,529 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import uiPlugin from '@nuxt/ui/vue-plugin'
|
||||
import router from '@/router'
|
||||
import { ApiError, NetworkError, useApi } from '@/composables/useApi'
|
||||
import PurgeDuplicatesDialog from '@/components/PurgeDuplicatesDialog.vue'
|
||||
import {
|
||||
BUSY_COPY,
|
||||
FAILED_COPY,
|
||||
PREVIEW_PATH,
|
||||
PURGE_PATH,
|
||||
SETTINGS_PATH,
|
||||
STALE_COPY,
|
||||
bodyOf,
|
||||
callsTo,
|
||||
deferredValue,
|
||||
dialogEl,
|
||||
emptyPreview,
|
||||
enabledSettings,
|
||||
findButton,
|
||||
goOffline,
|
||||
groupRows,
|
||||
isBefore,
|
||||
lastLine,
|
||||
manyPreview,
|
||||
mountSettings,
|
||||
nextLine,
|
||||
openDialog,
|
||||
previewOf,
|
||||
pressEscape,
|
||||
purgeButton,
|
||||
purgeResult,
|
||||
singlePreview,
|
||||
textOf,
|
||||
unmountSettings,
|
||||
type PurgeMocks,
|
||||
} from './purgeTestKit'
|
||||
|
||||
vi.mock('@/composables/useApi', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/composables/useApi')>()
|
||||
return { ...actual, useApi: vi.fn() }
|
||||
})
|
||||
|
||||
const toasts = vi.hoisted(() => ({ add: vi.fn(), remove: vi.fn(), update: vi.fn() }))
|
||||
|
||||
vi.mock('@nuxt/ui/composables', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@nuxt/ui/composables')>()
|
||||
return { ...actual, useToast: () => toasts }
|
||||
})
|
||||
|
||||
const DESCRIPTION =
|
||||
"Articles that use a duplicate will be switched to the most used copy of that image, and the other copies will be deleted. This can't be undone."
|
||||
const SUMMARY = '4 groups · 9 copies will be deleted · 12 articles will be updated'
|
||||
const LAST_RUN_LINE = 'Last purge: 14 Jun 2026 · 3 copies merged · 5 articles updated'
|
||||
const AFTER_PURGE_LINE = 'Last purge: 5 Oct 2026 · 9 copies merged · 12 articles updated'
|
||||
|
||||
async function opened(mocks: PurgeMocks = {}) {
|
||||
await mountSettings({ settings: () => enabledSettings(), ...mocks })
|
||||
return openDialog()
|
||||
}
|
||||
|
||||
function buttonLabels() {
|
||||
return Array.from(dialogEl()!.querySelectorAll('button')).map(textOf)
|
||||
}
|
||||
|
||||
function confirmButton(label: string) {
|
||||
return findButton(label, dialogEl()!)!
|
||||
}
|
||||
|
||||
function noticeText() {
|
||||
return textOf(dialogEl()!.querySelector('[role="alert"]'))
|
||||
}
|
||||
|
||||
function afterRefresh(settings = enabledSettings()) {
|
||||
let reads = 0
|
||||
return () => (reads++ === 0 ? enabledSettings() : settings)
|
||||
}
|
||||
|
||||
function purgedSettings(overrides = {}) {
|
||||
return enabledSettings({
|
||||
last_purge_at: '2026-10-05T09:00:00Z',
|
||||
last_purge_merged: 9,
|
||||
last_purge_articles_updated: 12,
|
||||
next_purge_at: '2026-11-04T09:00:00Z',
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
toasts.add.mockClear()
|
||||
vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(true)
|
||||
})
|
||||
|
||||
afterEach(unmountSettings)
|
||||
|
||||
describe('Purge dialog: opening', () => {
|
||||
it('sends the preview request, shows the title, description, focuses Cancel and delays the skeleton', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const pending = deferredValue<unknown>()
|
||||
await opened({ preview: () => pending.promise })
|
||||
|
||||
expect(callsTo('GET', PREVIEW_PATH)).toHaveLength(1)
|
||||
const box = dialogEl()!
|
||||
expect(textOf(box)).toContain('Purge duplicates?')
|
||||
expect(textOf(box)).toContain(DESCRIPTION)
|
||||
expect(document.activeElement).toBe(findButton('Cancel', box))
|
||||
|
||||
vi.advanceTimersByTime(149)
|
||||
await flushPromises()
|
||||
expect(box.querySelector('[data-testid="purge-skeleton"]')).toBeNull()
|
||||
|
||||
vi.advanceTimersByTime(1)
|
||||
await flushPromises()
|
||||
expect(box.querySelector('[data-testid="purge-skeleton"]')).not.toBeNull()
|
||||
expect(box.querySelectorAll('.animate-pulse').length).toBeGreaterThan(3)
|
||||
expect(confirmButton('Purge duplicates').disabled).toBe(true)
|
||||
expect(buttonLabels()).toEqual(['Cancel', 'Purge duplicates'])
|
||||
})
|
||||
|
||||
it('is not openable while offline', async () => {
|
||||
await mountSettings({ settings: () => enabledSettings() })
|
||||
await goOffline()
|
||||
|
||||
purgeButton().click()
|
||||
await flushPromises()
|
||||
|
||||
expect(purgeButton().disabled).toBe(true)
|
||||
expect(dialogEl()).toBeNull()
|
||||
expect(callsTo('GET', PREVIEW_PATH)).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Purge dialog: the preview', () => {
|
||||
it('shows the summary, the rows, the link and both buttons', async () => {
|
||||
await opened()
|
||||
|
||||
const box = dialogEl()!
|
||||
expect(textOf(box.querySelector('[data-testid="purge-summary"]'))).toBe(SUMMARY)
|
||||
const rows = groupRows()
|
||||
expect(rows).toHaveLength(4)
|
||||
expect(
|
||||
rows.map((row) =>
|
||||
Array.from(row.querySelectorAll('span')).map(textOf).filter(Boolean).join(' '),
|
||||
),
|
||||
).toEqual([
|
||||
'floorboards.jpg Kept · used in 4 articles 2 copies merged',
|
||||
'IMG_4471.jpg Kept · used in 2 articles 1 copy merged',
|
||||
'kitchen-after.webp Kept · used in 3 articles 3 copies merged',
|
||||
'logo-renew.svg Kept · used in 5 articles 3 copies merged',
|
||||
])
|
||||
expect(rows[0]!.querySelector('img')!.getAttribute('src')).toBe('/media/floorboards.jpg')
|
||||
expect(box.querySelector('[data-testid="purge-more"]')).toBeNull()
|
||||
const link = box.querySelector('a')!
|
||||
expect(link.getAttribute('href')).toBe('/media?filter=duplicates')
|
||||
expect(textOf(link)).toBe('Review in Duplicates')
|
||||
expect(link.querySelector('svg')).not.toBeNull()
|
||||
expect(buttonLabels()).toEqual(['Cancel', 'Purge 4 groups'])
|
||||
expect(confirmButton('Purge 4 groups').disabled).toBe(false)
|
||||
})
|
||||
|
||||
it.each([
|
||||
[previewOf(), SUMMARY, 'Purge 4 groups', 'Kept · used in 4 articles', '2 copies merged'],
|
||||
[
|
||||
singlePreview(2, 1, 2),
|
||||
'1 group · 1 copy will be deleted · 2 articles will be updated',
|
||||
'Purge 1 group',
|
||||
'Kept · used in 2 articles',
|
||||
'1 copy merged',
|
||||
],
|
||||
[
|
||||
singlePreview(1, 1, 1),
|
||||
'1 group · 1 copy will be deleted · 1 article will be updated',
|
||||
'Purge 1 group',
|
||||
'Kept · used in 1 article',
|
||||
'1 copy merged',
|
||||
],
|
||||
])(
|
||||
'pluralises the summary, the confirm and the rows',
|
||||
async (preview, summary, confirm, kept, merged) => {
|
||||
await opened({ preview: () => preview })
|
||||
|
||||
expect(textOf(dialogEl()!.querySelector('[data-testid="purge-summary"]'))).toBe(summary)
|
||||
expect(buttonLabels()).toEqual(['Cancel', confirm])
|
||||
const row = textOf(groupRows()[0])
|
||||
expect(row).toContain(kept)
|
||||
expect(row).toContain(merged)
|
||||
},
|
||||
)
|
||||
|
||||
it('stops at 5 rows and says how many groups are left', async () => {
|
||||
await opened({ preview: manyPreview })
|
||||
|
||||
expect(groupRows()).toHaveLength(5)
|
||||
expect(textOf(dialogEl()!.querySelector('[data-testid="purge-more"]'))).toBe(
|
||||
'and 3 more groups',
|
||||
)
|
||||
expect(buttonLabels()).toEqual(['Cancel', 'Purge 8 groups'])
|
||||
})
|
||||
|
||||
it('says "and 1 more group" for one', async () => {
|
||||
await opened({ preview: () => ({ ...manyPreview(), more: 1 }) })
|
||||
|
||||
expect(textOf(dialogEl()!.querySelector('[data-testid="purge-more"]'))).toBe(
|
||||
'and 1 more group',
|
||||
)
|
||||
})
|
||||
|
||||
it('shows Nothing to purge with a single Close button', async () => {
|
||||
await opened({ preview: emptyPreview })
|
||||
|
||||
const box = dialogEl()!
|
||||
expect(textOf(box)).toContain('Nothing to purge')
|
||||
expect(textOf(box)).toContain(
|
||||
'No duplicate is used in an article, so there is nothing to merge.',
|
||||
)
|
||||
expect(box.querySelector('svg')).not.toBeNull()
|
||||
expect(buttonLabels()).toEqual(['Close'])
|
||||
expect(box.querySelector('[data-testid="purge-summary"]')).toBeNull()
|
||||
expect(box.querySelector('a')).toBeNull()
|
||||
|
||||
findButton('Close', box)!.click()
|
||||
await flushPromises()
|
||||
expect(dialogEl()).toBeNull()
|
||||
expect(callsTo('POST', PURGE_PATH)).toHaveLength(0)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['a 500', () => Promise.reject(new ApiError(500))],
|
||||
['a network failure', () => Promise.reject(new NetworkError())],
|
||||
])('shows the error notice after %s and offers no purge', async (_name, preview) => {
|
||||
await opened({ preview })
|
||||
|
||||
expect(noticeText()).toBe(FAILED_COPY)
|
||||
expect(confirmButton('Purge duplicates').disabled).toBe(true)
|
||||
expect(findButton('Cancel', dialogEl()!)!.disabled).toBe(false)
|
||||
expect(dialogEl()!.querySelector('[data-testid="purge-summary"]')).toBeNull()
|
||||
confirmButton('Purge duplicates').click()
|
||||
await flushPromises()
|
||||
expect(callsTo('POST', PURGE_PATH)).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Purge dialog: leaving', () => {
|
||||
it('closes with Cancel, sends nothing and returns focus to the opener', async () => {
|
||||
const opener = await opened()
|
||||
|
||||
findButton('Cancel', dialogEl()!)!.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(dialogEl()).toBeNull()
|
||||
expect(callsTo('POST', PURGE_PATH)).toHaveLength(0)
|
||||
expect(document.activeElement).toBe(opener)
|
||||
})
|
||||
|
||||
it('closes with Escape, sends nothing and returns focus to the opener', async () => {
|
||||
const opener = await opened()
|
||||
|
||||
pressEscape(dialogEl())
|
||||
await flushPromises()
|
||||
|
||||
expect(dialogEl()).toBeNull()
|
||||
expect(callsTo('POST', PURGE_PATH)).toHaveLength(0)
|
||||
expect(document.activeElement).toBe(opener)
|
||||
})
|
||||
|
||||
it('closes with a click on the overlay and returns focus to the opener', async () => {
|
||||
const opener = await opened()
|
||||
|
||||
const overlay = document.body.querySelector('[data-slot="overlay"]')!
|
||||
overlay.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, cancelable: true }))
|
||||
overlay.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
||||
await flushPromises()
|
||||
|
||||
expect(dialogEl()).toBeNull()
|
||||
expect(callsTo('POST', PURGE_PATH)).toHaveLength(0)
|
||||
expect(document.activeElement).toBe(opener)
|
||||
})
|
||||
|
||||
it('follows the review link to the Duplicates tab', async () => {
|
||||
await opened()
|
||||
|
||||
dialogEl()!.querySelector('a')!.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(dialogEl()).toBeNull()
|
||||
await vi.waitFor(() => expect(router.currentRoute.value.path).toBe('/media'))
|
||||
expect(router.currentRoute.value.query).toEqual({ filter: 'duplicates' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Purge dialog: purging', () => {
|
||||
it('sends exactly the fingerprint of the preview, once', async () => {
|
||||
await opened({ preview: () => previewOf({ fingerprint: 'ab12' }) })
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
const calls = callsTo('POST', PURGE_PATH)
|
||||
expect(calls).toHaveLength(1)
|
||||
expect(bodyOf(calls[0]!)).toEqual({ fingerprint: 'ab12' })
|
||||
expect(Object.keys(bodyOf(calls[0]!))).toEqual(['fingerprint'])
|
||||
})
|
||||
|
||||
it('cannot be dismissed while the request runs', async () => {
|
||||
const pending = deferredValue<unknown>()
|
||||
await opened({ purge: () => pending.promise })
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
const confirm = confirmButton('Purging…')
|
||||
expect(confirm.disabled).toBe(true)
|
||||
await vi.waitFor(() => expect(confirm.querySelector('svg.iconify--lucide')).toBeTruthy())
|
||||
expect(findButton('Cancel', dialogEl()!)!.disabled).toBe(true)
|
||||
pressEscape(dialogEl())
|
||||
document.body
|
||||
.querySelector('[data-slot="overlay"]')
|
||||
?.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, cancelable: true }))
|
||||
await flushPromises()
|
||||
expect(dialogEl()).not.toBeNull()
|
||||
|
||||
pending.resolve(purgeResult())
|
||||
await flushPromises()
|
||||
expect(dialogEl()).toBeNull()
|
||||
})
|
||||
|
||||
it('closes, shows the toast, refreshes Last purge in place and returns focus on success', async () => {
|
||||
const opener = await opened({ settings: afterRefresh(purgedSettings()) })
|
||||
expect(lastLine()).toBe(LAST_RUN_LINE)
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
expect(dialogEl()).toBeNull()
|
||||
expect(toasts.add).toHaveBeenCalledTimes(1)
|
||||
expect(toasts.add).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
color: 'success',
|
||||
icon: 'i-lucide-circle-check',
|
||||
title: 'Duplicates purged',
|
||||
description: '9 copies merged. 12 articles updated.',
|
||||
duration: 5000,
|
||||
}),
|
||||
)
|
||||
expect(callsTo('GET', SETTINGS_PATH)).toHaveLength(2)
|
||||
expect(lastLine()).toBe(AFTER_PURGE_LINE)
|
||||
expect(textOf(nextLine())).toBe('Next purge: 4 Nov 2026')
|
||||
expect(document.activeElement).toBe(opener)
|
||||
})
|
||||
|
||||
it('shows no Next purge when automatic purge is off', async () => {
|
||||
await opened({
|
||||
settings: afterRefresh(
|
||||
purgedSettings({ auto_purge_enabled: false, next_purge_at: null }),
|
||||
),
|
||||
})
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
expect(lastLine()).toBe(AFTER_PURGE_LINE)
|
||||
expect(nextLine()).toBeNull()
|
||||
})
|
||||
|
||||
it('pluralises the toast', async () => {
|
||||
await opened({ purge: () => purgeResult({ merged: 1, articles_updated: 1 }) })
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
expect(toasts.add).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ description: '1 copy merged. 1 article updated.' }),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits purged with the counts and then closes', async () => {
|
||||
vi.mocked(useApi).mockReturnValue({
|
||||
getMe: vi.fn(),
|
||||
request: vi.fn(async (path: string) =>
|
||||
path === PREVIEW_PATH ? previewOf() : purgeResult(),
|
||||
),
|
||||
})
|
||||
const wrapper = mount(PurgeDuplicatesDialog, {
|
||||
attachTo: document.body,
|
||||
global: { plugins: [router, uiPlugin] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.emitted('purged')).toEqual([[purgeResult()]])
|
||||
expect(wrapper.emitted('close')).toHaveLength(1)
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Purge dialog: failures', () => {
|
||||
it.each([
|
||||
['a 500', () => Promise.reject(new ApiError(500))],
|
||||
['a network error', () => Promise.reject(new NetworkError())],
|
||||
])(
|
||||
'keeps the dialog open after %s and retries with the same fingerprint',
|
||||
async (_name, purge) => {
|
||||
await opened({ preview: () => previewOf({ fingerprint: 'ab12' }), purge })
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
const box = dialogEl()!
|
||||
const alert = box.querySelector('[role="alert"]')!
|
||||
expect(textOf(alert)).toBe(FAILED_COPY)
|
||||
expect(alert.querySelector('svg')).not.toBeNull()
|
||||
expect(isBefore(alert, box.querySelector('[data-testid="purge-summary"]')!)).toBe(true)
|
||||
expect(textOf(box.querySelector('[data-testid="purge-summary"]'))).toBe(SUMMARY)
|
||||
expect(groupRows()).toHaveLength(4)
|
||||
expect(findButton('Cancel', box)!.disabled).toBe(false)
|
||||
expect(confirmButton('Purge 4 groups').disabled).toBe(false)
|
||||
expect(toasts.add).not.toHaveBeenCalled()
|
||||
expect(lastLine()).toBe(LAST_RUN_LINE)
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
const calls = callsTo('POST', PURGE_PATH)
|
||||
expect(calls).toHaveLength(2)
|
||||
expect(bodyOf(calls[1]!)).toEqual({ fingerprint: 'ab12' })
|
||||
},
|
||||
)
|
||||
|
||||
it('warns about a partial result, refreshes the preview and the settings, and retries with the new fingerprint', async () => {
|
||||
let previews = 0
|
||||
await opened({
|
||||
settings: afterRefresh(purgedSettings()),
|
||||
preview: () => (previews++ === 0 ? previewOf() : singlePreview(2, 1, 2)),
|
||||
purge: () =>
|
||||
purgeResult({
|
||||
groups_total: 4,
|
||||
groups_purged: 3,
|
||||
merged: 8,
|
||||
articles_updated: 10,
|
||||
failed_groups: 1,
|
||||
}),
|
||||
})
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
const box = dialogEl()!
|
||||
expect(noticeText()).toBe(
|
||||
"Some groups couldn't be merged. 3 of 4 groups were purged. Try again to finish.",
|
||||
)
|
||||
expect(box.querySelector('[role="alert"] svg')).not.toBeNull()
|
||||
expect(callsTo('GET', PREVIEW_PATH)).toHaveLength(2)
|
||||
expect(callsTo('GET', SETTINGS_PATH)).toHaveLength(2)
|
||||
expect(textOf(box.querySelector('[data-testid="purge-summary"]'))).toBe(
|
||||
'1 group · 1 copy will be deleted · 2 articles will be updated',
|
||||
)
|
||||
expect(buttonLabels()).toEqual(['Close', 'Retry'])
|
||||
expect(toasts.add).not.toHaveBeenCalled()
|
||||
expect(lastLine()).toBe(AFTER_PURGE_LINE)
|
||||
|
||||
confirmButton('Retry').click()
|
||||
await flushPromises()
|
||||
|
||||
const calls = callsTo('POST', PURGE_PATH)
|
||||
expect(calls).toHaveLength(2)
|
||||
expect(bodyOf(calls[1]!)).toEqual({ fingerprint: 'one' })
|
||||
})
|
||||
|
||||
it('shows Nothing to purge when a partial result leaves no group', async () => {
|
||||
let previews = 0
|
||||
await opened({
|
||||
preview: () => (previews++ === 0 ? previewOf() : emptyPreview()),
|
||||
purge: () => purgeResult({ groups_purged: 3, failed_groups: 1 }),
|
||||
})
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
expect(textOf(dialogEl())).toContain('Nothing to purge')
|
||||
expect(buttonLabels()).toEqual(['Close'])
|
||||
})
|
||||
|
||||
it('refreshes the summary after the library changed and confirms with the new fingerprint', async () => {
|
||||
let previews = 0
|
||||
const refreshed = previewOf({
|
||||
groups: 3,
|
||||
copies: 7,
|
||||
articles: 10,
|
||||
list: previewOf().list.slice(0, 3),
|
||||
fingerprint: 'cd34',
|
||||
})
|
||||
await opened({
|
||||
preview: () => (previews++ === 0 ? previewOf({ fingerprint: 'ab12' }) : refreshed),
|
||||
purge: () => Promise.reject(new ApiError(409, undefined, STALE_COPY)),
|
||||
})
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
const box = dialogEl()!
|
||||
expect(noticeText()).toBe(STALE_COPY)
|
||||
expect(box.querySelector('[role="alert"] svg')).not.toBeNull()
|
||||
expect(callsTo('GET', PREVIEW_PATH)).toHaveLength(2)
|
||||
expect(textOf(box.querySelector('[data-testid="purge-summary"]'))).toBe(
|
||||
'3 groups · 7 copies will be deleted · 10 articles will be updated',
|
||||
)
|
||||
expect(buttonLabels()).toEqual(['Cancel', 'Purge 3 groups'])
|
||||
expect(toasts.add).not.toHaveBeenCalled()
|
||||
expect(lastLine()).toBe(LAST_RUN_LINE)
|
||||
|
||||
confirmButton('Purge 3 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
expect(bodyOf(callsTo('POST', PURGE_PATH)[1]!)).toEqual({ fingerprint: 'cd34' })
|
||||
})
|
||||
|
||||
it('shows the server text when a purge is already running', async () => {
|
||||
await opened({ purge: () => Promise.reject(new ApiError(409, undefined, BUSY_COPY)) })
|
||||
|
||||
confirmButton('Purge 4 groups').click()
|
||||
await flushPromises()
|
||||
|
||||
expect(noticeText()).toBe(BUSY_COPY)
|
||||
expect(callsTo('GET', PREVIEW_PATH)).toHaveLength(2)
|
||||
expect(dialogEl()).not.toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user