* fix(HH-598): hide unavailable AI takeover * fix: isolate inbox assistant availability --------- Co-authored-by: Rogee <rogee@ipao.vip>
199 lines
6.0 KiB
JavaScript
199 lines
6.0 KiB
JavaScript
import axios from 'axios';
|
|
import { flushPromises, mount } from '@vue/test-utils';
|
|
import { createStore } from 'vuex';
|
|
import { ref } from 'vue';
|
|
|
|
import conversationActions from 'dashboard/store/modules/conversations/actions';
|
|
import types from 'dashboard/store/mutation-types';
|
|
import CopilotMenuBar from './CopilotMenuBar.vue';
|
|
import ReplyTopPanel from './ReplyTopPanel.vue';
|
|
|
|
vi.mock('axios');
|
|
|
|
vi.mock('dashboard/composables/useCaptain', () => ({
|
|
useCaptain: () => ({ captainTasksEnabled: ref(true) }),
|
|
}));
|
|
|
|
vi.mock('dashboard/composables/useKeyboardEvents', () => ({
|
|
useKeyboardEvents: vi.fn(),
|
|
}));
|
|
|
|
const createWrapper = ({
|
|
aiTakeoverActive = false,
|
|
inboxAssistant = { id: 7 },
|
|
useRealAssistantAction = false,
|
|
props = {},
|
|
} = {}) => {
|
|
const startAITakeover = vi.fn();
|
|
const exitAITakeover = vi.fn();
|
|
const getInboxCaptainAssistantById = vi.fn();
|
|
const store = createStore({
|
|
state: {
|
|
currentChat: { id: 42, ai_takeover_active: aiTakeoverActive },
|
|
copilotAssistant: inboxAssistant,
|
|
},
|
|
getters: {
|
|
getSelectedChat: state => state.currentChat,
|
|
getCopilotAssistant: state => state.copilotAssistant,
|
|
},
|
|
mutations: {
|
|
[types.SET_INBOX_CAPTAIN_ASSISTANT](state, data) {
|
|
state.copilotAssistant = data.assistant;
|
|
},
|
|
},
|
|
actions: {
|
|
startAITakeover,
|
|
exitAITakeover,
|
|
getInboxCaptainAssistantById: useRealAssistantAction
|
|
? conversationActions.getInboxCaptainAssistantById
|
|
: getInboxCaptainAssistantById,
|
|
},
|
|
modules: {
|
|
draftMessages: {
|
|
namespaced: true,
|
|
getters: { getReplyEditorMode: () => 'REPLY' },
|
|
},
|
|
},
|
|
});
|
|
|
|
return {
|
|
store,
|
|
exitAITakeover,
|
|
startAITakeover,
|
|
getInboxCaptainAssistantById,
|
|
wrapper: mount(ReplyTopPanel, {
|
|
props: { conversationId: 42, ...props },
|
|
global: {
|
|
plugins: [store],
|
|
stubs: { NextButton: false },
|
|
},
|
|
}),
|
|
};
|
|
};
|
|
|
|
describe('ReplyTopPanel', () => {
|
|
it('shows AI takeover and flat Copilot actions in the same action row', () => {
|
|
const { wrapper } = createWrapper();
|
|
const actions = wrapper.get('[data-testid="reply-top-panel-actions"]');
|
|
const copilotActions = actions.getComponent(CopilotMenuBar);
|
|
|
|
expect(copilotActions.props('inline')).toBe(true);
|
|
expect(actions.text()).toContain('AI takeover');
|
|
expect(actions.get('[data-testid="copilot-inline-actions"]')).toBeTruthy();
|
|
});
|
|
|
|
it('wraps all eight primary actions on narrow screens when content is present', () => {
|
|
const { wrapper } = createWrapper({ props: { hasContent: true } });
|
|
const actions = wrapper.get('[data-testid="reply-top-panel-actions"]');
|
|
const copilotActions = actions.get(
|
|
'[data-testid="copilot-inline-actions"]'
|
|
);
|
|
|
|
expect(wrapper.classes()).toEqual(
|
|
expect.arrayContaining(['flex-wrap', 'sm:flex-nowrap'])
|
|
);
|
|
expect(actions.classes()).toEqual(
|
|
expect.arrayContaining([
|
|
'w-full',
|
|
'flex-wrap',
|
|
'sm:w-auto',
|
|
'sm:flex-nowrap',
|
|
])
|
|
);
|
|
expect(actions.findAll(':scope > button')).toHaveLength(2);
|
|
expect(copilotActions.findAll(':scope > div > button')).toHaveLength(6);
|
|
});
|
|
|
|
it('keeps AI takeover behavior in the new action row', async () => {
|
|
const { startAITakeover, wrapper } = createWrapper();
|
|
const takeoverButton = wrapper
|
|
.get('[data-testid="reply-top-panel-actions"]')
|
|
.findAll('button')
|
|
.find(button => button.text() === 'AI takeover');
|
|
|
|
await takeoverButton.trigger('click');
|
|
|
|
expect(startAITakeover).toHaveBeenCalledWith(expect.any(Object), 42);
|
|
});
|
|
|
|
it('hides AI takeover when the inbox has no AI configured', () => {
|
|
const { getInboxCaptainAssistantById, startAITakeover, wrapper } =
|
|
createWrapper({ inboxAssistant: null });
|
|
|
|
expect(getInboxCaptainAssistantById).toHaveBeenCalledWith(
|
|
expect.any(Object),
|
|
42
|
|
);
|
|
expect(wrapper.text()).not.toContain('AI takeover');
|
|
expect(startAITakeover).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('updates after the inbox assistant API returns null', async () => {
|
|
axios.get.mockResolvedValue({ data: { assistant: null } });
|
|
const { store, wrapper } = createWrapper({
|
|
useRealAssistantAction: true,
|
|
});
|
|
|
|
await flushPromises();
|
|
|
|
expect(store.getters.getCopilotAssistant).toBeNull();
|
|
expect(wrapper.text()).not.toContain('AI takeover');
|
|
});
|
|
|
|
it('keeps the exit action when takeover is active without an assistant', async () => {
|
|
const { exitAITakeover, wrapper } = createWrapper({
|
|
aiTakeoverActive: true,
|
|
inboxAssistant: null,
|
|
});
|
|
const exitButton = wrapper
|
|
.get('[data-testid="reply-top-panel-actions"]')
|
|
.findAll('button')
|
|
.find(button => button.text() === 'Exit AI takeover');
|
|
|
|
await exitButton.trigger('click');
|
|
|
|
expect(exitAITakeover).toHaveBeenCalledWith(expect.any(Object), 42);
|
|
});
|
|
|
|
it('renders Copilot actions inline and forwards the selected action', async () => {
|
|
const store = createStore({
|
|
modules: {
|
|
draftMessages: {
|
|
namespaced: true,
|
|
getters: { getReplyEditorMode: () => 'REPLY' },
|
|
},
|
|
},
|
|
});
|
|
const wrapper = mount(CopilotMenuBar, {
|
|
props: { inline: true, conversationId: 42 },
|
|
global: {
|
|
plugins: [store],
|
|
stubs: { NextButton: false },
|
|
},
|
|
});
|
|
const suggestionButton = wrapper.get(
|
|
'[data-testid="copilot-inline-actions"] button[aria-label="Suggest a reply"]'
|
|
);
|
|
|
|
await suggestionButton.trigger('click');
|
|
|
|
expect(wrapper.emitted('executeCopilotAction')).toEqual([
|
|
['reply_suggestion'],
|
|
]);
|
|
});
|
|
|
|
it('disables every flat Copilot action with the editor', () => {
|
|
const { wrapper } = createWrapper({
|
|
props: { hasContent: true, isEditorDisabled: true },
|
|
});
|
|
const copilotButtons = wrapper
|
|
.get('[data-testid="copilot-inline-actions"]')
|
|
.findAll('button');
|
|
|
|
expect(copilotButtons.length).toBeGreaterThan(0);
|
|
expect(
|
|
copilotButtons.every(button => button.attributes('disabled') === '')
|
|
).toBe(true);
|
|
});
|
|
});
|