opsdash-app/opsdash/test/WidgetOptionsMenu.test.ts

211 lines
7.1 KiB
TypeScript

import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import { nextTick } from 'vue'
import WidgetOptionsMenu from '../src/components/layout/WidgetOptionsMenu.vue'
describe('WidgetOptionsMenu', () => {
const entry = {
controls: [
{ key: 'customToggle', label: 'Custom toggle', type: 'toggle' },
{ key: 'cardBg', label: 'Card background', type: 'color' }, // duplicate should be deduped
],
}
it('shows core layout controls and widget controls without duplicate cardBg', async () => {
const wrapper = mount(WidgetOptionsMenu, {
props: {
entry,
options: {},
open: true,
},
})
const sections = wrapper.findAll('.opt-section')
expect(sections.length).toBe(2) // layout/title + widget options
const labels = wrapper.findAll('label').map((n) => n.text())
expect(labels).toContain('Title prefix')
expect(labels).toContain('Card background')
expect(labels).toContain('Scale')
expect(labels).toContain('Dense mode')
expect(labels.filter((l) => l === 'Card background').length).toBe(1)
expect(labels).toContain('Custom toggle')
const scaleSelect = wrapper.find('#opt-scale')
const scaleOptions = scaleSelect.findAll('option').map((opt) => opt.text())
expect(scaleOptions).toContain('Extra large')
})
it('builds custom filters with the filter builder control', async () => {
const wrapper = mount(WidgetOptionsMenu, {
props: {
entry: {
controls: [{ key: 'customFilters', label: 'Custom filters', type: 'filterbuilder' }],
},
options: {},
open: true,
},
})
const addButton = wrapper.findAll('button').find((btn) => btn.text().includes('Add filter'))
expect(addButton).toBeTruthy()
await addButton!.trigger('click')
await nextTick()
const addEmission = wrapper.emitted('change')?.at(-1)
expect(addEmission).toBeTruthy()
await wrapper.setProps({ options: { customFilters: addEmission?.[1] } })
await nextTick()
const labelInput = wrapper.find('input.filter-builder__label')
const tagInput = wrapper.findAll('input.filter-builder__input')[0]
const assigneeInput = wrapper.findAll('input.filter-builder__input')[1]
await labelInput.setValue('Urgent')
await nextTick()
let lastEmission = wrapper.emitted('change')?.at(-1)
await wrapper.setProps({ options: { customFilters: lastEmission?.[1] } })
await nextTick()
await tagInput.setValue('Ops, QA')
await nextTick()
lastEmission = wrapper.emitted('change')?.at(-1)
await wrapper.setProps({ options: { customFilters: lastEmission?.[1] } })
await nextTick()
await assigneeInput.setValue('me, qa')
await nextTick()
lastEmission = wrapper.emitted('change')?.at(-1)
const emissions = wrapper.emitted('change') ?? []
const last = emissions.at(-1)
expect(last).toBeTruthy()
expect(last![0]).toBe('customFilters')
expect(last![1]).toEqual([
{
label: 'Urgent',
labels: ['Ops', 'QA'],
assignees: ['me', 'qa'],
},
])
})
it('shows tab actions and emits move/copy targets', async () => {
const wrapper = mount(WidgetOptionsMenu, {
props: {
entry,
options: {},
open: true,
tabs: [
{ id: 'tab-1', label: 'Overview' },
{ id: 'tab-2', label: 'Charts' },
],
currentTabId: 'tab-1',
},
})
const text = wrapper.text().replace(/\s+/g, ' ')
expect(text).toContain('Tab actions')
expect(text).toContain('Current tab')
expect(text).toContain('Charts')
const moveButtons = wrapper.findAll('.tab-actions__buttons .ghost.sm')
await moveButtons[2].trigger('click')
await moveButtons[3].trigger('click')
expect(wrapper.emitted('move-to-tab')?.[0]).toEqual(['tab-2'])
expect(wrapper.emitted('duplicate-to-tab')?.[0]).toEqual(['tab-2'])
})
it('applies core defaults to checkbox and select controls', () => {
const wrapper = mount(WidgetOptionsMenu, {
props: {
entry: {
controls: [],
},
options: {},
open: true,
},
})
expect((wrapper.get('#opt-showHeader').element as HTMLInputElement).checked).toBe(true)
expect((wrapper.get('#opt-dense').element as HTMLInputElement).checked).toBe(false)
expect((wrapper.get('#opt-scale').element as HTMLSelectElement).value).toBe('md')
})
it('passes merged defaults into dynamic controls', () => {
const wrapper = mount(WidgetOptionsMenu, {
props: {
entry: {
dynamicControls: (options: Record<string, any>) => [
{
key: 'debugToggle',
label: options.showHeader ? 'ShowHeader defaulted on' : 'ShowHeader defaulted off',
type: 'toggle',
},
],
},
options: {},
open: true,
},
})
expect(wrapper.text()).toContain('ShowHeader defaulted on')
})
it('uses registry-resolved effective options for checkbox controls', () => {
const wrapper = mount(WidgetOptionsMenu, {
props: {
entry: {
controls: [{ key: 'showTotalDelta', label: 'Show total delta', type: 'toggle' }],
resolveOptions: () => ({ showTotalDelta: true }),
},
options: {},
open: true,
},
})
expect((wrapper.get('#opt-showTotalDelta').element as HTMLInputElement).checked).toBe(true)
})
it('renders ColorPickerPopover for color controls and emits change on pick', async () => {
const wrapper = mount(WidgetOptionsMenu, {
props: {
entry: { controls: [{ key: 'trendColor', label: 'Trend color', type: 'color' }] },
options: { trendColor: '#2563EB' },
open: true,
},
})
// find the picker for trendColor specifically (not the core cardBg one)
const pickers = wrapper.findAllComponents({ name: 'ColorPickerPopover' })
const trendPicker = pickers.find((p) => p.props('modelValue') === '#2563EB')
expect(trendPicker).toBeTruthy()
await trendPicker!.vm.$emit('update:modelValue', '#F97316')
const emissions = wrapper.emitted('change') ?? []
expect(emissions.at(-1)).toEqual(['trendColor', '#F97316'])
})
it('renders ColorPickerPopover for each colorlist entry and emits updated array', async () => {
const wrapper = mount(WidgetOptionsMenu, {
props: {
entry: { controls: [{ key: 'palette', label: 'Palette', type: 'colorlist' }] },
options: { palette: ['#ff0000', '#00ff00'] },
open: true,
},
})
// core controls add a cardBg picker; find the palette pickers by their values
const pickers = wrapper.findAllComponents({ name: 'ColorPickerPopover' })
const palettePickers = pickers.filter((p) => ['#ff0000', '#00ff00'].includes(p.props('modelValue') as string))
expect(palettePickers).toHaveLength(2)
await palettePickers[0].vm.$emit('update:modelValue', '#0000ff')
const emissions = wrapper.emitted('change') ?? []
const last = emissions.at(-1) as [string, string[]]
expect(last[0]).toBe('palette')
expect(last[1][0]).toBe('#0000ff')
expect(last[1][1]).toBe('#00ff00')
})
})