Files
gochat/frontend/app/javascript/dashboard/components-next/captain/pageComponents/skill/SkillDialog.spec.js
T
Rogeeandrogee f50eacdc92 H-289: add Captain Skill management UI (#47)
* H-289: add Captain Skill management UI

* H-289: guard Captain Skill assistant switches

---------

Co-authored-by: Rogee <rogee@ipao.vip>
2026-08-18 15:34:26 +08:00

159 lines
4.0 KiB
JavaScript

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: '<div><slot /><slot name="footer" /></div>',
};
const Button = {
name: 'Button',
props: ['label'],
emits: ['click'],
template:
'<button type="button" @click="$emit(\'click\')">{{ label }}</button>',
};
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();
});
});