feat: guard CommandBar busy actions (AEG-V16-019)
Keep action order while preventing disabled or busy actions from emitting a command. Preserve test evidence; status remains IN_PROGRESS pending predecessor acceptance.
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import KsButton from './KsButton.vue'
|
||||
export interface CommandBarAction { id: string; label: string; severity?: 'primary'|'secondary'|'success'|'info'|'warning'|'danger'; disabled?: boolean; busy?: boolean }
|
||||
defineProps<{ actions: readonly CommandBarAction[]; ariaLabel?: string }>()
|
||||
const props = defineProps<{ actions: readonly CommandBarAction[]; ariaLabel?: string }>()
|
||||
const emit = defineEmits<{ execute: [actionId: string] }>()
|
||||
|
||||
function execute(action: CommandBarAction): void {
|
||||
if (action.disabled || action.busy) return
|
||||
emit('execute', action.id)
|
||||
}
|
||||
</script>
|
||||
<template><nav class="ks-command-bar" :aria-label="ariaLabel ?? 'Page actions'"><KsButton v-for="action in actions" :key="action.id" :label="action.label" :severity="action.severity" :disabled="action.disabled" :loading="action.busy" @click="emit('execute', action.id)" /></nav></template>
|
||||
<template><nav class="ks-command-bar" :aria-label="ariaLabel ?? 'Page actions'" :aria-busy="actions.some(action => action.busy) || undefined"><KsButton v-for="action in actions" :key="action.id" :label="action.label" :severity="action.severity" :disabled="action.disabled" :loading="action.busy" @click="execute(action)" /></nav></template>
|
||||
<style scoped>.ks-command-bar{display:flex;gap:var(--ks-space-2);flex-wrap:wrap;justify-content:flex-end}</style>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import KsCommandBar from '../KsCommandBar.vue'
|
||||
|
||||
const actions = [
|
||||
{ id: 'save', label: 'Save' },
|
||||
{ id: 'review', label: 'Review', disabled: true },
|
||||
{ id: 'publish', label: 'Publish', busy: true }
|
||||
] as const
|
||||
|
||||
describe('KsCommandBar', () => {
|
||||
it('preserves supplied action order and executes only enabled, idle actions', async () => {
|
||||
const wrapper = mount(KsCommandBar, {
|
||||
props: { actions, ariaLabel: 'Decision actions' },
|
||||
global: { stubs: { KsButton: { name: 'KsButton', props: ['label'], template: '<button>{{ label }}</button>' } } }
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('button').map(button => button.text())).toEqual(['Save', 'Review', 'Publish'])
|
||||
const buttons = wrapper.findAllComponents({ name: 'KsButton' })
|
||||
buttons[0].vm.$emit('click')
|
||||
buttons[1].vm.$emit('click')
|
||||
buttons[2].vm.$emit('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.emitted('execute')).toEqual([['save']])
|
||||
expect(wrapper.get('nav').attributes()).toMatchObject({ 'aria-label': 'Decision actions', 'aria-busy': 'true' })
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user