Compare commits

...

7 Commits

Author SHA1 Message Date
Connor Byrne
f62238f465 test(e2e): add login button sidebar fallback test
Adds regression test verifying login button remains visible when
workflow tabs are moved to sidebar position.

Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/11451

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 12:45:00 -07:00
Connor Byrne
dd59b61780 fix: remove legacy tab bar E2E tests
Delete tests for TabBarLayout setting since legacy option no longer exists.
Remove integratedTabBarActions selector and Topbar property.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 14:05:21 -07:00
Connor Byrne
2447a9282b fix: remove legacy tab bar test from WorkflowTabs
Delete test that expected feedback button to be hidden in legacy mode.
Legacy tab bar option no longer exists.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 13:45:54 -07:00
Connor Byrne
6fef9f9382 fix: simplify migration to always return 'Default'
Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/11451#discussion_r3188420484

Also deletes orphaned test file for removed cloudFeedbackTopbarButton.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 13:23:32 -07:00
Glary-Bot
afe77ec928 test: add cloud-mode auth fallback regression test
Make isCloud dynamic in test mock so cloud mode can be toggled per test.
Add test asserting CurrentUserButton is shown for logged-out cloud users
when workflow tabs are in the sidebar (covers showCurrentUser = isCloud || isLoggedIn).
2026-05-13 13:19:09 -07:00
Glary-Bot
dc9eda326a fix: align auth button conditions with WorkflowTabs logic
Mirror WorkflowTabs auth visibility: use isCloud || isLoggedIn for
CurrentUserButton and flags.showSignInButton ?? isDesktop for LoginButton.
Fixes cloud/web logged-out users not seeing sign-in controls when
workflow tabs are in the sidebar.

Add test coverage for auth fallback when WorkflowTabsPosition is Sidebar.
2026-05-13 13:19:09 -07:00
Glary-Bot
655596b26d fix: remove legacy tab bar layout option
Remove the 'Legacy' option from Comfy.UI.TabBarLayout setting since the
integrated tab bar is now the standard layout. The setting is kept as a
hidden setting that always resolves to 'Default'.

Changes:
- Simplify TabBarLayout setting to hidden type with migration for Legacy/Integrated values
- Remove isIntegratedTabBar computed from WorkflowTabs.vue and TopMenuSection.vue
- Move auth buttons (CurrentUserButton/LoginButton) to show in TopMenuSection when
  WorkflowTabsPosition is not Topbar (ensuring they remain visible when
  WorkflowTabs is not mounted)
- Delete cloudFeedbackTopbarButton.ts (only showed in Legacy mode; feedback already in WorkflowTabs)
- Delete legacy tab bar test suite from TopMenuSection.test.ts
- Update apiSchema to use z.literal('Default')
2026-05-13 13:19:09 -07:00
13 changed files with 57 additions and 217 deletions

View File

@@ -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[]> {

View File

@@ -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: {

View File

@@ -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.

View File

@@ -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', () => {

View File

@@ -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
})

View File

@@ -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(

View File

@@ -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()
})
})

View File

@@ -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() {

View File

@@ -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([])
})
})

View File

@@ -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
: []
}
})

View File

@@ -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')

View File

@@ -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',

View File

@@ -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(),