mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-22 05:19:03 +00:00
Enables eslint/func-style in oxlint with declaration mode to enforce function declarations over function expressions and arrow expressions assigned to variables. Vendored litegraph is excluded via override. Converts existing function expressions and variable-initialized arrow functions to function declarations across src/, browser_tests/, apps/, packages/, and scripts/. Adjusts a handful of let-reassignable callback placeholders, narrowed variable patterns, and typed widget constructors to keep type safety intact. Pre-existing type-aware oxlint errors (no-console, no-floating-promises, no-explicit-any) are unchanged from main.
279 lines
8.8 KiB
TypeScript
279 lines
8.8 KiB
TypeScript
import {
|
|
comfyExpect as expect,
|
|
comfyPageFixture as test
|
|
} from '@e2e/fixtures/ComfyPage'
|
|
|
|
test.describe('Node search box V2', { tag: '@node' }, () => {
|
|
test.beforeEach(async ({ comfyPage }) => {
|
|
await comfyPage.searchBoxV2.setup()
|
|
})
|
|
|
|
test('Can open search and add node', async ({ comfyPage }) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
const initialCount = await comfyPage.nodeOps.getGraphNodesCount()
|
|
|
|
await searchBoxV2.open()
|
|
await searchBoxV2.input.fill('KSampler')
|
|
await expect(searchBoxV2.results.first()).toBeVisible()
|
|
|
|
await comfyPage.page.keyboard.press('Enter')
|
|
await expect(searchBoxV2.input).toBeHidden()
|
|
await expect
|
|
.poll(() => comfyPage.nodeOps.getGraphNodesCount())
|
|
.toBe(initialCount + 1)
|
|
})
|
|
|
|
test('Can add first default result with Enter', async ({ comfyPage }) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
const initialCount = await comfyPage.nodeOps.getGraphNodesCount()
|
|
|
|
await searchBoxV2.open()
|
|
// Default results should be visible without typing.
|
|
await expect(searchBoxV2.results.first()).toBeVisible()
|
|
|
|
await comfyPage.page.keyboard.press('Enter')
|
|
await expect(searchBoxV2.input).toBeHidden()
|
|
await expect
|
|
.poll(() => comfyPage.nodeOps.getGraphNodesCount())
|
|
.toBe(initialCount + 1)
|
|
})
|
|
|
|
test.describe('Category navigation', () => {
|
|
test('Bookmarked filter shows only bookmarked nodes', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
await comfyPage.settings.setSetting('Comfy.NodeLibrary.Bookmarks.V2', [
|
|
'KSampler'
|
|
])
|
|
|
|
await searchBoxV2.open()
|
|
await searchBoxV2.rootCategoryButton('favorites').click()
|
|
|
|
await expect(searchBoxV2.results).toHaveCount(1)
|
|
await expect(searchBoxV2.results.first()).toContainText('KSampler')
|
|
})
|
|
|
|
test('Category filters results to matching nodes', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
|
|
await searchBoxV2.open()
|
|
await searchBoxV2.categoryButton('sampling').click()
|
|
|
|
await expect(searchBoxV2.results.first()).toBeVisible()
|
|
})
|
|
})
|
|
|
|
test.describe('Filter workflow', () => {
|
|
test('Can filter by input type via filter bar', async ({ comfyPage }) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
|
|
await searchBoxV2.open()
|
|
|
|
await test.step('Open Input filter popover', async () => {
|
|
await searchBoxV2.typeFilterButton('input').click()
|
|
await expect(searchBoxV2.filterOptions.first()).toBeVisible()
|
|
})
|
|
|
|
await test.step('Select MODEL type', async () => {
|
|
await searchBoxV2.filterSearch.fill('MODEL')
|
|
await searchBoxV2.filterOptions
|
|
.filter({ hasText: 'MODEL' })
|
|
.first()
|
|
.click()
|
|
})
|
|
|
|
await expect(searchBoxV2.filterChips).toHaveCount(1)
|
|
await expect(searchBoxV2.filterChips.first()).toContainText('MODEL')
|
|
await expect(searchBoxV2.results.first()).toBeVisible()
|
|
})
|
|
})
|
|
|
|
test.describe('Keyboard navigation', () => {
|
|
test('Can navigate and select with keyboard', async ({ comfyPage }) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
const initialCount = await comfyPage.nodeOps.getGraphNodesCount()
|
|
|
|
await searchBoxV2.open()
|
|
await searchBoxV2.input.fill('KSampler')
|
|
const results = searchBoxV2.results
|
|
await expect(results.first()).toBeVisible()
|
|
|
|
await test.step('First result is selected by default', async () => {
|
|
await expect(results.first()).toHaveAttribute('aria-selected', 'true')
|
|
})
|
|
|
|
await test.step('ArrowDown moves selection to next result', async () => {
|
|
await comfyPage.page.keyboard.press('ArrowDown')
|
|
await expect(results.nth(1)).toHaveAttribute('aria-selected', 'true')
|
|
await expect(results.first()).toHaveAttribute('aria-selected', 'false')
|
|
})
|
|
|
|
await test.step('ArrowUp moves selection back', async () => {
|
|
await comfyPage.page.keyboard.press('ArrowUp')
|
|
await expect(results.first()).toHaveAttribute('aria-selected', 'true')
|
|
})
|
|
|
|
await test.step('Enter selects and adds the node', async () => {
|
|
await comfyPage.page.keyboard.press('Enter')
|
|
await expect(searchBoxV2.input).toBeHidden()
|
|
await expect
|
|
.poll(() => comfyPage.nodeOps.getGraphNodesCount())
|
|
.toBe(initialCount + 1)
|
|
})
|
|
})
|
|
})
|
|
|
|
test.describe('Category sidebar', () => {
|
|
test('Sidebar toggle hides and shows the category sidebar', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
await searchBoxV2.open()
|
|
|
|
const samplingCategory = searchBoxV2.categoryButton('sampling')
|
|
await expect(samplingCategory).toBeVisible()
|
|
await expect(searchBoxV2.sidebarToggle).toHaveAttribute(
|
|
'aria-expanded',
|
|
'true'
|
|
)
|
|
|
|
await searchBoxV2.sidebarToggle.click()
|
|
await expect(searchBoxV2.sidebarToggle).toHaveAttribute(
|
|
'aria-expanded',
|
|
'false'
|
|
)
|
|
await expect(samplingCategory).toBeHidden()
|
|
|
|
await searchBoxV2.sidebarToggle.click()
|
|
await expect(searchBoxV2.sidebarToggle).toHaveAttribute(
|
|
'aria-expanded',
|
|
'true'
|
|
)
|
|
await expect(samplingCategory).toBeVisible()
|
|
})
|
|
|
|
test('Filter bar scrolls horizontally while the sidebar toggle stays pinned', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
// Narrow viewport so the chips overflow the filter bar
|
|
await comfyPage.page.setViewportSize({ width: 360, height: 800 })
|
|
await searchBoxV2.open()
|
|
|
|
const scrollEl = searchBoxV2.filterChipsScroll
|
|
const dims = await scrollEl.evaluate((el) => ({
|
|
scrollWidth: el.scrollWidth,
|
|
clientWidth: el.clientWidth
|
|
}))
|
|
expect(dims.scrollWidth).toBeGreaterThan(dims.clientWidth)
|
|
|
|
await scrollEl.evaluate((el) => {
|
|
el.scrollLeft = el.scrollWidth
|
|
})
|
|
|
|
// The toggle lives outside the scroll container, so even when the
|
|
// chips scroll hundreds of px it must remain visible in the viewport.
|
|
await expect(searchBoxV2.sidebarToggle).toBeInViewport()
|
|
})
|
|
|
|
test('@mobile Sidebar is collapsed by default on mobile', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
await searchBoxV2.open()
|
|
|
|
await expect(searchBoxV2.sidebarToggle).toHaveAttribute(
|
|
'aria-expanded',
|
|
'false'
|
|
)
|
|
await expect(searchBoxV2.categoryButton('sampling')).toBeHidden()
|
|
})
|
|
|
|
test('@mobile Clicking outside the sidebar closes it', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
await searchBoxV2.open()
|
|
|
|
await searchBoxV2.sidebarToggle.click()
|
|
await expect(searchBoxV2.sidebarToggle).toHaveAttribute(
|
|
'aria-expanded',
|
|
'true'
|
|
)
|
|
await expect(searchBoxV2.categoryButton('sampling')).toBeVisible()
|
|
await expect(searchBoxV2.sidebarBackdrop).toBeVisible()
|
|
|
|
// The backdrop spans the full content area, but the sidebar (z-20)
|
|
// covers its left ~208px (w-52). Click past that to land on the
|
|
// backdrop rather than the sidebar.
|
|
await searchBoxV2.sidebarBackdrop.click({ position: { x: 240, y: 40 } })
|
|
|
|
await expect(searchBoxV2.sidebarToggle).toHaveAttribute(
|
|
'aria-expanded',
|
|
'false'
|
|
)
|
|
await expect(searchBoxV2.categoryButton('sampling')).toBeHidden()
|
|
await expect(searchBoxV2.sidebarBackdrop).toBeHidden()
|
|
})
|
|
|
|
test('@mobile Focusing the search input closes the sidebar', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
await searchBoxV2.open()
|
|
|
|
await searchBoxV2.sidebarToggle.click()
|
|
await expect(searchBoxV2.sidebarToggle).toHaveAttribute(
|
|
'aria-expanded',
|
|
'true'
|
|
)
|
|
|
|
await searchBoxV2.input.focus()
|
|
|
|
await expect(searchBoxV2.sidebarToggle).toHaveAttribute(
|
|
'aria-expanded',
|
|
'false'
|
|
)
|
|
})
|
|
|
|
test('Sidebar state across mobile/desktop resizes', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { searchBoxV2 } = comfyPage
|
|
function switchToDesktop() {
|
|
return comfyPage.page.setViewportSize({ width: 1280, height: 800 })
|
|
}
|
|
function switchToMobile() {
|
|
return comfyPage.page.setViewportSize({ width: 360, height: 800 })
|
|
}
|
|
function expectExpanded(value: 'true' | 'false') {
|
|
return expect(searchBoxV2.sidebarToggle).toHaveAttribute(
|
|
'aria-expanded',
|
|
value
|
|
)
|
|
}
|
|
|
|
await switchToDesktop()
|
|
await searchBoxV2.open()
|
|
await expectExpanded('true')
|
|
|
|
await switchToMobile()
|
|
await expectExpanded('false')
|
|
|
|
await searchBoxV2.sidebarToggle.click()
|
|
await switchToDesktop()
|
|
await expectExpanded('true')
|
|
|
|
await searchBoxV2.sidebarToggle.click()
|
|
await switchToMobile()
|
|
await expectExpanded('false')
|
|
|
|
await switchToDesktop()
|
|
await expectExpanded('false')
|
|
})
|
|
})
|
|
})
|