[System Pop Up] Add setting to disable version update notifications (#4388)

This commit is contained in:
bmcomfy
2025-07-08 14:43:11 -07:00
committed by Terry Jia
parent fc35d2227c
commit 6e8a9ae41f
7 changed files with 441 additions and 5 deletions

View File

@@ -130,4 +130,239 @@ test.describe('Release Notifications', () => {
whatsNewSection.locator('text=No recent releases')
).toBeVisible()
})
test('should hide "What\'s New" section when notifications are disabled', async ({
comfyPage
}) => {
// Disable version update notifications
await comfyPage.setSetting('Comfy.Notification.ShowVersionUpdates', false)
// Mock release API with test data
await comfyPage.page.route('**/releases**', async (route) => {
const url = route.request().url()
if (
url.includes('api.comfy.org') ||
url.includes('stagingapi.comfy.org')
) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{
id: 1,
project: 'comfyui',
version: 'v0.3.44',
attention: 'high',
content: '## New Features\n\n- Added awesome feature',
published_at: new Date().toISOString()
}
])
})
} else {
await route.continue()
}
})
await comfyPage.setup({ mockReleases: false })
// Open help center
const helpCenterButton = comfyPage.page.locator('.comfy-help-center-btn')
await helpCenterButton.waitFor({ state: 'visible' })
await helpCenterButton.click()
// Verify help center menu appears
const helpMenu = comfyPage.page.locator('.help-center-menu')
await expect(helpMenu).toBeVisible()
// Verify "What's New?" section is hidden
const whatsNewSection = comfyPage.page.locator('.whats-new-section')
await expect(whatsNewSection).not.toBeVisible()
// Should not show any popups or toasts
await expect(comfyPage.page.locator('.whats-new-popup')).not.toBeVisible()
await expect(
comfyPage.page.locator('.release-notification-toast')
).not.toBeVisible()
})
test('should not make API calls when notifications are disabled', async ({
comfyPage
}) => {
// Disable version update notifications
await comfyPage.setSetting('Comfy.Notification.ShowVersionUpdates', false)
// Track API calls
let apiCallCount = 0
await comfyPage.page.route('**/releases**', async (route) => {
const url = route.request().url()
if (
url.includes('api.comfy.org') ||
url.includes('stagingapi.comfy.org')
) {
apiCallCount++
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([])
})
} else {
await route.continue()
}
})
await comfyPage.setup({ mockReleases: false })
// Wait a bit to ensure any potential API calls would have been made
await comfyPage.page.waitForTimeout(1000)
// Verify no API calls were made
expect(apiCallCount).toBe(0)
})
test('should show "What\'s New" section when notifications are enabled', async ({
comfyPage
}) => {
// Enable version update notifications (default behavior)
await comfyPage.setSetting('Comfy.Notification.ShowVersionUpdates', true)
// Mock release API with test data
await comfyPage.page.route('**/releases**', async (route) => {
const url = route.request().url()
if (
url.includes('api.comfy.org') ||
url.includes('stagingapi.comfy.org')
) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{
id: 1,
project: 'comfyui',
version: 'v0.3.44',
attention: 'medium',
content: '## New Features\n\n- Added awesome feature',
published_at: new Date().toISOString()
}
])
})
} else {
await route.continue()
}
})
await comfyPage.setup({ mockReleases: false })
// Open help center
const helpCenterButton = comfyPage.page.locator('.comfy-help-center-btn')
await helpCenterButton.waitFor({ state: 'visible' })
await helpCenterButton.click()
// Verify help center menu appears
const helpMenu = comfyPage.page.locator('.help-center-menu')
await expect(helpMenu).toBeVisible()
// Verify "What's New?" section is visible
const whatsNewSection = comfyPage.page.locator('.whats-new-section')
await expect(whatsNewSection).toBeVisible()
// Should show the release
await expect(
whatsNewSection.locator('text=Comfy v0.3.44 Release')
).toBeVisible()
})
test('should toggle "What\'s New" section when setting changes', async ({
comfyPage
}) => {
// Mock release API with test data
await comfyPage.page.route('**/releases**', async (route) => {
const url = route.request().url()
if (
url.includes('api.comfy.org') ||
url.includes('stagingapi.comfy.org')
) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{
id: 1,
project: 'comfyui',
version: 'v0.3.44',
attention: 'low',
content: '## Bug Fixes\n\n- Fixed minor issue',
published_at: new Date().toISOString()
}
])
})
} else {
await route.continue()
}
})
// Start with notifications enabled
await comfyPage.setSetting('Comfy.Notification.ShowVersionUpdates', true)
await comfyPage.setup({ mockReleases: false })
// Open help center
const helpCenterButton = comfyPage.page.locator('.comfy-help-center-btn')
await helpCenterButton.waitFor({ state: 'visible' })
await helpCenterButton.click()
// Verify "What's New?" section is visible
const whatsNewSection = comfyPage.page.locator('.whats-new-section')
await expect(whatsNewSection).toBeVisible()
// Close help center
await comfyPage.page.click('.help-center-backdrop')
// Disable notifications
await comfyPage.setSetting('Comfy.Notification.ShowVersionUpdates', false)
// Reopen help center
await helpCenterButton.click()
// Verify "What's New?" section is now hidden
await expect(whatsNewSection).not.toBeVisible()
})
test('should handle edge case with empty releases and disabled notifications', async ({
comfyPage
}) => {
// Disable notifications
await comfyPage.setSetting('Comfy.Notification.ShowVersionUpdates', false)
// Mock empty releases
await comfyPage.page.route('**/releases**', async (route) => {
const url = route.request().url()
if (
url.includes('api.comfy.org') ||
url.includes('stagingapi.comfy.org')
) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([])
})
} else {
await route.continue()
}
})
await comfyPage.setup({ mockReleases: false })
// Open help center
const helpCenterButton = comfyPage.page.locator('.comfy-help-center-btn')
await helpCenterButton.waitFor({ state: 'visible' })
await helpCenterButton.click()
// Verify help center still works
const helpMenu = comfyPage.page.locator('.help-center-menu')
await expect(helpMenu).toBeVisible()
// Section should be hidden regardless of empty releases
const whatsNewSection = comfyPage.page.locator('.whats-new-section')
await expect(whatsNewSection).not.toBeVisible()
})
})

