import { beforeEach, describe, expect, it, vi } from 'vitest'; import { mount } from '@vue/test-utils'; vi.mock('@inertiajs/vue3', async () => import('../../stubs/inertia.js')); import Index from '../../../../resources/js/Pages/Prompts/Index.vue'; import { inertiaMock, resetInertiaMock } from '../../stubs/inertia.js'; const promptFixture = (id, name, extra = {}) => ({ id, name, description: null, tags: [{ id: 1, name: 'chat' }], is_favorite: false, usage_count: 0, last_used_at: null, ...extra, }); const baseProps = () => ({ prompts: { data: [ promptFixture(1, 'Greeter', { description: 'Says hi', is_favorite: true, usage_count: 5, last_used_at: '2026-10-01T09:30:00.000000Z', }), promptFixture(2, 'Summarizer'), ], current_page: 1, last_page: 1, total: 2, }, tags: ['chat', 'code', 'writing'], filters: { search: '', tag: null, favorites: 0 }, sort: 'created_at', direction: 'desc', }); const last = () => inertiaMock.routerCalls[inertiaMock.routerCalls.length - 1]; describe('Pages/Prompts/Index', () => { beforeEach(() => { resetInertiaMock(); }); it('renders rows with name, description, tags, usage, and last-used date', () => { const wrapper = mount(Index, { props: baseProps() }); expect(wrapper.text()).toContain('2 prompt(s) in your vault'); const firstRow = wrapper.findAll('tbody tr')[0]; expect(firstRow.find('a[href="/prompts/1"]').text()).toBe('Greeter'); expect(firstRow.text()).toContain('Says hi'); expect(firstRow.text()).toContain('5×'); expect(firstRow.text()).toContain('chat'); expect(firstRow.text()).toMatch(/Oct\s+1,?\s+2026/); expect(firstRow.find('a[href="/prompts/1/edit"]').exists()).toBe(true); const secondRow = wrapper.findAll('tbody tr')[1]; expect(secondRow.text()).toContain('Summarizer'); expect(secondRow.text()).not.toContain('Says hi'); expect(secondRow.text()).toContain('—'); }); it('shows the empty state with a create link when there are no rows', () => { const props = { ...baseProps(), prompts: { data: [], current_page: 1, last_page: 1, total: 0 }, }; const wrapper = mount(Index, { props }); expect(wrapper.text()).toContain('No prompts match.'); expect(wrapper.find('a[href="/prompts/create"]').exists()).toBe(true); expect(wrapper.findAll('tbody tr')).toHaveLength(1); }); it('marks favorite rows with an Unfavorite star and posts the toggle', async () => { const wrapper = mount(Index, { props: baseProps() }); expect(wrapper.find('button[aria-label="Favorite"]').exists()).toBe(true); await wrapper.find('button[aria-label="Unfavorite"]').trigger('click'); const call = last(); expect(call.method).toBe('post'); expect(call.url).toBe('/prompts/1/toggle-favorite'); }); it('submits the search form into a GET refresh', async () => { const wrapper = mount(Index, { props: baseProps() }); await wrapper.find('input[type="search"]').setValue('ada'); await wrapper.find('form').trigger('submit'); const call = last(); expect(call.method).toBe('get'); expect(call.url).toBe('/prompts'); expect(call.data.search).toBe('ada'); expect(call.data.sort).toBe('created_at'); expect(call.data.direction).toBe('desc'); }); it('refreshes with the selected tag', async () => { const wrapper = mount(Index, { props: baseProps() }); await wrapper.findAll('select')[0].setValue('code'); expect(last().data.tag).toBe('code'); }); it('toggles the favorites filter on and off', async () => { const wrapper = mount(Index, { props: baseProps() }); const checkbox = wrapper.find('input[type="checkbox"]'); await checkbox.setValue(true); expect(last().data.favorites).toBe('1'); await checkbox.setValue(false); expect(last().data.favorites).toBeUndefined(); }); it('refreshes with sort and direction changes', async () => { const wrapper = mount(Index, { props: baseProps() }); const selects = wrapper.findAll('select'); await selects[1].setValue('usage_count'); expect(last().data.sort).toBe('usage_count'); await selects[2].setValue('asc'); expect(last().data.direction).toBe('asc'); }); it('duplicates a row via router.post', async () => { const wrapper = mount(Index, { props: baseProps() }); await wrapper.findAll('button').find((b) => b.text() === 'Duplicate').trigger('click'); const call = last(); expect(call.method).toBe('post'); expect(call.url).toBe('/prompts/1/duplicate'); }); it('asks for confirmation, then deletes and refreshes', async () => { const wrapper = mount(Index, { props: baseProps() }); await wrapper.findAll('button').find((b) => b.text() === 'Delete').trigger('click'); const dialog = wrapper.find('[role="dialog"]'); expect(dialog.text()).toContain('Delete prompt?'); expect(dialog.text()).toContain('Greeter'); expect(dialog.text()).toContain('will be permanently deleted'); await dialog.findAll('button').find((b) => b.text() === 'Delete').trigger('click'); const call = last(); expect(call.method).toBe('delete'); expect(call.url).toBe('/prompts/1'); expect(wrapper.find('[role="dialog"]').exists()).toBe(false); call.data.onFinish(); expect(last().method).toBe('get'); expect(last().url).toBe('/prompts'); }); it('safely no-ops confirmDelete when no prompt is targeted', () => { const wrapper = mount(Index, { props: baseProps() }); expect(() => wrapper.vm.confirmDelete()).not.toThrow(); expect(inertiaMock.routerCalls.filter((c) => c.method === 'delete')).toHaveLength(0); }); it('cancels the delete dialog without deleting', async () => { const wrapper = mount(Index, { props: baseProps() }); await wrapper.findAll('button').find((b) => b.text() === 'Delete').trigger('click'); await wrapper .find('[role="dialog"]') .findAll('button') .find((b) => b.text() === 'Cancel') .trigger('click'); expect(wrapper.find('[role="dialog"]').exists()).toBe(false); expect(inertiaMock.routerCalls.filter((c) => c.method === 'delete')).toHaveLength(0); }); it('navigates pages when there is more than one page', async () => { const props = { ...baseProps(), prompts: { ...baseProps().prompts, current_page: 2, last_page: 3 }, }; const wrapper = mount(Index, { props }); expect(wrapper.text()).toContain('Page 2 of 3'); const prev = wrapper.findAll('button').find((b) => b.text().includes('Prev')); const next = wrapper.findAll('button').find((b) => b.text().includes('Next')); expect(prev.attributes('disabled')).toBeUndefined(); expect(next.attributes('disabled')).toBeUndefined(); await prev.trigger('click'); expect(last().data.page).toBe(1); await next.trigger('click'); expect(last().data.page).toBe(3); }); it('disables prev/next at the page boundaries', () => { const paged = (current_page) => ({ ...baseProps(), prompts: { ...baseProps().prompts, current_page, last_page: 3 }, }); const onFirst = mount(Index, { props: paged(1) }); const onLast = mount(Index, { props: paged(3) }); const prev = onFirst.findAll('button').find((b) => b.text().includes('Prev')); expect(prev.attributes('disabled')).toBeDefined(); const next = onLast.findAll('button').find((b) => b.text().includes('Next')); expect(next.attributes('disabled')).toBeDefined(); }); });