Files
ComfyUI_frontend/tests-ui/platform/assets/composables/useAssetBrowserDialog.test.ts
Christian Byrne 8120ed9dfa load assets browser before fetch completes and show loading state (#6189)
## Summary

Moves the fetch and post-fetch logic associated with the asset browser
into the component and shows a loading state while fetching.

To test, use this branch:
https://github.com/comfyanonymous/ComfyUI/pull/10045



https://github.com/user-attachments/assets/718974d5-efc7-46a0-bcd6-e82596d4c389

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6189-load-assets-browser-before-fetch-completes-and-show-loading-state-2946d73d365081879d1bd05d86e8c036)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2025-10-23 13:43:34 -07:00

180 lines
5.3 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import { useDialogStore } from '@/stores/dialogStore'
vi.mock('@/stores/dialogStore')
vi.mock('@/i18n', () => ({
t: (key: string, params?: Record<string, string>) => {
if (params) {
return `${key}:${JSON.stringify(params)}`
}
return key
}
}))
function createMockAsset(overrides: Partial<AssetItem> = {}): AssetItem {
return {
id: 'asset-123',
name: 'test-model.safetensors',
size: 1024,
created_at: '2025-10-01T00:00:00Z',
tags: ['models', 'checkpoints'],
user_metadata: {
filename: 'models/checkpoints/test-model.safetensors'
},
...overrides
}
}
function setupDialogMocks() {
const mockShowDialog = vi.fn()
const mockCloseDialog = vi.fn()
vi.mocked(useDialogStore, { partial: true }).mockReturnValue({
showDialog: mockShowDialog,
closeDialog: mockCloseDialog
})
return { mockShowDialog, mockCloseDialog }
}
describe('useAssetBrowserDialog', () => {
describe('Asset Selection Flow', () => {
it('auto-closes dialog when asset is selected', async () => {
const { mockShowDialog, mockCloseDialog } = setupDialogMocks()
const assetBrowserDialog = useAssetBrowserDialog()
const onAssetSelected = vi.fn()
await assetBrowserDialog.show({
nodeType: 'CheckpointLoaderSimple',
inputName: 'ckpt_name',
onAssetSelected
})
const dialogCall = mockShowDialog.mock.calls[0][0]
const onSelectHandler = dialogCall.props.onSelect
const mockAsset = {
id: 'test-asset-id',
name: 'test.safetensors',
size: 1024,
created_at: '2025-10-01T00:00:00Z',
tags: ['models', 'checkpoints'],
user_metadata: { filename: 'selected-asset-path' }
}
onSelectHandler(mockAsset)
expect(onAssetSelected).toHaveBeenCalledWith(mockAsset)
expect(mockCloseDialog).toHaveBeenCalledWith({
key: 'global-asset-browser'
})
})
it('closes dialog when close handler is called', async () => {
const { mockShowDialog, mockCloseDialog } = setupDialogMocks()
const assetBrowserDialog = useAssetBrowserDialog()
await assetBrowserDialog.show({
nodeType: 'CheckpointLoaderSimple',
inputName: 'ckpt_name'
})
const dialogCall = mockShowDialog.mock.calls[0][0]
const onCloseHandler = dialogCall.props.onClose
onCloseHandler()
expect(mockCloseDialog).toHaveBeenCalledWith({
key: 'global-asset-browser'
})
})
})
describe('.browse() method', () => {
it('opens asset browser dialog with tag-based filtering', async () => {
const { mockShowDialog } = setupDialogMocks()
const assetBrowserDialog = useAssetBrowserDialog()
await assetBrowserDialog.browse({
assetType: 'models',
title: 'Model Library'
})
expect(mockShowDialog).toHaveBeenCalledWith(
expect.objectContaining({
key: 'global-asset-browser',
props: expect.objectContaining({
showLeftPanel: true,
assetType: 'models'
})
})
)
})
it('calls onAssetSelected callback when asset is selected', async () => {
const { mockShowDialog } = setupDialogMocks()
const assetBrowserDialog = useAssetBrowserDialog()
const mockAsset = createMockAsset()
const onAssetSelected = vi.fn()
await assetBrowserDialog.browse({
assetType: 'models',
onAssetSelected
})
const dialogCall = mockShowDialog.mock.calls[0][0]
const onSelectHandler = dialogCall.props.onSelect
onSelectHandler(mockAsset)
expect(onAssetSelected).toHaveBeenCalledWith(mockAsset)
})
it('closes dialog after asset selection', async () => {
const { mockShowDialog, mockCloseDialog } = setupDialogMocks()
const assetBrowserDialog = useAssetBrowserDialog()
const mockAsset = createMockAsset()
await assetBrowserDialog.browse({
assetType: 'models'
})
const dialogCall = mockShowDialog.mock.calls[0][0]
const onSelectHandler = dialogCall.props.onSelect
onSelectHandler(mockAsset)
expect(mockCloseDialog).toHaveBeenCalledWith({
key: 'global-asset-browser'
})
})
it('uses custom title when provided', async () => {
const { mockShowDialog } = setupDialogMocks()
const assetBrowserDialog = useAssetBrowserDialog()
await assetBrowserDialog.browse({
assetType: 'models',
title: 'Custom Model Browser'
})
const dialogCall = mockShowDialog.mock.calls[0][0]
expect(dialogCall.props.title).toBe('Custom Model Browser')
})
})
describe('.show() behavior', () => {
it('opens dialog without pre-fetched assets', async () => {
const { mockShowDialog } = setupDialogMocks()
const assetBrowserDialog = useAssetBrowserDialog()
await assetBrowserDialog.show({
nodeType: 'CheckpointLoaderSimple',
inputName: 'ckpt_name'
})
const dialogCall = mockShowDialog.mock.calls[0][0]
expect(dialogCall.props.nodeType).toBe('CheckpointLoaderSimple')
expect(dialogCall.props.assets).toBeUndefined()
})
})
})