Files
ComfyUI_frontend/docs/testing/store-testing.md
Alexander Brown 10feb1fd5b chore: migrate tests from tests-ui/ to colocate with source files (#7811)
## Summary

Migrates all unit tests from `tests-ui/` to colocate with their source
files in `src/`, improving discoverability and maintainability.

## Changes

- **What**: Relocated all unit tests to be adjacent to the code they
test, following the `<source>.test.ts` naming convention
- **Config**: Updated `vitest.config.ts` to remove `tests-ui` include
pattern and `@tests-ui` alias
- **Docs**: Moved testing documentation to `docs/testing/` with updated
paths and patterns

## Review Focus

- Migration patterns documented in
`temp/plans/migrate-tests-ui-to-src.md`
- Tests use `@/` path aliases instead of relative imports
- Shared fixtures placed in `__fixtures__/` directories

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7811-chore-migrate-tests-from-tests-ui-to-colocate-with-source-files-2da6d73d36508147a4cce85365dee614)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
2026-01-05 16:32:24 -08:00

8.0 KiB

Pinia Store Testing Guide

This guide covers patterns and examples for testing Pinia stores in the ComfyUI Frontend codebase.

Table of Contents

  1. Setting Up Store Tests
  2. Testing Store State
  3. Testing Store Actions
  4. Testing Store Getters
  5. Mocking Dependencies in Stores
  6. Testing Store Watchers
  7. Testing Store Integration

Setting Up Store Tests

Basic setup for testing Pinia stores:

// Example from: tests-ui/tests/store/workflowStore.test.ts
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { useWorkflowStore } from '@/domains/workflow/ui/stores/workflowStore'

describe('useWorkflowStore', () => {
  let store: ReturnType<typeof useWorkflowStore>

  beforeEach(() => {
    // Create a fresh pinia and activate it for each test
    setActivePinia(createPinia())
    
    // Initialize the store
    store = useWorkflowStore()
    
    // Clear any mocks
    vi.clearAllMocks()
  })

  it('should initialize with default state', () => {
    expect(store.workflows).toEqual([])
    expect(store.activeWorkflow).toBeUndefined()
    expect(store.openWorkflows).toEqual([])
  })
})

Testing Store State

Testing store state changes:

// Example from: tests-ui/tests/store/workflowStore.test.ts
it('should create a temporary workflow with a unique path', () => {
  const workflow = store.createTemporary()
  expect(workflow.path).toBe('workflows/Unsaved Workflow.json')

  const workflow2 = store.createTemporary()
  expect(workflow2.path).toBe('workflows/Unsaved Workflow (2).json')
})

it('should create a temporary workflow not clashing with persisted workflows', async () => {
  await syncRemoteWorkflows(['a.json'])
  const workflow = store.createTemporary('a.json')
  expect(workflow.path).toBe('workflows/a (2).json')
})

Testing Store Actions

Testing store actions:

// Example from: tests-ui/tests/store/workflowStore.test.ts
describe('openWorkflow', () => {
  it('should load and open a temporary workflow', async () => {
    // Create a test workflow
    const workflow = store.createTemporary('test.json')
    const mockWorkflowData = { nodes: [], links: [] }

    // Mock the load response
    vi.spyOn(workflow, 'load').mockImplementation(async () => {
      workflow.changeTracker = { activeState: mockWorkflowData } as any
      return workflow as LoadedComfyWorkflow
    })

    // Open the workflow
    await store.openWorkflow(workflow)

    // Verify the workflow is now active
    expect(store.activeWorkflow?.path).toBe(workflow.path)

    // Verify the workflow is in the open workflows list
    expect(store.isOpen(workflow)).toBe(true)
  })

  it('should not reload an already active workflow', async () => {
    const workflow = await store.createTemporary('test.json').load()
    vi.spyOn(workflow, 'load')

    // Set as active workflow
    store.activeWorkflow = workflow

    await store.openWorkflow(workflow)

    // Verify load was not called
    expect(workflow.load).not.toHaveBeenCalled()
  })
})

Testing Store Getters

Testing store getters:

