c774042a85
First pattern-consistency DRY pass (process #594). The overflow kebab was hand-rolled 7 ways in two divergent activator strategies — Pattern A (#activator + v-bind) which silently breaks inside the teleported image modal (#711), and Pattern B (manual v-model + activator=parent + open-on-click=false + z-index 2400) the modal kebabs needed as a workaround. New <KebabMenu> (components/common) bakes in the modal-safe strategy UNIVERSALLY, so every kebab works in modal and non-modal contexts — folding the latent #711-class bug fix into all five Pattern-A sites. Menu items go in the default slot; variations (size/variant/location/label/min-width) are props. Adopted across all 7: TagChip, SuggestionItem, TagCard, SeriesView card, SeriesManageView, BackupRunsTable, SourceActions. Exhaustiveness (§8b): mdi-dots-vertical now lives only in KebabMenu. Labeled dropdowns / nav menus / filter popovers are a different concept and left alone. Seeded the pattern catalog so new code reuses the primitive. Test: KebabMenu renders slot items + trigger label/glyph + presentational props. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
39 lines
1.4 KiB
JavaScript
39 lines
1.4 KiB
JavaScript
// @vitest-environment happy-dom
|
|
import { describe, it, expect } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
|
|
import KebabMenu from '../../src/components/common/KebabMenu.vue'
|
|
|
|
// Canonical kebab primitive (DRY pattern-consistency sweep). Vuetify components
|
|
// are left unresolved by the test harness, so we assert the wiring the consumers
|
|
// rely on: the trigger's aria-label, the default kebab glyph, and that the
|
|
// consumer's menu items (default slot) are rendered inside the menu.
|
|
function mountKebab (props = {}) {
|
|
return mount(KebabMenu, {
|
|
props,
|
|
slots: {
|
|
default: '<v-list-item>Rename</v-list-item><v-list-item>Delete</v-list-item>',
|
|
},
|
|
})
|
|
}
|
|
|
|
describe('KebabMenu', () => {
|
|
it('renders the trigger with the provided aria-label and the slotted items', () => {
|
|
const w = mountKebab({ label: 'Actions for Foo' })
|
|
expect(w.text()).toContain('Rename')
|
|
expect(w.text()).toContain('Delete')
|
|
expect(w.html()).toContain('Actions for Foo')
|
|
})
|
|
|
|
it('defaults the trigger glyph to the kebab icon', () => {
|
|
expect(mountKebab().html()).toContain('mdi-dots-vertical')
|
|
})
|
|
|
|
it('passes presentational props through to the trigger/menu', () => {
|
|
const w = mountKebab({ size: 'small', variant: 'flat', location: 'bottom end' })
|
|
const html = w.html()
|
|
expect(html).toContain('small')
|
|
expect(html).toContain('flat')
|
|
})
|
|
})
|