import { flushPromises, mount } from '@vue/test-utils' import { afterEach, describe, expect, it, vi } from 'vitest' import ChartContainer from '../../components/ChartContainer.vue' import DataFrame from '../../components/DataFrame.vue' import GeneratedResultRenderer from '../../components/GeneratedResultRenderer.vue' describe('GeneratedResultRenderer', () => { afterEach(() => { vi.unstubAllGlobals() vi.restoreAllMocks() }) it('renders tabular results with the standard DataFrame component', () => { const wrapper = mount(GeneratedResultRenderer, { props: { result: { kind: 'table', columns: ['sample', 'value'], rows: [{ sample: 'A', value: 1.5 }], }, }, }) expect(wrapper.findComponent(DataFrame).props()).toMatchObject({ data: [{ sample: 'A', value: 1.5 }], columns: [ { key: 'sample', label: 'Sample', sortable: true }, { key: 'value', label: 'Value', sortable: true }, ], }) }) it('renders Matplotlib images inside the standard chart container', () => { const wrapper = mount(GeneratedResultRenderer, { props: { result: { kind: 'image', media_type: 'image/png', data: 'cG5n', alt: 'PCA' }, }, }) expect({ chart: wrapper.findComponent(ChartContainer).exists(), source: wrapper.get('img').attributes('src'), alt: wrapper.get('img').attributes('alt'), }).toEqual({ chart: true, source: 'data:image/png;base64,cG5n', alt: 'PCA' }) }) it('never interprets JSON results as HTML', () => { const wrapper = mount(GeneratedResultRenderer, { props: { result: { kind: 'json', value: { unsafe: '' } }, }, }) expect({ scripts: wrapper.findAll('script').length, text: wrapper.text() }).toEqual({ scripts: 0, text: expect.stringContaining(''), }) }) it('should preserve the artifact filename when downloading a result', () => { const wrapper = mount(GeneratedResultRenderer, { props: { result: { kind: 'artifact', id: 'artifact-1', name: 'analysis.csv', media_type: 'text/csv', size: 3, data: 'YSxi', }, }, }) expect(wrapper.get('a').attributes('download')).toBe('analysis.csv') }) it('uses the host download route for a managed file result', () => { const wrapper = mount(GeneratedResultRenderer, { props: { result: { kind: 'artifact', id: 'artifact-2', name: 'large.msd', media_type: 'application/octet-stream', size: 512_000_000, download_url: '/api/leaf/jobs/job-1/result/file/scoped-token', managed: true, }, }, }) expect(wrapper.get('a').attributes()).toMatchObject({ href: '/api/leaf/jobs/job-1/result/file/scoped-token', download: 'large.msd', }) }) describe('managed file downloads', () => { const managed = { kind: 'artifact' as const, id: 'artifact-2', name: 'large.msd', media_type: 'application/octet-stream', size: 3, download_url: '/api/leaf/jobs/job-1/result/file/scoped-token', managed: true, } async function clickDownload(): Promise<{ fetch: ReturnType, saved: string[] }> { vi.stubGlobal('localStorage', { getItem: (key: string) => (key === 'mint-auth-token' ? 'session-jwt' : null) }) const fetch = vi.fn(async () => new Response('msd')) vi.stubGlobal('fetch', fetch) vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:managed') vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}) const saved: string[] = [] vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(function (this: HTMLAnchorElement) { saved.push(this.download) }) const wrapper = mount(GeneratedResultRenderer, { props: { result: managed } }) await wrapper.get('a').trigger('click') await flushPromises() return { fetch, saved } } it('should send the Bearer credential when downloading a managed file', async () => { const { fetch } = await clickDownload() expect(new Headers(fetch.mock.calls[0]?.[1]?.headers).get('Authorization')).toBe('Bearer session-jwt') }) it('should save the fetched file under the artifact name when downloading a managed file', async () => { const { saved } = await clickDownload() expect(saved).toEqual(['large.msd']) }) }) })