Files
hermes-ui/tests/client/session-settings.test.ts
toor 836d0228d2 Phase 007: migrate every remaining view from naive-ui to Arco Design Vue
The project now has exactly one UI library. Every naive-ui component in
packages/client/src is replaced by its Arco equivalent across 24 views and
roughly 80 components, and naive-ui is dropped from package.json along with
its transitive vueuc / @css-render / vdirs / seemly dependencies.

The migration is rendering-layer only. No store logic, no API contract, and
no /chat-run payload was touched.

Three classes of leftover were invisible to `grep naive-ui` and are now
covered by tests/client/no-naive-ui.test.ts, which asserts all three:
  - dead .n-* class names in style blocks
  - dead --n-* custom properties
  - naive-ui in package.json

Notable behavioural traps handled along the way, recorded in
docs/arco-migration-rules.md:
  - a-input-password defaults to masked; passing default-visibility="false"
    would have exposed API keys in clear text
  - a-upload fires a real XHR unless auto-upload is false
  - nested Trigger components fight over the first element child, so the
    group-chat settings dropdown was flattened
  - Arco renders tooltip default-slot text inline, which was silently
    collapsing the chat and group-chat toolbars
2026-10-18 22:57:25 +08:00

126 lines
4.2 KiB
TypeScript

// @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<string, any>) => {
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: '<div class="setting-row"><div class="setting-row-label">{{ label }}</div><slot /></div>',
}
// `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')
})
})