import { flushPromises, mount } from '@vue/test-utils' import { createPinia } from 'pinia' import { computed, ref } from 'vue' import { beforeEach, describe, expect, it, vi } from 'vitest' import GeneratedPluginView from '../../components/GeneratedPluginView.vue' import GeneratedResultRenderer from '../../components/GeneratedResultRenderer.vue' import JobsStatusTray from '../../components/JobsStatusTray.vue' import PluginWorkspaceView from '../../components/PluginWorkspaceView.vue' const generated = vi.hoisted(() => ({ initialize: vi.fn(), submit: vi.fn(), loadResult: vi.fn(), resultForJob: vi.fn(), options: undefined as unknown, jobs: [] as Array>, })) vi.mock('../../composables/useGeneratedAnalysis', async () => { const { computed, ref, shallowRef } = await import('vue') return { useGeneratedAnalysis: (options: unknown) => { generated.options = options const jobs = shallowRef(generated.jobs) return ({ manifest: shallowRef({ schema_version: '1', plugin: { name: 'Demo', version: '1.0.0', description: 'Demo plugin', routes_prefix: '/demo', }, ui: { title: 'Demo analysis', description: 'Backend-only workspace' }, jobs: [ { id: 'run', title: 'Run analysis', description: 'Analyze one dataset', cpu: 2, input_schema: { type: 'object', properties: { threshold: { type: 'number', title: 'Threshold', default: 2.5 } }, }, output_schema: {}, }, { id: 'sync', title: 'Sync service', description: 'Synchronize remote metadata', cpu: 0, lifecycle: 'service', profile: { kind: 'service', timeoutSeconds: 60, maxConcurrency: 2 }, input_schema: { type: 'object', properties: {} }, output_schema: {}, }, ], }), session: shallowRef({ id: 'session-1' }), jobs, latestResult: computed(() => ({ kind: 'text', text: 'complete' })), isLoading: ref(false), isSubmitting: ref(false), error: shallowRef(null), initialize: generated.initialize, submit: generated.submit, loadResult: generated.loadResult, resultForJob: generated.resultForJob, refreshJobs: vi.fn(), }) }, } }) vi.mock('../../composables/usePlatformContext', () => ({ usePlatformContext: vi.fn(() => ({ isIntegrated: computed(() => false), context: ref({ isIntegrated: false, theme: 'system' }), plugin: computed(() => undefined), user: computed(() => undefined), theme: computed(() => 'system'), features: computed(() => undefined), navigate: vi.fn(), notify: vi.fn(), sendToPlatform: vi.fn(), })), })) vi.mock('../../composables/useTheme', () => ({ useTheme: vi.fn(() => ({ isDark: ref(false), theme: ref('light'), toggleTheme: vi.fn() })), })) describe('GeneratedPluginView', () => { beforeEach(() => { vi.clearAllMocks() const baseJob = { kind: 'analysis', name: 'Run analysis', status: 'completed', progress: { current_file: 0, total_files: 0, current_filename: null, percent: 100, message: '', stage: 'completed', }, queue_position: 0, warnings: [], error: null, output_path: null, save_path: null, session_id: 'session-1', folders: [], folder_path: null, user_id: 'user-1', owner_user_id: 'user-1', experiment_id: null, extraction_result_id: null, metadata: { definition_id: 'run', has_result: true }, created_at: '', started_at: '', completed_at: '', can_cancel: false, can_delete: true, } generated.jobs = [ { ...baseJob, id: 'job-1', job_id: 'job-1' }, { ...baseJob, id: 'job-2', job_id: 'job-2', session_id: 'shared-session' }, ] generated.initialize.mockResolvedValue(undefined) generated.submit.mockResolvedValue(undefined) generated.loadResult.mockResolvedValue({ kind: 'text', text: 'complete' }) generated.resultForJob.mockReturnValue({ kind: 'text', text: 'complete' }) }) it('composes the selected B workspace from SDK-owned layout, form, jobs, and results', async () => { const wrapper = mount(GeneratedPluginView, { props: { baseUrl: '/api/demo' }, global: { plugins: [createPinia()], stubs: { 'router-link': { template: '' }, Teleport: true, }, }, }) await flushPromises() expect({ workspace: wrapper.findComponent(PluginWorkspaceView).props(), result: wrapper.findComponent(GeneratedResultRenderer).text(), jobs: wrapper.findComponent(JobsStatusTray).exists(), jobSource: wrapper.findComponent(JobsStatusTray).props('source'), }).toMatchObject({ workspace: { title: 'Demo analysis', subtitle: 'Backend-only workspace', sidebarPosition: 'left', showFormActions: true, values: { threshold: 2.5 }, }, result: 'complete', jobs: true, jobSource: expect.any(Object), }) }) it('submits normalized form values through the current job definition', async () => { const wrapper = mount(GeneratedPluginView, { props: { baseUrl: '/api/demo' }, global: { plugins: [createPinia()], stubs: { 'router-link': true, Teleport: true } }, }) await flushPromises() wrapper.findComponent(PluginWorkspaceView).vm.$emit('form-submit', 'run', { threshold: 4 }) await flushPromises() expect(generated.submit).toHaveBeenCalledWith('run', { threshold: 4 }) }) it('describes host service jobs without a misleading CPU reservation', async () => { const wrapper = mount(GeneratedPluginView, { props: { baseUrl: '/api/demo' }, global: { plugins: [createPinia()], stubs: { 'router-link': true, Teleport: true } }, }) await flushPromises() wrapper.findComponent(PluginWorkspaceView).vm.$emit('update:activeView', 'sync') await flushPromises() expect(wrapper.find('[aria-label="Job resource requirements"]').text()).toBe('Host service') }) it('describes process CPU declarations as an upper bound', async () => { const wrapper = mount(GeneratedPluginView, { props: { baseUrl: '/api/demo' }, global: { plugins: [createPinia()], stubs: { 'router-link': true, Teleport: true } }, }) await flushPromises() expect(wrapper.find('[aria-label="Job resource requirements"]').text()).toBe('Up to 2 CPU') }) it('should pass the host transport to the generated analysis composable', async () => { const transport = vi.fn() mount(GeneratedPluginView, { props: { baseUrl: '/api/demo', transport }, global: { plugins: [createPinia()], stubs: { 'router-link': true, Teleport: true } }, }) await flushPromises() expect(generated.options).toMatchObject({ baseUrl: '/api/demo', transport }) }) it('should lazily load the current job result by job id', async () => { mount(GeneratedPluginView, { props: { baseUrl: '/api/demo' }, global: { plugins: [createPinia()], stubs: { 'router-link': true, Teleport: true } }, }) await flushPromises() expect(generated.loadResult).toHaveBeenCalledWith(expect.objectContaining({ job_id: 'job-1' })) }) it('should display the exact historical job selected from the jobs tray', async () => { generated.resultForJob.mockImplementation(jobId => ({ kind: 'text', text: String(jobId) })) const wrapper = mount(GeneratedPluginView, { props: { baseUrl: '/api/demo' }, global: { plugins: [createPinia()], stubs: { 'router-link': true, Teleport: true } }, }) await flushPromises() wrapper.findComponent(JobsStatusTray).vm.$emit('select', generated.jobs[1]) await flushPromises() expect({ result: wrapper.findComponent(GeneratedResultRenderer).text(), loadedJob: generated.loadResult.mock.calls.at(-1)?.[0]?.job_id, }).toEqual({ result: 'job-2', loadedJob: 'job-2' }) }) })