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>
This commit is contained in:
Alexander Brown
2026-01-05 16:32:24 -08:00
committed by GitHub
parent 832588c7a9
commit 10feb1fd5b
272 changed files with 483 additions and 1239 deletions

View File

@@ -0,0 +1,166 @@
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import type { BottomPanelExtension } from '@/types/extensionTypes'
// Mock dependencies
vi.mock('@/composables/bottomPanelTabs/useShortcutsTab', () => ({
useShortcutsTab: () => [
{
id: 'shortcuts-essentials',
title: 'Essentials',
component: {},
type: 'vue',
targetPanel: 'shortcuts'
},
{
id: 'shortcuts-view-controls',
title: 'View Controls',
component: {},
type: 'vue',
targetPanel: 'shortcuts'
}
]
}))
vi.mock('@/composables/bottomPanelTabs/useTerminalTabs', () => ({
useLogsTerminalTab: () => ({
id: 'logs',
title: 'Logs',
component: {},
type: 'vue',
targetPanel: 'terminal'
}),
useCommandTerminalTab: () => ({
id: 'command',
title: 'Command',
component: {},
type: 'vue',
targetPanel: 'terminal'
})
}))
vi.mock('@/stores/commandStore', () => ({
useCommandStore: () => ({
registerCommand: vi.fn()
})
}))
vi.mock('@/utils/envUtil', () => ({
isElectron: () => false
}))
describe('useBottomPanelStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('should initialize with empty panels', () => {
const store = useBottomPanelStore()
expect(store.activePanel).toBeNull()
expect(store.bottomPanelVisible).toBe(false)
expect(store.bottomPanelTabs).toEqual([])
expect(store.activeBottomPanelTab).toBeNull()
})
it('should register bottom panel tabs', () => {
const store = useBottomPanelStore()
const tab: BottomPanelExtension = {
id: 'test-tab',
title: 'Test Tab',
component: {},
type: 'vue',
targetPanel: 'terminal'
}
store.registerBottomPanelTab(tab)
expect(store.panels.terminal.tabs.find((t) => t.id === 'test-tab')).toEqual(
tab
)
expect(store.panels.terminal.activeTabId).toBe('test-tab')
})
it('should toggle panel visibility', () => {
const store = useBottomPanelStore()
const tab: BottomPanelExtension = {
id: 'test-tab',
title: 'Test Tab',
component: {},
type: 'vue',
targetPanel: 'shortcuts'
}
store.registerBottomPanelTab(tab)
// Panel should be hidden initially
expect(store.activePanel).toBeNull()
// Toggle should show panel
store.togglePanel('shortcuts')
expect(store.activePanel).toBe('shortcuts')
expect(store.bottomPanelVisible).toBe(true)
// Toggle again should hide panel
store.togglePanel('shortcuts')
expect(store.activePanel).toBeNull()
expect(store.bottomPanelVisible).toBe(false)
})
it('should switch between panel types', () => {
const store = useBottomPanelStore()
const terminalTab: BottomPanelExtension = {
id: 'terminal-tab',
title: 'Terminal',
component: {},
type: 'vue',
targetPanel: 'terminal'
}
const shortcutsTab: BottomPanelExtension = {
id: 'shortcuts-tab',
title: 'Shortcuts',
component: {},
type: 'vue',
targetPanel: 'shortcuts'
}
store.registerBottomPanelTab(terminalTab)
store.registerBottomPanelTab(shortcutsTab)
// Show terminal panel
store.togglePanel('terminal')
expect(store.activePanel).toBe('terminal')
expect(store.activeBottomPanelTab?.id).toBe('terminal-tab')
// Switch to shortcuts panel
store.togglePanel('shortcuts')
expect(store.activePanel).toBe('shortcuts')
expect(store.activeBottomPanelTab?.id).toBe('shortcuts-tab')
})
it('should toggle specific tabs', () => {
const store = useBottomPanelStore()
const tab: BottomPanelExtension = {
id: 'specific-tab',
title: 'Specific Tab',
component: {},
type: 'vue',
targetPanel: 'shortcuts'
}
store.registerBottomPanelTab(tab)
// Toggle specific tab should show it
store.toggleBottomPanelTab('specific-tab')
expect(store.activePanel).toBe('shortcuts')
expect(store.panels.shortcuts.activeTabId).toBe('specific-tab')
// Toggle same tab again should hide panel
store.toggleBottomPanelTab('specific-tab')
expect(store.activePanel).toBeNull()
})
})

View File

