test: slim down job history sidebar tab coverage

This commit is contained in:
Benjamin Lu
2026-03-21 20:34:01 -07:00
parent f8399e3545
commit 210ebc8815

View File

@@ -1,121 +1,76 @@
import { mount } from '@vue/test-utils'
import { createI18n } from 'vue-i18n'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { defineComponent, nextTick } from 'vue'
import { describe, expect, it, beforeEach, vi } from 'vitest'
import { computed, defineComponent, ref } from 'vue'
import type { JobListItem } from '@/composables/queue/useJobList'
import JobHistorySidebarTab from './JobHistorySidebarTab.vue'
vi.mock('@/components/queue/job/JobDetailsPopover.vue', () => ({
default: {
name: 'JobDetailsPopover',
template: '<div class="job-details-popover-stub" />'
}
const testState = vi.hoisted(() => ({
groupedJobItems: [] as Array<{
key: string
label: string
items: JobListItem[]
}>,
filteredTasks: [] as JobListItem[],
getJobMenuEntries: vi.fn(() => []),
cancelJob: vi.fn(),
openResultGallery: vi.fn(),
showQueueClearHistoryDialog: vi.fn(),
commandExecute: vi.fn(),
showDialog: vi.fn(),
clearInitializationByJobIds: vi.fn(),
queueDelete: vi.fn()
}))
vi.mock('@/components/queue/JobHistoryActionsMenu.vue', () => ({
default: {
name: 'JobHistoryActionsMenu',
template: '<div />'
}
}))
vi.mock('@/components/queue/job/JobFilterActions.vue', () => ({
default: {
name: 'JobFilterActions',
template: '<div />'
}
}))
vi.mock('@/components/queue/job/JobFilterTabs.vue', () => ({
default: {
name: 'JobFilterTabs',
template: '<div />'
}
}))
vi.mock('@/components/sidebar/tabs/SidebarTabTemplate.vue', () => ({
default: {
name: 'SidebarTabTemplate',
template:
'<div><slot name="alt-title" /><slot name="header" /><slot name="body" /></div>'
}
}))
vi.mock('@/components/sidebar/tabs/queue/MediaLightbox.vue', () => ({
default: {
name: 'MediaLightbox',
template: '<div />'
}
}))
const JobDetailsPopoverStub = defineComponent({
name: 'JobDetailsPopover',
const JobAssetsListStub = defineComponent({
name: 'JobAssetsList',
props: {
jobId: { type: String, required: true },
workflowId: { type: String, default: undefined }
},
template: '<div class="job-details-popover-stub" />'
})
vi.mock('@/composables/queue/useJobList', async () => {
const { ref } = await import('vue')
const jobHistoryItem = {
id: 'job-1',
title: 'Job 1',
meta: 'meta',
state: 'completed',
taskRef: {
workflowId: 'workflow-1',
previewOutput: {
isImage: true,
isVideo: false,
url: '/api/view/job-1.png'
}
displayedJobGroups: {
type: Array,
required: true
},
getMenuEntries: {
type: Function,
required: true
}
}
return {
useJobList: () => ({
selectedJobTab: ref('All'),
selectedWorkflowFilter: ref('all'),
selectedSortMode: ref('mostRecent'),
searchQuery: ref(''),
hasFailedJobs: ref(false),
filteredTasks: ref([]),
groupedJobItems: ref([
{
key: 'group-1',
label: 'Group 1',
items: [jobHistoryItem]
}
])
})
}
},
template: '<div class="job-assets-list-stub" />'
})
vi.mock('@/composables/queue/useJobList', () => ({
useJobList: () => ({
selectedJobTab: ref('All'),
selectedWorkflowFilter: ref('all'),
selectedSortMode: ref('mostRecent'),
searchQuery: ref(''),
hasFailedJobs: ref(false),
filteredTasks: computed(() => testState.filteredTasks),
groupedJobItems: computed(() => testState.groupedJobItems)
})
}))
vi.mock('@/composables/queue/useJobMenu', () => ({
useJobMenu: () => ({
getJobMenuEntries: () => [],
cancelJob: vi.fn()
getJobMenuEntries: testState.getJobMenuEntries,
cancelJob: testState.cancelJob
})
}))
vi.mock('@/composables/queue/useQueueClearHistoryDialog', () => ({
useQueueClearHistoryDialog: () => ({
showQueueClearHistoryDialog: vi.fn()
showQueueClearHistoryDialog: testState.showQueueClearHistoryDialog
})
}))
vi.mock('@/composables/queue/useResultGallery', async () => {
const { ref } = await import('vue')
return {
useResultGallery: () => ({
galleryActiveIndex: ref(-1),
galleryItems: ref([]),
onViewItem: vi.fn()
})
}
})
vi.mock('@/composables/queue/useResultGallery', () => ({
useResultGallery: () => ({
galleryActiveIndex: ref(-1),
galleryItems: ref([]),
onViewItem: testState.openResultGallery
})
}))
vi.mock('@/composables/useErrorHandling', () => ({
useErrorHandling: () => ({
@@ -127,19 +82,19 @@ vi.mock('@/composables/useErrorHandling', () => ({
vi.mock('@/stores/commandStore', () => ({
useCommandStore: () => ({
execute: vi.fn()
execute: testState.commandExecute
})
}))
vi.mock('@/stores/dialogStore', () => ({
useDialogStore: () => ({
showDialog: vi.fn()
showDialog: testState.showDialog
})
}))
vi.mock('@/stores/executionStore', () => ({
useExecutionStore: () => ({
clearInitializationByJobIds: vi.fn()
clearInitializationByJobIds: testState.clearInitializationByJobIds
})
}))
@@ -147,7 +102,7 @@ vi.mock('@/stores/queueStore', () => ({
useQueueStore: () => ({
runningTasks: [],
pendingTasks: [],
delete: vi.fn()
delete: testState.queueDelete
})
}))
@@ -157,11 +112,33 @@ const i18n = createI18n({
messages: { en: {} }
})
const SidebarTabTemplateStub = {
name: 'SidebarTabTemplate',
props: ['title'],
template:
'<div><slot name="alt-title" /><slot name="header" /><slot name="body" /></div>'
const buildJob = (overrides: Partial<JobListItem> = {}): JobListItem =>
({
id: 'job-1',
title: 'Job 1',
meta: 'meta',
state: 'completed',
taskRef: {
workflowId: 'workflow-1',
previewOutput: {
isImage: true,
isVideo: false,
is3D: false,
url: '/api/view/job-1.png'
}
},
...overrides
}) as JobListItem
const setDisplayedJobs = (items: JobListItem[]) => {
testState.filteredTasks = items
testState.groupedJobItems = [
{
key: 'group-1',
label: 'Group 1',
items
}
]
}
function mountComponent() {
@@ -169,37 +146,106 @@ function mountComponent() {
global: {
plugins: [i18n],
stubs: {
SidebarTabTemplate: SidebarTabTemplateStub,
SidebarTabTemplate: {
name: 'SidebarTabTemplate',
template:
'<div><slot name="alt-title" /><slot name="header" /><slot name="body" /></div>'
},
JobFilterTabs: true,
JobFilterActions: true,
JobHistoryActionsMenu: true,
ResultGallery: true,
teleport: true,
JobDetailsPopover: JobDetailsPopoverStub
MediaLightbox: true,
JobAssetsList: JobAssetsListStub,
teleport: true
}
}
})
}
afterEach(() => {
vi.useRealTimers()
})
describe('JobHistorySidebarTab', () => {
it('shows the job details popover for jobs in the history panel', async () => {
vi.useFakeTimers()
beforeEach(() => {
vi.clearAllMocks()
setDisplayedJobs([buildJob()])
})
it('passes grouped jobs and menu getter to JobAssetsList', () => {
const wrapper = mountComponent()
const jobRow = wrapper.find('[data-job-id="job-1"]')
const jobAssetsList = wrapper.findComponent(JobAssetsListStub)
await jobRow.trigger('mouseenter')
await vi.advanceTimersByTimeAsync(200)
await nextTick()
expect(jobAssetsList.props('displayedJobGroups')).toEqual(
testState.groupedJobItems
)
expect(jobAssetsList.props('getMenuEntries')).toBe(
testState.getJobMenuEntries
)
})
const popover = wrapper.findComponent(JobDetailsPopoverStub)
expect(popover.exists()).toBe(true)
expect(popover.props()).toMatchObject({
jobId: 'job-1',
workflowId: 'workflow-1'
it('forwards regular view-item events to the result gallery', async () => {
const job = buildJob()
setDisplayedJobs([job])
const wrapper = mountComponent()
wrapper.findComponent(JobAssetsListStub).vm.$emit('view-item', job)
expect(testState.openResultGallery).toHaveBeenCalledWith(job)
expect(testState.showDialog).not.toHaveBeenCalled()
})
it('opens the 3D viewer dialog for 3D view-item events', async () => {
const job = buildJob({
taskRef: {
workflowId: 'workflow-1',
previewOutput: {
isImage: false,
isVideo: false,
is3D: true,
url: '/api/view/job-1.glb'
}
} as JobListItem['taskRef']
})
setDisplayedJobs([job])
const wrapper = mountComponent()
wrapper.findComponent(JobAssetsListStub).vm.$emit('view-item', job)
expect(testState.showDialog).toHaveBeenCalledWith(
expect.objectContaining({
key: 'asset-3d-viewer',
title: job.title,
props: { modelUrl: '/api/view/job-1.glb' }
})
)
expect(testState.openResultGallery).not.toHaveBeenCalled()
})
it('forwards cancel-item events to useJobMenu.cancelJob', async () => {
const job = buildJob({ state: 'running' })
setDisplayedJobs([job])
const wrapper = mountComponent()
wrapper.findComponent(JobAssetsListStub).vm.$emit('cancel-item', job)
expect(testState.cancelJob).toHaveBeenCalledWith(job)
})
it('forwards delete-item events to queueStore.delete', async () => {
const job = buildJob()
const taskRef = job.taskRef
const wrapper = mountComponent()
wrapper.findComponent(JobAssetsListStub).vm.$emit('delete-item', job)
expect(testState.queueDelete).toHaveBeenCalledWith(taskRef)
})
it('runs menu actions emitted by JobAssetsList', async () => {
const onClick = vi.fn()
const wrapper = mountComponent()
wrapper
.findComponent(JobAssetsListStub)
.vm.$emit('menu-action', { key: 'test', label: 'Test', onClick })
expect(onClick).toHaveBeenCalled()
})
})