Files
RenewCMS/web/src/views/__tests__/EditorSeoMemory.spec.ts
T

154 lines
4.6 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { config, flushPromises } from '@vue/test-utils'
import { MOBILE_WIDTH } from '@/components/__tests__/viewport'
import { META_SECTION_OPEN_KEY } from '@/lib/metaSectionMemory'
import { SEO_SECTION_OPEN_KEY } from '@/lib/seoSectionMemory'
import { click, unmountMeta } from './metaTestKit'
import {
FOUR_SUGGESTIONS,
answerChecks,
checkAnswer,
mountSeo,
openPanel,
openSection,
section,
sectionBody,
sectionToggle,
seoBackend,
} from './seoTestKit'
vi.mock('@/composables/useApi', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/composables/useApi')>()
return { ...actual, useApi: vi.fn() }
})
beforeEach(() => {
localStorage.clear()
sessionStorage.clear()
})
afterEach(async () => {
config.global.config.errorHandler = undefined
vi.restoreAllMocks()
await unmountMeta()
})
function securityError() {
return new DOMException('Access denied', 'SecurityError')
}
function collectVueErrors() {
const uncaught: unknown[] = []
const previous = config.global.config.errorHandler
config.global.config.errorHandler = (error) => {
uncaught.push(error)
}
return {
uncaught,
stop: () => {
config.global.config.errorHandler = previous
},
}
}
describe('The choice is remembered per browser, with its own key', () => {
it('uses a key of its own, independent of the Site and authors key', () => {
expect(SEO_SECTION_OPEN_KEY).not.toBe(META_SECTION_OPEN_KEY)
})
it('keeps an opening for the next article and a collapse after a reload, sending nothing to the API', async () => {
const backend = seoBackend()
await mountSeo(backend)
const callsBefore = backend.calls.length
await openSection()
expect(localStorage.getItem(SEO_SECTION_OPEN_KEY)).toBe('true')
expect(localStorage.getItem(META_SECTION_OPEN_KEY)).toBeNull()
expect(backend.calls).toHaveLength(callsBefore)
await unmountMeta()
await mountSeo(seoBackend())
expect(sectionBody()).not.toBeNull()
sectionToggle().click()
await flushPromises()
expect(localStorage.getItem(SEO_SECTION_OPEN_KEY)).toBe('false')
await unmountMeta()
await mountSeo(seoBackend())
expect(sectionBody()).toBeNull()
})
it('shares the choice between the sidebar and the Details sheet', async () => {
await mountSeo(seoBackend(), { width: MOBILE_WIDTH })
await click('Details')
const sheet = document.body.querySelector<HTMLElement>('[role="dialog"]')!
sheet.querySelector<HTMLElement>('[data-testid="seo-toggle"]')!.click()
await flushPromises()
expect(localStorage.getItem(SEO_SECTION_OPEN_KEY)).toBe('true')
const sidebar = document.body.querySelector('[data-testid="article-details"]')!
expect(sidebar.querySelector('[data-testid="seo-body"]')).not.toBeNull()
})
it('does not change the stored choice when the panel or the dialog is opened from the section', async () => {
const backend = seoBackend()
answerChecks(backend, checkAnswer(FOUR_SUGGESTIONS))
await mountSeo(backend)
await openSection()
const writes = vi.spyOn(Storage.prototype, 'setItem')
const panel = await openPanel()
expect(panel).not.toBeNull()
expect(writes).not.toHaveBeenCalled()
expect(localStorage.getItem(SEO_SECTION_OPEN_KEY)).toBe('true')
expect(section()).not.toBeNull()
})
})
describe('A storage that throws never breaks the editor', () => {
it.each(['getItem', 'setItem'] as const)(
'renders the editor and toggles the section for this page view when localStorage.%s throws',
async (call) => {
const errors = collectVueErrors()
vi.spyOn(Storage.prototype, call).mockImplementation(() => {
throw securityError()
})
await mountSeo(seoBackend())
expect(section()).not.toBeNull()
expect(sectionBody()).toBeNull()
sectionToggle().click()
await flushPromises()
expect(sectionBody()).not.toBeNull()
sectionToggle().click()
await flushPromises()
expect(sectionBody()).toBeNull()
errors.stop()
expect(errors.uncaught).toEqual([])
},
)
it('falls back to collapsed when reading throws, even if a choice was stored', async () => {
localStorage.setItem(SEO_SECTION_OPEN_KEY, 'true')
vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
throw securityError()
})
await mountSeo(seoBackend())
expect(sectionBody()).toBeNull()
})
it('silently skips the write when setItem throws', async () => {
const setItem = vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
throw securityError()
})
await mountSeo(seoBackend())
await openSection()
expect(setItem).toHaveBeenCalled()
expect(localStorage.getItem(SEO_SECTION_OPEN_KEY)).toBeNull()
})
})