// @vitest-environment jsdom import { beforeEach, describe, expect, it, vi } from 'vitest' import { flushPromises, mount } from '@vue/test-utils' import ArcoVue from '@arco-design/web-vue' const mockSettingsStore = vi.hoisted(() => ({ sessionReset: { mode: 'both', idle_minutes: 60, at_hour: 0 }, approvals: { mode: 'manual' }, memory: { write_approval: false }, skills: { write_approval: true }, updateLocal: vi.fn((section: string, values: Record) => { Object.assign((mockSettingsStore as any)[section], values) }), saveSection: vi.fn(() => Promise.resolve()), })) const mockPrefsStore = vi.hoisted(() => ({ humanOnly: true, setHumanOnly: vi.fn((value: boolean) => { mockPrefsStore.humanOnly = value }), })) vi.mock('@/stores/hermes/settings', () => ({ useSettingsStore: () => mockSettingsStore, })) vi.mock('@/stores/hermes/session-browser-prefs', () => ({ useSessionBrowserPrefsStore: () => mockPrefsStore, })) vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key, }), })) vi.mock('@/composables/useMessage', () => ({ useMessage: () => ({ success: vi.fn(), error: vi.fn(), }), })) import SessionSettings from '@/components/hermes/settings/SessionSettings.vue' const SettingRowStub = { props: ['label', 'hint'], template: '
{{ label }}
', } // `app.use(ArcoVue)` registers the components globally, so test-utils stubs only // match Arco's internal component names, not the kebab-case template tags. const arcoGlobal = { plugins: [ArcoVue], stubs: { SettingRow: SettingRowStub, Select: true, InputNumber: true, }, } /** The Arco switch rendered inside the SettingRow carrying `label`. */ function switchByLabel(wrapper: any, label: string) { const row = wrapper .findAll('.setting-row') .find((node: any) => node.get('.setting-row-label').text() === label) if (!row) throw new Error(`no SettingRow labelled "${label}"`) return row.get('.arco-switch') } describe('SessionSettings', () => { beforeEach(() => { vi.clearAllMocks() mockPrefsStore.humanOnly = true mockSettingsStore.memory.write_approval = false mockSettingsStore.skills.write_approval = true }) it('surfaces the human-only preference in the Session tab', async () => { const wrapper = mount(SessionSettings, { global: arcoGlobal }) await flushPromises() expect(wrapper.text()).toContain('settings.session.liveMonitorHumanOnly') // requireAuth + memory + skills write approval + human-only expect(wrapper.findAll('.arco-switch').length).toBe(4) const humanOnlyToggle = switchByLabel(wrapper, 'settings.session.liveMonitorHumanOnly') expect(humanOnlyToggle.attributes('aria-checked')).toBe('true') await humanOnlyToggle.trigger('click') await Promise.resolve() expect(mockPrefsStore.setHumanOnly).toHaveBeenCalledWith(false) }) it('saves write approval toggles to memory and skills config sections', async () => { const wrapper = mount(SessionSettings, { global: arcoGlobal }) await flushPromises() const memoryToggle = switchByLabel(wrapper, 'settings.session.memoryWriteApproval') const skillsToggle = switchByLabel(wrapper, 'settings.session.skillsWriteApproval') expect(memoryToggle.attributes('aria-checked')).toBe('false') expect(skillsToggle.attributes('aria-checked')).toBe('true') await memoryToggle.trigger('click') await Promise.resolve() await skillsToggle.trigger('click') await Promise.resolve() expect(mockSettingsStore.updateLocal).toHaveBeenCalledWith('memory', { write_approval: true }) expect(mockSettingsStore.saveSection).toHaveBeenCalledWith('memory', { write_approval: true }) expect(mockSettingsStore.updateLocal).toHaveBeenCalledWith('skills', { write_approval: false }) expect(mockSettingsStore.saveSection).toHaveBeenCalledWith('skills', { write_approval: false }) }) it('shows write approval toggles without probing Hermes Agent support', async () => { const wrapper = mount(SessionSettings, { global: arcoGlobal }) await flushPromises() expect(wrapper.text()).toContain('settings.session.memoryWriteApproval') expect(wrapper.text()).toContain('settings.session.skillsWriteApproval') }) })