import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { useSynapseReactionsStore } from '@/stores/synapseReactions.js'
import SynapseReactionsPanel from '@/components/settings/SynapseReactionsPanel.vue'
vi.mock('@/api/index.js', () => ({
getSynapseReactions: vi.fn(),
updateSynapseReactions: vi.fn(),
getSynapseReactionVersions: vi.fn(),
}))
// No GnToastProvider in tests — swallow toast calls.
const { toastCalls } = await vi.hoisted(() => ({ toastCalls: [] }))
vi.mock('gnexus-ui-kit/vue', async (importOriginal) => {
const mod = await importOriginal()
const toast = {
show: (a) => toastCalls.push(['show', a]),
info: (a) => toastCalls.push(['info', a]),
success: (a) => toastCalls.push(['success', a]),
warning: (a) => toastCalls.push(['warning', a]),
danger: (a) => toastCalls.push(['danger', a]),
error: (a) => toastCalls.push(['error', a]),
close: (a) => toastCalls.push(['close', a]),
}
return { ...mod, useToast: () => toast }
})
import * as api from '@/api/index.js'
function makeSettings(overrides = {}) {
return {
reactions_enabled: false,
push_target: 'app',
completion_notify: 'important',
instructions: '',
source_ready: true,
...overrides,
}
}
describe('SynapseReactionsPanel', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
api.getSynapseReactions.mockResolvedValue(makeSettings())
api.getSynapseReactionVersions.mockResolvedValue({ items: [] })
api.updateSynapseReactions.mockImplementation(async (payload) => ({
...makeSettings(),
...payload,
}))
})
it('renders the toggle and radio groups from fetched settings', async () => {
const wrapper = mount(SynapseReactionsPanel, { global: { plugins: [] } })
// let onMounted fetches settle
await new Promise(r => setTimeout(r))
expect(wrapper.text()).toContain('Synapse reactions')
expect(wrapper.text()).toContain('Enable reactions')
const radios = wrapper.findAll('input[type="radio"]')
// 3 completion options + 3 push-target options
expect(radios.length).toBe(6)
})
it('shows the not-configured alert and disables Synapse targets', async () => {
api.getSynapseReactions.mockResolvedValue(
makeSettings({ source_ready: false, push_target: 'app' })
)
const wrapper = mount(SynapseReactionsPanel, { global: { plugins: [] } })
await new Promise(r => setTimeout(r))
expect(wrapper.text()).toContain('not configured')
const radios = wrapper.findAll('input[type="radio"]')
const disabled = radios.filter(r => r.attributes('disabled') !== undefined)
// the two Synapse push-target radios
expect(disabled.length).toBe(2)
})
it('saving a dirty instructions patch PUTs full settings and reloads versions', async () => {
api.getSynapseReactions.mockResolvedValue(
makeSettings({ instructions: 'first version' })
)
const wrapper = mount(SynapseReactionsPanel, { global: { plugins: [] } })
await new Promise(r => setTimeout(r))
const textarea = wrapper.find('textarea')
textarea.element.value = 'first version 2'
await textarea.trigger('input')
await new Promise(r => setTimeout(r))
const saveBtn = wrapper
.findAll('button')
.find(b => b.text().includes('Save instructions'))
expect(saveBtn.attributes('disabled')).toBeUndefined()
await saveBtn.trigger('click')
await new Promise(r => setTimeout(r))
expect(api.updateSynapseReactions).toHaveBeenCalledWith(
expect.objectContaining({ instructions: 'first version 2' })
)
// versions reloaded after the save
expect(api.getSynapseReactionVersions).toHaveBeenCalledTimes(2)
})
})