@@ -0,0 +1,399 @@
import { flushPromises } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore'
vi.mock('@/scripts/api', () => ({
api: {
fileURL: vi.fn((url) => url)
}
}))
vi.mock('@/i18n', () => ({
i18n: {
global: {
locale: {
value: 'en'
}
}
}
}))
vi.mock('@/types/nodeSource', () => ({
NodeSourceType: {
Core: 'core',
CustomNodes: 'custom_nodes'
},
getNodeSource: vi.fn((pythonModule) => {
if (pythonModule?.startsWith('custom_nodes.')) {
return { type: 'custom_nodes' }
}
return { type: 'core' }
})
}))
vi.mock('dompurify', () => ({
default: {
sanitize: vi.fn((html) => html)
}
}))
vi.mock('marked', () => ({
marked: {
parse: vi.fn((markdown, options) => {
if (options?.renderer) {
if (markdown.includes('![')) {
const matches = markdown.match(/!\[(.*?)\]\((.*?)\)/)
if (matches) {
const [, text, href] = matches
return options.renderer.image({ href, text, title: '' })
}
}
}
return `<p>${markdown}</p>`
})
},
Renderer: class Renderer {
image = vi.fn(
({ href, title, text }) =>
`<img src="${href}" alt="${text}"${title ? ` title="${title}"` : ''} />`
)
link = vi.fn(
({ href, title, text }) =>
`<a href="${href}"${title ? ` title="${title}"` : ''}>${text}</a>`
)
}
}))
describe('nodeHelpStore', () => {
// Define a mock node for testing
const mockCoreNode = {
name: 'TestNode',
display_name: 'Test Node',
description: 'A test node',
inputs: {},
outputs: [],
python_module: 'comfy.test_node'
}
const mockCustomNode = {
name: 'CustomNode',
display_name: 'Custom Node',
description: 'A custom node',
inputs: {},
outputs: [],
python_module: 'custom_nodes.test_module.custom@1.0.0'
}
// Mock fetch responses
const mockFetch = vi.fn()
global.fetch = mockFetch
beforeEach(() => {
// Setup Pinia
setActivePinia(createPinia())
mockFetch.mockReset()
})
it('should initialize with empty state', () => {
const nodeHelpStore = useNodeHelpStore()
expect(nodeHelpStore.currentHelpNode).toBeNull()
expect(nodeHelpStore.isHelpOpen).toBe(false)
})
it('should open help for a node', () => {
const nodeHelpStore = useNodeHelpStore()
nodeHelpStore.openHelp(mockCoreNode as any)
expect(nodeHelpStore.currentHelpNode).toStrictEqual(mockCoreNode)
expect(nodeHelpStore.isHelpOpen).toBe(true)
})
it('should close help', () => {
const nodeHelpStore = useNodeHelpStore()
nodeHelpStore.openHelp(mockCoreNode as any)
expect(nodeHelpStore.isHelpOpen).toBe(true)
nodeHelpStore.closeHelp()
expect(nodeHelpStore.currentHelpNode).toBeNull()
expect(nodeHelpStore.isHelpOpen).toBe(false)
})
it('should generate correct baseUrl for core nodes', async () => {
const nodeHelpStore = useNodeHelpStore()
nodeHelpStore.openHelp(mockCoreNode as any)
await nextTick()
expect(nodeHelpStore.baseUrl).toBe(`/docs/${mockCoreNode.name}/`)
})
it('should generate correct baseUrl for custom nodes', async () => {
const nodeHelpStore = useNodeHelpStore()
nodeHelpStore.openHelp(mockCustomNode as any)
await nextTick()
expect(nodeHelpStore.baseUrl).toBe('/extensions/test_module/docs/')
})
it('should render markdown content correctly', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () => '# Test Help\nThis is test help content'
})
nodeHelpStore.openHelp(mockCoreNode as any)
await flushPromises()
expect(nodeHelpStore.renderedHelpHtml).toContain(
'This is test help content'
)
})
it('should handle fetch errors and fall back to description', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: false,
statusText: 'Not Found'
})
nodeHelpStore.openHelp(mockCoreNode as any)
await flushPromises()
expect(nodeHelpStore.error).toBe('Not Found')
expect(nodeHelpStore.renderedHelpHtml).toContain(mockCoreNode.description)
})
it('should include alt attribute for images', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () => '![image](test.jpg)'
})
nodeHelpStore.openHelp(mockCustomNode as any)
await flushPromises()
expect(nodeHelpStore.renderedHelpHtml).toContain('alt="image"')
})
it('should prefix relative video src in custom nodes', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () => '<video src="video.mp4"></video>'
})
nodeHelpStore.openHelp(mockCustomNode as any)
await flushPromises()
expect(nodeHelpStore.renderedHelpHtml).toContain(
'src="/extensions/test_module/docs/video.mp4"'
)
})
it('should prefix relative video src for core nodes with node-specific base URL', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () => '<video src="video.mp4"></video>'
})
nodeHelpStore.openHelp(mockCoreNode as any)
await flushPromises()
expect(nodeHelpStore.renderedHelpHtml).toContain(
`src="/docs/${mockCoreNode.name}/video.mp4"`
)
})
it('should prefix relative source src in custom nodes', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () =>
'<video><source src="video.mp4" type="video/mp4" /></video>'
})
nodeHelpStore.openHelp(mockCustomNode as any)
await flushPromises()
expect(nodeHelpStore.renderedHelpHtml).toContain(
'src="/extensions/test_module/docs/video.mp4"'
)
})
it('should prefix relative source src for core nodes with node-specific base URL', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () =>
'<video><source src="video.webm" type="video/webm" /></video>'
})
nodeHelpStore.openHelp(mockCoreNode as any)
await flushPromises()
expect(nodeHelpStore.renderedHelpHtml).toContain(
`src="/docs/${mockCoreNode.name}/video.webm"`
)
})
it('should handle loading state', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockImplementationOnce(() => new Promise(() => {})) // Never resolves
nodeHelpStore.openHelp(mockCoreNode as any)
await nextTick()
expect(nodeHelpStore.isLoading).toBe(true)
})
it('should try fallback URL for custom nodes', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch
.mockResolvedValueOnce({
ok: false,
statusText: 'Not Found'
})
.mockResolvedValueOnce({
ok: true,
text: async () => '# Fallback content'
})
nodeHelpStore.openHelp(mockCustomNode as any)
await flushPromises()
expect(mockFetch).toHaveBeenCalledTimes(2)
expect(mockFetch).toHaveBeenCalledWith(
'/extensions/test_module/docs/CustomNode/en.md'
)
expect(mockFetch).toHaveBeenCalledWith(
'/extensions/test_module/docs/CustomNode.md'
)
})
it('should prefix relative img src in raw HTML for custom nodes', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () => '# Test\n<img src="image.png" alt="Test image">'
})
nodeHelpStore.openHelp(mockCustomNode as any)
await flushPromises()
expect(nodeHelpStore.renderedHelpHtml).toContain(
'src="/extensions/test_module/docs/image.png"'
)
expect(nodeHelpStore.renderedHelpHtml).toContain('alt="Test image"')
})
it('should prefix relative img src in raw HTML for core nodes', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () => '# Test\n<img src="image.png" alt="Test image">'
})
nodeHelpStore.openHelp(mockCoreNode as any)
await flushPromises()
expect(nodeHelpStore.renderedHelpHtml).toContain(
`src="/docs/${mockCoreNode.name}/image.png"`
)
expect(nodeHelpStore.renderedHelpHtml).toContain('alt="Test image"')
})
it('should not prefix absolute img src in raw HTML', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () => '<img src="/absolute/image.png" alt="Absolute">'
})
nodeHelpStore.openHelp(mockCustomNode as any)
await flushPromises()
expect(nodeHelpStore.renderedHelpHtml).toContain(
'src="/absolute/image.png"'
)
expect(nodeHelpStore.renderedHelpHtml).toContain('alt="Absolute"')
})
it('should not prefix external img src in raw HTML', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () =>
'<img src="https://example.com/image.png" alt="External">'
})
nodeHelpStore.openHelp(mockCustomNode as any)
await flushPromises()
expect(nodeHelpStore.renderedHelpHtml).toContain(
'src="https://example.com/image.png"'
)
expect(nodeHelpStore.renderedHelpHtml).toContain('alt="External"')
})
it('should handle various quote styles in media src attributes', async () => {
const nodeHelpStore = useNodeHelpStore()
mockFetch.mockResolvedValueOnce({
ok: true,
text: async () => `# Media Test
Testing quote styles in properly formed HTML:
<video src="video1.mp4" controls></video>
<video src='video2.mp4' controls></video>
<img src="image1.png" alt="Double quotes">
<img src='image2.png' alt='Single quotes'>
<video controls>
<source src="video3.mp4" type="video/mp4">
<source src='video3.webm' type='video/webm'>
</video>
The MEDIA_SRC_REGEX handles both single and double quotes in img, video and source tags.`
})
nodeHelpStore.openHelp(mockCoreNode as any)
await flushPromises()
// Check that all media elements with different quote styles are prefixed correctly
// Double quotes remain as double quotes
expect(nodeHelpStore.renderedHelpHtml).toContain(
`src="/docs/${mockCoreNode.name}/video1.mp4"`
)
expect(nodeHelpStore.renderedHelpHtml).toContain(
`src="/docs/${mockCoreNode.name}/image1.png"`
)
expect(nodeHelpStore.renderedHelpHtml).toContain(
`src="/docs/${mockCoreNode.name}/video3.mp4"`
)
// Single quotes remain as single quotes in the output
expect(nodeHelpStore.renderedHelpHtml).toContain(
`src='/docs/${mockCoreNode.name}/video2.mp4'`
)
expect(nodeHelpStore.renderedHelpHtml).toContain(
`src='/docs/${mockCoreNode.name}/image2.png'`
)
expect(nodeHelpStore.renderedHelpHtml).toContain(
`src='/docs/${mockCoreNode.name}/video3.webm'`
)
})
})

