mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-28 02:02:08 +00:00
[fix] Improve version mismatch warning dismissal and eliminate code duplication (#4542)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
117
browser_tests/tests/versionMismatchWarnings.spec.ts
Normal file
117
browser_tests/tests/versionMismatchWarnings.spec.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { SystemStats } from '../../src/schemas/apiSchema'
|
||||||
|
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||||
|
|
||||||
|
test.describe('Version Mismatch Warnings', () => {
|
||||||
|
const ALWAYS_AHEAD_OF_INSTALLED_VERSION = '100.100.100'
|
||||||
|
const ALWAYS_BEHIND_INSTALLED_VERSION = '0.0.0'
|
||||||
|
|
||||||
|
const createMockSystemStatsRes = (
|
||||||
|
requiredFrontendVersion: string
|
||||||
|
): SystemStats => {
|
||||||
|
return {
|
||||||
|
system: {
|
||||||
|
os: 'posix',
|
||||||
|
ram_total: 67235385344,
|
||||||
|
ram_free: 13464207360,
|
||||||
|
comfyui_version: '0.3.46',
|
||||||
|
required_frontend_version: requiredFrontendVersion,
|
||||||
|
python_version: '3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0]',
|
||||||
|
pytorch_version: '2.6.0+cu124',
|
||||||
|
embedded_python: false,
|
||||||
|
argv: ['main.py']
|
||||||
|
},
|
||||||
|
devices: [
|
||||||
|
{
|
||||||
|
name: 'cuda:0 NVIDIA GeForce RTX 4070 : cudaMallocAsync',
|
||||||
|
type: 'cuda',
|
||||||
|
index: 0,
|
||||||
|
vram_total: 12557156352,
|
||||||
|
vram_free: 2439249920,
|
||||||
|
torch_vram_total: 0,
|
||||||
|
torch_vram_free: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeEach(async ({ comfyPage }) => {
|
||||||
|
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should show version mismatch warnings when installed version lower than required', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
// Mock system_stats route to indicate that the installed version is always ahead of the required version
|
||||||
|
await comfyPage.page.route('**/system_stats**', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify(
|
||||||
|
createMockSystemStatsRes(ALWAYS_AHEAD_OF_INSTALLED_VERSION)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await comfyPage.setup()
|
||||||
|
|
||||||
|
// Expect a warning toast to be shown
|
||||||
|
await expect(
|
||||||
|
comfyPage.page.getByText('Version Compatibility Warning')
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not show version mismatch warnings when installed version is ahead of required', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
// Mock system_stats route to indicate that the installed version is always ahead of the required version
|
||||||
|
await comfyPage.page.route('**/system_stats**', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify(
|
||||||
|
createMockSystemStatsRes(ALWAYS_BEHIND_INSTALLED_VERSION)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await comfyPage.setup()
|
||||||
|
|
||||||
|
// Expect no warning toast to be shown
|
||||||
|
await expect(
|
||||||
|
comfyPage.page.getByText('Version Compatibility Warning')
|
||||||
|
).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should persist dismissed state across sessions', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
// Mock system_stats route to indicate that the installed version is always ahead of the required version
|
||||||
|
await comfyPage.page.route('**/system_stats**', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify(
|
||||||
|
createMockSystemStatsRes(ALWAYS_AHEAD_OF_INSTALLED_VERSION)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await comfyPage.setup()
|
||||||
|
|
||||||
|
// Locate the warning toast and dismiss it
|
||||||
|
const warningToast = comfyPage.page
|
||||||
|
.locator('div')
|
||||||
|
.filter({ hasText: 'Version Compatibility' })
|
||||||
|
.nth(3)
|
||||||
|
await warningToast.waitFor({ state: 'visible' })
|
||||||
|
const dismissButton = warningToast.getByRole('button', { name: 'Close' })
|
||||||
|
await dismissButton.click()
|
||||||
|
|
||||||
|
// Reload the page, keeping local storage
|
||||||
|
await comfyPage.setup({ clearStorage: false })
|
||||||
|
|
||||||
|
// The same warning from same versions should not be shown to the user again
|
||||||
|
await expect(
|
||||||
|
comfyPage.page.getByText('Version Compatibility Warning')
|
||||||
|
).not.toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
198
package-lock.json
generated
198
package-lock.json
generated
@@ -40,6 +40,7 @@
|
|||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primevue": "^4.2.5",
|
"primevue": "^4.2.5",
|
||||||
|
"semver": "^7.7.2",
|
||||||
"three": "^0.170.0",
|
"three": "^0.170.0",
|
||||||
"tiptap-markdown": "^0.8.10",
|
"tiptap-markdown": "^0.8.10",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
@@ -62,6 +63,7 @@
|
|||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/lodash": "^4.17.6",
|
"@types/lodash": "^4.17.6",
|
||||||
"@types/node": "^20.14.8",
|
"@types/node": "^20.14.8",
|
||||||
|
"@types/semver": "^7.7.0",
|
||||||
"@types/three": "^0.169.0",
|
"@types/three": "^0.169.0",
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
@@ -557,6 +559,15 @@
|
|||||||
"url": "https://opencollective.com/babel"
|
"url": "https://opencollective.com/babel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/core/node_modules/semver": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/generator": {
|
"node_modules/@babel/generator": {
|
||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz",
|
||||||
@@ -601,6 +612,15 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-create-class-features-plugin": {
|
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz",
|
||||||
@@ -622,6 +642,15 @@
|
|||||||
"@babel/core": "^7.0.0"
|
"@babel/core": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-member-expression-to-functions": {
|
"node_modules/@babel/helper-member-expression-to-functions": {
|
||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
|
||||||
@@ -2422,18 +2451,6 @@
|
|||||||
"js-yaml": "bin/js-yaml.js"
|
"js-yaml": "bin/js-yaml.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/semver": {
|
|
||||||
"version": "7.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
|
||||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/synckit": {
|
"node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/synckit": {
|
||||||
"version": "0.9.3",
|
"version": "0.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.3.tgz",
|
||||||
@@ -4523,6 +4540,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/semver": {
|
||||||
|
"version": "7.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz",
|
||||||
|
"integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/stats.js": {
|
"node_modules/@types/stats.js": {
|
||||||
"version": "0.17.3",
|
"version": "0.17.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz",
|
||||||
@@ -4754,19 +4777,6 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
|
|
||||||
"version": "7.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0.tgz",
|
||||||
@@ -6537,19 +6547,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/conf/node_modules/semver": {
|
|
||||||
"version": "7.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/confbox": {
|
"node_modules/confbox": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz",
|
||||||
@@ -7449,19 +7446,6 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/editorconfig/node_modules/semver": {
|
|
||||||
"version": "7.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -7741,18 +7725,6 @@
|
|||||||
"eslint": ">=6.0.0"
|
"eslint": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-compat-utils/node_modules/semver": {
|
|
||||||
"version": "7.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
|
||||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/eslint-config-prettier": {
|
"node_modules/eslint-config-prettier": {
|
||||||
"version": "10.1.2",
|
"version": "10.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz",
|
||||||
@@ -7852,19 +7824,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-vue/node_modules/semver": {
|
|
||||||
"version": "7.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/eslint-plugin-vue/node_modules/type-fest": {
|
"node_modules/eslint-plugin-vue/node_modules/type-fest": {
|
||||||
"version": "0.20.2",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
||||||
@@ -10284,18 +10243,6 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsonc-eslint-parser/node_modules/semver": {
|
|
||||||
"version": "7.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
|
||||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/jsondiffpatch": {
|
"node_modules/jsondiffpatch": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
|
||||||
@@ -10726,19 +10673,6 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/langsmith/node_modules/semver": {
|
|
||||||
"version": "7.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/latest-version": {
|
"node_modules/latest-version": {
|
||||||
"version": "9.0.0",
|
"version": "9.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz",
|
||||||
@@ -12688,19 +12622,6 @@
|
|||||||
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
|
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/package-json/node_modules/semver": {
|
|
||||||
"version": "7.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/package-manager-detector": {
|
"node_modules/package-manager-detector": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.0.tgz",
|
||||||
@@ -14343,12 +14264,14 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "6.3.1",
|
"version": "7.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/send": {
|
"node_modules/send": {
|
||||||
@@ -16207,19 +16130,6 @@
|
|||||||
"url": "https://github.com/yeoman/update-notifier?sponsor=1"
|
"url": "https://github.com/yeoman/update-notifier?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/update-notifier/node_modules/semver": {
|
|
||||||
"version": "7.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/uri-js": {
|
"node_modules/uri-js": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||||
@@ -17039,19 +16949,6 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue-eslint-parser/node_modules/semver": {
|
|
||||||
"version": "7.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vue-i18n": {
|
"node_modules/vue-i18n": {
|
||||||
"version": "9.14.3",
|
"version": "9.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.3.tgz",
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.3.tgz",
|
||||||
@@ -17104,19 +17001,6 @@
|
|||||||
"typescript": ">=5.0.0"
|
"typescript": ">=5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue-tsc/node_modules/semver": {
|
|
||||||
"version": "7.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vuefire": {
|
"node_modules/vuefire": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/vuefire/-/vuefire-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/vuefire/-/vuefire-3.2.1.tgz",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/lodash": "^4.17.6",
|
"@types/lodash": "^4.17.6",
|
||||||
"@types/node": "^20.14.8",
|
"@types/node": "^20.14.8",
|
||||||
|
"@types/semver": "^7.7.0",
|
||||||
"@types/three": "^0.169.0",
|
"@types/three": "^0.169.0",
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
@@ -105,6 +106,7 @@
|
|||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primevue": "^4.2.5",
|
"primevue": "^4.2.5",
|
||||||
|
"semver": "^7.7.2",
|
||||||
"three": "^0.170.0",
|
"three": "^0.170.0",
|
||||||
"tiptap-markdown": "^0.8.10",
|
"tiptap-markdown": "^0.8.10",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Message
|
|
||||||
v-if="versionStore.shouldShowWarning"
|
|
||||||
severity="warn"
|
|
||||||
icon="pi pi-exclamation-triangle"
|
|
||||||
class="my-2 mx-2 version-warning-fix"
|
|
||||||
:pt="{
|
|
||||||
root: { class: 'flex-col' },
|
|
||||||
text: { class: 'flex-1' },
|
|
||||||
icon: { class: 'flex items-start mt-1' }
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
<!-- Warning Message -->
|
|
||||||
<div class="font-medium">
|
|
||||||
{{ $t('versionMismatchWarning.title') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Version Details -->
|
|
||||||
<div v-if="versionStore.warningMessage">
|
|
||||||
<div v-if="versionStore.warningMessage.type === 'outdated'">
|
|
||||||
{{
|
|
||||||
$t('versionMismatchWarning.frontendOutdated', {
|
|
||||||
frontendVersion: versionStore.warningMessage.frontendVersion,
|
|
||||||
requiredVersion: versionStore.warningMessage.requiredVersion
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<div v-else-if="versionStore.warningMessage.type === 'newer'">
|
|
||||||
{{
|
|
||||||
$t('versionMismatchWarning.frontendNewer', {
|
|
||||||
frontendVersion: versionStore.warningMessage.frontendVersion,
|
|
||||||
backendVersion: versionStore.warningMessage.backendVersion
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
|
||||||
<div class="flex gap-2 justify-end">
|
|
||||||
<Button
|
|
||||||
v-if="versionStore.isFrontendOutdated"
|
|
||||||
:label="$t('versionMismatchWarning.updateFrontend')"
|
|
||||||
size="small"
|
|
||||||
severity="warn"
|
|
||||||
@click="handleUpdate"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
:label="$t('versionMismatchWarning.dismiss')"
|
|
||||||
size="small"
|
|
||||||
severity="secondary"
|
|
||||||
@click="handleDismiss"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Message>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import Button from 'primevue/button'
|
|
||||||
import Message from 'primevue/message'
|
|
||||||
|
|
||||||
import { useVersionCompatibilityStore } from '@/stores/versionCompatibilityStore'
|
|
||||||
|
|
||||||
const versionStore = useVersionCompatibilityStore()
|
|
||||||
|
|
||||||
const handleDismiss = () => {
|
|
||||||
void versionStore.dismissWarning()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUpdate = () => {
|
|
||||||
// Open ComfyUI documentation or update instructions
|
|
||||||
window.open(
|
|
||||||
'https://docs.comfy.org/installation/update_comfyui#missing-or-outdated-frontend%2C-workflow-templates%2C-node-after-updates',
|
|
||||||
'_blank'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.version-warning-fix :deep(.p-message-icon) {
|
|
||||||
align-self: flex-start;
|
|
||||||
margin-top: 0.125rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
94
src/composables/useFrontendVersionMismatchWarning.ts
Normal file
94
src/composables/useFrontendVersionMismatchWarning.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { whenever } from '@vueuse/core'
|
||||||
|
import { computed, onMounted } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import { useToastStore } from '@/stores/toastStore'
|
||||||
|
import { useVersionCompatibilityStore } from '@/stores/versionCompatibilityStore'
|
||||||
|
|
||||||
|
export interface UseFrontendVersionMismatchWarningOptions {
|
||||||
|
immediate?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable for handling frontend version mismatch warnings.
|
||||||
|
*
|
||||||
|
* Displays toast notifications when the frontend version is incompatible with the backend,
|
||||||
|
* either because the frontend is outdated or newer than the backend expects.
|
||||||
|
* Automatically dismisses warnings when shown and persists dismissal state for 7 days.
|
||||||
|
*
|
||||||
|
* @param options - Configuration options
|
||||||
|
* @param options.immediate - If true, automatically shows warning when version mismatch is detected
|
||||||
|
* @returns Object with methods and computed properties for managing version warnings
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Show warning immediately when mismatch detected
|
||||||
|
* const { showWarning, shouldShowWarning } = useFrontendVersionMismatchWarning({ immediate: true })
|
||||||
|
*
|
||||||
|
* // Manual control
|
||||||
|
* const { showWarning } = useFrontendVersionMismatchWarning()
|
||||||
|
* showWarning() // Call when needed
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useFrontendVersionMismatchWarning(
|
||||||
|
options: UseFrontendVersionMismatchWarningOptions = {}
|
||||||
|
) {
|
||||||
|
const { immediate = false } = options
|
||||||
|
const { t } = useI18n()
|
||||||
|
const toastStore = useToastStore()
|
||||||
|
const versionCompatibilityStore = useVersionCompatibilityStore()
|
||||||
|
|
||||||
|
// Track if we've already shown the warning
|
||||||
|
let hasShownWarning = false
|
||||||
|
|
||||||
|
const showWarning = () => {
|
||||||
|
// Prevent showing the warning multiple times
|
||||||
|
if (hasShownWarning) return
|
||||||
|
|
||||||
|
const message = versionCompatibilityStore.warningMessage
|
||||||
|
if (!message) return
|
||||||
|
|
||||||
|
const detailMessage = t('g.frontendOutdated', {
|
||||||
|
frontendVersion: message.frontendVersion,
|
||||||
|
requiredVersion: message.requiredVersion
|
||||||
|
})
|
||||||
|
|
||||||
|
const fullMessage = t('g.versionMismatchWarningMessage', {
|
||||||
|
warning: t('g.versionMismatchWarning'),
|
||||||
|
detail: detailMessage
|
||||||
|
})
|
||||||
|
|
||||||
|
toastStore.addAlert(fullMessage)
|
||||||
|
hasShownWarning = true
|
||||||
|
|
||||||
|
// Automatically dismiss the warning so it won't show again for 7 days
|
||||||
|
versionCompatibilityStore.dismissWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Only set up the watcher if immediate is true
|
||||||
|
if (immediate) {
|
||||||
|
whenever(
|
||||||
|
() => versionCompatibilityStore.shouldShowWarning,
|
||||||
|
() => {
|
||||||
|
showWarning()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
once: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
showWarning,
|
||||||
|
shouldShowWarning: computed(
|
||||||
|
() => versionCompatibilityStore.shouldShowWarning
|
||||||
|
),
|
||||||
|
dismissWarning: versionCompatibilityStore.dismissWarning,
|
||||||
|
hasVersionMismatch: computed(
|
||||||
|
() => versionCompatibilityStore.hasVersionMismatch
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -98,6 +98,12 @@
|
|||||||
"nodes": "Nodes",
|
"nodes": "Nodes",
|
||||||
"community": "Community",
|
"community": "Community",
|
||||||
"all": "All",
|
"all": "All",
|
||||||
|
"versionMismatchWarning": "Version Compatibility Warning",
|
||||||
|
"versionMismatchWarningMessage": "{warning}: {detail} Visit https://docs.comfy.org/installation/update_comfyui#common-update-issues for update instructions.",
|
||||||
|
"frontendOutdated": "Frontend version {frontendVersion} is outdated. Backend requires {requiredVersion} or higher.",
|
||||||
|
"frontendNewer": "Frontend version {frontendVersion} may not be compatible with backend version {backendVersion}.",
|
||||||
|
"updateFrontend": "Update Frontend",
|
||||||
|
"dismiss": "Dismiss",
|
||||||
"update": "Update",
|
"update": "Update",
|
||||||
"updated": "Updated",
|
"updated": "Updated",
|
||||||
"resultsCount": "Found {count} Results",
|
"resultsCount": "Found {count} Results",
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { computed, ref } from 'vue'
|
import * as semver from 'semver'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import config from '@/config'
|
import config from '@/config'
|
||||||
import { useSettingStore } from '@/stores/settingStore'
|
|
||||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||||
import { compareVersions, isSemVer } from '@/utils/formatUtil'
|
|
||||||
|
const DISMISSAL_DURATION_MS = 7 * 24 * 60 * 60 * 1000 // 7 days
|
||||||
|
|
||||||
export const useVersionCompatibilityStore = defineStore(
|
export const useVersionCompatibilityStore = defineStore(
|
||||||
'versionCompatibility',
|
'versionCompatibility',
|
||||||
() => {
|
() => {
|
||||||
const systemStatsStore = useSystemStatsStore()
|
const systemStatsStore = useSystemStatsStore()
|
||||||
const settingStore = useSettingStore()
|
|
||||||
|
|
||||||
const isDismissed = ref(false)
|
|
||||||
const dismissedVersion = ref<string | null>(
|
|
||||||
settingStore.get('Comfy.VersionMismatch.DismissedVersion') ?? null
|
|
||||||
)
|
|
||||||
|
|
||||||
const frontendVersion = computed(() => config.app_version)
|
const frontendVersion = computed(() => config.app_version)
|
||||||
const backendVersion = computed(
|
const backendVersion = computed(
|
||||||
@@ -30,47 +26,68 @@ export const useVersionCompatibilityStore = defineStore(
|
|||||||
if (
|
if (
|
||||||
!frontendVersion.value ||
|
!frontendVersion.value ||
|
||||||
!requiredFrontendVersion.value ||
|
!requiredFrontendVersion.value ||
|
||||||
!isSemVer(frontendVersion.value) ||
|
!semver.valid(frontendVersion.value) ||
|
||||||
!isSemVer(requiredFrontendVersion.value)
|
!semver.valid(requiredFrontendVersion.value)
|
||||||
) {
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return (
|
// Returns true if required version is greater than frontend version
|
||||||
compareVersions(requiredFrontendVersion.value, frontendVersion.value) >
|
return semver.gt(requiredFrontendVersion.value, frontendVersion.value)
|
||||||
0
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const isFrontendNewer = computed(() => {
|
const isFrontendNewer = computed(() => {
|
||||||
if (
|
// We don't warn about frontend being newer than backend
|
||||||
!frontendVersion.value ||
|
// Only warn when frontend is outdated (behind required version)
|
||||||
!backendVersion.value ||
|
return false
|
||||||
!isSemVer(frontendVersion.value) ||
|
|
||||||
!isSemVer(backendVersion.value)
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const versionDiff = compareVersions(
|
|
||||||
frontendVersion.value,
|
|
||||||
backendVersion.value
|
|
||||||
)
|
|
||||||
return versionDiff > 0
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasVersionMismatch = computed(() => {
|
const hasVersionMismatch = computed(() => {
|
||||||
return isFrontendOutdated.value || isFrontendNewer.value
|
return isFrontendOutdated.value
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentVersionKey = computed(
|
const versionKey = computed(() => {
|
||||||
() =>
|
if (
|
||||||
`${frontendVersion.value}-${backendVersion.value}-${requiredFrontendVersion.value}`
|
!frontendVersion.value ||
|
||||||
|
!backendVersion.value ||
|
||||||
|
!requiredFrontendVersion.value
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return `${frontendVersion.value}-${backendVersion.value}-${requiredFrontendVersion.value}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Use reactive storage for dismissals - creates a reactive ref that syncs with localStorage
|
||||||
|
// All version mismatch dismissals are stored in a single object for clean localStorage organization
|
||||||
|
const dismissalStorage = useStorage(
|
||||||
|
'comfy.versionMismatch.dismissals',
|
||||||
|
{} as Record<string, number>,
|
||||||
|
localStorage,
|
||||||
|
{
|
||||||
|
serializer: {
|
||||||
|
read: (value: string) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value)
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
write: (value: Record<string, number>) => JSON.stringify(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isDismissed = computed(() => {
|
||||||
|
if (!versionKey.value) return false
|
||||||
|
|
||||||
|
const dismissedUntil = dismissalStorage.value[versionKey.value]
|
||||||
|
if (!dismissedUntil) return false
|
||||||
|
|
||||||
|
// Check if dismissal has expired
|
||||||
|
return Date.now() < dismissedUntil
|
||||||
|
})
|
||||||
|
|
||||||
const shouldShowWarning = computed(() => {
|
const shouldShowWarning = computed(() => {
|
||||||
if (!hasVersionMismatch.value || isDismissed.value) {
|
return hasVersionMismatch.value && !isDismissed.value
|
||||||
return false
|
|
||||||
}
|
|
||||||
return dismissedVersion.value !== currentVersionKey.value
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const warningMessage = computed(() => {
|
const warningMessage = computed(() => {
|
||||||
@@ -80,12 +97,6 @@ export const useVersionCompatibilityStore = defineStore(
|
|||||||
frontendVersion: frontendVersion.value,
|
frontendVersion: frontendVersion.value,
|
||||||
requiredVersion: requiredFrontendVersion.value
|
requiredVersion: requiredFrontendVersion.value
|
||||||
}
|
}
|
||||||
} else if (isFrontendNewer.value) {
|
|
||||||
return {
|
|
||||||
type: 'newer' as const,
|
|
||||||
frontendVersion: frontendVersion.value,
|
|
||||||
backendVersion: backendVersion.value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
@@ -96,29 +107,18 @@ export const useVersionCompatibilityStore = defineStore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function dismissWarning() {
|
function dismissWarning() {
|
||||||
isDismissed.value = true
|
if (!versionKey.value) return
|
||||||
dismissedVersion.value = currentVersionKey.value
|
|
||||||
|
|
||||||
await settingStore.set(
|
const dismissUntil = Date.now() + DISMISSAL_DURATION_MS
|
||||||
'Comfy.VersionMismatch.DismissedVersion',
|
dismissalStorage.value = {
|
||||||
currentVersionKey.value
|
...dismissalStorage.value,
|
||||||
)
|
[versionKey.value]: dismissUntil
|
||||||
}
|
|
||||||
|
|
||||||
function restoreDismissalState() {
|
|
||||||
const dismissed = settingStore.get(
|
|
||||||
'Comfy.VersionMismatch.DismissedVersion'
|
|
||||||
)
|
|
||||||
if (dismissed) {
|
|
||||||
dismissedVersion.value = dismissed
|
|
||||||
isDismissed.value = dismissed === currentVersionKey.value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initialize() {
|
async function initialize() {
|
||||||
await checkVersionCompatibility()
|
await checkVersionCompatibility()
|
||||||
restoreDismissalState()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
<div id="comfyui-body-left" class="comfyui-body-left" />
|
<div id="comfyui-body-left" class="comfyui-body-left" />
|
||||||
<div id="comfyui-body-right" class="comfyui-body-right" />
|
<div id="comfyui-body-right" class="comfyui-body-right" />
|
||||||
<div id="graph-canvas-container" class="graph-canvas-container">
|
<div id="graph-canvas-container" class="graph-canvas-container">
|
||||||
<VersionMismatchWarning />
|
|
||||||
<GraphCanvas @ready="onGraphReady" />
|
<GraphCanvas @ready="onGraphReady" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -24,12 +23,18 @@
|
|||||||
import { useBreakpoints, useEventListener } from '@vueuse/core'
|
import { useBreakpoints, useEventListener } from '@vueuse/core'
|
||||||
import type { ToastMessageOptions } from 'primevue/toast'
|
import type { ToastMessageOptions } from 'primevue/toast'
|
||||||
import { useToast } from 'primevue/usetoast'
|
import { useToast } from 'primevue/usetoast'
|
||||||
import { computed, onBeforeUnmount, onMounted, watch, watchEffect } from 'vue'
|
import {
|
||||||
|
computed,
|
||||||
|
nextTick,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
watch,
|
||||||
|
watchEffect
|
||||||
|
} from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import MenuHamburger from '@/components/MenuHamburger.vue'
|
import MenuHamburger from '@/components/MenuHamburger.vue'
|
||||||
import UnloadWindowConfirmDialog from '@/components/dialog/UnloadWindowConfirmDialog.vue'
|
import UnloadWindowConfirmDialog from '@/components/dialog/UnloadWindowConfirmDialog.vue'
|
||||||
import VersionMismatchWarning from '@/components/dialog/content/VersionMismatchWarning.vue'
|
|
||||||
import GraphCanvas from '@/components/graph/GraphCanvas.vue'
|
import GraphCanvas from '@/components/graph/GraphCanvas.vue'
|
||||||
import GlobalToast from '@/components/toast/GlobalToast.vue'
|
import GlobalToast from '@/components/toast/GlobalToast.vue'
|
||||||
import RerouteMigrationToast from '@/components/toast/RerouteMigrationToast.vue'
|
import RerouteMigrationToast from '@/components/toast/RerouteMigrationToast.vue'
|
||||||
@@ -37,6 +42,7 @@ import TopMenubar from '@/components/topbar/TopMenubar.vue'
|
|||||||
import { useBrowserTabTitle } from '@/composables/useBrowserTabTitle'
|
import { useBrowserTabTitle } from '@/composables/useBrowserTabTitle'
|
||||||
import { useCoreCommands } from '@/composables/useCoreCommands'
|
import { useCoreCommands } from '@/composables/useCoreCommands'
|
||||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||||
|
import { useFrontendVersionMismatchWarning } from '@/composables/useFrontendVersionMismatchWarning'
|
||||||
import { useProgressFavicon } from '@/composables/useProgressFavicon'
|
import { useProgressFavicon } from '@/composables/useProgressFavicon'
|
||||||
import { SERVER_CONFIG_ITEMS } from '@/constants/serverConfig'
|
import { SERVER_CONFIG_ITEMS } from '@/constants/serverConfig'
|
||||||
import { i18n } from '@/i18n'
|
import { i18n } from '@/i18n'
|
||||||
@@ -229,6 +235,22 @@ onBeforeUnmount(() => {
|
|||||||
useEventListener(window, 'keydown', useKeybindingService().keybindHandler)
|
useEventListener(window, 'keydown', useKeybindingService().keybindHandler)
|
||||||
|
|
||||||
const { wrapWithErrorHandling, wrapWithErrorHandlingAsync } = useErrorHandling()
|
const { wrapWithErrorHandling, wrapWithErrorHandlingAsync } = useErrorHandling()
|
||||||
|
|
||||||
|
// Initialize version mismatch warning in setup context
|
||||||
|
// It will be triggered automatically when the store is ready
|
||||||
|
useFrontendVersionMismatchWarning({ immediate: true })
|
||||||
|
|
||||||
|
// Initialize version compatibility check completely independently of app setup
|
||||||
|
// This runs asynchronously after component setup and won't block the main application
|
||||||
|
void nextTick(() => {
|
||||||
|
// Use setTimeout to ensure this happens after all other immediate tasks
|
||||||
|
setTimeout(() => {
|
||||||
|
versionCompatibilityStore.initialize().catch((error) => {
|
||||||
|
console.warn('Version compatibility check failed:', error)
|
||||||
|
})
|
||||||
|
}, 100) // Small delay to ensure app is fully loaded
|
||||||
|
})
|
||||||
|
|
||||||
const onGraphReady = () => {
|
const onGraphReady = () => {
|
||||||
requestIdleCallback(
|
requestIdleCallback(
|
||||||
() => {
|
() => {
|
||||||
@@ -254,7 +276,6 @@ const onGraphReady = () => {
|
|||||||
// Explicitly initialize nodeSearchService to avoid indexing delay when
|
// Explicitly initialize nodeSearchService to avoid indexing delay when
|
||||||
// node search is triggered
|
// node search is triggered
|
||||||
useNodeDefStore().nodeSearchService.searchNode('')
|
useNodeDefStore().nodeSearchService.searchNode('')
|
||||||
void versionCompatibilityStore.initialize()
|
|
||||||
},
|
},
|
||||||
{ timeout: 1000 }
|
{ timeout: 1000 }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
import { createPinia, setActivePinia } from 'pinia'
|
||||||
|
import { vi } from 'vitest'
|
||||||
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||||
|
import { nextTick } from 'vue'
|
||||||
|
|
||||||
|
import { useFrontendVersionMismatchWarning } from '@/composables/useFrontendVersionMismatchWarning'
|
||||||
|
import { useToastStore } from '@/stores/toastStore'
|
||||||
|
import { useVersionCompatibilityStore } from '@/stores/versionCompatibilityStore'
|
||||||
|
|
||||||
|
// Mock globals
|
||||||
|
//@ts-expect-error Define global for the test
|
||||||
|
global.__COMFYUI_FRONTEND_VERSION__ = '1.0.0'
|
||||||
|
|
||||||
|
// Mock config first - this needs to be before any imports
|
||||||
|
vi.mock('@/config', () => ({
|
||||||
|
default: {
|
||||||
|
app_title: 'ComfyUI',
|
||||||
|
app_version: '1.0.0'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Mock app
|
||||||
|
vi.mock('@/scripts/app', () => ({
|
||||||
|
app: {
|
||||||
|
ui: {
|
||||||
|
settings: {
|
||||||
|
dispatchChange: vi.fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Mock api
|
||||||
|
vi.mock('@/scripts/api', () => ({
|
||||||
|
api: {
|
||||||
|
getSettings: vi.fn(() => Promise.resolve({})),
|
||||||
|
storeSetting: vi.fn(() => Promise.resolve(undefined))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Mock vue-i18n
|
||||||
|
vi.mock('vue-i18n', () => ({
|
||||||
|
useI18n: () => ({
|
||||||
|
t: (key: string, params?: any) => {
|
||||||
|
if (key === 'g.versionMismatchWarning')
|
||||||
|
return 'Version Compatibility Warning'
|
||||||
|
if (key === 'g.versionMismatchWarningMessage' && params) {
|
||||||
|
return `${params.warning}: ${params.detail} Visit https://docs.comfy.org/installation/update_comfyui#common-update-issues for update instructions.`
|
||||||
|
}
|
||||||
|
if (key === 'g.frontendOutdated' && params) {
|
||||||
|
return `Frontend version ${params.frontendVersion} is outdated. Backend requires ${params.requiredVersion} or higher.`
|
||||||
|
}
|
||||||
|
if (key === 'g.frontendNewer' && params) {
|
||||||
|
return `Frontend version ${params.frontendVersion} may not be compatible with backend version ${params.backendVersion}.`
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
createI18n: vi.fn(() => ({
|
||||||
|
global: {
|
||||||
|
locale: { value: 'en' },
|
||||||
|
t: vi.fn()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Mock lifecycle hooks to track their calls
|
||||||
|
const mockOnMounted = vi.fn()
|
||||||
|
vi.mock('vue', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('vue')>()
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
onMounted: (fn: () => void) => {
|
||||||
|
mockOnMounted()
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('useFrontendVersionMismatchWarning', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
setActivePinia(createPinia())
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not show warning when there is no version mismatch', () => {
|
||||||
|
const toastStore = useToastStore()
|
||||||
|
const versionStore = useVersionCompatibilityStore()
|
||||||
|
const addAlertSpy = vi.spyOn(toastStore, 'addAlert')
|
||||||
|
|
||||||
|
// Mock no version mismatch
|
||||||
|
vi.spyOn(versionStore, 'shouldShowWarning', 'get').mockReturnValue(false)
|
||||||
|
|
||||||
|
useFrontendVersionMismatchWarning()
|
||||||
|
|
||||||
|
expect(addAlertSpy).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should show warning immediately when immediate option is true and there is a mismatch', async () => {
|
||||||
|
const toastStore = useToastStore()
|
||||||
|
const versionStore = useVersionCompatibilityStore()
|
||||||
|
const addAlertSpy = vi.spyOn(toastStore, 'addAlert')
|
||||||
|
const dismissWarningSpy = vi.spyOn(versionStore, 'dismissWarning')
|
||||||
|
|
||||||
|
// Mock version mismatch
|
||||||
|
vi.spyOn(versionStore, 'shouldShowWarning', 'get').mockReturnValue(true)
|
||||||
|
vi.spyOn(versionStore, 'warningMessage', 'get').mockReturnValue({
|
||||||
|
type: 'outdated',
|
||||||
|
frontendVersion: '1.0.0',
|
||||||
|
requiredVersion: '2.0.0'
|
||||||
|
})
|
||||||
|
|
||||||
|
useFrontendVersionMismatchWarning({ immediate: true })
|
||||||
|
|
||||||
|
// For immediate: true, the watcher should fire immediately in onMounted
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(addAlertSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('Version Compatibility Warning')
|
||||||
|
)
|
||||||
|
expect(addAlertSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('Frontend version 1.0.0 is outdated')
|
||||||
|
)
|
||||||
|
// Should automatically dismiss the warning
|
||||||
|
expect(dismissWarningSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not show warning immediately when immediate option is false', async () => {
|
||||||
|
const toastStore = useToastStore()
|
||||||
|
const versionStore = useVersionCompatibilityStore()
|
||||||
|
const addAlertSpy = vi.spyOn(toastStore, 'addAlert')
|
||||||
|
|
||||||
|
// Mock version mismatch
|
||||||
|
vi.spyOn(versionStore, 'shouldShowWarning', 'get').mockReturnValue(true)
|
||||||
|
vi.spyOn(versionStore, 'warningMessage', 'get').mockReturnValue({
|
||||||
|
type: 'outdated',
|
||||||
|
frontendVersion: '1.0.0',
|
||||||
|
requiredVersion: '2.0.0'
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = useFrontendVersionMismatchWarning({ immediate: false })
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
// Should not show automatically
|
||||||
|
expect(addAlertSpy).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
// But should show when called manually
|
||||||
|
result.showWarning()
|
||||||
|
expect(addAlertSpy).toHaveBeenCalledOnce()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call showWarning method manually', () => {
|
||||||
|
const toastStore = useToastStore()
|
||||||
|
const versionStore = useVersionCompatibilityStore()
|
||||||
|
const addAlertSpy = vi.spyOn(toastStore, 'addAlert')
|
||||||
|
const dismissWarningSpy = vi.spyOn(versionStore, 'dismissWarning')
|
||||||
|
|
||||||
|
vi.spyOn(versionStore, 'warningMessage', 'get').mockReturnValue({
|
||||||
|
type: 'outdated',
|
||||||
|
frontendVersion: '1.0.0',
|
||||||
|
requiredVersion: '2.0.0'
|
||||||
|
})
|
||||||
|
|
||||||
|
const { showWarning } = useFrontendVersionMismatchWarning()
|
||||||
|
showWarning()
|
||||||
|
|
||||||
|
expect(addAlertSpy).toHaveBeenCalledOnce()
|
||||||
|
expect(dismissWarningSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should expose store methods and computed values', () => {
|
||||||
|
const versionStore = useVersionCompatibilityStore()
|
||||||
|
|
||||||
|
const mockDismissWarning = vi.fn()
|
||||||
|
vi.spyOn(versionStore, 'dismissWarning').mockImplementation(
|
||||||
|
mockDismissWarning
|
||||||
|
)
|
||||||
|
vi.spyOn(versionStore, 'shouldShowWarning', 'get').mockReturnValue(true)
|
||||||
|
vi.spyOn(versionStore, 'hasVersionMismatch', 'get').mockReturnValue(true)
|
||||||
|
|
||||||
|
const result = useFrontendVersionMismatchWarning()
|
||||||
|
|
||||||
|
expect(result.shouldShowWarning.value).toBe(true)
|
||||||
|
expect(result.hasVersionMismatch.value).toBe(true)
|
||||||
|
|
||||||
|
void result.dismissWarning()
|
||||||
|
expect(mockDismissWarning).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should register onMounted hook', () => {
|
||||||
|
useFrontendVersionMismatchWarning()
|
||||||
|
|
||||||
|
expect(mockOnMounted).toHaveBeenCalledOnce()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not show warning when warningMessage is null', () => {
|
||||||
|
const toastStore = useToastStore()
|
||||||
|
const versionStore = useVersionCompatibilityStore()
|
||||||
|
const addAlertSpy = vi.spyOn(toastStore, 'addAlert')
|
||||||
|
|
||||||
|
vi.spyOn(versionStore, 'warningMessage', 'get').mockReturnValue(null)
|
||||||
|
|
||||||
|
const { showWarning } = useFrontendVersionMismatchWarning()
|
||||||
|
showWarning()
|
||||||
|
|
||||||
|
expect(addAlertSpy).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should only show warning once even if called multiple times', () => {
|
||||||
|
const toastStore = useToastStore()
|
||||||
|
const versionStore = useVersionCompatibilityStore()
|
||||||
|
const addAlertSpy = vi.spyOn(toastStore, 'addAlert')
|
||||||
|
|
||||||
|
vi.spyOn(versionStore, 'warningMessage', 'get').mockReturnValue({
|
||||||
|
type: 'outdated',
|
||||||
|
frontendVersion: '1.0.0',
|
||||||
|
requiredVersion: '2.0.0'
|
||||||
|
})
|
||||||
|
|
||||||
|
const { showWarning } = useFrontendVersionMismatchWarning()
|
||||||
|
|
||||||
|
// Call showWarning multiple times
|
||||||
|
showWarning()
|
||||||
|
showWarning()
|
||||||
|
showWarning()
|
||||||
|
|
||||||
|
// Should only have been called once
|
||||||
|
expect(addAlertSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createPinia, setActivePinia } from 'pinia'
|
import { createPinia, setActivePinia } from 'pinia'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import { useSettingStore } from '@/stores/settingStore'
|
|
||||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||||
import { useVersionCompatibilityStore } from '@/stores/versionCompatibilityStore'
|
import { useVersionCompatibilityStore } from '@/stores/versionCompatibilityStore'
|
||||||
|
|
||||||
@@ -12,32 +12,37 @@ vi.mock('@/config', () => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/stores/systemStatsStore')
|
vi.mock('@/stores/systemStatsStore')
|
||||||
vi.mock('@/stores/settingStore')
|
|
||||||
|
// Mock useStorage from VueUse
|
||||||
|
const mockDismissalStorage = ref({} as Record<string, number>)
|
||||||
|
vi.mock('@vueuse/core', () => ({
|
||||||
|
useStorage: vi.fn(() => mockDismissalStorage)
|
||||||
|
}))
|
||||||
|
|
||||||
describe('useVersionCompatibilityStore', () => {
|
describe('useVersionCompatibilityStore', () => {
|
||||||
let store: ReturnType<typeof useVersionCompatibilityStore>
|
let store: ReturnType<typeof useVersionCompatibilityStore>
|
||||||
let mockSystemStatsStore: any
|
let mockSystemStatsStore: any
|
||||||
let mockSettingStore: any
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setActivePinia(createPinia())
|
setActivePinia(createPinia())
|
||||||
|
|
||||||
|
// Clear the mock dismissal storage
|
||||||
|
mockDismissalStorage.value = {}
|
||||||
|
|
||||||
mockSystemStatsStore = {
|
mockSystemStatsStore = {
|
||||||
systemStats: null,
|
systemStats: null,
|
||||||
fetchSystemStats: vi.fn()
|
fetchSystemStats: vi.fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
mockSettingStore = {
|
|
||||||
get: vi.fn(),
|
|
||||||
set: vi.fn()
|
|
||||||
}
|
|
||||||
|
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue(mockSystemStatsStore)
|
vi.mocked(useSystemStatsStore).mockReturnValue(mockSystemStatsStore)
|
||||||
vi.mocked(useSettingStore).mockReturnValue(mockSettingStore)
|
|
||||||
|
|
||||||
store = useVersionCompatibilityStore()
|
store = useVersionCompatibilityStore()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
describe('version compatibility detection', () => {
|
describe('version compatibility detection', () => {
|
||||||
it('should detect frontend is outdated when required version is higher', async () => {
|
it('should detect frontend is outdated when required version is higher', async () => {
|
||||||
mockSystemStatsStore.systemStats = {
|
mockSystemStatsStore.systemStats = {
|
||||||
@@ -54,7 +59,9 @@ describe('useVersionCompatibilityStore', () => {
|
|||||||
expect(store.hasVersionMismatch).toBe(true)
|
expect(store.hasVersionMismatch).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should detect frontend is newer when frontend version is higher than backend', async () => {
|
it('should not warn when frontend is newer than backend', async () => {
|
||||||
|
// Frontend: 1.24.0, Backend: 1.23.0, Required: 1.23.0
|
||||||
|
// Frontend meets required version, no warning needed
|
||||||
mockSystemStatsStore.systemStats = {
|
mockSystemStatsStore.systemStats = {
|
||||||
system: {
|
system: {
|
||||||
comfyui_version: '1.23.0',
|
comfyui_version: '1.23.0',
|
||||||
@@ -65,8 +72,8 @@ describe('useVersionCompatibilityStore', () => {
|
|||||||
await store.checkVersionCompatibility()
|
await store.checkVersionCompatibility()
|
||||||
|
|
||||||
expect(store.isFrontendOutdated).toBe(false)
|
expect(store.isFrontendOutdated).toBe(false)
|
||||||
expect(store.isFrontendNewer).toBe(true)
|
expect(store.isFrontendNewer).toBe(false)
|
||||||
expect(store.hasVersionMismatch).toBe(true)
|
expect(store.hasVersionMismatch).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not detect mismatch when versions are compatible', async () => {
|
it('should not detect mismatch when versions are compatible', async () => {
|
||||||
@@ -103,7 +110,7 @@ describe('useVersionCompatibilityStore', () => {
|
|||||||
mockSystemStatsStore.systemStats = {
|
mockSystemStatsStore.systemStats = {
|
||||||
system: {
|
system: {
|
||||||
comfyui_version: '080e6d4af809a46852d1c4b7ed85f06e8a3a72be', // git hash
|
comfyui_version: '080e6d4af809a46852d1c4b7ed85f06e8a3a72be', // git hash
|
||||||
required_frontend_version: '1.29.2.45' // invalid semver
|
required_frontend_version: 'not-a-version' // invalid semver format
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,14 +120,28 @@ describe('useVersionCompatibilityStore', () => {
|
|||||||
expect(store.isFrontendNewer).toBe(false)
|
expect(store.isFrontendNewer).toBe(false)
|
||||||
expect(store.hasVersionMismatch).toBe(false)
|
expect(store.hasVersionMismatch).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not warn when frontend exceeds required version', async () => {
|
||||||
|
// Frontend: 1.24.0 (from mock config)
|
||||||
|
mockSystemStatsStore.systemStats = {
|
||||||
|
system: {
|
||||||
|
comfyui_version: '1.22.0', // Backend is older
|
||||||
|
required_frontend_version: '1.23.0' // Required is 1.23.0, frontend 1.24.0 meets this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.checkVersionCompatibility()
|
||||||
|
|
||||||
|
expect(store.isFrontendOutdated).toBe(false) // Frontend 1.24.0 >= Required 1.23.0
|
||||||
|
expect(store.isFrontendNewer).toBe(false) // Never warns about being newer
|
||||||
|
expect(store.hasVersionMismatch).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('warning display logic', () => {
|
describe('warning display logic', () => {
|
||||||
beforeEach(() => {
|
|
||||||
mockSettingStore.get.mockReturnValue('')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show warning when there is a version mismatch and not dismissed', async () => {
|
it('should show warning when there is a version mismatch and not dismissed', async () => {
|
||||||
|
// No dismissals in storage
|
||||||
|
mockDismissalStorage.value = {}
|
||||||
mockSystemStatsStore.systemStats = {
|
mockSystemStatsStore.systemStats = {
|
||||||
system: {
|
system: {
|
||||||
comfyui_version: '1.25.0',
|
comfyui_version: '1.25.0',
|
||||||
@@ -134,6 +155,12 @@ describe('useVersionCompatibilityStore', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not show warning when dismissed', async () => {
|
it('should not show warning when dismissed', async () => {
|
||||||
|
const futureTime = Date.now() + 1000000
|
||||||
|
// Set dismissal in reactive storage
|
||||||
|
mockDismissalStorage.value = {
|
||||||
|
'1.24.0-1.25.0-1.25.0': futureTime
|
||||||
|
}
|
||||||
|
|
||||||
mockSystemStatsStore.systemStats = {
|
mockSystemStatsStore.systemStats = {
|
||||||
system: {
|
system: {
|
||||||
comfyui_version: '1.25.0',
|
comfyui_version: '1.25.0',
|
||||||
@@ -142,7 +169,6 @@ describe('useVersionCompatibilityStore', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await store.checkVersionCompatibility()
|
await store.checkVersionCompatibility()
|
||||||
void store.dismissWarning()
|
|
||||||
|
|
||||||
expect(store.shouldShowWarning).toBe(false)
|
expect(store.shouldShowWarning).toBe(false)
|
||||||
})
|
})
|
||||||
@@ -179,23 +205,6 @@ describe('useVersionCompatibilityStore', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should generate newer message when frontend is newer', async () => {
|
|
||||||
mockSystemStatsStore.systemStats = {
|
|
||||||
system: {
|
|
||||||
comfyui_version: '1.23.0',
|
|
||||||
required_frontend_version: '1.23.0'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await store.checkVersionCompatibility()
|
|
||||||
|
|
||||||
expect(store.warningMessage).toEqual({
|
|
||||||
type: 'newer',
|
|
||||||
frontendVersion: '1.24.0',
|
|
||||||
backendVersion: '1.23.0'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return null when no mismatch', async () => {
|
it('should return null when no mismatch', async () => {
|
||||||
mockSystemStatsStore.systemStats = {
|
mockSystemStatsStore.systemStats = {
|
||||||
system: {
|
system: {
|
||||||
@@ -211,7 +220,10 @@ describe('useVersionCompatibilityStore', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('dismissal persistence', () => {
|
describe('dismissal persistence', () => {
|
||||||
it('should save dismissal to settings', async () => {
|
it('should save dismissal to reactive storage with expiration', async () => {
|
||||||
|
const mockNow = 1000000
|
||||||
|
vi.spyOn(Date, 'now').mockReturnValue(mockNow)
|
||||||
|
|
||||||
mockSystemStatsStore.systemStats = {
|
mockSystemStatsStore.systemStats = {
|
||||||
system: {
|
system: {
|
||||||
comfyui_version: '1.25.0',
|
comfyui_version: '1.25.0',
|
||||||
@@ -220,16 +232,20 @@ describe('useVersionCompatibilityStore', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await store.checkVersionCompatibility()
|
await store.checkVersionCompatibility()
|
||||||
await store.dismissWarning()
|
store.dismissWarning()
|
||||||
|
|
||||||
expect(mockSettingStore.set).toHaveBeenCalledWith(
|
// Check that the dismissal was added to reactive storage
|
||||||
'Comfy.VersionMismatch.DismissedVersion',
|
expect(mockDismissalStorage.value).toEqual({
|
||||||
'1.24.0-1.25.0-1.25.0'
|
'1.24.0-1.25.0-1.25.0': mockNow + 7 * 24 * 60 * 60 * 1000
|
||||||
)
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should restore dismissal state from settings', async () => {
|
it('should check dismissal state from reactive storage', async () => {
|
||||||
mockSettingStore.get.mockReturnValue('1.24.0-1.25.0-1.25.0')
|
const futureTime = Date.now() + 1000000 // Still valid
|
||||||
|
mockDismissalStorage.value = {
|
||||||
|
'1.24.0-1.25.0-1.25.0': futureTime
|
||||||
|
}
|
||||||
|
|
||||||
mockSystemStatsStore.systemStats = {
|
mockSystemStatsStore.systemStats = {
|
||||||
system: {
|
system: {
|
||||||
comfyui_version: '1.25.0',
|
comfyui_version: '1.25.0',
|
||||||
@@ -242,8 +258,31 @@ describe('useVersionCompatibilityStore', () => {
|
|||||||
expect(store.shouldShowWarning).toBe(false)
|
expect(store.shouldShowWarning).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should show warning if dismissal has expired', async () => {
|
||||||
|
const pastTime = Date.now() - 1000 // Expired
|
||||||
|
mockDismissalStorage.value = {
|
||||||
|
'1.24.0-1.25.0-1.25.0': pastTime
|
||||||
|
}
|
||||||
|
|
||||||
|
mockSystemStatsStore.systemStats = {
|
||||||
|
system: {
|
||||||
|
comfyui_version: '1.25.0',
|
||||||
|
required_frontend_version: '1.25.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.initialize()
|
||||||
|
|
||||||
|
expect(store.shouldShowWarning).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
it('should show warning for different version combinations even if previous was dismissed', async () => {
|
it('should show warning for different version combinations even if previous was dismissed', async () => {
|
||||||
mockSettingStore.get.mockReturnValue('1.24.0-1.25.0-1.25.0')
|
const futureTime = Date.now() + 1000000
|
||||||
|
// Dismissed for different version combination (1.25.0) but current is 1.26.0
|
||||||
|
mockDismissalStorage.value = {
|
||||||
|
'1.24.0-1.25.0-1.25.0': futureTime // Different version was dismissed
|
||||||
|
}
|
||||||
|
|
||||||
mockSystemStatsStore.systemStats = {
|
mockSystemStatsStore.systemStats = {
|
||||||
system: {
|
system: {
|
||||||
comfyui_version: '1.26.0',
|
comfyui_version: '1.26.0',
|
||||||
|
|||||||
Reference in New Issue
Block a user