feat(dashboard): verbatim modal + defect feed on /defects (T8)
This commit is contained in:
51
tests/defect-feed.nuxt.spec.ts
Normal file
51
tests/defect-feed.nuxt.spec.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// @vitest-environment nuxt
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { mountSuspended } from '@nuxt/test-utils/runtime'
|
||||
import DefectFeed from '../app/components/DefectFeed.vue'
|
||||
|
||||
const defect = (over: Partial<Record<string, unknown>> = {}) => ({
|
||||
id: 'd1',
|
||||
sectionId: 'macroplan',
|
||||
projectName: 'Acme',
|
||||
reporterEmail: 'dev@theodo.com',
|
||||
verbatim: 'builds are flaky',
|
||||
createdAt: '2026-05-28T10:00:00Z',
|
||||
...over,
|
||||
})
|
||||
|
||||
describe('DefectFeed', () => {
|
||||
it('renders each defect with its date, project, reporter and verbatim', async () => {
|
||||
const wrapper = await mountSuspended(DefectFeed, {
|
||||
props: { defects: [defect()] },
|
||||
})
|
||||
|
||||
const items = wrapper.findAll('[data-test="feed-item"]')
|
||||
expect(items).toHaveLength(1)
|
||||
const text = items[0]!.text()
|
||||
expect(text).toContain('28/05')
|
||||
expect(text).toContain('Acme')
|
||||
expect(text).toContain('dev@theodo.com')
|
||||
expect(text).toContain('builds are flaky')
|
||||
})
|
||||
|
||||
it('preserves the feed order it is given (newest-first from the API)', async () => {
|
||||
const wrapper = await mountSuspended(DefectFeed, {
|
||||
props: {
|
||||
defects: [
|
||||
defect({ id: 'newer', verbatim: 'newer one' }),
|
||||
defect({ id: 'older', verbatim: 'older one' }),
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const items = wrapper.findAll('[data-test="feed-item"]')
|
||||
expect(items[0]!.text()).toContain('newer one')
|
||||
expect(items[1]!.text()).toContain('older one')
|
||||
})
|
||||
|
||||
it('shows an empty state when there are no defects', async () => {
|
||||
const wrapper = await mountSuspended(DefectFeed, { props: { defects: [] } })
|
||||
expect(wrapper.findAll('[data-test="feed-item"]')).toHaveLength(0)
|
||||
expect(wrapper.find('[data-test="feed-empty"]').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -56,4 +56,16 @@ describe('DotMap', () => {
|
||||
|
||||
expect(section(wrapper, 'macroplan').find('[data-test="more"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('emits the clicked section id (so the page can open its verbatim modal)', async () => {
|
||||
const wrapper = await mountSuspended(DotMap, {
|
||||
props: {
|
||||
defects: [{ id: 'd1', sectionId: 'macroplan', createdAt: '2026-05-28T10:00:00Z' }],
|
||||
counts: { macroplan: 1 },
|
||||
},
|
||||
})
|
||||
|
||||
await section(wrapper, 'macroplan').trigger('click')
|
||||
expect(wrapper.emitted('select')?.[0]).toEqual(['macroplan'])
|
||||
})
|
||||
})
|
||||
|
||||
75
tests/verbatim-modal.nuxt.spec.ts
Normal file
75
tests/verbatim-modal.nuxt.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
// @vitest-environment nuxt
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime'
|
||||
import { flushPromises } from '@vue/test-utils'
|
||||
import { defineEventHandler, getQuery } from 'h3'
|
||||
import VerbatimModal from '../app/components/VerbatimModal.vue'
|
||||
|
||||
// Capture the section the modal lazily queried, and answer with that section's
|
||||
// history (T6's `/api/defects?section=ID`).
|
||||
let queried: string | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
queried = undefined
|
||||
registerEndpoint(
|
||||
'/api/defects',
|
||||
defineEventHandler((event) => {
|
||||
queried = getQuery(event).section as string | undefined
|
||||
return [
|
||||
{
|
||||
id: 'd1',
|
||||
sectionId: 'macroplan',
|
||||
projectName: 'Acme',
|
||||
reporterEmail: 'dev@theodo.com',
|
||||
verbatim: 'builds are flaky',
|
||||
createdAt: '2026-05-28T10:00:00Z',
|
||||
},
|
||||
]
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
type Wrapper = Awaited<ReturnType<typeof mountSuspended>>
|
||||
const dialogEl = (w: Wrapper) => w.find('dialog').element as HTMLDialogElement
|
||||
|
||||
describe('VerbatimModal', () => {
|
||||
it('stays closed when no section is selected', async () => {
|
||||
const wrapper = await mountSuspended(VerbatimModal, { props: { sectionId: null } })
|
||||
await flushPromises()
|
||||
expect(dialogEl(wrapper).open).toBe(false)
|
||||
})
|
||||
|
||||
it('opens for the clicked section and lists its defects with verbatim, project, reporter and date', async () => {
|
||||
const wrapper = await mountSuspended(VerbatimModal, { props: { sectionId: 'macroplan' } })
|
||||
await flushPromises()
|
||||
|
||||
expect(dialogEl(wrapper).open).toBe(true)
|
||||
expect(queried).toBe('macroplan')
|
||||
// The section's human label heads the modal.
|
||||
expect(wrapper.text()).toContain('Macroplan')
|
||||
|
||||
const items = wrapper.findAll('[data-test="modal-item"]')
|
||||
expect(items).toHaveLength(1)
|
||||
const text = items[0]!.text()
|
||||
expect(text).toContain('builds are flaky')
|
||||
expect(text).toContain('Acme')
|
||||
expect(text).toContain('dev@theodo.com')
|
||||
expect(text).toContain('28/05')
|
||||
})
|
||||
|
||||
it('closes when the section is cleared', async () => {
|
||||
const wrapper = await mountSuspended(VerbatimModal, { props: { sectionId: 'macroplan' } })
|
||||
await flushPromises()
|
||||
await wrapper.setProps({ sectionId: null })
|
||||
await flushPromises()
|
||||
expect(dialogEl(wrapper).open).toBe(false)
|
||||
})
|
||||
|
||||
it('emits close when the dialog is dismissed (Escape/backdrop fire native close)', async () => {
|
||||
const wrapper = await mountSuspended(VerbatimModal, { props: { sectionId: 'macroplan' } })
|
||||
await flushPromises()
|
||||
|
||||
dialogEl(wrapper).dispatchEvent(new Event('close'))
|
||||
expect(wrapper.emitted('close')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user