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'])
})
})
})