feat: unify completed filters in topbar
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 3m30s

This commit is contained in:
2026-09-10 12:16:45 +08:00
parent 1bc481d2e4
commit bf61e69776
6 changed files with 133 additions and 33 deletions
@@ -0,0 +1,57 @@
import { afterEach, describe, expect, it } from 'vitest'
import { createApp, h, nextTick, ref } from 'vue'
import CompletedFilterPill from './CompletedFilterPill.vue'
const cleanups: Array<() => void> = []
async function mountPill(initial = false, disabled = false) {
const host = document.createElement('div')
document.body.append(host)
const value = ref(initial)
const updates: boolean[] = []
const app = createApp({
setup: () => () => h(CompletedFilterPill, {
modelValue: value.value,
disabled,
'onUpdate:modelValue': (next: boolean) => { updates.push(next); value.value = next },
}),
})
app.mount(host)
cleanups.push(() => { app.unmount(); host.remove() })
await nextTick()
return { button: host.querySelector('button')!, updates, value }
}
afterEach(() => cleanups.splice(0).forEach((cleanup) => cleanup()))
describe('CompletedFilterPill', () => {
it.each([false, true])('reflects modelValue %s as switch state', async (initial) => {
const { button } = await mountPill(initial)
expect(button.getAttribute('role')).toBe('switch')
expect(button.getAttribute('aria-checked')).toBe(String(initial))
expect(button.getAttribute('aria-label')).toBe('显示已完成')
expect(button.textContent).toContain('显示已完成')
expect(button.querySelector('.completed-filter-pill__track')?.getAttribute('aria-hidden')).toBe('true')
})
it('toggles once when the whole pill is clicked', async () => {
const { button, updates } = await mountPill(false)
button.click()
expect(updates).toEqual([true])
})
it.each(['Enter', ' '])('toggles with %s through native button activation', async (key) => {
const { button, updates } = await mountPill(false)
button.focus()
button.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }))
button.click()
expect(updates).toEqual([true])
})
it('does not emit while disabled', async () => {
const { button, updates } = await mountPill(false, true)
expect(button.disabled).toBe(true)
button.click()
expect(updates).toEqual([])
})
})
@@ -0,0 +1,31 @@
<script setup lang="ts">
const props = withDefaults(defineProps<{
modelValue: boolean
disabled?: boolean
}>(), { disabled: false })
const emit = defineEmits<{
'update:modelValue': [value: boolean]
}>()
function toggle() {
if (!props.disabled) emit('update:modelValue', !props.modelValue)
}
</script>
<template>
<button
type="button"
class="completed-filter-pill"
role="switch"
:aria-checked="modelValue"
aria-label="显示已完成"
:disabled="disabled"
@click="toggle"
>
<span class="completed-filter-pill__label">显示已完成</span>
<span class="completed-filter-pill__track" aria-hidden="true">
<span class="completed-filter-pill__thumb"></span>
</span>
</button>
</template>