feat: unify completed filters in topbar
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user