import { shallowMount } from '@vue/test-utils'; import SkillDialog from './SkillDialog.vue'; vi.mock('dashboard/composables', () => ({ useAlert: vi.fn() })); const mockedStore = vi.hoisted(() => ({ dispatch: vi.fn() })); vi.mock('dashboard/composables/store', () => ({ useStore: () => mockedStore, })); const Dialog = { name: 'Dialog', emits: ['close'], methods: { open() {}, close() { this.$emit('close'); }, }, template: '
', }; const Button = { name: 'Button', props: ['label'], emits: ['click'], template: '', }; const mountDialog = ({ skill = null, dispatch = vi.fn() } = {}) => { mockedStore.dispatch = dispatch; return shallowMount(SkillDialog, { props: { skill, assistantId: 9 }, global: { stubs: { Dialog, Button, Input: true, TextArea: true, Editor: true, }, }, }); }; const detail = { id: 3, name: 'Refunds', description: 'Handle refund requests', instructions_md: 'Ask for the order number.', status: 'active', version: 4, bound_assistant_count: 2, references: [ { reference_key: 'refund-policy', content_md: 'Thirty days.', position: 0, }, ], }; describe('SkillDialog', () => { beforeEach(() => { vi.restoreAllMocks(); }); it('creates a validated draft with the management API shape', async () => { const dispatch = vi.fn().mockResolvedValue({ id: 5 }); const wrapper = mountDialog({ dispatch }); Object.assign(wrapper.vm.form, { name: ' Refunds ', description: ' Handle refunds ', instructions_md: 'Use the policy.', }); await wrapper.vm.submit('draft'); expect(dispatch).toHaveBeenCalledWith('captainSkills/create', { name: 'Refunds', description: 'Handle refunds', instructions_md: 'Use the policy.', status: 'draft', references: [], }); expect(wrapper.emitted('saved')).toHaveLength(1); }); it('publishes then binds to only the current assistant', async () => { let resolveCreate; const dispatch = vi.fn().mockReturnValueOnce( new Promise(resolve => { resolveCreate = resolve; }) ); const wrapper = mountDialog({ dispatch }); Object.assign(wrapper.vm.form, { name: 'Refunds', description: 'Handle refunds', instructions_md: 'Use the policy.', }); const publish = wrapper.vm.submit('active', true); await wrapper.setProps({ assistantId: 10 }); resolveCreate({ id: 5 }); await publish; expect(dispatch).toHaveBeenLastCalledWith('captainSkills/bind', { assistantId: 9, skillId: 5, }); }); it('warns before changing an active skill used by assistants', async () => { const dispatch = vi.fn(); vi.spyOn(window, 'confirm').mockReturnValue(false); const wrapper = mountDialog({ skill: detail, dispatch }); await wrapper.vm.submit('active'); expect(window.confirm).toHaveBeenCalledWith( expect.stringContaining('2 assistants') ); expect(dispatch).not.toHaveBeenCalled(); }); it('refreshes the saved skill when binding fails instead of creating a duplicate', async () => { const dispatch = vi .fn() .mockResolvedValueOnce({ id: 5 }) .mockRejectedValueOnce({ response: { status: 409 } }); const wrapper = mountDialog({ dispatch }); Object.assign(wrapper.vm.form, { name: 'Refunds', description: 'Handle refunds', instructions_md: 'Use the policy.', }); await wrapper.vm.submit('active', true); expect(wrapper.emitted('saved')).toHaveLength(1); expect(dispatch).toHaveBeenCalledTimes(2); }); it('keeps form input and reports a concurrent update', async () => { const dispatch = vi.fn().mockRejectedValue({ response: { status: 409, data: { error: 'version conflict' } }, }); vi.spyOn(window, 'confirm').mockReturnValue(true); const wrapper = mountDialog({ skill: detail, dispatch }); await wrapper.vm.submit('active'); expect(wrapper.vm.errorMessage).toContain('version conflict'); expect(wrapper.vm.form.name).toBe('Refunds'); expect(wrapper.emitted('saved')).toBeUndefined(); }); });