Files
ComfyUI_frontend/docs/testing/vitest-patterns.md
Alexander Brown b98d53e740 [backport cloud/1.37] feat(assets): add ModelInfoPanel for asset browser right panel (#8241)
## Summary

Backport of #8090 to cloud/1.37 branch.

Cherry-picked from main commit 93e7a4f9f9.

## Conflict Resolutions

- `src/components/rightSidePanel/layout/PropertiesAccordionItem.vue`:
Took PR version but removed `TransitionCollapse` dependency (not present
in cloud/1.37). The transition animation is omitted; collapse/expand
works without animation.

## Original PR Description

Adds an editable Model Info Panel to show and modify asset details in
the asset browser.

### Changes

- Add `ModelInfoPanel` component with editable display name,
description, model type, base models, and tags
- Add `updateAssetMetadata` action in `assetsStore` with optimistic
cache updates
- Add shadcn-vue `Select` components with design system styling
- Add utility functions in `assetMetadataUtils` for extracting model
metadata
- Convert `BaseModalLayout` right panel state to `defineModel` pattern
- Add slide-in animation and collapse button for right panel
- Add `class` prop to `PropertiesAccordionItem` for custom styling
- Fix keyboard handling: Escape in TagsInput/TextArea doesn't close
parent modal

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8241-backport-cloud-1-37-feat-assets-add-ModelInfoPanel-for-asset-browser-right-panel-2f06d73d365081ffb57dca42a82349b6)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-01-21 20:30:42 -08:00

2.7 KiB

globs
globs
**/*.test.ts
**/*.spec.ts

Vitest Patterns

Setup

Use createTestingPinia from @pinia/testing, not createPinia:

import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

describe('MyStore', () => {
  beforeEach(() => {
    setActivePinia(createTestingPinia({ stubActions: false }))
    vi.useFakeTimers()
    vi.resetAllMocks()
  })

  afterEach(() => {
    vi.useRealTimers()
  })
})

Why stubActions: false? By default, testing pinia stubs all actions. Set to false when testing actual store behavior.

i18n in Component Tests

Use real createI18n with empty messages instead of mocking vue-i18n. See SearchBox.test.ts for example.

Mock Patterns

Reset all mocks at once

beforeEach(() => {
  vi.resetAllMocks()  // Not individual mock.mockReset() calls
})

Module mocks with vi.mock()

vi.mock('@/scripts/api', () => ({
  api: {
    addEventListener: vi.fn(),
    fetchData: vi.fn()
  }
}))

vi.mock('@/services/myService', () => ({
  myService: {
    doThing: vi.fn()
  }
}))

Configure mocks in tests

import { api } from '@/scripts/api'
import { myService } from '@/services/myService'

it('handles success', () => {
  vi.mocked(myService.doThing).mockResolvedValue({ data: 'test' })
  // ... test code
})

Testing Event Listeners

When a store registers event listeners at module load time:

function getEventHandler() {
  const call = vi.mocked(api.addEventListener).mock.calls.find(
    ([event]) => event === 'my_event'
  )
  return call?.[1] as (e: CustomEvent<MyEventType>) => void
}

function dispatch(data: MyEventType) {
  const handler = getEventHandler()
  handler(new CustomEvent('my_event', { detail: data }))
}

it('handles events', () => {
  const store = useMyStore()
  dispatch({ field: 'value' })
  expect(store.items).toHaveLength(1)
})

Testing with Fake Timers

For stores with intervals, timeouts, or polling:

beforeEach(() => {
  vi.useFakeTimers()
})

afterEach(() => {
  vi.useRealTimers()
})

it('polls after delay', async () => {
  const store = useMyStore()
  store.startPolling()

  await vi.advanceTimersByTimeAsync(30000)

  expect(mockService.fetch).toHaveBeenCalled()
})

Assertion Style

Prefer .toHaveLength() over .length.toBe():

// Good
expect(store.items).toHaveLength(1)

// Avoid
expect(store.items.length).toBe(1)

Use .toMatchObject() for partial matching:

expect(store.completedItems[0]).toMatchObject({
  id: 'task-123',
  status: 'done'
})