View File

@@ -54,7 +54,7 @@
</Teleport>
<!-- What's New Section -->
<section class="whats-new-section">
<section v-if="showVersionUpdates" class="whats-new-section">
<h3 class="section-description">{{ $t('helpCenter.whatsNew') }}</h3>
<!-- Release Items -->
@@ -126,6 +126,7 @@ import { useI18n } from 'vue-i18n'
import { type ReleaseNote } from '@/services/releaseService'
import { useCommandStore } from '@/stores/commandStore'
import { useReleaseStore } from '@/stores/releaseStore'
import { useSettingStore } from '@/stores/settingStore'
import { electronAPI, isElectron } from '@/utils/envUtil'
import { formatVersionAnchor } from '@/utils/formatUtil'
@@ -168,6 +169,7 @@ const SUBMENU_CONFIG = {
const { t, locale } = useI18n()
const releaseStore = useReleaseStore()
const commandStore = useCommandStore()
const settingStore = useSettingStore()
// Emits
const emit = defineEmits<{
@@ -182,6 +184,9 @@ let hoverTimeout: number | null = null
// Computed
const hasReleases = computed(() => releaseStore.releases.length > 0)
const showVersionUpdates = computed(() =>
settingStore.get('Comfy.Notification.ShowVersionUpdates')
)
const moreMenuItem = computed(() =>
menuItems.value.find((item) => item.key === 'more')

View File

@@ -330,6 +330,14 @@ export const CORE_SETTINGS: SettingParams[] = [
defaultValue: true,
versionAdded: '1.20.3'
},
{
id: 'Comfy.Notification.ShowVersionUpdates',
category: ['Comfy', 'Notification Preferences'],
name: 'Show version updates',
tooltip: 'Show updates for new models, and major new features.',
type: 'boolean',
defaultValue: true
},
{
id: 'Comfy.ConfirmClear',
category: ['Comfy', 'Workflow', 'ConfirmClear'],

View File

@@ -232,6 +232,10 @@
"Comfy_NodeBadge_ShowApiPricing": {
"name": "Show API node pricing badge"
},
"Comfy_Notification_ShowVersionUpdates": {
"name": "Show version updates",
"tooltip": "Show updates for new models, and major new features."
},
"Comfy_NodeSearchBoxImpl": {
"name": "Node search box implementation",
"options": {

View File

@@ -426,6 +426,7 @@ const zSettings = z.object({
'Comfy.NodeBadge.NodeIdBadgeMode': zNodeBadgeMode,
'Comfy.NodeBadge.NodeLifeCycleBadgeMode': zNodeBadgeMode,
'Comfy.NodeBadge.ShowApiPricing': z.boolean(),
'Comfy.Notification.ShowVersionUpdates': z.boolean(),
'Comfy.QueueButton.BatchCountLimit': z.number(),
'Comfy.Queue.MaxHistoryItems': z.number(),
'Comfy.Keybinding.UnsetBindings': z.array(zKeybinding),

View File

@@ -32,6 +32,9 @@ export const useReleaseStore = defineStore('release', () => {
const releaseTimestamp = computed(() =>
settingStore.get('Comfy.Release.Timestamp')
)
const showVersionUpdates = computed(() =>
settingStore.get('Comfy.Notification.ShowVersionUpdates')
)
// Most recent release
const recentRelease = computed(() => {
@@ -73,6 +76,11 @@ export const useReleaseStore = defineStore('release', () => {
// Show toast if needed
const shouldShowToast = computed(() => {
// Skip if notifications are disabled
if (!showVersionUpdates.value) {
return false
}
if (!isNewVersionAvailable.value) {
return false
}
@@ -85,7 +93,7 @@ export const useReleaseStore = defineStore('release', () => {
// Skip if user already skipped or changelog seen
if (
releaseVersion.value === recentRelease.value?.version &&
!['skipped', 'changelog seen'].includes(releaseStatus.value)
['skipped', 'changelog seen'].includes(releaseStatus.value)
) {
return false
}
@@ -95,6 +103,11 @@ export const useReleaseStore = defineStore('release', () => {
// Show red-dot indicator
const shouldShowRedDot = computed(() => {
// Skip if notifications are disabled
if (!showVersionUpdates.value) {
return false
}
// Already latest → no dot
if (!isNewVersionAvailable.value) {
return false
@@ -132,6 +145,11 @@ export const useReleaseStore = defineStore('release', () => {
// Show "What's New" popup
const shouldShowPopup = computed(() => {
// Skip if notifications are disabled
if (!showVersionUpdates.value) {
return false
}
if (!isLatestVersion.value) {
return false
}
@@ -183,7 +201,14 @@ export const useReleaseStore = defineStore('release', () => {
// Fetch releases from API
async function fetchReleases(): Promise<void> {
if (isLoading.value) return
if (isLoading.value) {
return
}
// Skip fetching if notifications are disabled
if (!showVersionUpdates.value) {
return
}
isLoading.value = true
error.value = null

View File

@@ -61,6 +61,12 @@ describe('useReleaseStore', () => {
vi.mocked(useSettingStore).mockReturnValue(mockSettingStore)
vi.mocked(useSystemStatsStore).mockReturnValue(mockSystemStatsStore)
// Default showVersionUpdates to true
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
store = useReleaseStore()
})
@@ -114,6 +120,107 @@ describe('useReleaseStore', () => {
})
})
describe('showVersionUpdates setting', () => {
beforeEach(() => {
store.releases = [mockRelease]
})
describe('when notifications are enabled', () => {
beforeEach(() => {
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
})
it('should show toast for medium/high attention releases', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
// Need multiple releases for hasMediumOrHighAttention to work
const mediumRelease = {
...mockRelease,
id: 2,
attention: 'medium' as const
}
store.releases = [mockRelease, mediumRelease]
expect(store.shouldShowToast).toBe(true)
})
it('should show red dot for new versions', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
expect(store.shouldShowRedDot).toBe(true)
})
it('should show popup for latest version', async () => {
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0'
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0)
expect(store.shouldShowPopup).toBe(true)
})
it('should fetch releases during initialization', async () => {
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
await store.initialize()
expect(mockReleaseService.getReleases).toHaveBeenCalledWith({
project: 'comfyui',
current_version: '1.0.0',
form_factor: 'git-windows'
})
})
})
describe('when notifications are disabled', () => {
beforeEach(() => {
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return false
return null
})
})
it('should not show toast even with new version available', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
expect(store.shouldShowToast).toBe(false)
})
it('should not show red dot even with new version available', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
expect(store.shouldShowRedDot).toBe(false)
})
it('should not show popup even for latest version', async () => {
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0'
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0)
expect(store.shouldShowPopup).toBe(false)
})
it('should skip fetching releases during initialization', async () => {
await store.initialize()
expect(mockReleaseService.getReleases).not.toHaveBeenCalled()
})
it('should not fetch releases when calling fetchReleases directly', async () => {
await store.fetchReleases()
expect(mockReleaseService.getReleases).not.toHaveBeenCalled()
expect(store.isLoading).toBe(false)
})
})
})
describe('release initialization', () => {
it('should fetch releases successfully', async () => {
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
@@ -184,6 +291,17 @@ describe('useReleaseStore', () => {
expect(mockSystemStatsStore.fetchSystemStats).toHaveBeenCalled()
})
it('should not set loading state when notifications disabled', async () => {
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return false
return null
})
await store.initialize()
expect(store.isLoading).toBe(false)
})
})
describe('action handlers', () => {
@@ -248,6 +366,7 @@ describe('useReleaseStore', () => {
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Release.Version') return null
if (key === 'Comfy.Release.Status') return null
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
@@ -267,7 +386,10 @@ describe('useReleaseStore', () => {
it('should show red dot for new versions', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
mockSettingStore.get.mockReturnValue(null)
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
store.releases = [mockRelease]
@@ -276,7 +398,10 @@ describe('useReleaseStore', () => {
it('should show popup for latest version', async () => {
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' // Same as release
mockSettingStore.get.mockReturnValue(null)
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0) // versions are equal (latest version)
@@ -286,4 +411,37 @@ describe('useReleaseStore', () => {
expect(store.shouldShowPopup).toBe(true)
})
})
describe('edge cases', () => {
it('should handle missing system stats gracefully', async () => {
mockSystemStatsStore.systemStats = null
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return false
return null
})
await store.initialize()
// Should not fetch system stats when notifications disabled
expect(mockSystemStatsStore.fetchSystemStats).not.toHaveBeenCalled()
})
it('should handle concurrent fetchReleases calls', async () => {
mockReleaseService.getReleases.mockImplementation(
() =>
new Promise((resolve) =>
setTimeout(() => resolve([mockRelease]), 100)
)
)
// Start two concurrent calls
const promise1 = store.fetchReleases()
const promise2 = store.fetchReleases()
await Promise.all([promise1, promise2])
// Should only call API once due to loading check
expect(mockReleaseService.getReleases).toHaveBeenCalledTimes(1)
})
})
})