// Example from: tests-ui/tests/store/modelStore.test.ts
describe('getters', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
    store = useModelStore()
    
    // Set up test data
    store.models = {
      checkpoints: [
        { name: 'model1.safetensors', path: 'models/checkpoints/model1.safetensors' },
        { name: 'model2.ckpt', path: 'models/checkpoints/model2.ckpt' }
      ],
      loras: [
        { name: 'lora1.safetensors', path: 'models/loras/lora1.safetensors' }
      ]
    }
    
    // Mock API
    vi.mocked(api.getModelInfo).mockImplementation(async (modelName) => {
      if (modelName.includes('model1')) {
        return { info: { resolution: 768 } }
      }
      return { info: { resolution: 512 } }
    })
  })

  it('should return models grouped by type', () => {
    expect(store.modelsByType.checkpoints.length).toBe(2)
    expect(store.modelsByType.loras.length).toBe(1)
  })

  it('should filter models by name', () => {
    store.searchTerm = 'model1'
    expect(store.filteredModels.checkpoints.length).toBe(1)
    expect(store.filteredModels.checkpoints[0].name).toBe('model1.safetensors')
  })
})

Mocking Dependencies in Stores

Mocking API and other dependencies:

// Example from: tests-ui/tests/store/workflowStore.test.ts
// Add mock for api at the top of the file
vi.mock('@/scripts/api', () => ({
  api: {
    getUserData: vi.fn(),
    storeUserData: vi.fn(),
    listUserDataFullInfo: vi.fn(),
    apiURL: vi.fn(),
    addEventListener: vi.fn()
  }
}))

// Mock comfyApp globally for the store setup
vi.mock('@/scripts/app', () => ({
  app: {
    canvas: null // Start with canvas potentially undefined or null
  }
}))

describe('syncWorkflows', () => {
  const syncRemoteWorkflows = async (filenames: string[]) => {
    vi.mocked(api.listUserDataFullInfo).mockResolvedValue(
      filenames.map((filename) => ({
        path: filename,
        modified: new Date().getTime(),
        size: 1 // size !== -1 for remote workflows
      }))
    )
    return await store.syncWorkflows()
  }

  it('should sync workflows', async () => {
    await syncRemoteWorkflows(['a.json', 'b.json'])
    expect(store.workflows.length).toBe(2)
  })
})

Testing Store Watchers

Testing store watchers and reactive behavior:

// Example from: tests-ui/tests/store/workflowStore.test.ts
import { nextTick } from 'vue'

describe('Subgraphs', () => {
  it('should update automatically when activeWorkflow changes', async () => {
    // Arrange: Set initial canvas state
    const initialSubgraph = {
      name: 'Initial Subgraph',
      pathToRootGraph: [{ name: 'Root' }, { name: 'Initial Subgraph' }],
      isRootGraph: false
    }
    vi.mocked(comfyApp.canvas).subgraph = initialSubgraph as any

    // Trigger initial update
    store.updateActiveGraph()
    await nextTick()

    // Verify initial state
    expect(store.isSubgraphActive).toBe(true)
    expect(store.subgraphNamePath).toEqual(['Initial Subgraph'])

    // Act: Change the active workflow and canvas state
    const workflow2 = store.createTemporary('workflow2.json')
    vi.spyOn(workflow2, 'load').mockImplementation(async () => {
      workflow2.changeTracker = { activeState: {} } as any
      workflow2.originalContent = '{}'
      workflow2.content = '{}'
      return workflow2 as LoadedComfyWorkflow
    })

    // Change canvas state
    vi.mocked(comfyApp.canvas).subgraph = undefined

    await store.openWorkflow(workflow2)
    await nextTick() // Allow watcher to trigger

    // Assert: Check state was updated by the watcher
    expect(store.isSubgraphActive).toBe(false)
    expect(store.subgraphNamePath).toEqual([])
  })
})

Testing Store Integration

Testing store integration with other parts of the application:

// Example from: tests-ui/tests/store/workflowStore.test.ts
describe('renameWorkflow', () => {
  it('should rename workflow and update bookmarks', async () => {
    const workflow = store.createTemporary('dir/test.json')
    const bookmarkStore = useWorkflowBookmarkStore()

    // Set up initial bookmark
    expect(workflow.path).toBe('workflows/dir/test.json')
    await bookmarkStore.setBookmarked(workflow.path, true)
    expect(bookmarkStore.isBookmarked(workflow.path)).toBe(true)

    // Mock super.rename
    vi.spyOn(Object.getPrototypeOf(workflow), 'rename').mockImplementation(
      async function (this: any, newPath: string) {
        this.path = newPath
        return this
      } as any
    )

    // Perform rename
    const newPath = 'workflows/dir/renamed.json'
    await store.renameWorkflow(workflow, newPath)

    // Check that bookmark was transferred
    expect(bookmarkStore.isBookmarked(newPath)).toBe(true)
    expect(bookmarkStore.isBookmarked('workflows/dir/test.json')).toBe(false)
  })
})