mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
[backport core/1.41] fix: prevent subscription UI from rendering on non-cloud distributions (#10028)
Backport of #9958 to `core/1.41` Automatically created by backport workflow. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10028-backport-core-1-41-fix-prevent-subscription-UI-from-rendering-on-non-cloud-distributi-3256d73d3650819f8f76c460041de1ed) by [Unito](https://www.unito.io) Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
This commit is contained in:
@@ -167,9 +167,12 @@ vi.mock('@/platform/telemetry', () => ({
|
||||
}))
|
||||
}))
|
||||
|
||||
// Mock isCloud
|
||||
// Mock isCloud with hoisted state for per-test toggling
|
||||
const mockIsCloud = vi.hoisted(() => ({ value: true }))
|
||||
vi.mock('@/platform/distribution/types', () => ({
|
||||
isCloud: true
|
||||
get isCloud() {
|
||||
return mockIsCloud.value
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/cloud/subscription/components/SubscribeButton.vue', () => ({
|
||||
@@ -184,6 +187,7 @@ vi.mock('@/platform/cloud/subscription/components/SubscribeButton.vue', () => ({
|
||||
describe('CurrentUserPopoverLegacy', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockIsCloud.value = true
|
||||
mockAuthStoreState.balance = {
|
||||
amount_micros: 100_000,
|
||||
effective_balance_micros: 100_000,
|
||||
@@ -425,4 +429,60 @@ describe('CurrentUserPopoverLegacy', () => {
|
||||
expect(wrapper.text()).toContain('0')
|
||||
})
|
||||
})
|
||||
|
||||
describe('non-cloud distribution', () => {
|
||||
beforeEach(() => {
|
||||
mockIsCloud.value = false
|
||||
})
|
||||
|
||||
it('hides credits section', () => {
|
||||
const wrapper = mountComponent()
|
||||
expect(wrapper.find('[data-testid="add-credits-button"]').exists()).toBe(
|
||||
false
|
||||
)
|
||||
expect(
|
||||
wrapper.find('[data-testid="upgrade-to-add-credits-button"]').exists()
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('hides subscribe button', () => {
|
||||
const wrapper = mountComponent()
|
||||
expect(wrapper.text()).not.toContain('Subscribe Button')
|
||||
})
|
||||
|
||||
it('hides partner nodes menu item', () => {
|
||||
const wrapper = mountComponent()
|
||||
expect(
|
||||
wrapper.find('[data-testid="partner-nodes-menu-item"]').exists()
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('hides plans & pricing menu item', () => {
|
||||
const wrapper = mountComponent()
|
||||
expect(
|
||||
wrapper.find('[data-testid="plans-pricing-menu-item"]').exists()
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('hides manage plan menu item', () => {
|
||||
const wrapper = mountComponent()
|
||||
expect(
|
||||
wrapper.find('[data-testid="manage-plan-menu-item"]').exists()
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('still shows user settings menu item', () => {
|
||||
const wrapper = mountComponent()
|
||||
expect(
|
||||
wrapper.find('[data-testid="user-settings-menu-item"]').exists()
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('still shows logout menu item', () => {
|
||||
const wrapper = mountComponent()
|
||||
expect(wrapper.find('[data-testid="logout-menu-item"]').exists()).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -29,8 +29,11 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Credits Section -->
|
||||
<div v-if="isActiveSubscription" class="flex items-center gap-2 px-4 py-2">
|
||||
<!-- Credits Section (cloud only) -->
|
||||
<div
|
||||
v-if="isCloud && isActiveSubscription"
|
||||
class="flex items-center gap-2 px-4 py-2"
|
||||
>
|
||||
<i class="icon-[lucide--component] text-sm text-amber-400" />
|
||||
<Skeleton
|
||||
v-if="authStore.isFetchingBalance"
|
||||
@@ -66,7 +69,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex justify-center px-4">
|
||||
<div v-else-if="isCloud" class="flex justify-center px-4">
|
||||
<SubscribeButton
|
||||
:fluid="false"
|
||||
:label="$t('subscription.subscribeToComfyCloud')"
|
||||
@@ -79,7 +82,7 @@
|
||||
<Divider class="mx-0 my-2" />
|
||||
|
||||
<div
|
||||
v-if="isActiveSubscription"
|
||||
v-if="isCloud && isActiveSubscription"
|
||||
class="flex cursor-pointer items-center gap-2 px-4 py-2 hover:bg-secondary-background-hover"
|
||||
data-testid="partner-nodes-menu-item"
|
||||
@click="handleOpenPartnerNodesInfo"
|
||||
@@ -91,6 +94,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isCloud"
|
||||
class="flex cursor-pointer items-center gap-2 px-4 py-2 hover:bg-secondary-background-hover"
|
||||
data-testid="plans-pricing-menu-item"
|
||||
@click="handleOpenPlansAndPricing"
|
||||
@@ -108,7 +112,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isActiveSubscription"
|
||||
v-if="isCloud && isActiveSubscription"
|
||||
class="flex cursor-pointer items-center gap-2 px-4 py-2 hover:bg-secondary-background-hover"
|
||||
data-testid="manage-plan-menu-item"
|
||||
@click="handleOpenPlanAndCreditsSettings"
|
||||
|
||||
79
src/components/topbar/TopbarSubscribeButton.test.ts
Normal file
79
src/components/topbar/TopbarSubscribeButton.test.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import enMessages from '@/locales/en/main.json' with { type: 'json' }
|
||||
|
||||
import TopbarSubscribeButton from './TopbarSubscribeButton.vue'
|
||||
|
||||
const mockIsCloud = vi.hoisted(() => ({ value: true }))
|
||||
|
||||
vi.mock('@/platform/distribution/types', () => ({
|
||||
get isCloud() {
|
||||
return mockIsCloud.value
|
||||
}
|
||||
}))
|
||||
|
||||
const mockShowPricingTable = vi.fn()
|
||||
|
||||
vi.mock(
|
||||
'@/platform/cloud/subscription/composables/useSubscriptionDialog',
|
||||
() => ({
|
||||
useSubscriptionDialog: vi.fn(() => ({
|
||||
showPricingTable: mockShowPricingTable
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
vi.mock('@/composables/billing/useBillingContext', () => ({
|
||||
useBillingContext: vi.fn(() => ({
|
||||
isFreeTier: { value: true }
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('pinia')
|
||||
|
||||
vi.mock('firebase/app', () => ({
|
||||
initializeApp: vi.fn(),
|
||||
getApp: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('firebase/auth', () => ({
|
||||
getAuth: vi.fn(),
|
||||
setPersistence: vi.fn(),
|
||||
browserLocalPersistence: {},
|
||||
onAuthStateChanged: vi.fn(),
|
||||
signOut: vi.fn()
|
||||
}))
|
||||
|
||||
function mountComponent() {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: { en: enMessages }
|
||||
})
|
||||
|
||||
return mount(TopbarSubscribeButton, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('TopbarSubscribeButton', () => {
|
||||
it('renders on cloud when isFreeTier is true', () => {
|
||||
mockIsCloud.value = true
|
||||
const wrapper = mountComponent()
|
||||
expect(
|
||||
wrapper.find('[data-testid="topbar-subscribe-button"]').exists()
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('hides on non-cloud distribution', () => {
|
||||
mockIsCloud.value = false
|
||||
const wrapper = mountComponent()
|
||||
expect(
|
||||
wrapper.find('[data-testid="topbar-subscribe-button"]').exists()
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Button
|
||||
v-if="isFreeTier"
|
||||
v-if="isCloud && isFreeTier"
|
||||
class="mr-2 shrink-0 whitespace-nowrap"
|
||||
variant="gradient"
|
||||
size="sm"
|
||||
@@ -15,6 +15,7 @@
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useBillingContext } from '@/composables/billing/useBillingContext'
|
||||
import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
const { isFreeTier } = useBillingContext()
|
||||
const subscriptionDialog = useSubscriptionDialog()
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mockIsCloud = vi.hoisted(() => ({ value: true }))
|
||||
|
||||
vi.mock('@/platform/distribution/types', () => ({
|
||||
get isCloud() {
|
||||
return mockIsCloud.value
|
||||
}
|
||||
}))
|
||||
|
||||
const mockShowLayoutDialog = vi.fn()
|
||||
vi.mock('@/services/dialogService', () => ({
|
||||
useDialogService: vi.fn(() => ({
|
||||
showLayoutDialog: mockShowLayoutDialog
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/dialogStore', () => ({
|
||||
useDialogStore: vi.fn(() => ({
|
||||
closeDialog: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useFeatureFlags', () => ({
|
||||
useFeatureFlags: vi.fn(() => ({
|
||||
flags: { teamWorkspacesEnabled: false }
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
|
||||
useSubscription: vi.fn(() => ({
|
||||
isFreeTier: { value: false }
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/workspace/stores/teamWorkspaceStore', () => ({
|
||||
useTeamWorkspaceStore: vi.fn(() => ({
|
||||
isInPersonalWorkspace: true
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('pinia')
|
||||
|
||||
vi.mock('firebase/app', () => ({
|
||||
initializeApp: vi.fn(),
|
||||
getApp: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('firebase/auth', () => ({
|
||||
getAuth: vi.fn(),
|
||||
setPersistence: vi.fn(),
|
||||
browserLocalPersistence: {},
|
||||
onAuthStateChanged: vi.fn(),
|
||||
signOut: vi.fn()
|
||||
}))
|
||||
|
||||
describe('useSubscriptionDialog', () => {
|
||||
it('showPricingTable does not open dialog on non-cloud', async () => {
|
||||
mockIsCloud.value = false
|
||||
const { useSubscriptionDialog } = await import('./useSubscriptionDialog')
|
||||
const dialog = useSubscriptionDialog()
|
||||
|
||||
dialog.showPricingTable()
|
||||
|
||||
expect(mockShowLayoutDialog).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('showPricingTable opens dialog on cloud', async () => {
|
||||
mockIsCloud.value = true
|
||||
const { useSubscriptionDialog } = await import('./useSubscriptionDialog')
|
||||
const dialog = useSubscriptionDialog()
|
||||
|
||||
dialog.showPricingTable()
|
||||
|
||||
expect(mockShowLayoutDialog).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import { useDialogService } from '@/services/dialogService'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTeamWorkspaceStore } from '@/platform/workspace/stores/teamWorkspaceStore'
|
||||
|
||||
const DIALOG_KEY = 'subscription-required'
|
||||
@@ -26,6 +27,8 @@ export const useSubscriptionDialog = () => {
|
||||
}
|
||||
|
||||
function showPricingTable(options?: { reason?: SubscriptionDialogReason }) {
|
||||
if (!isCloud) return
|
||||
|
||||
const useWorkspaceVariant =
|
||||
flags.teamWorkspacesEnabled && !workspaceStore.isInPersonalWorkspace
|
||||
|
||||
|
||||
Reference in New Issue
Block a user