View File

@@ -0,0 +1,137 @@
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type NodeSearchBoxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
import type { useSettingStore } from '@/platform/settings/settingStore'
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
// Mock dependencies
vi.mock('@vueuse/core', () => ({
useMouse: vi.fn(() => ({
x: { value: 100 },
y: { value: 200 }
}))
}))
const mockSettingStore = createMockSettingStore()
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: vi.fn(() => mockSettingStore)
}))
function createMockPopover(): InstanceType<typeof NodeSearchBoxPopover> {
return { showSearchBox: vi.fn() } satisfies Partial<
InstanceType<typeof NodeSearchBoxPopover>
> as unknown as InstanceType<typeof NodeSearchBoxPopover>
}
function createMockSettingStore(): ReturnType<typeof useSettingStore> {
return {
get: vi.fn()
} satisfies Partial<
ReturnType<typeof useSettingStore>
> as unknown as ReturnType<typeof useSettingStore>
}
describe('useSearchBoxStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.restoreAllMocks()
})
describe('when user has new search box enabled', () => {
beforeEach(() => {
vi.mocked(mockSettingStore.get).mockReturnValue('default')
})
it('should show new search box is enabled', () => {
const store = useSearchBoxStore()
expect(store.newSearchBoxEnabled).toBe(true)
})
it('should toggle search box visibility when user presses shortcut', () => {
const store = useSearchBoxStore()
expect(store.visible).toBe(false)
store.toggleVisible()
expect(store.visible).toBe(true)
store.toggleVisible()
expect(store.visible).toBe(false)
})
})
describe('when user has legacy search box enabled', () => {
beforeEach(() => {
vi.mocked(mockSettingStore.get).mockReturnValue('legacy')
})
it('should show new search box is disabled', () => {
const store = useSearchBoxStore()
expect(store.newSearchBoxEnabled).toBe(false)
})
it('should open legacy search box at mouse position when user presses shortcut', () => {
const store = useSearchBoxStore()
const mockPopover = createMockPopover()
store.setPopoverRef(mockPopover)
expect(vi.mocked(store.visible)).toBe(false)
store.toggleVisible()
expect(vi.mocked(store.visible)).toBe(false) // Doesn't become visible in legacy mode.
expect(vi.mocked(mockPopover.showSearchBox)).toHaveBeenCalledWith(
expect.objectContaining({
clientX: 100,
clientY: 200
})
)
})
it('should do nothing when user presses shortcut but popover is not ready', () => {
const store = useSearchBoxStore()
store.setPopoverRef(null)
store.toggleVisible()
expect(store.visible).toBe(false)
})
})
describe('when user configures popover reference', () => {
beforeEach(() => {
vi.mocked(mockSettingStore.get).mockReturnValue('legacy')
})
it('should enable legacy search when popover is set', () => {
const store = useSearchBoxStore()
const mockPopover = createMockPopover()
store.setPopoverRef(mockPopover)
store.toggleVisible()
expect(vi.mocked(mockPopover.showSearchBox)).toHaveBeenCalled()
})
it('should disable legacy search when popover is cleared', () => {
const store = useSearchBoxStore()
const mockPopover = createMockPopover()
store.setPopoverRef(mockPopover)
store.setPopoverRef(null)
store.toggleVisible()
expect(vi.mocked(mockPopover.showSearchBox)).not.toHaveBeenCalled()
})
})
describe('when user first loads the application', () => {
it('should have search box hidden by default', () => {
const store = useSearchBoxStore()
expect(store.visible).toBe(false)
})
})
})