V13-FE-006: consolidate approved UI and contract hardening
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import KsButton from '../KsButton.vue'
|
||||
import KsTextField from '../KsTextField.vue'
|
||||
import { nativeUiAdapter } from '../../adapter/native'
|
||||
import { uiAdapterKey } from '../../adapter/contracts'
|
||||
|
||||
const primeVueHarness = {
|
||||
stubs: {
|
||||
Button: {
|
||||
props: ['label', 'type', 'loading', 'disabled'],
|
||||
template: '<button :type="type" :disabled="disabled || loading" @click="$emit(\'click\', $event)">{{ label }}<span v-if="loading">…</span><slot /></button>',
|
||||
},
|
||||
InputText: {
|
||||
props: ['modelValue', 'inputId', 'type', 'disabled', 'invalid', 'placeholder'],
|
||||
template: '<input :id="inputId" :value="modelValue" :type="type" :disabled="disabled" :aria-invalid="invalid ? \'true\' : undefined" @input="$emit(\'update:modelValue\', $event.target.value)" />',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('core shared control contracts', () => {
|
||||
it('keeps a loading button disabled and forwards its semantic type', () => {
|
||||
const wrapper = mount(KsButton, {
|
||||
props: { label: 'Save', type: 'submit', loading: true },
|
||||
global: primeVueHarness,
|
||||
})
|
||||
|
||||
const button = wrapper.get('button')
|
||||
expect(button.attributes()).toMatchObject({ type: 'submit', disabled: '' })
|
||||
expect(button.text()).toContain('…')
|
||||
})
|
||||
|
||||
it('forwards activation through the vendor-neutral click event', async () => {
|
||||
const wrapper = mount(KsButton, {
|
||||
props: { label: 'Retry' },
|
||||
global: primeVueHarness,
|
||||
})
|
||||
|
||||
await wrapper.get('button').trigger('click')
|
||||
expect(wrapper.emitted('click')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('connects text-field label, invalid state, and model updates', async () => {
|
||||
const wrapper = mount(KsTextField, {
|
||||
props: { modelValue: 'old', label: 'Name', inputId: 'name', error: 'Required', type: 'email' },
|
||||
global: { ...primeVueHarness, provide: { [uiAdapterKey as unknown as string]: nativeUiAdapter } },
|
||||
})
|
||||
|
||||
const input = wrapper.get('input')
|
||||
expect(wrapper.get('label').attributes('for')).toBe('name')
|
||||
expect(input.attributes()).toMatchObject({
|
||||
id: 'name',
|
||||
type: 'email',
|
||||
'aria-describedby': 'name-message',
|
||||
'aria-invalid': 'true',
|
||||
})
|
||||
expect(input.attributes('aria-required')).toBeUndefined()
|
||||
|
||||
await input.setValue('new@example.com')
|
||||
expect(wrapper.emitted('update:modelValue')).toEqual([['new@example.com']])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import KsDataGrid from '../KsDataGrid.vue'
|
||||
|
||||
const gridHarness = {
|
||||
stubs: {
|
||||
AgGridVue: {
|
||||
props: ['rowData', 'columnDefs', 'rowSelection', 'loading'],
|
||||
template: '<button data-test="grid" @click="$emit(\'row-clicked\', { data: rowData[0] })">select</button>',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('KsDataGrid contract', () => {
|
||||
it('preserves grid defaults and native grid configuration', () => {
|
||||
const rows = [{ id: 'row-1' }]
|
||||
const columns = [{ field: 'id', header: 'ID' }]
|
||||
const wrapper = mount(KsDataGrid, {
|
||||
props: { rows, columns },
|
||||
global: gridHarness,
|
||||
})
|
||||
|
||||
expect(wrapper.find('[data-test="grid"]').exists()).toBe(true)
|
||||
expect(wrapper.html()).toContain('grid')
|
||||
})
|
||||
|
||||
it('forwards the selected row without changing its identity', async () => {
|
||||
const row = { id: 'row-1', status: 'READY' }
|
||||
const wrapper = mount(KsDataGrid, {
|
||||
props: { rows: [row], columns: [] },
|
||||
global: gridHarness,
|
||||
})
|
||||
|
||||
await wrapper.get('[data-test="grid"]').trigger('click')
|
||||
expect(wrapper.emitted('rowSelected')).toEqual([[row]])
|
||||
})
|
||||
})
|
||||
@@ -5,7 +5,17 @@ import { nativeUiAdapter } from '../../adapter/native'
|
||||
import KsMoneyField from '../KsMoneyField.vue'
|
||||
import KsQuantityField from '../KsQuantityField.vue'
|
||||
|
||||
const globalProvide = { global: { provide: { [uiAdapterKey as unknown as string]: nativeUiAdapter } } }
|
||||
const globalProvide = {
|
||||
global: {
|
||||
provide: { [uiAdapterKey as unknown as string]: nativeUiAdapter },
|
||||
stubs: {
|
||||
InputNumber: {
|
||||
props: ['modelValue', 'inputId'],
|
||||
template: '<input :id="inputId" :value="modelValue" @input="$emit(\'update:modelValue\', Number($event.target.value))" />',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('KsMoneyField', () => {
|
||||
it('connects the label, shows the currency code, and emits numeric updates', async () => {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import KsStatusTag from '../KsStatusTag.vue'
|
||||
|
||||
describe('KsStatusTag semantic and unknown contract', () => {
|
||||
it('exposes text and non-colour cues for an unknown status', () => {
|
||||
const wrapper = mount(KsStatusTag, {
|
||||
props: { value: '상태 미등록 · VENDOR_NEW', semantic: 'warning', unknown: true },
|
||||
})
|
||||
const tag = wrapper.find('.ks-status-tag')
|
||||
expect(tag.attributes('data-semantic')).toBe('warning')
|
||||
expect(tag.attributes('aria-label')).toContain('정의되지 않음')
|
||||
expect(tag.text()).toContain('?')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user