174 lines
5.7 KiB
TypeScript
174 lines
5.7 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { flushPromises } from '@vue/test-utils'
|
|
import {
|
|
button,
|
|
callsTo,
|
|
chooseMenuItem,
|
|
click,
|
|
createBackend,
|
|
dialog,
|
|
failWith,
|
|
mountDirectory,
|
|
networkFailure,
|
|
openRowMenu,
|
|
pending,
|
|
rowTexts,
|
|
textOf,
|
|
unmountDirectory,
|
|
} from './directoryTestKit'
|
|
|
|
vi.mock('@/composables/useApi', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('@/composables/useApi')>()
|
|
return { ...actual, useApi: vi.fn() }
|
|
})
|
|
|
|
afterEach(unmountDirectory)
|
|
|
|
async function openDelete(name: string, backend = createBackend()) {
|
|
await mountDirectory('sites', '/settings/sites', { backend })
|
|
await openRowMenu(name)
|
|
await chooseMenuItem('Delete…')
|
|
return backend
|
|
}
|
|
|
|
function box() {
|
|
return dialog()!
|
|
}
|
|
|
|
function trigger() {
|
|
return box().querySelector<HTMLElement>('button[aria-haspopup="listbox"]')!
|
|
}
|
|
|
|
describe('Sites delete: site with articles', () => {
|
|
it('explains, defaults the destination to the default site and shows the move summary', async () => {
|
|
await openDelete('Journal')
|
|
|
|
const text = textOf(box())
|
|
expect(box().getAttribute('role')).toBe('alertdialog')
|
|
expect(text).toContain('Delete this site?')
|
|
expect(text).toContain(
|
|
'"Journal" will be deleted. 38 articles belong to it, so choose a site to move them to. This can\'t be undone.',
|
|
)
|
|
expect(text).toContain('Move articles to')
|
|
expect(textOf(trigger())).toBe('Renew · renew.example')
|
|
expect(text).toContain(
|
|
'38 articles will move to Renew. Their canonical host changes to renew.example.',
|
|
)
|
|
expect(button('Cancel', box())).toBeTruthy()
|
|
expect(button('Move 38 articles and delete', box())).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
describe('Sites delete: choose destination', () => {
|
|
it('does not offer the deleted site and shows the name and the host on each option', async () => {
|
|
await openDelete('Journal')
|
|
trigger().click()
|
|
await flushPromises()
|
|
|
|
const options = Array.from(box().querySelectorAll('[role="option"]')).map((option) =>
|
|
textOf(option),
|
|
)
|
|
expect(options).toEqual([
|
|
'Renew renew.example',
|
|
'Projects projects.example',
|
|
'Archive archive.example',
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('Sites delete: with articles succeeds', () => {
|
|
it('sends one DELETE with reassign_to, reads Moving articles… and toasts', async () => {
|
|
const backend = createBackend()
|
|
const hold = pending<unknown>()
|
|
backend.responders['DELETE /v1/site/2'] = () => hold.promise
|
|
await openDelete('Journal', backend)
|
|
await click('Move 38 articles and delete', box())
|
|
|
|
expect(button('Moving articles…', box())).toBeTruthy()
|
|
hold.resolve(undefined)
|
|
await flushPromises()
|
|
|
|
const deletes = callsTo(backend, 'DELETE', '/v1/site/2')
|
|
expect(deletes).toHaveLength(1)
|
|
expect(deletes[0]!.query.get('reassign_to')).toBe('1')
|
|
expect(dialog()).toBeNull()
|
|
expect(rowTexts().join(' ')).not.toContain('Journal')
|
|
expect(document.body.textContent).toContain('Site deleted')
|
|
expect(document.body.textContent).toContain('38 articles moved to Renew.')
|
|
})
|
|
})
|
|
|
|
describe('Sites delete: no articles', () => {
|
|
it('asks a plain confirmation with no select and sends no reassign_to', async () => {
|
|
const backend = await openDelete('Archive')
|
|
|
|
const text = textOf(box())
|
|
expect(text).toContain('Delete this site?')
|
|
expect(text).toContain(
|
|
'"Archive" will be deleted. No articles belong to it. This can\'t be undone.',
|
|
)
|
|
expect(trigger()).toBeNull()
|
|
expect(button('Delete site', box())).toBeTruthy()
|
|
|
|
await click('Delete site', box())
|
|
|
|
const deletes = callsTo(backend, 'DELETE', '/v1/site/4')
|
|
expect(deletes).toHaveLength(1)
|
|
expect(deletes[0]!.query.has('reassign_to')).toBe(false)
|
|
expect(rowTexts().join(' ')).not.toContain('Archive')
|
|
})
|
|
})
|
|
|
|
describe.each([
|
|
['status 500', failWith(500)],
|
|
['a network error', networkFailure],
|
|
])('Sites delete: failure with %s', (_name, responder) => {
|
|
it('keeps the dialog open with the notice and enables the buttons again', async () => {
|
|
const backend = createBackend()
|
|
backend.responders['DELETE /v1/site/2'] = responder
|
|
await openDelete('Journal', backend)
|
|
await click('Move 38 articles and delete', box())
|
|
|
|
const alert = textOf(box().querySelector('[role="alert"]'))
|
|
expect(alert).toContain("Couldn't delete the site")
|
|
expect(alert).toContain('Nothing was changed. Try again in a moment.')
|
|
expect(button('Cancel', box())!.hasAttribute('disabled')).toBe(false)
|
|
expect(button('Move 38 articles and delete', box())!.hasAttribute('disabled')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('Sites delete: server refusals', () => {
|
|
it('keeps the dialog open and asks for a destination on 409 site_has_articles', async () => {
|
|
const backend = createBackend()
|
|
backend.responders['DELETE /v1/site/2'] = failWith(409, 'site_has_articles')
|
|
await openDelete('Journal', backend)
|
|
await click('Move 38 articles and delete', box())
|
|
|
|
expect(dialog()).not.toBeNull()
|
|
expect(textOf(box())).toContain('Choose where the articles go. This site has 38 articles.')
|
|
})
|
|
|
|
it('closes and toasts on 403 default_site', async () => {
|
|
const backend = createBackend()
|
|
backend.responders['DELETE /v1/site/2'] = failWith(403, 'default_site')
|
|
await openDelete('Journal', backend)
|
|
await click('Move 38 articles and delete', box())
|
|
|
|
expect(dialog()).toBeNull()
|
|
expect(document.body.textContent).toContain(
|
|
"The default site can't be deleted. Make another site the default first.",
|
|
)
|
|
})
|
|
|
|
it('closes, removes the row and toasts on 404', async () => {
|
|
const backend = createBackend()
|
|
backend.responders['DELETE /v1/site/2'] = failWith(404, 'site_not_found')
|
|
await openDelete('Journal', backend)
|
|
await click('Move 38 articles and delete', box())
|
|
|
|
expect(dialog()).toBeNull()
|
|
expect(rowTexts().join(' ')).not.toContain('Journal')
|
|
expect(document.body.textContent).toContain('That site no longer exists')
|
|
})
|
|
})
|