// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { flushPromises, mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { setLocale } from '@/i18n/index.js'
const getSharedChat = vi.fn()
const getSharedHistory = vi.fn()
vi.mock('@/api/index.js', () => ({
getSharedChat: (...args) => getSharedChat(...args),
getSharedHistory: (...args) => getSharedHistory(...args),
}))
import SharedChatView from '@/views/SharedChatView.vue'
const TOKEN = 'abcdefghijklmnopqrstuvwxyz012345'
// One user turn, then an assistant turn that both runs a tool carrying a live
// UI component and answers — the two things this page must handle specially:
// the component is dropped, the answer is kept.
function history() {
return [
{
role: 'user',
content: 'hello',
created_at: '2026-01-01T10:00:00Z',
metadata: { display_index: 0 },
},
{
role: 'assistant',
content: '',
created_at: '2026-01-01T10:00:05Z',
tool_calls: [{ id: 't1', name: 'ui_form', arguments: {} }],
metadata: { display_index: 1 },
},
{
role: 'tool',
tool_call_id: 't1',
content: '{}',
metadata: { ui_component: { component: 'form', payload: {} } },
},
{
role: 'assistant',
content: 'Here is the answer.',
created_at: '2026-01-01T10:00:09Z',
metadata: { display_index: 3 },
},
]
}
function payload(overrides = {}) {
return {
name: 'A shared chat',
profile_id: 'assistant',
profile_name: 'Assistant',
created_at: '2026-01-01T10:00:00Z',
last_active: '2026-01-01T10:05:00Z',
messages: history(),
has_more: false,
next_before_seq: null,
...overrides,
}
}
async function mountView() {
const wrapper = mount(SharedChatView, { props: { token: TOKEN } })
await flushPromises()
return wrapper
}
beforeEach(() => {
setActivePinia(createPinia())
setLocale('en')
getSharedChat.mockReset()
getSharedHistory.mockReset()
})
describe('SharedChatView', () => {
it('renders the chat history from the token, without an account', async () => {
getSharedChat.mockResolvedValue(payload())
const wrapper = await mountView()
expect(getSharedChat).toHaveBeenCalledWith(TOKEN)
expect(wrapper.text()).toContain('A shared chat')
expect(wrapper.text()).toContain('Assistant')
expect(wrapper.text()).toContain('hello')
expect(wrapper.text()).toContain('Here is the answer.')
wrapper.unmount()
})
it('offers no rating controls on a read-only page', async () => {
getSharedChat.mockResolvedValue(payload())
const wrapper = await mountView()
// The copy button stays (a local action); the thumbs must not.
expect(wrapper.find('.msg-copy-btn').exists()).toBe(true)
expect(wrapper.find('.msg-rate-btn').exists()).toBe(false)
wrapper.unmount()
})
it('drops interactive UI components, keeping the rest of the turn', async () => {
getSharedChat.mockResolvedValue(payload())
const wrapper = await mountView()
// The tool result still produced its tool card; only the live widget is gone.
expect(wrapper.findComponent({ name: 'UiComponentCard' }).exists()).toBe(false)
expect(wrapper.text()).toContain('Here is the answer.')
wrapper.unmount()
})
it('says the chat is gone when the token no longer resolves', async () => {
getSharedChat.mockRejectedValue(new Error('GET /shared/x → 404: not found'))
const wrapper = await mountView()
expect(wrapper.text()).toContain('This chat is no longer available')
expect(wrapper.find('.shared-scroller').exists()).toBe(false)
wrapper.unmount()
})
it('loads earlier messages above the current ones', async () => {
getSharedChat.mockResolvedValue(
payload({
messages: [
{
role: 'user',
content: 'newer message',
created_at: '2026-02-01T10:00:00Z',
metadata: { display_index: 5 },
},
],
has_more: true,
next_before_seq: 5,
})
)
getSharedHistory.mockResolvedValue({
messages: [
{
role: 'user',
content: 'older message',
created_at: '2026-01-01T10:00:00Z',
metadata: { display_index: 1 },
},
],
has_more: false,
next_before_seq: null,
})
const wrapper = await mountView()
expect(wrapper.text()).toContain('newer message')
expect(wrapper.text()).not.toContain('older message')
await wrapper.find('.shared-load-older').trigger('click')
await flushPromises()
expect(getSharedHistory).toHaveBeenCalledWith(TOKEN, 5)
expect(wrapper.text()).toContain('older message')
expect(wrapper.find('.shared-load-older').exists()).toBe(false)
wrapper.unmount()
})
})