Files
ComfyUI_frontend/src/components/builder/BuilderFooterToolbar.test.ts
pythongosssss c759fe517f feat: Replace BuilderExitButton with new BuilderFooterToolbar (#9378)
## Summary

Makes it easier and more obvious for users to navigate between steps

## Changes

- **What**: 
- add back/next navigation to builder footer alongside exit button
- extract shared step logic into useBuilderSteps composable

## Screenshots (if applicable)

<img width="428" height="102" alt="image"
src="https://github.com/user-attachments/assets/91b33e8f-53ae-4895-a2eb-fb1316b2b367"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9378-feat-Replace-BuilderExitButton-with-new-BuilderFooterToolbar-3196d73d3650819392efc171cf277326)
by [Unito](https://www.unito.io)
2026-03-04 09:58:59 -08:00

148 lines
4.0 KiB
TypeScript

import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { computed, ref } from 'vue'
import { createI18n } from 'vue-i18n'
import type { AppMode } from '@/composables/useAppMode'
import BuilderFooterToolbar from '@/components/builder/BuilderFooterToolbar.vue'
const mockSetMode = vi.hoisted(() => vi.fn())
const mockExitBuilder = vi.hoisted(() => vi.fn())
const mockShowDialog = vi.hoisted(() => vi.fn())
const mockState = {
mode: 'builder:select' as AppMode,
settingView: false
}
vi.mock('@/composables/useAppMode', () => ({
useAppMode: () => ({
mode: computed(() => mockState.mode),
isBuilderMode: ref(true),
setMode: mockSetMode
})
}))
const mockHasOutputs = ref(true)
vi.mock('@/stores/appModeStore', () => ({
useAppModeStore: () => ({
exitBuilder: mockExitBuilder,
hasOutputs: mockHasOutputs,
$id: 'appMode'
})
}))
vi.mock('@/stores/dialogStore', () => ({
useDialogStore: () => ({
dialogStack: []
})
}))
vi.mock('@/components/builder/useAppSetDefaultView', () => ({
useAppSetDefaultView: () => ({
settingView: computed(() => mockState.settingView),
showDialog: mockShowDialog
})
}))
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: {
builderMenu: { exitAppBuilder: 'Exit app builder' },
g: { back: 'Back', next: 'Next' }
}
}
})
describe('BuilderFooterToolbar', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
mockState.mode = 'builder:select'
mockHasOutputs.value = true
mockState.settingView = false
})
function mountComponent() {
return mount(BuilderFooterToolbar, {
global: {
plugins: [i18n],
stubs: { Button: false }
}
})
}
function getButtons(wrapper: ReturnType<typeof mountComponent>) {
const buttons = wrapper.findAll('button')
return {
exit: buttons[0],
back: buttons[1],
next: buttons[2]
}
}
it('disables back on the first step', () => {
mockState.mode = 'builder:select'
const { back } = getButtons(mountComponent())
expect(back.attributes('disabled')).toBeDefined()
})
it('enables back on the second step', () => {
mockState.mode = 'builder:arrange'
const { back } = getButtons(mountComponent())
expect(back.attributes('disabled')).toBeUndefined()
})
it('disables next on the setDefaultView step', () => {
mockState.settingView = true
const { next } = getButtons(mountComponent())
expect(next.attributes('disabled')).toBeDefined()
})
it('disables next on arrange step when no outputs', () => {
mockState.mode = 'builder:arrange'
mockHasOutputs.value = false
const { next } = getButtons(mountComponent())
expect(next.attributes('disabled')).toBeDefined()
})
it('enables next on select step', () => {
mockState.mode = 'builder:select'
const { next } = getButtons(mountComponent())
expect(next.attributes('disabled')).toBeUndefined()
})
it('calls setMode on back click', async () => {
mockState.mode = 'builder:arrange'
const { back } = getButtons(mountComponent())
await back.trigger('click')
expect(mockSetMode).toHaveBeenCalledWith('builder:select')
})
it('calls setMode on next click from select step', async () => {
mockState.mode = 'builder:select'
const { next } = getButtons(mountComponent())
await next.trigger('click')
expect(mockSetMode).toHaveBeenCalledWith('builder:arrange')
})
it('opens default view dialog on next click from arrange step', async () => {
mockState.mode = 'builder:arrange'
const { next } = getButtons(mountComponent())
await next.trigger('click')
expect(mockSetMode).toHaveBeenCalledWith('builder:arrange')
expect(mockShowDialog).toHaveBeenCalledOnce()
})
it('calls exitBuilder on exit button click', async () => {
const { exit } = getButtons(mountComponent())
await exit.trigger('click')
expect(mockExitBuilder).toHaveBeenCalledOnce()
})
})