import { beforeEach, describe, expect, it, vi } from 'vitest' import { flushPromises, mount } from '@vue/test-utils' import type { ImageResult, PromptRecordChain } from '@prompt-optimizer/core' import { createTestPinia } from '../../utils/pinia-test-helpers' import SaveTestResultExampleButton from '../../../src/components/SaveTestResultExampleButton.vue' import { useBasicSystemSession } from '../../../src/stores/session/useBasicSystemSession' import { useImageMultiImageSession } from '../../../src/stores/session/useImageMultiImageSession' const toastMock = vi.hoisted(() => ({ error: vi.fn(), success: vi.fn(), warning: vi.fn(), })) vi.mock('../../../src/composables/ui/useToast', () => ({ useToast: () => toastMock, })) vi.mock('vue-i18n', async (importOriginal) => { const actual = await importOriginal() return { ...actual, useI18n: () => ({ t: (key: string) => key, }), } }) const naiveStubs = { NButton: { name: 'NButton', template: '', props: ['disabled', 'size', 'quaternary', 'circle'], emits: ['click'], }, NIcon: { name: 'NIcon', template: '', }, NTooltip: { name: 'NTooltip', template: '', props: ['trigger'], }, } const mountButton = ( pinia: ReturnType['pinia'], handleSaveFavorite: ReturnType, props: Record, services?: unknown, ) => mount(SaveTestResultExampleButton, { props: { testId: 'save-example', ...props, }, global: { plugins: [pinia], provide: { handleSaveFavorite, ...(services ? { services } : {}), }, stubs: naiveStubs, }, }) const triggerSaveExample = async ( wrapper: ReturnType, ) => { await wrapper.get('[data-testid="save-example"]').trigger('click') await flushPromises() } const createHistoryChain = (): PromptRecordChain => { const rootRecord = { id: 'record-1', originalPrompt: 'Original prompt', optimizedPrompt: 'Optimized prompt', type: 'optimize' as const, chainId: 'chain-1', version: 1, timestamp: 1000, modelKey: 'text-model', templateId: 'template-a', } const currentRecord = { ...rootRecord, id: 'record-2', originalPrompt: 'Optimized prompt', optimizedPrompt: 'Optimized prompt v2', type: 'iterate' as const, version: 2, previousId: 'record-1', timestamp: 2000, } return { chainId: 'chain-1', rootRecord, currentRecord, versions: [rootRecord, currentRecord], } } describe('SaveTestResultExampleButton', () => { beforeEach(() => { toastMock.error.mockClear() toastMock.success.mockClear() toastMock.warning.mockClear() }) it('opens save favorite with a text test result example draft', async () => { const { pinia } = createTestPinia() const handleSaveFavorite = vi.fn() const session = useBasicSystemSession(pinia) session.prompt = 'Original {{topic}}' session.optimizedPrompt = 'Optimized {{topic}}' session.testContent = 'Input about {{topic}}' session.testVariants = [ { id: 'a', version: 'workspace', modelKey: 'text-model' }, { id: 'b', version: 'workspace', modelKey: '' }, { id: 'c', version: 'workspace', modelKey: '' }, { id: 'd', version: 'workspace', modelKey: '' }, ] session.testVariantResults = { a: { result: 'Saved output', reasoning: 'brief reasoning' }, b: { result: '', reasoning: '' }, c: { result: '', reasoning: '' }, d: { result: '', reasoning: '' }, } const wrapper = mountButton(pinia, handleSaveFavorite, { subModeKey: 'basic-system', variantId: 'a', content: 'Optimized {{topic}}', originalContent: 'Original {{topic}}', functionMode: 'basic', optimizationMode: 'system', }) await triggerSaveExample(wrapper) expect(handleSaveFavorite).toHaveBeenCalledTimes(1) const payload = handleSaveFavorite.mock.calls[0][0] expect(payload).toMatchObject({ content: 'Optimized {{topic}}', originalContent: 'Original {{topic}}', prefill: { functionMode: 'basic', optimizationMode: 'system', reproducibilityDraft: { variables: [], examples: [ { basedOnVersionId: 'implicit:basic-system:draft', text: 'Input about {{topic}}', outputText: 'Saved output', metadata: { modelKey: 'text-model', }, }, ], }, }, }) }) it('opens the save dialog with the linked favorite as the candidate target', async () => { const { pinia } = createTestPinia() const handleSaveFavorite = vi.fn() const session = useBasicSystemSession(pinia) session.origin = { kind: 'favorite', id: 'favorite-linked' } session.prompt = 'Original {{topic}}' session.optimizedPrompt = 'Optimized {{topic}}' session.testContent = 'Input about {{topic}}' session.testVariants = [ { id: 'a', version: 'workspace', modelKey: 'text-model' }, { id: 'b', version: 'workspace', modelKey: '' }, { id: 'c', version: 'workspace', modelKey: '' }, { id: 'd', version: 'workspace', modelKey: '' }, ] session.testVariantResults = { a: { result: 'Saved output', reasoning: '' }, b: { result: '', reasoning: '' }, c: { result: '', reasoning: '' }, d: { result: '', reasoning: '' }, } const wrapper = mountButton(pinia, handleSaveFavorite, { subModeKey: 'basic-system', variantId: 'a', content: 'Optimized {{topic}}', originalContent: 'Original {{topic}}', functionMode: 'basic', optimizationMode: 'system', }) await triggerSaveExample(wrapper) expect(handleSaveFavorite).toHaveBeenCalledTimes(1) expect(handleSaveFavorite.mock.calls[0][0]).toMatchObject({ candidateSource: { favoriteId: 'favorite-linked' }, content: 'Optimized {{topic}}', originalContent: 'Original {{topic}}', prefill: { reproducibilityDraft: { examples: [ { basedOnVersionId: 'implicit:basic-system:draft', text: 'Input about {{topic}}', outputText: 'Saved output', metadata: { modelKey: 'text-model', }, }, ], }, }, }) }) it('opens the save dialog instead of appending when linked favorite content differs', async () => { const { pinia } = createTestPinia() const handleSaveFavorite = vi.fn() const session = useBasicSystemSession(pinia) session.origin = { kind: 'favorite', id: 'favorite-linked' } session.optimizedPrompt = 'Edited workspace {{topic}}' session.testContent = 'Input about {{topic}}' session.testVariants = [ { id: 'a', version: 'workspace', modelKey: 'text-model' }, { id: 'b', version: 'workspace', modelKey: '' }, { id: 'c', version: 'workspace', modelKey: '' }, { id: 'd', version: 'workspace', modelKey: '' }, ] session.testVariantResults = { a: { result: 'Saved output', reasoning: '' }, b: { result: '', reasoning: '' }, c: { result: '', reasoning: '' }, d: { result: '', reasoning: '' }, } const wrapper = mountButton(pinia, handleSaveFavorite, { subModeKey: 'basic-system', variantId: 'a', content: 'Edited workspace {{topic}}', functionMode: 'basic', optimizationMode: 'system', }) await triggerSaveExample(wrapper) expect(toastMock.warning).not.toHaveBeenCalled() expect(handleSaveFavorite).toHaveBeenCalledTimes(1) expect(handleSaveFavorite.mock.calls[0][0]).toMatchObject({ content: 'Edited workspace {{topic}}', prefill: { reproducibilityDraft: { examples: [ { text: 'Input about {{topic}}', outputText: 'Saved output', }, ], }, }, }) }) it('does not implicitly update a linked favorite when saving a test example', async () => { const { pinia } = createTestPinia() const handleSaveFavorite = vi.fn() const session = useBasicSystemSession(pinia) session.origin = { kind: 'favorite', id: 'favorite-linked' } session.optimizedPrompt = 'Optimized prompt' session.testContent = 'Input' session.testVariants = [ { id: 'a', version: 'workspace', modelKey: 'text-model' }, { id: 'b', version: 'workspace', modelKey: '' }, { id: 'c', version: 'workspace', modelKey: '' }, { id: 'd', version: 'workspace', modelKey: '' }, ] session.testVariantResults = { a: { result: 'Saved output', reasoning: '' }, b: { result: '', reasoning: '' }, c: { result: '', reasoning: '' }, d: { result: '', reasoning: '' }, } const wrapper = mountButton(pinia, handleSaveFavorite, { subModeKey: 'basic-system', variantId: 'a', content: 'Optimized prompt', functionMode: 'basic', optimizationMode: 'system', }) await triggerSaveExample(wrapper) expect(toastMock.error).not.toHaveBeenCalled() expect(handleSaveFavorite).toHaveBeenCalledTimes(1) expect(handleSaveFavorite.mock.calls[0][0].prefill.reproducibilityDraft.examples[0]).toMatchObject({ outputText: 'Saved output', }) }) it('preserves image input and output refs when saving a multiimage result example', async () => { const { pinia } = createTestPinia() const handleSaveFavorite = vi.fn() const session = useImageMultiImageSession(pinia) session.originalPrompt = 'Generate {{scene}}' session.optimizedPrompt = 'Generate vivid {{scene}}' session.assetBinding = { assetId: 'asset-image', versionId: 'asset-version-image', status: 'linked' } session.origin = { kind: 'favorite', id: 'favorite-image' } session.temporaryVariables = { scene: 'city' } session.inputImages = [ { id: 'runtime-1', assetId: null, b64: 'INPUT_B64', mimeType: 'image/png', }, ] session.testVariants = [ { id: 'a', version: 'workspace', modelKey: 'image-model' }, { id: 'b', version: 'workspace', modelKey: '' }, { id: 'c', version: 'workspace', modelKey: '' }, { id: 'd', version: 'workspace', modelKey: '' }, ] session.testVariantResults = { a: { images: [ { _type: 'image-ref', id: 'output-asset' }, { b64: 'OUTPUT_B64', mimeType: 'image/jpeg' }, ], text: 'Image output note', metadata: { providerId: 'provider', modelId: 'model', configId: 'image-model', }, } as unknown as ImageResult, b: null, c: null, d: null, } const wrapper = mountButton(pinia, handleSaveFavorite, { subModeKey: 'image-multiimage', variantId: 'a', content: 'Generate vivid {{scene}}', originalContent: 'Generate {{scene}}', functionMode: 'image', imageSubMode: 'multiimage', }) await triggerSaveExample(wrapper) expect(handleSaveFavorite).toHaveBeenCalledTimes(1) const draft = handleSaveFavorite.mock.calls[0][0].prefill.reproducibilityDraft expect(draft.variables).toEqual([ { name: 'scene', required: false, options: [], source: 'test-run', }, ]) expect(draft.examples[0]).toMatchObject({ basedOnVersionId: 'implicit:image-multiimage:draft', parameters: { scene: 'city' }, outputText: 'Image output note', images: ['data:image/jpeg;base64,OUTPUT_B64'], inputImages: ['data:image/png;base64,INPUT_B64'], imageAssetIds: ['output-asset'], source: { kind: 'workspace', id: 'implicit:image-multiimage', metadata: { assetBinding: { assetId: 'asset-image', versionId: 'asset-version-image', status: 'linked', }, origin: { kind: 'favorite', id: 'favorite-image', }, }, }, metadata: { modelKey: 'image-model', }, }) }) it('resolves based-on ids for root, record, and workspace draft test runs', async () => { const { pinia } = createTestPinia() const handleSaveFavorite = vi.fn() const session = useBasicSystemSession(pinia) session.prompt = 'Original prompt' session.optimizedPrompt = 'Optimized prompt' session.chainId = 'chain-1' session.versionId = 'record-current' session.testContent = 'Input' session.testVariants = [ { id: 'a', version: 0, modelKey: 'text-model' }, { id: 'b', version: 2, modelKey: 'text-model' }, { id: 'c', version: 'workspace', modelKey: 'text-model' }, { id: 'd', version: 'workspace', modelKey: '' }, ] session.testVariantResults = { a: { result: 'Root output', reasoning: '' }, b: { result: 'Record output', reasoning: '' }, c: { result: 'Workspace output', reasoning: '' }, d: { result: '', reasoning: '' }, } for (const variantId of ['a', 'b', 'c']) { const wrapper = mountButton(pinia, handleSaveFavorite, { subModeKey: 'basic-system', variantId, content: 'Optimized prompt', functionMode: 'basic', optimizationMode: 'system', }) await triggerSaveExample(wrapper) wrapper.unmount() } expect(handleSaveFavorite.mock.calls.map((call) => call[0].prefill.reproducibilityDraft.examples[0].basedOnVersionId, )).toEqual([ 'chain-1:root', 'legacy-version:2', 'implicit:basic-system:draft', ]) }) it('uses hydrated history ids for root, previous, and numeric record test runs when available', async () => { const historyManager = { getChain: vi.fn(async () => createHistoryChain()), } const { pinia } = createTestPinia({ historyManager: historyManager as any, }) const handleSaveFavorite = vi.fn() const session = useBasicSystemSession(pinia) session.prompt = 'Original prompt' session.optimizedPrompt = 'Optimized prompt' session.chainId = 'chain-1' session.assetBinding = { assetId: 'asset-text', versionId: 'asset-version-text', status: 'linked' } session.origin = { kind: 'favorite', id: 'favorite-text' } session.testContent = 'Input' session.testVariants = [ { id: 'a', version: 0, modelKey: 'text-model' }, { id: 'b', version: 2, modelKey: 'text-model' }, { id: 'c', version: 'workspace', modelKey: 'text-model' }, { id: 'd', version: 'previous', modelKey: 'text-model' }, ] session.testVariantResults = { a: { result: 'Root output', reasoning: '' }, b: { result: 'Record output', reasoning: '' }, c: { result: 'Workspace output', reasoning: '' }, d: { result: 'Previous output', reasoning: '' }, } for (const variantId of ['a', 'b', 'd', 'c']) { const wrapper = mountButton(pinia, handleSaveFavorite, { subModeKey: 'basic-system', variantId, content: 'Optimized prompt', functionMode: 'basic', optimizationMode: 'system', }) await triggerSaveExample(wrapper) wrapper.unmount() } expect(historyManager.getChain).toHaveBeenCalledWith('chain-1') const savedExamples = handleSaveFavorite.mock.calls.map((call) => call[0].prefill.reproducibilityDraft.examples[0], ) expect(savedExamples.map((example) => example.basedOnVersionId)).toEqual([ 'chain-1:root', 'record-2', 'record-1', 'implicit:basic-system:draft', ]) expect(savedExamples[3].source).toMatchObject({ kind: 'workspace', id: 'implicit:basic-system', metadata: { chainId: 'chain-1', assetBinding: { assetId: 'asset-text', versionId: 'asset-version-text', status: 'linked', }, origin: { kind: 'favorite', id: 'favorite-text', }, }, }) }) it('falls back to the synchronous projection when history hydration fails', async () => { const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) const historyManager = { getChain: vi.fn(async () => { throw new Error('history unavailable') }), } const { pinia } = createTestPinia({ historyManager: historyManager as any, }) const handleSaveFavorite = vi.fn() const session = useBasicSystemSession(pinia) session.prompt = 'Original prompt' session.optimizedPrompt = 'Optimized prompt' session.chainId = 'chain-1' session.testContent = 'Input' session.testVariants = [ { id: 'a', version: 2, modelKey: 'text-model' }, { id: 'b', version: 'workspace', modelKey: '' }, { id: 'c', version: 'workspace', modelKey: '' }, { id: 'd', version: 'workspace', modelKey: '' }, ] session.testVariantResults = { a: { result: 'Record output', reasoning: '' }, b: { result: '', reasoning: '' }, c: { result: '', reasoning: '' }, d: { result: '', reasoning: '' }, } const wrapper = mountButton(pinia, handleSaveFavorite, { subModeKey: 'basic-system', variantId: 'a', content: 'Optimized prompt', functionMode: 'basic', optimizationMode: 'system', }) await triggerSaveExample(wrapper) expect(handleSaveFavorite).toHaveBeenCalledTimes(1) expect(handleSaveFavorite.mock.calls[0][0].prefill.reproducibilityDraft.examples[0].basedOnVersionId) .toBe('legacy-version:2') consoleWarnSpy.mockRestore() }) it('ignores duplicate clicks while the hydrated session is loading', async () => { let resolveChain: (chain: PromptRecordChain) => void = () => {} const pendingChain = new Promise((resolve) => { resolveChain = resolve }) const historyManager = { getChain: vi.fn(() => pendingChain), } const { pinia } = createTestPinia({ historyManager: historyManager as any, }) const handleSaveFavorite = vi.fn() const session = useBasicSystemSession(pinia) session.prompt = 'Original prompt' session.optimizedPrompt = 'Optimized prompt' session.chainId = 'chain-1' session.testContent = 'Input' session.testVariants = [ { id: 'a', version: 2, modelKey: 'text-model' }, { id: 'b', version: 'workspace', modelKey: '' }, { id: 'c', version: 'workspace', modelKey: '' }, { id: 'd', version: 'workspace', modelKey: '' }, ] session.testVariantResults = { a: { result: 'Record output', reasoning: '' }, b: { result: '', reasoning: '' }, c: { result: '', reasoning: '' }, d: { result: '', reasoning: '' }, } const wrapper = mountButton(pinia, handleSaveFavorite, { subModeKey: 'basic-system', variantId: 'a', content: 'Optimized prompt', functionMode: 'basic', optimizationMode: 'system', }) const button = wrapper.get('[data-testid="save-example"]') await button.trigger('click') expect(button.attributes('disabled')).toBeDefined() await button.trigger('click') expect(historyManager.getChain).toHaveBeenCalledTimes(1) expect(handleSaveFavorite).not.toHaveBeenCalled() resolveChain(createHistoryChain()) await flushPromises() expect(handleSaveFavorite).toHaveBeenCalledTimes(1) }) it('warns instead of opening the save dialog when the selected variant has no result', async () => { const { pinia } = createTestPinia() const handleSaveFavorite = vi.fn() const session = useBasicSystemSession(pinia) session.optimizedPrompt = 'Optimized prompt' session.testContent = 'Input' session.testVariants = [ { id: 'a', version: 'workspace', modelKey: 'text-model' }, { id: 'b', version: 'workspace', modelKey: '' }, { id: 'c', version: 'workspace', modelKey: '' }, { id: 'd', version: 'workspace', modelKey: '' }, ] session.testVariantResults = { a: { result: '', reasoning: '' }, b: { result: '', reasoning: '' }, c: { result: '', reasoning: '' }, d: { result: '', reasoning: '' }, } const wrapper = mountButton(pinia, handleSaveFavorite, { subModeKey: 'basic-system', variantId: 'a', content: 'Optimized prompt', functionMode: 'basic', optimizationMode: 'system', }) await triggerSaveExample(wrapper) expect(handleSaveFavorite).not.toHaveBeenCalled() expect(toastMock.warning).toHaveBeenCalledWith('favorites.dialog.reproducibility.noTestResultToSave') }) })