fix: wait for settings before cloud desktop promo

This commit is contained in:
Benjamin Lu
2026-03-25 12:24:21 -07:00
parent 437f41c553
commit 264a3fcc12
2 changed files with 215 additions and 16 deletions

183
src/App.test.ts Normal file
View File

@@ -0,0 +1,183 @@
import { flushPromises, shallowMount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick, reactive } from 'vue'
import App from '@/App.vue'
const workspaceStore = reactive({
spinner: false
})
const conflictDetection = {
initializeConflictDetection: vi.fn()
}
let shownCloudNotification = false
const settingStore = {
load: vi.fn<() => Promise<void>>(),
get: vi.fn(() => shownCloudNotification),
set: vi.fn(async (_key: string, value: boolean) => {
shownCloudNotification = value
})
}
const dialogService = {
showCloudNotification: vi.fn<() => Promise<void>>()
}
let platform = 'darwin'
const showContextMenu = vi.fn()
vi.mock('@sentry/vue', () => ({
captureException: vi.fn()
}))
vi.mock('@/config', () => ({
default: {
app_version: 'test-version'
}
}))
vi.mock('@/components/dialog/GlobalDialog.vue', () => ({
default: {
template: '<div />'
}
}))
vi.mock('@/platform/distribution/types', () => ({
isCloud: false,
isDesktop: true
}))
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: () => settingStore
}))
vi.mock('primevue/blockui', () => ({
default: {
template: '<div><slot /></div>'
}
}))
vi.mock('@/scripts/app', () => ({
app: {}
}))
vi.mock('@/services/dialogService', () => ({
useDialogService: () => dialogService
}))
vi.mock('@/stores/workspaceStore', () => ({
useWorkspaceStore: () => workspaceStore
}))
vi.mock('@/utils/envUtil', () => ({
electronAPI: () => ({
getPlatform: () => platform,
showContextMenu
})
}))
vi.mock('@/utils/preloadErrorUtil', () => ({
parsePreloadError: vi.fn(() => ({
url: '',
fileType: 'script',
chunkName: '',
message: ''
}))
}))
vi.mock(
'@/workbench/extensions/manager/composables/useConflictDetection',
() => ({
useConflictDetection: () => conflictDetection
})
)
function createDeferred() {
let resolve!: () => void
const promise = new Promise<void>((res) => {
resolve = res
})
return { promise, resolve }
}
describe('App cloud notification', () => {
beforeEach(() => {
vi.useFakeTimers()
vi.clearAllMocks()
workspaceStore.spinner = false
platform = 'darwin'
shownCloudNotification = false
settingStore.load.mockResolvedValue(undefined)
settingStore.get.mockImplementation(() => shownCloudNotification)
settingStore.set.mockImplementation(
async (_key: string, value: boolean) => {
shownCloudNotification = value
}
)
dialogService.showCloudNotification.mockResolvedValue(undefined)
})
afterEach(() => {
vi.useRealTimers()
})
function mountApp() {
return shallowMount(App, {
global: {
stubs: {
RouterView: true,
GlobalDialog: true,
BlockUI: true
}
}
})
}
it('waits for settings to load before deciding whether to show the notification', async () => {
const loadSettings = createDeferred()
settingStore.load.mockImplementation(() => loadSettings.promise)
const wrapper = mountApp()
await nextTick()
shownCloudNotification = true
loadSettings.resolve()
await flushPromises()
await vi.advanceTimersByTimeAsync(2000)
expect(dialogService.showCloudNotification).not.toHaveBeenCalled()
wrapper.unmount()
})
it('marks the notification as shown before awaiting dialog close', async () => {
const dialogOpen = createDeferred()
dialogService.showCloudNotification.mockImplementation(
() => dialogOpen.promise
)
const wrapper = mountApp()
await flushPromises()
await vi.advanceTimersByTimeAsync(2000)
expect(settingStore.set).toHaveBeenCalledWith(
'Comfy.Desktop.CloudNotificationShown',
true
)
expect(settingStore.set.mock.invocationCallOrder[0]).toBeLessThan(
dialogService.showCloudNotification.mock.invocationCallOrder[0]
)
dialogOpen.resolve()
await flushPromises()
wrapper.unmount()
})
})

View File

@@ -14,10 +14,10 @@ import config from '@/config'
import { isDesktop } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { app } from '@/scripts/app'
import { useDialogService } from '@/services/dialogService'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { electronAPI } from '@/utils/envUtil'
import { parsePreloadError } from '@/utils/preloadErrorUtil'
import { useDialogService } from '@/services/dialogService'
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
const workspaceStore = useWorkspaceStore()
@@ -25,6 +25,9 @@ app.extensionManager = useWorkspaceStore()
const conflictDetection = useConflictDetection()
const isLoading = computed<boolean>(() => workspaceStore.spinner)
const settingStore = useSettingStore()
const dialogService = useDialogService()
let cloudNotificationTimer: ReturnType<typeof setTimeout> | undefined
watch(
isLoading,
@@ -130,24 +133,37 @@ onMounted(() => {
// This runs async and doesn't block UI setup
void conflictDetection.initializeConflictDetection()
// Show cloud notification for macOS desktop users (one-time)
if (isDesktop && electronAPI()?.getPlatform() === 'darwin') {
const settingStore = useSettingStore()
if (!settingStore.get('Comfy.Desktop.CloudNotificationShown')) {
const dialogService = useDialogService()
cloudNotificationTimer = setTimeout(async () => {
try {
await dialogService.showCloudNotification()
} catch (e) {
console.warn('[CloudNotification] Failed to show', e)
}
await settingStore.set('Comfy.Desktop.CloudNotificationShown', true)
}, 2000)
if (!isDesktop || electronAPI()?.getPlatform() !== 'darwin') return
void (async () => {
try {
await settingStore.load()
} catch (error) {
console.warn('[CloudNotification] Failed to load settings', error)
return
}
}
if (settingStore.get('Comfy.Desktop.CloudNotificationShown')) return
cloudNotificationTimer = setTimeout(async () => {
try {
await settingStore.set('Comfy.Desktop.CloudNotificationShown', true)
await dialogService.showCloudNotification()
} catch (error) {
console.warn('[CloudNotification] Failed to show', error)
await settingStore
.set('Comfy.Desktop.CloudNotificationShown', false)
.catch((resetError) => {
console.warn(
'[CloudNotification] Failed to reset shown state',
resetError
)
})
}
}, 2000)
})()
})
let cloudNotificationTimer: ReturnType<typeof setTimeout> | undefined
onUnmounted(() => {
if (cloudNotificationTimer) clearTimeout(cloudNotificationTimer)
})