mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-06-26 09:37:36 +00:00
Compare commits
7 Commits
DynamicGro
...
glary/remo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f62238f465 | ||
|
|
dd59b61780 | ||
|
|
2447a9282b | ||
|
|
6fef9f9382 | ||
|
|
afe77ec928 | ||
|
|
dc9eda326a | ||
|
|
655596b26d |
@@ -8,16 +8,12 @@ export class Topbar {
|
||||
private readonly menuTrigger: Locator
|
||||
readonly newWorkflowButton: Locator
|
||||
readonly workflowTabs: Locator
|
||||
readonly integratedTabBarActions: Locator
|
||||
|
||||
constructor(public readonly page: Page) {
|
||||
this.menuLocator = page.locator('.comfy-command-menu')
|
||||
this.menuTrigger = page.locator('.comfy-menu-button-wrapper')
|
||||
this.newWorkflowButton = page.locator('.new-blank-workflow-button')
|
||||
this.workflowTabs = page.getByTestId(TestIds.topbar.workflowTabs)
|
||||
this.integratedTabBarActions = this.workflowTabs.getByTestId(
|
||||
TestIds.topbar.integratedTabBarActions
|
||||
)
|
||||
}
|
||||
|
||||
async getTabNames(): Promise<string[]> {
|
||||
|
||||
@@ -92,7 +92,6 @@ export const TestIds = {
|
||||
loginButtonPopover: 'login-button-popover',
|
||||
loginButtonPopoverLearnMore: 'login-button-popover-learn-more',
|
||||
workflowTabs: 'topbar-workflow-tabs',
|
||||
integratedTabBarActions: 'integrated-tab-bar-actions',
|
||||
actionBarButtons: 'action-bar-buttons'
|
||||
},
|
||||
nodeLibrary: {
|
||||
|
||||
@@ -78,22 +78,6 @@ test.describe('Layout & sidebar settings', { tag: ['@settings'] }, () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Comfy.UI.TabBarLayout', () => {
|
||||
test('"Default" renders integrated tab bar actions container', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UI.TabBarLayout', 'Default')
|
||||
await expect(comfyPage.menu.topbar.integratedTabBarActions).toBeAttached()
|
||||
})
|
||||
|
||||
test('"Legacy" does not render integrated tab bar actions container', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting('Comfy.UI.TabBarLayout', 'Legacy')
|
||||
await expect(comfyPage.menu.topbar.integratedTabBarActions).toHaveCount(0)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Comfy.TreeExplorer.ItemPadding', () => {
|
||||
// The setting writes a CSS var consumed by .p-tree-node-content,
|
||||
// which only renders in the legacy PrimeVue Tree.
|
||||
|
||||
@@ -47,6 +47,19 @@ test.describe('Login Button', { tag: ['@ui'] }, () => {
|
||||
comfyPage.page.getByTestId(TestIds.topbar.loginButton)
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('button falls back to TopMenuSection when workflow tabs are in sidebar', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.Workflow.WorkflowTabsPosition',
|
||||
'Sidebar'
|
||||
)
|
||||
await enableLoginButtonFlag(comfyPage.page)
|
||||
await expect(
|
||||
comfyPage.page.getByTestId(TestIds.topbar.loginButton)
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('ARIA', () => {
|
||||
|
||||
@@ -24,6 +24,7 @@ import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
||||
const mockData = vi.hoisted(() => ({
|
||||
isLoggedIn: false,
|
||||
isDesktop: false,
|
||||
isCloud: false,
|
||||
setShowConflictRedDot: (_value: boolean) => {}
|
||||
}))
|
||||
|
||||
@@ -36,7 +37,9 @@ vi.mock('@/composables/auth/useCurrentUser', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/distribution/types', () => ({
|
||||
isCloud: false,
|
||||
get isCloud() {
|
||||
return mockData.isCloud
|
||||
},
|
||||
isNightly: false,
|
||||
get isDesktop() {
|
||||
return mockData.isDesktop
|
||||
@@ -193,54 +196,41 @@ describe('TopMenuSection', () => {
|
||||
localStorage.clear()
|
||||
mockData.isDesktop = false
|
||||
mockData.isLoggedIn = false
|
||||
mockData.isCloud = false
|
||||
mockData.setShowConflictRedDot(false)
|
||||
})
|
||||
|
||||
describe('authentication state', () => {
|
||||
function createLegacyTabBarWrapper() {
|
||||
describe('auth fallback when workflow tabs are not in topbar', () => {
|
||||
function createSidebarTabsWrapper() {
|
||||
const pinia = createTestingPinia({ createSpy: vi.fn })
|
||||
const settingStore = useSettingStore(pinia)
|
||||
vi.mocked(settingStore.get).mockImplementation((key) =>
|
||||
key === 'Comfy.UI.TabBarLayout' ? 'Legacy' : undefined
|
||||
key === 'Comfy.Workflow.WorkflowTabsPosition' ? 'Sidebar' : undefined
|
||||
)
|
||||
return createWrapper({ pinia })
|
||||
}
|
||||
|
||||
describe('when user is logged in', () => {
|
||||
beforeEach(() => {
|
||||
mockData.isLoggedIn = true
|
||||
})
|
||||
|
||||
it('should display CurrentUserButton and not display LoginButton', () => {
|
||||
const { container } = createLegacyTabBarWrapper()
|
||||
expect(
|
||||
container.querySelector('current-user-button-stub')
|
||||
).not.toBeNull()
|
||||
expect(container.querySelector('login-button-stub')).toBeNull()
|
||||
})
|
||||
it('should display CurrentUserButton when user is logged in', () => {
|
||||
mockData.isLoggedIn = true
|
||||
const { container } = createSidebarTabsWrapper()
|
||||
expect(container.querySelector('current-user-button-stub')).not.toBeNull()
|
||||
expect(container.querySelector('login-button-stub')).toBeNull()
|
||||
})
|
||||
|
||||
describe('when user is not logged in', () => {
|
||||
beforeEach(() => {
|
||||
mockData.isLoggedIn = false
|
||||
})
|
||||
it('should display LoginButton when user is not logged in on desktop', () => {
|
||||
mockData.isLoggedIn = false
|
||||
mockData.isDesktop = true
|
||||
const { container } = createSidebarTabsWrapper()
|
||||
expect(container.querySelector('login-button-stub')).not.toBeNull()
|
||||
expect(container.querySelector('current-user-button-stub')).toBeNull()
|
||||
})
|
||||
|
||||
describe('on desktop platform', () => {
|
||||
it('should display LoginButton and not display CurrentUserButton', () => {
|
||||
mockData.isDesktop = true
|
||||
const { container } = createLegacyTabBarWrapper()
|
||||
expect(container.querySelector('login-button-stub')).not.toBeNull()
|
||||
expect(container.querySelector('current-user-button-stub')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('on web platform', () => {
|
||||
it('should not display CurrentUserButton and not display LoginButton', () => {
|
||||
const { container } = createLegacyTabBarWrapper()
|
||||
expect(container.querySelector('current-user-button-stub')).toBeNull()
|
||||
expect(container.querySelector('login-button-stub')).toBeNull()
|
||||
})
|
||||
})
|
||||
it('should display CurrentUserButton when user is logged out on cloud', () => {
|
||||
mockData.isLoggedIn = false
|
||||
mockData.isCloud = true
|
||||
const { container } = createSidebarTabsWrapper()
|
||||
expect(container.querySelector('current-user-button-stub')).not.toBeNull()
|
||||
expect(container.querySelector('login-button-stub')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -557,7 +547,7 @@ describe('TopMenuSection', () => {
|
||||
const settingStore = useSettingStore(pinia)
|
||||
vi.mocked(settingStore.get).mockImplementation((key) => {
|
||||
if (key === 'Comfy.UseNewMenu') return 'Top'
|
||||
if (key === 'Comfy.UI.TabBarLayout') return 'Integrated'
|
||||
if (key === 'Comfy.UI.TabBarLayout') return 'Default'
|
||||
if (key === 'Comfy.RightSidePanel.IsOpen') return true
|
||||
return undefined
|
||||
})
|
||||
|
||||
@@ -49,10 +49,12 @@
|
||||
@update:progress-target="updateProgressTarget"
|
||||
/>
|
||||
<CurrentUserButton
|
||||
v-if="isLoggedIn && !isIntegratedTabBar"
|
||||
v-if="showCurrentUser && !isWorkflowTabsInTopbar"
|
||||
class="shrink-0"
|
||||
/>
|
||||
<LoginButton v-else-if="isDesktop && !isIntegratedTabBar" />
|
||||
<LoginButton
|
||||
v-else-if="showLoginButton && !isWorkflowTabsInTopbar"
|
||||
/>
|
||||
<Button
|
||||
v-if="isCloud && flags.workflowSharingEnabled"
|
||||
v-tooltip.bottom="shareTooltipConfig"
|
||||
@@ -189,6 +191,13 @@ const isActionbarEnabled = computed(
|
||||
const isActionbarFloating = computed(
|
||||
() => isActionbarEnabled.value && !isActionbarDocked.value
|
||||
)
|
||||
const isWorkflowTabsInTopbar = computed(
|
||||
() => settingStore.get('Comfy.Workflow.WorkflowTabsPosition') === 'Topbar'
|
||||
)
|
||||
const showCurrentUser = computed(() => isCloud || isLoggedIn.value)
|
||||
const showLoginButton = computed(
|
||||
() => !showCurrentUser.value && (flags.showSignInButton ?? isDesktop)
|
||||
)
|
||||
/**
|
||||
* Whether the actionbar container has any visible docked buttons
|
||||
* (excluding ComfyActionbar, which uses position:fixed when floating
|
||||
@@ -197,8 +206,8 @@ const isActionbarFloating = computed(
|
||||
const hasDockedButtons = computed(() => {
|
||||
if (actionBarButtonStore.buttons.length > 0) return true
|
||||
if (hasLegacyContent.value) return true
|
||||
if (isLoggedIn.value && !isIntegratedTabBar.value) return true
|
||||
if (isDesktop && !isIntegratedTabBar.value) return true
|
||||
if (showCurrentUser.value && !isWorkflowTabsInTopbar.value) return true
|
||||
if (showLoginButton.value && !isWorkflowTabsInTopbar.value) return true
|
||||
if (isCloud && flags.workflowSharingEnabled) return true
|
||||
if (!isRightSidePanelOpen.value) return true
|
||||
return false
|
||||
@@ -221,9 +230,6 @@ const actionbarContainerClass = computed(() => {
|
||||
|
||||
return cn(base, 'px-2', 'border-interface-stroke')
|
||||
})
|
||||
const isIntegratedTabBar = computed(
|
||||
() => settingStore.get('Comfy.UI.TabBarLayout') !== 'Legacy'
|
||||
)
|
||||
const { isQueuePanelV2Enabled, isRunProgressBarEnabled } =
|
||||
useQueueFeatureFlags()
|
||||
const isQueueProgressOverlayEnabled = computed(
|
||||
|
||||
@@ -14,8 +14,6 @@ const distribution = vi.hoisted(() => ({
|
||||
isNightly: false
|
||||
}))
|
||||
|
||||
const tabBarLayout = vi.hoisted(() => ({ value: 'Default' }))
|
||||
|
||||
vi.mock('@/platform/distribution/types', () => ({
|
||||
get isCloud() {
|
||||
return distribution.isCloud
|
||||
@@ -28,13 +26,6 @@ vi.mock('@/platform/distribution/types', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: () => ({
|
||||
get: (key: string) =>
|
||||
key === 'Comfy.UI.TabBarLayout' ? tabBarLayout.value : undefined
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/auth/useCurrentUser', () => ({
|
||||
useCurrentUser: () => ({ isLoggedIn: { value: false } })
|
||||
}))
|
||||
@@ -134,7 +125,6 @@ describe('WorkflowTabs feedback button', () => {
|
||||
distribution.isCloud = false
|
||||
distribution.isDesktop = false
|
||||
distribution.isNightly = false
|
||||
tabBarLayout.value = 'Default'
|
||||
openSpy = vi.spyOn(window, 'open').mockReturnValue(null)
|
||||
})
|
||||
|
||||
@@ -174,13 +164,4 @@ describe('WorkflowTabs feedback button', () => {
|
||||
screen.queryByRole('button', { name: 'Feedback' })
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render the feedback button when the legacy tab bar is active', () => {
|
||||
distribution.isCloud = true
|
||||
tabBarLayout.value = 'Legacy'
|
||||
renderComponent()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'Feedback' })
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -79,11 +79,7 @@
|
||||
>
|
||||
<i class="pi pi-plus" />
|
||||
</Button>
|
||||
<div
|
||||
v-if="isIntegratedTabBar"
|
||||
data-testid="integrated-tab-bar-actions"
|
||||
class="ml-auto flex shrink-0 items-center gap-2 px-2"
|
||||
>
|
||||
<div class="ml-auto flex shrink-0 items-center gap-2 px-2">
|
||||
<Button
|
||||
v-if="isCloud || isNightly"
|
||||
v-tooltip="{ value: $t('actionbar.feedbackTooltip'), showDelay: 300 }"
|
||||
@@ -118,7 +114,6 @@ import Button from '@/components/ui/button/Button.vue'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { buildFeedbackTypeformUrl } from '@/platform/support/config'
|
||||
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
||||
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
|
||||
@@ -139,7 +134,6 @@ const props = defineProps<{
|
||||
class?: string
|
||||
}>()
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const workflowService = useWorkflowService()
|
||||
@@ -147,9 +141,6 @@ const commandStore = useCommandStore()
|
||||
const { isLoggedIn } = useCurrentUser()
|
||||
const { flags } = useFeatureFlags()
|
||||
|
||||
const isIntegratedTabBar = computed(
|
||||
() => settingStore.get('Comfy.UI.TabBarLayout') !== 'Legacy'
|
||||
)
|
||||
const showCurrentUser = computed(() => isCloud || isLoggedIn.value)
|
||||
|
||||
function openFeedback() {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { ActionBarButton } from '@/types/comfy'
|
||||
|
||||
const distribution = vi.hoisted(() => ({ isCloud: false, isNightly: false }))
|
||||
|
||||
const tabBarLayout = vi.hoisted(() => ({ value: 'Default' }))
|
||||
const registerExtension = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/i18n', () => ({
|
||||
t: (key: string) => key
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: () => ({
|
||||
get: (key: string) =>
|
||||
key === 'Comfy.UI.TabBarLayout' ? tabBarLayout.value : undefined
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/services/extensionService', () => ({
|
||||
useExtensionService: () => ({
|
||||
registerExtension
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/distribution/types', () => ({
|
||||
get isCloud() {
|
||||
return distribution.isCloud
|
||||
},
|
||||
get isNightly() {
|
||||
return distribution.isNightly
|
||||
}
|
||||
}))
|
||||
|
||||
describe('cloudFeedbackTopbarButton', () => {
|
||||
let openSpy: ReturnType<typeof vi.spyOn>
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
registerExtension.mockReset()
|
||||
distribution.isCloud = false
|
||||
distribution.isNightly = false
|
||||
openSpy = vi.spyOn(window, 'open').mockReturnValue(null)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
openSpy.mockRestore()
|
||||
})
|
||||
|
||||
function getRegisteredButtons(): ActionBarButton[] {
|
||||
expect(registerExtension).toHaveBeenCalledTimes(1)
|
||||
const extension = registerExtension.mock.calls[0]?.[0] as {
|
||||
actionBarButtons: ActionBarButton[]
|
||||
}
|
||||
return extension.actionBarButtons
|
||||
}
|
||||
|
||||
it('opens the Typeform survey tagged with action-bar source on Cloud', async () => {
|
||||
tabBarLayout.value = 'Legacy'
|
||||
distribution.isCloud = true
|
||||
await import('./cloudFeedbackTopbarButton')
|
||||
|
||||
const buttons = getRegisteredButtons()
|
||||
expect(buttons).toHaveLength(1)
|
||||
buttons[0].onClick?.()
|
||||
|
||||
expect(openSpy).toHaveBeenCalledTimes(1)
|
||||
const [url, target, features] = openSpy.mock.calls[0]
|
||||
expect(url).toBe(
|
||||
'https://form.typeform.com/to/q7azbWPi#distribution=ccloud&source=action-bar'
|
||||
)
|
||||
expect(target).toBe('_blank')
|
||||
expect(features).toBe('noopener,noreferrer')
|
||||
})
|
||||
|
||||
it('only registers the action bar button when the tab bar is Legacy', async () => {
|
||||
tabBarLayout.value = 'Default'
|
||||
await import('./cloudFeedbackTopbarButton')
|
||||
|
||||
expect(getRegisteredButtons()).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -1,29 +0,0 @@
|
||||
import { t } from '@/i18n'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { buildFeedbackTypeformUrl } from '@/platform/support/config'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
import type { ActionBarButton } from '@/types/comfy'
|
||||
|
||||
const buttons: ActionBarButton[] = [
|
||||
{
|
||||
icon: 'icon-[lucide--message-square-text]',
|
||||
label: t('actionbar.feedback'),
|
||||
tooltip: t('actionbar.feedbackTooltip'),
|
||||
onClick: () => {
|
||||
window.open(
|
||||
buildFeedbackTypeformUrl('action-bar'),
|
||||
'_blank',
|
||||
'noopener,noreferrer'
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
useExtensionService().registerExtension({
|
||||
name: 'Comfy.FeedbackButton',
|
||||
get actionBarButtons() {
|
||||
return useSettingStore().get('Comfy.UI.TabBarLayout') === 'Legacy'
|
||||
? buttons
|
||||
: []
|
||||
}
|
||||
})
|
||||
@@ -43,11 +43,6 @@ if (isCloud) {
|
||||
}
|
||||
}
|
||||
|
||||
// Feedback button for cloud and nightly builds
|
||||
if (isCloud || isNightly) {
|
||||
await import('./cloudFeedbackTopbarButton')
|
||||
}
|
||||
|
||||
// Nightly-only extensions
|
||||
if (isNightly && !isCloud) {
|
||||
await import('./nightlyBadges')
|
||||
|
||||
@@ -584,12 +584,9 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
id: 'Comfy.UI.TabBarLayout',
|
||||
category: ['Appearance', 'General'],
|
||||
name: 'Tab Bar Layout',
|
||||
type: 'combo',
|
||||
options: ['Default', 'Legacy'],
|
||||
tooltip: 'Controls the elements contained in the integrated tab bar.',
|
||||
type: 'hidden',
|
||||
defaultValue: 'Default',
|
||||
migrateDeprecatedValue: (value: unknown) =>
|
||||
value === 'Integrated' ? 'Default' : value
|
||||
migrateDeprecatedValue: () => 'Default'
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Appearance.DisableAnimations',
|
||||
|
||||
@@ -301,7 +301,7 @@ const zSettings = z.object({
|
||||
'Comfy.ConfirmClear': z.boolean(),
|
||||
'Comfy.DevMode': z.boolean(),
|
||||
'Comfy.Appearance.DisableAnimations': z.boolean(),
|
||||
'Comfy.UI.TabBarLayout': z.enum(['Default', 'Legacy']),
|
||||
'Comfy.UI.TabBarLayout': z.literal('Default'),
|
||||
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
|
||||
'Comfy.Workflow.WarnBlueprintOverwrite': z.boolean(),
|
||||
'Comfy.Desktop.CloudNotificationShown': z.boolean(),
|
||||
|
||||
Reference in New Issue
Block a user