Files
FabledCurator/frontend/test/components/kebabMenu.spec.js
T
bvandeusen c774042a85
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 40s
CI / integration (push) Successful in 3m18s
refactor(ui): consolidate 7 hand-rolled kebabs into one KebabMenu (DRY pattern sweep)
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>
2026-06-09 22:25:11 -04:00

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')
})
})