mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-12 10:17:08 +00:00
Compare commits
2 Commits
prompt-nod
...
matt/be-22
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0df1a5456 | ||
|
|
49d4f252b3 |
@@ -0,0 +1,50 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/vue'
|
||||
import { h } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import PanelSearchHeader from './PanelSearchHeader.vue'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: { en: { g: { searchPlaceholder: 'Search', clear: 'Clear' } } }
|
||||
})
|
||||
|
||||
function renderHeader(searcher: (query: string) => Promise<void>) {
|
||||
return render(PanelSearchHeader, {
|
||||
props: { searcher, updateKey: [], modelValue: '' },
|
||||
slots: { default: () => h('button', { 'data-testid': 'slot' }, 'toggle') },
|
||||
global: { plugins: [i18n] }
|
||||
})
|
||||
}
|
||||
|
||||
describe('PanelSearchHeader', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('forwards the searcher to the embedded search input', async () => {
|
||||
const searcher = vi.fn().mockResolvedValue(undefined)
|
||||
renderHeader(searcher)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(searcher).toHaveBeenCalledWith('', expect.any(Function))
|
||||
)
|
||||
})
|
||||
|
||||
it('renders default slot content alongside the search input', () => {
|
||||
renderHeader(vi.fn().mockResolvedValue(undefined))
|
||||
|
||||
expect(screen.getByTestId('slot')).toBeTruthy()
|
||||
expect(screen.getByRole('textbox')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('emits query updates from the search input through v-model', async () => {
|
||||
const { emitted } = renderHeader(vi.fn().mockResolvedValue(undefined))
|
||||
|
||||
await fireEvent.update(screen.getByRole('textbox'), 'seed')
|
||||
|
||||
expect(emitted()['update:modelValue']).toContainEqual(['seed'])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||
|
||||
import AsyncSearchInput from '@/components/ui/search-input/AsyncSearchInput.vue'
|
||||
|
||||
type AsyncSearchInputProps = ComponentProps<typeof AsyncSearchInput>
|
||||
|
||||
const { searcher, updateKey } = defineProps<{
|
||||
searcher?: AsyncSearchInputProps['searcher']
|
||||
updateKey?: AsyncSearchInputProps['updateKey']
|
||||
}>()
|
||||
|
||||
const query = defineModel<string>({ default: '' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center border-b border-interface-stroke px-4 pt-1 pb-4"
|
||||
>
|
||||
<AsyncSearchInput
|
||||
v-model="query"
|
||||
:searcher
|
||||
:update-key="updateKey"
|
||||
class="flex-1"
|
||||
/>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,39 @@
|
||||
import { render, screen } from '@testing-library/vue'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import TabGlobalParameters from './TabGlobalParameters.vue'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
g: { searchPlaceholder: 'Search', clear: 'Clear' },
|
||||
rightSidePanel: { favorites: 'Favorites', favoritesNone: 'No favorites' }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function renderTab() {
|
||||
return render(TabGlobalParameters, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
stubs: { SectionWidgets: true }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('TabGlobalParameters', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
it('renders the shared search header', () => {
|
||||
renderTab()
|
||||
|
||||
expect(screen.getByRole('textbox')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -3,12 +3,12 @@ import { storeToRefs } from 'pinia'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import AsyncSearchInput from '@/components/ui/search-input/AsyncSearchInput.vue'
|
||||
import { useFavoritedWidgetsStore } from '@/stores/workspace/favoritedWidgetsStore'
|
||||
import type { ValidFavoritedWidget } from '@/stores/workspace/favoritedWidgetsStore'
|
||||
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||
|
||||
import { searchWidgets } from '../shared'
|
||||
import PanelSearchHeader from './PanelSearchHeader.vue'
|
||||
import SectionWidgets from './SectionWidgets.vue'
|
||||
|
||||
const favoritedWidgetsStore = useFavoritedWidgetsStore()
|
||||
@@ -55,16 +55,11 @@ function handleReorder({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center border-b border-interface-stroke px-4 pt-1 pb-4"
|
||||
>
|
||||
<AsyncSearchInput
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
:update-key="favoritedWidgets"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
<PanelSearchHeader
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
:update-key="favoritedWidgets"
|
||||
/>
|
||||
<SectionWidgets
|
||||
:label
|
||||
:widgets="searchedFavoritedWidgets"
|
||||
|
||||
48
src/components/rightSidePanel/parameters/TabNodes.test.ts
Normal file
48
src/components/rightSidePanel/parameters/TabNodes.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { render, screen } from '@testing-library/vue'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import TabNodes from './TabNodes.vue'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
g: { searchPlaceholder: 'Search', clear: 'Clear' },
|
||||
rightSidePanel: { noneSearchDesc: 'None', inputsNoneTooltip: '' }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const CollapseToggleButtonStub = {
|
||||
props: ['modelValue', 'show'],
|
||||
template: '<button data-testid="collapse-toggle" />'
|
||||
}
|
||||
|
||||
function renderTab() {
|
||||
return render(TabNodes, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
stubs: {
|
||||
SectionWidgets: true,
|
||||
CollapseToggleButton: CollapseToggleButtonStub
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('TabNodes', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
it('composes the shared search header with the collapse toggle in its slot', () => {
|
||||
renderTab()
|
||||
|
||||
expect(screen.getByRole('textbox')).toBeTruthy()
|
||||
expect(screen.getByTestId('collapse-toggle')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -7,12 +7,12 @@ import CollapseToggleButton from '@/components/rightSidePanel/layout/CollapseTog
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import AsyncSearchInput from '@/components/ui/search-input/AsyncSearchInput.vue'
|
||||
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
|
||||
import { computedSectionDataList, searchWidgetsAndNodes } from '../shared'
|
||||
import type { NodeWidgetsListList } from '../shared'
|
||||
import PanelSearchHeader from './PanelSearchHeader.vue'
|
||||
import SectionWidgets from './SectionWidgets.vue'
|
||||
|
||||
const canvasStore = useCanvasStore()
|
||||
@@ -76,20 +76,16 @@ async function searcher(query: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center border-b border-interface-stroke px-4 pt-1 pb-4"
|
||||
<PanelSearchHeader
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
:update-key="widgetsSectionDataList"
|
||||
>
|
||||
<AsyncSearchInput
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
:update-key="widgetsSectionDataList"
|
||||
class="flex-1"
|
||||
/>
|
||||
<CollapseToggleButton
|
||||
v-model="isAllCollapsed"
|
||||
:show="!isSearching && widgetsSectionDataList.length > 1"
|
||||
/>
|
||||
</div>
|
||||
</PanelSearchHeader>
|
||||
<TransitionGroup tag="div" name="list-scale" class="relative">
|
||||
<div
|
||||
v-if="isSearching && searchedWidgetsSectionDataList.length === 0"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { render, screen } from '@testing-library/vue'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import TabNormalInputs from './TabNormalInputs.vue'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
g: { searchPlaceholder: 'Search', clear: 'Clear' },
|
||||
rightSidePanel: { advanced: 'Advanced', inputsNone: 'None' }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const CollapseToggleButtonStub = {
|
||||
props: ['modelValue', 'show'],
|
||||
template: '<button data-testid="collapse-toggle" />'
|
||||
}
|
||||
|
||||
function renderTab() {
|
||||
return render(TabNormalInputs, {
|
||||
props: { nodes: [] },
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
stubs: {
|
||||
SectionWidgets: true,
|
||||
CollapseToggleButton: CollapseToggleButtonStub
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('TabNormalInputs', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
it('composes the shared search header with the collapse toggle in its slot', () => {
|
||||
renderTab()
|
||||
|
||||
expect(screen.getByRole('textbox')).toBeTruthy()
|
||||
expect(screen.getByTestId('collapse-toggle')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -5,13 +5,13 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import CollapseToggleButton from '@/components/rightSidePanel/layout/CollapseToggleButton.vue'
|
||||
import AsyncSearchInput from '@/components/ui/search-input/AsyncSearchInput.vue'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
|
||||
import { computedSectionDataList, searchWidgetsAndNodes } from '../shared'
|
||||
import type { NodeWidgetsListList } from '../shared'
|
||||
import PanelSearchHeader from './PanelSearchHeader.vue'
|
||||
import SectionWidgets from './SectionWidgets.vue'
|
||||
|
||||
const { nodes, mustShowNodeTitle } = defineProps<{
|
||||
@@ -124,15 +124,11 @@ const advancedLabel = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center border-b border-interface-stroke px-4 pt-1 pb-4"
|
||||
<PanelSearchHeader
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
:update-key="widgetsSectionDataList"
|
||||
>
|
||||
<AsyncSearchInput
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
:update-key="widgetsSectionDataList"
|
||||
class="flex-1"
|
||||
/>
|
||||
<CollapseToggleButton
|
||||
v-model="isAllCollapsed"
|
||||
:show="
|
||||
@@ -141,7 +137,7 @@ const advancedLabel = computed(() => {
|
||||
1
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</PanelSearchHeader>
|
||||
<TransitionGroup tag="div" name="list-scale" class="relative">
|
||||
<div
|
||||
v-if="searchedWidgetsSectionDataList.length === 0"
|
||||
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
} from '@/core/graph/subgraph/promotionUtils'
|
||||
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import AsyncSearchInput from '@/components/ui/search-input/AsyncSearchInput.vue'
|
||||
import CollapseToggleButton from '@/components/rightSidePanel/layout/CollapseToggleButton.vue'
|
||||
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||
|
||||
import { searchWidgets } from '../shared'
|
||||
import type { NodeWidgetsList } from '../shared'
|
||||
import PanelSearchHeader from './PanelSearchHeader.vue'
|
||||
import SectionWidgets from './SectionWidgets.vue'
|
||||
|
||||
const { node } = defineProps<{
|
||||
@@ -122,20 +122,12 @@ const label = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center border-b border-interface-stroke px-4 pt-1 pb-4"
|
||||
>
|
||||
<AsyncSearchInput
|
||||
v-model="searchQuery"
|
||||
:searcher
|
||||
:update-key="widgetsList"
|
||||
class="flex-1"
|
||||
/>
|
||||
<PanelSearchHeader v-model="searchQuery" :searcher :update-key="widgetsList">
|
||||
<CollapseToggleButton
|
||||
v-model="isAllCollapsed"
|
||||
:show="!isSearching && advancedInputsWidgets.length > 0"
|
||||
/>
|
||||
</div>
|
||||
</PanelSearchHeader>
|
||||
<SectionWidgets
|
||||
:collapse="firstSectionCollapsed && !isSearching"
|
||||
:node
|
||||
|
||||
Reference in New Issue
Block a user