mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-05 13:41:59 +00:00
merge main into rh-test
This commit is contained in:
207
tests-ui/tests/utils/conflictUtils.test.ts
Normal file
207
tests-ui/tests/utils/conflictUtils.test.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type {
|
||||
ConflictDetail,
|
||||
ConflictDetectionResult
|
||||
} from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
||||
import {
|
||||
consolidateConflictsByPackage,
|
||||
createBannedConflict,
|
||||
createPendingConflict
|
||||
} from '@/workbench/extensions/manager/utils/conflictUtils'
|
||||
|
||||
describe('conflictUtils', () => {
|
||||
describe('createBannedConflict', () => {
|
||||
it('should return banned conflict when isBanned is true', () => {
|
||||
const result = createBannedConflict(true)
|
||||
expect(result).toEqual({
|
||||
type: 'banned',
|
||||
current_value: 'installed',
|
||||
required_value: 'not_banned'
|
||||
})
|
||||
})
|
||||
|
||||
it('should return null when isBanned is false', () => {
|
||||
const result = createBannedConflict(false)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when isBanned is undefined', () => {
|
||||
const result = createBannedConflict(undefined)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('createPendingConflict', () => {
|
||||
it('should return pending conflict when isPending is true', () => {
|
||||
const result = createPendingConflict(true)
|
||||
expect(result).toEqual({
|
||||
type: 'pending',
|
||||
current_value: 'installed',
|
||||
required_value: 'not_pending'
|
||||
})
|
||||
})
|
||||
|
||||
it('should return null when isPending is false', () => {
|
||||
const result = createPendingConflict(false)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when isPending is undefined', () => {
|
||||
const result = createPendingConflict(undefined)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('consolidateConflictsByPackage', () => {
|
||||
it('should group conflicts by normalized package name', () => {
|
||||
const conflicts: ConflictDetectionResult[] = [
|
||||
{
|
||||
package_name: 'mypack@1_0_3',
|
||||
package_id: 'mypack@1_0_3',
|
||||
conflicts: [
|
||||
{ type: 'os', current_value: 'Windows', required_value: 'Linux' }
|
||||
],
|
||||
has_conflict: true,
|
||||
is_compatible: false
|
||||
},
|
||||
{
|
||||
package_name: 'mypack',
|
||||
package_id: 'mypack',
|
||||
conflicts: [
|
||||
{
|
||||
type: 'comfyui_version',
|
||||
current_value: '1.0.0',
|
||||
required_value: '>=2.0.0'
|
||||
}
|
||||
],
|
||||
has_conflict: true,
|
||||
is_compatible: false
|
||||
}
|
||||
]
|
||||
|
||||
const result = consolidateConflictsByPackage(conflicts)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].package_name).toBe('mypack')
|
||||
expect(result[0].conflicts).toHaveLength(2)
|
||||
expect(result[0].has_conflict).toBe(true)
|
||||
expect(result[0].is_compatible).toBe(false)
|
||||
})
|
||||
|
||||
it('should deduplicate identical conflicts', () => {
|
||||
const duplicateConflict: ConflictDetail = {
|
||||
type: 'os',
|
||||
current_value: 'Windows',
|
||||
required_value: 'Linux'
|
||||
}
|
||||
|
||||
const conflicts: ConflictDetectionResult[] = [
|
||||
{
|
||||
package_name: 'pack',
|
||||
package_id: 'pack',
|
||||
conflicts: [duplicateConflict],
|
||||
has_conflict: true,
|
||||
is_compatible: false
|
||||
},
|
||||
{
|
||||
package_name: 'pack@version',
|
||||
package_id: 'pack@version',
|
||||
conflicts: [duplicateConflict],
|
||||
has_conflict: true,
|
||||
is_compatible: false
|
||||
}
|
||||
]
|
||||
|
||||
const result = consolidateConflictsByPackage(conflicts)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].conflicts).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should handle packages without conflicts', () => {
|
||||
const conflicts: ConflictDetectionResult[] = [
|
||||
{
|
||||
package_name: 'compatible-pack',
|
||||
package_id: 'compatible-pack',
|
||||
conflicts: [],
|
||||
has_conflict: false,
|
||||
is_compatible: true
|
||||
}
|
||||
]
|
||||
|
||||
const result = consolidateConflictsByPackage(conflicts)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].conflicts).toHaveLength(0)
|
||||
expect(result[0].has_conflict).toBe(false)
|
||||
expect(result[0].is_compatible).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle empty input', () => {
|
||||
const result = consolidateConflictsByPackage([])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it('should merge conflicts from multiple versions of same package', () => {
|
||||
const conflicts: ConflictDetectionResult[] = [
|
||||
{
|
||||
package_name: 'mynode@1_0_0',
|
||||
package_id: 'mynode@1_0_0',
|
||||
conflicts: [
|
||||
{ type: 'os', current_value: 'Windows', required_value: 'Linux' }
|
||||
],
|
||||
has_conflict: true,
|
||||
is_compatible: false
|
||||
},
|
||||
{
|
||||
package_name: 'mynode@2_0_0',
|
||||
package_id: 'mynode@2_0_0',
|
||||
conflicts: [
|
||||
{
|
||||
type: 'accelerator',
|
||||
current_value: 'CPU',
|
||||
required_value: 'CUDA'
|
||||
}
|
||||
],
|
||||
has_conflict: true,
|
||||
is_compatible: false
|
||||
},
|
||||
{
|
||||
package_name: 'mynode',
|
||||
package_id: 'mynode',
|
||||
conflicts: [
|
||||
{
|
||||
type: 'comfyui_version',
|
||||
current_value: '1.0.0',
|
||||
required_value: '>=2.0.0'
|
||||
}
|
||||
],
|
||||
has_conflict: true,
|
||||
is_compatible: false
|
||||
}
|
||||
]
|
||||
|
||||
const result = consolidateConflictsByPackage(conflicts)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].package_name).toBe('mynode')
|
||||
expect(result[0].conflicts).toHaveLength(3)
|
||||
expect(result[0].conflicts).toContainEqual({
|
||||
type: 'os',
|
||||
current_value: 'Windows',
|
||||
required_value: 'Linux'
|
||||
})
|
||||
expect(result[0].conflicts).toContainEqual({
|
||||
type: 'accelerator',
|
||||
current_value: 'CPU',
|
||||
required_value: 'CUDA'
|
||||
})
|
||||
expect(result[0].conflicts).toContainEqual({
|
||||
type: 'comfyui_version',
|
||||
current_value: '1.0.0',
|
||||
required_value: '>=2.0.0'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { ISerialisedGraph } from '@/lib/litegraph/src/types/serialisation'
|
||||
import type { ISerialisedGraph } from '@/lib/litegraph/src/types/serialisation'
|
||||
import type { IWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import {
|
||||
|
||||
97
tests-ui/tests/utils/mathUtil.test.ts
Normal file
97
tests-ui/tests/utils/mathUtil.test.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces'
|
||||
import { computeUnionBounds, gcd, lcm } from '@/utils/mathUtil'
|
||||
|
||||
describe('mathUtil', () => {
|
||||
describe('gcd', () => {
|
||||
it('should compute greatest common divisor correctly', () => {
|
||||
expect(gcd(48, 18)).toBe(6)
|
||||
expect(gcd(100, 25)).toBe(25)
|
||||
expect(gcd(17, 13)).toBe(1)
|
||||
expect(gcd(0, 5)).toBe(5)
|
||||
expect(gcd(5, 0)).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('lcm', () => {
|
||||
it('should compute least common multiple correctly', () => {
|
||||
expect(lcm(4, 6)).toBe(12)
|
||||
expect(lcm(15, 20)).toBe(60)
|
||||
expect(lcm(7, 11)).toBe(77)
|
||||
})
|
||||
})
|
||||
|
||||
describe('computeUnionBounds', () => {
|
||||
it('should return null for empty input', () => {
|
||||
expect(computeUnionBounds([])).toBe(null)
|
||||
})
|
||||
|
||||
// Tests for tuple format (ReadOnlyRect)
|
||||
it('should work with ReadOnlyRect tuple format', () => {
|
||||
const tuples: ReadOnlyRect[] = [
|
||||
[10, 20, 30, 40] as const, // bounds: 10,20 to 40,60
|
||||
[50, 10, 20, 30] as const // bounds: 50,10 to 70,40
|
||||
]
|
||||
|
||||
const result = computeUnionBounds(tuples)
|
||||
|
||||
expect(result).toEqual({
|
||||
x: 10, // min(10, 50)
|
||||
y: 10, // min(20, 10)
|
||||
width: 60, // max(40, 70) - min(10, 50) = 70 - 10
|
||||
height: 50 // max(60, 40) - min(20, 10) = 60 - 10
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle single ReadOnlyRect tuple', () => {
|
||||
const tuple: ReadOnlyRect = [10, 20, 30, 40] as const
|
||||
const result = computeUnionBounds([tuple])
|
||||
|
||||
expect(result).toEqual({
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 30,
|
||||
height: 40
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle tuple format with negative dimensions', () => {
|
||||
const tuples: ReadOnlyRect[] = [
|
||||
[100, 50, -20, -10] as const, // x+width=80, y+height=40
|
||||
[90, 45, 15, 20] as const // x+width=105, y+height=65
|
||||
]
|
||||
|
||||
const result = computeUnionBounds(tuples)
|
||||
|
||||
expect(result).toEqual({
|
||||
x: 90, // min(100, 90)
|
||||
y: 45, // min(50, 45)
|
||||
width: 15, // max(80, 105) - min(100, 90) = 105 - 90
|
||||
height: 20 // max(40, 65) - min(50, 45) = 65 - 45
|
||||
})
|
||||
})
|
||||
|
||||
it('should maintain optimal performance with SoA tuples', () => {
|
||||
// Test that array access is as expected for typical selection sizes
|
||||
const tuples: ReadOnlyRect[] = Array.from(
|
||||
{ length: 10 },
|
||||
(_, i) =>
|
||||
[
|
||||
i * 20, // x
|
||||
i * 15, // y
|
||||
100 + i * 5, // width
|
||||
80 + i * 3 // height
|
||||
] as const
|
||||
)
|
||||
|
||||
const result = computeUnionBounds(tuples)
|
||||
|
||||
expect(result).toBeTruthy()
|
||||
expect(result!.x).toBe(0)
|
||||
expect(result!.y).toBe(0)
|
||||
expect(result!.width).toBe(325)
|
||||
expect(result!.height).toBe(242)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,7 +2,7 @@ import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { WorkflowJSON04 } from '@/schemas/comfyWorkflowSchema'
|
||||
import type { WorkflowJSON04 } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { migrateLegacyRerouteNodes } from '@/utils/migration/migrateReroute'
|
||||
|
||||
describe('migrateReroute', () => {
|
||||
@@ -19,7 +19,7 @@ describe('migrateReroute', () => {
|
||||
'single_connected.json',
|
||||
'floating.json',
|
||||
'floating_branch.json'
|
||||
])('should correctly migrate %s', (fileName) => {
|
||||
])('should correctly migrate %s', async (fileName) => {
|
||||
// Load the legacy workflow
|
||||
const legacyWorkflow = loadWorkflow(
|
||||
`workflows/reroute/legacy/${fileName}`
|
||||
@@ -29,9 +29,9 @@ describe('migrateReroute', () => {
|
||||
const migratedWorkflow = migrateLegacyRerouteNodes(legacyWorkflow)
|
||||
|
||||
// Compare with snapshot
|
||||
expect(JSON.stringify(migratedWorkflow, null, 2)).toMatchFileSnapshot(
|
||||
`workflows/reroute/native/${fileName}`
|
||||
)
|
||||
await expect(
|
||||
JSON.stringify(migratedWorkflow, null, 2)
|
||||
).toMatchFileSnapshot(`workflows/reroute/native/${fileName}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
254
tests-ui/tests/utils/packUtils.test.ts
Normal file
254
tests-ui/tests/utils/packUtils.test.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { normalizePackId, normalizePackKeys } from '@/utils/packUtils'
|
||||
|
||||
describe('packUtils', () => {
|
||||
describe('normalizePackId', () => {
|
||||
it('should return pack ID unchanged when no version suffix exists', () => {
|
||||
expect(normalizePackId('ComfyUI-GGUF')).toBe('ComfyUI-GGUF')
|
||||
expect(normalizePackId('ComfyUI-Manager')).toBe('ComfyUI-Manager')
|
||||
expect(normalizePackId('simple-pack')).toBe('simple-pack')
|
||||
})
|
||||
|
||||
it('should remove version suffix with underscores', () => {
|
||||
expect(normalizePackId('ComfyUI-GGUF@1_1_4')).toBe('ComfyUI-GGUF')
|
||||
expect(normalizePackId('ComfyUI-Manager@2_0_0')).toBe('ComfyUI-Manager')
|
||||
expect(normalizePackId('pack@1_0_0_beta')).toBe('pack')
|
||||
})
|
||||
|
||||
it('should remove version suffix with dots', () => {
|
||||
expect(normalizePackId('ComfyUI-GGUF@1.1.4')).toBe('ComfyUI-GGUF')
|
||||
expect(normalizePackId('pack@2.0.0')).toBe('pack')
|
||||
})
|
||||
|
||||
it('should handle multiple @ symbols by only removing after first @', () => {
|
||||
expect(normalizePackId('pack@1_0_0@extra')).toBe('pack')
|
||||
expect(normalizePackId('my@pack@1_0_0')).toBe('my')
|
||||
})
|
||||
|
||||
it('should handle empty string', () => {
|
||||
expect(normalizePackId('')).toBe('')
|
||||
})
|
||||
|
||||
it('should handle pack ID with @ but no version', () => {
|
||||
expect(normalizePackId('pack@')).toBe('pack')
|
||||
})
|
||||
|
||||
it('should handle special characters in pack name', () => {
|
||||
expect(normalizePackId('my-pack_v2@1_0_0')).toBe('my-pack_v2')
|
||||
expect(normalizePackId('pack.with.dots@2_0_0')).toBe('pack.with.dots')
|
||||
expect(normalizePackId('UPPERCASE-Pack@1_0_0')).toBe('UPPERCASE-Pack')
|
||||
})
|
||||
|
||||
it('should handle edge cases', () => {
|
||||
// Only @ symbol
|
||||
expect(normalizePackId('@')).toBe('')
|
||||
expect(normalizePackId('@1_0_0')).toBe('')
|
||||
|
||||
// Whitespace
|
||||
expect(normalizePackId(' pack @1_0_0')).toBe(' pack ')
|
||||
expect(normalizePackId('pack @1_0_0')).toBe('pack ')
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizePackKeys', () => {
|
||||
it('should normalize all keys with version suffixes', () => {
|
||||
const input = {
|
||||
'ComfyUI-GGUF': { ver: '1.1.4', enabled: true },
|
||||
'ComfyUI-Manager@2_0_0': { ver: '2.0.0', enabled: false },
|
||||
'another-pack@1_0_0': { ver: '1.0.0', enabled: true }
|
||||
}
|
||||
|
||||
const expected = {
|
||||
'ComfyUI-GGUF': { ver: '1.1.4', enabled: true },
|
||||
'ComfyUI-Manager': { ver: '2.0.0', enabled: false },
|
||||
'another-pack': { ver: '1.0.0', enabled: true }
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('should handle empty object', () => {
|
||||
expect(normalizePackKeys({})).toEqual({})
|
||||
})
|
||||
|
||||
it('should handle keys without version suffixes', () => {
|
||||
const input = {
|
||||
pack1: { data: 'value1' },
|
||||
pack2: { data: 'value2' }
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(input)
|
||||
})
|
||||
|
||||
it('should handle mixed keys (with and without versions)', () => {
|
||||
const input = {
|
||||
'normal-pack': { ver: '1.0.0' },
|
||||
'versioned-pack@2_0_0': { ver: '2.0.0' },
|
||||
'another-normal': { ver: '3.0.0' },
|
||||
'another-versioned@4_0_0': { ver: '4.0.0' }
|
||||
}
|
||||
|
||||
const expected = {
|
||||
'normal-pack': { ver: '1.0.0' },
|
||||
'versioned-pack': { ver: '2.0.0' },
|
||||
'another-normal': { ver: '3.0.0' },
|
||||
'another-versioned': { ver: '4.0.0' }
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('should handle duplicate keys after normalization (last one wins)', () => {
|
||||
const input = {
|
||||
'pack@1_0_0': { ver: '1.0.0', data: 'first' },
|
||||
'pack@2_0_0': { ver: '2.0.0', data: 'second' },
|
||||
pack: { ver: '3.0.0', data: 'third' }
|
||||
}
|
||||
|
||||
const result = normalizePackKeys(input)
|
||||
|
||||
// The exact behavior depends on object iteration order,
|
||||
// but there should only be one 'pack' key in the result
|
||||
expect(Object.keys(result)).toEqual(['pack'])
|
||||
expect(result.pack).toBeDefined()
|
||||
expect(result.pack.ver).toBeDefined()
|
||||
})
|
||||
|
||||
it('should preserve value references', () => {
|
||||
const value1 = { ver: '1.0.0', complex: { nested: 'data' } }
|
||||
const value2 = { ver: '2.0.0', complex: { nested: 'data2' } }
|
||||
|
||||
const input = {
|
||||
'pack1@1_0_0': value1,
|
||||
'pack2@2_0_0': value2
|
||||
}
|
||||
|
||||
const result = normalizePackKeys(input)
|
||||
|
||||
// Values should be the same references, not cloned
|
||||
expect(result.pack1).toBe(value1)
|
||||
expect(result.pack2).toBe(value2)
|
||||
})
|
||||
|
||||
it('should handle special characters in keys', () => {
|
||||
const input = {
|
||||
'@1_0_0': { ver: '1.0.0' },
|
||||
'my-pack.v2@2_0_0': { ver: '2.0.0' },
|
||||
'UPPERCASE@3_0_0': { ver: '3.0.0' }
|
||||
}
|
||||
|
||||
const expected = {
|
||||
'': { ver: '1.0.0' },
|
||||
'my-pack.v2': { ver: '2.0.0' },
|
||||
UPPERCASE: { ver: '3.0.0' }
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('should work with different value types', () => {
|
||||
const input = {
|
||||
'pack1@1_0_0': 'string value',
|
||||
'pack2@2_0_0': 123,
|
||||
'pack3@3_0_0': null,
|
||||
'pack4@4_0_0': undefined,
|
||||
'pack5@5_0_0': true,
|
||||
pack6: []
|
||||
}
|
||||
|
||||
const expected = {
|
||||
pack1: 'string value',
|
||||
pack2: 123,
|
||||
pack3: null,
|
||||
pack4: undefined,
|
||||
pack5: true,
|
||||
pack6: []
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Integration scenarios from JSDoc examples', () => {
|
||||
it('should handle the examples from normalizePackId JSDoc', () => {
|
||||
expect(normalizePackId('ComfyUI-GGUF')).toBe('ComfyUI-GGUF')
|
||||
expect(normalizePackId('ComfyUI-GGUF@1_1_4')).toBe('ComfyUI-GGUF')
|
||||
})
|
||||
|
||||
it('should handle the examples from normalizePackKeys JSDoc', () => {
|
||||
const input = {
|
||||
'ComfyUI-GGUF': { ver: '1.1.4', enabled: true },
|
||||
'ComfyUI-Manager@2_0_0': { ver: '2.0.0', enabled: false }
|
||||
}
|
||||
|
||||
const expected = {
|
||||
'ComfyUI-GGUF': { ver: '1.1.4', enabled: true },
|
||||
'ComfyUI-Manager': { ver: '2.0.0', enabled: false }
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Real-world scenarios', () => {
|
||||
it('should handle typical ComfyUI-Manager response with mixed enabled/disabled packs', () => {
|
||||
// Simulating actual server response pattern
|
||||
const serverResponse = {
|
||||
// Enabled packs come without version suffix
|
||||
'ComfyUI-Essential': { ver: '1.2.3', enabled: true, aux_id: undefined },
|
||||
'ComfyUI-Impact': { ver: '2.0.0', enabled: true, aux_id: undefined },
|
||||
// Disabled packs come with version suffix
|
||||
'ComfyUI-GGUF@1_1_4': {
|
||||
ver: '1.1.4',
|
||||
enabled: false,
|
||||
aux_id: undefined
|
||||
},
|
||||
'ComfyUI-Manager@2_5_0': {
|
||||
ver: '2.5.0',
|
||||
enabled: false,
|
||||
aux_id: undefined
|
||||
}
|
||||
}
|
||||
|
||||
const normalized = normalizePackKeys(serverResponse)
|
||||
|
||||
// All keys should be normalized (no version suffixes)
|
||||
expect(Object.keys(normalized)).toEqual([
|
||||
'ComfyUI-Essential',
|
||||
'ComfyUI-Impact',
|
||||
'ComfyUI-GGUF',
|
||||
'ComfyUI-Manager'
|
||||
])
|
||||
|
||||
// Values should be preserved
|
||||
expect(normalized['ComfyUI-GGUF']).toEqual({
|
||||
ver: '1.1.4',
|
||||
enabled: false,
|
||||
aux_id: undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow consistent access by pack ID regardless of enabled state', () => {
|
||||
const packsBeforeToggle = {
|
||||
'my-pack': { ver: '1.0.0', enabled: true }
|
||||
}
|
||||
|
||||
const packsAfterToggle = {
|
||||
'my-pack@1_0_0': { ver: '1.0.0', enabled: false }
|
||||
}
|
||||
|
||||
const normalizedBefore = normalizePackKeys(packsBeforeToggle)
|
||||
const normalizedAfter = normalizePackKeys(packsAfterToggle)
|
||||
|
||||
// Both should have the same key after normalization
|
||||
expect(normalizedBefore['my-pack']).toBeDefined()
|
||||
expect(normalizedAfter['my-pack']).toBeDefined()
|
||||
|
||||
// Can access by the same key regardless of the original format
|
||||
expect(Object.keys(normalizedBefore)).toEqual(
|
||||
Object.keys(normalizedAfter)
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
269
tests-ui/tests/utils/spatial/QuadTree.test.ts
Normal file
269
tests-ui/tests/utils/spatial/QuadTree.test.ts
Normal file
@@ -0,0 +1,269 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { type Bounds, QuadTree } from '@/renderer/core/spatial/QuadTree'
|
||||
|
||||
describe('QuadTree', () => {
|
||||
let quadTree: QuadTree<string>
|
||||
const worldBounds: Bounds = { x: 0, y: 0, width: 1000, height: 1000 }
|
||||
|
||||
beforeEach(() => {
|
||||
quadTree = new QuadTree<string>(worldBounds, {
|
||||
maxDepth: 4,
|
||||
maxItemsPerNode: 4
|
||||
})
|
||||
})
|
||||
|
||||
describe('insertion', () => {
|
||||
it('should insert items within bounds', () => {
|
||||
const success = quadTree.insert(
|
||||
'node1',
|
||||
{ x: 100, y: 100, width: 50, height: 50 },
|
||||
'node1'
|
||||
)
|
||||
expect(success).toBe(true)
|
||||
expect(quadTree.size).toBe(1)
|
||||
})
|
||||
|
||||
it('should reject items outside bounds', () => {
|
||||
const success = quadTree.insert(
|
||||
'node1',
|
||||
{ x: -100, y: -100, width: 50, height: 50 },
|
||||
'node1'
|
||||
)
|
||||
expect(success).toBe(false)
|
||||
expect(quadTree.size).toBe(0)
|
||||
})
|
||||
|
||||
it('should handle duplicate IDs by replacing', () => {
|
||||
quadTree.insert(
|
||||
'node1',
|
||||
{ x: 100, y: 100, width: 50, height: 50 },
|
||||
'data1'
|
||||
)
|
||||
quadTree.insert(
|
||||
'node1',
|
||||
{ x: 200, y: 200, width: 50, height: 50 },
|
||||
'data2'
|
||||
)
|
||||
|
||||
expect(quadTree.size).toBe(1)
|
||||
const results = quadTree.query({
|
||||
x: 150,
|
||||
y: 150,
|
||||
width: 100,
|
||||
height: 100
|
||||
})
|
||||
expect(results).toContain('data2')
|
||||
expect(results).not.toContain('data1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('querying', () => {
|
||||
beforeEach(() => {
|
||||
// Insert test nodes in a grid pattern
|
||||
for (let x = 0; x < 10; x++) {
|
||||
for (let y = 0; y < 10; y++) {
|
||||
const id = `node_${x}_${y}`
|
||||
quadTree.insert(
|
||||
id,
|
||||
{
|
||||
x: x * 100,
|
||||
y: y * 100,
|
||||
width: 50,
|
||||
height: 50
|
||||
},
|
||||
id
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should find nodes within query bounds', () => {
|
||||
const results = quadTree.query({ x: 0, y: 0, width: 250, height: 250 })
|
||||
expect(results.length).toBe(9) // 3x3 grid
|
||||
})
|
||||
|
||||
it('should return empty array for out-of-bounds query', () => {
|
||||
const results = quadTree.query({
|
||||
x: 2000,
|
||||
y: 2000,
|
||||
width: 100,
|
||||
height: 100
|
||||
})
|
||||
expect(results.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should handle partial overlaps', () => {
|
||||
const results = quadTree.query({ x: 25, y: 25, width: 100, height: 100 })
|
||||
expect(results.length).toBe(4) // 2x2 grid due to overlap
|
||||
})
|
||||
|
||||
it('should handle large query areas efficiently', () => {
|
||||
const startTime = performance.now()
|
||||
const results = quadTree.query({ x: 0, y: 0, width: 1000, height: 1000 })
|
||||
const queryTime = performance.now() - startTime
|
||||
|
||||
expect(results.length).toBe(100) // All nodes
|
||||
expect(queryTime).toBeLessThan(5) // Should be fast
|
||||
})
|
||||
})
|
||||
|
||||
describe('removal', () => {
|
||||
it('should remove existing items', () => {
|
||||
quadTree.insert(
|
||||
'node1',
|
||||
{ x: 100, y: 100, width: 50, height: 50 },
|
||||
'node1'
|
||||
)
|
||||
expect(quadTree.size).toBe(1)
|
||||
|
||||
const success = quadTree.remove('node1')
|
||||
expect(success).toBe(true)
|
||||
expect(quadTree.size).toBe(0)
|
||||
})
|
||||
|
||||
it('should handle removal of non-existent items', () => {
|
||||
const success = quadTree.remove('nonexistent')
|
||||
expect(success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('updating', () => {
|
||||
it('should update item position', () => {
|
||||
quadTree.insert(
|
||||
'node1',
|
||||
{ x: 100, y: 100, width: 50, height: 50 },
|
||||
'node1'
|
||||
)
|
||||
|
||||
const success = quadTree.update('node1', {
|
||||
x: 200,
|
||||
y: 200,
|
||||
width: 50,
|
||||
height: 50
|
||||
})
|
||||
expect(success).toBe(true)
|
||||
|
||||
// Should not find at old position
|
||||
const oldResults = quadTree.query({
|
||||
x: 75,
|
||||
y: 75,
|
||||
width: 100,
|
||||
height: 100
|
||||
})
|
||||
expect(oldResults).not.toContain('node1')
|
||||
|
||||
// Should find at new position
|
||||
const newResults = quadTree.query({
|
||||
x: 175,
|
||||
y: 175,
|
||||
width: 100,
|
||||
height: 100
|
||||
})
|
||||
expect(newResults).toContain('node1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('subdivision', () => {
|
||||
it('should subdivide when exceeding max items', () => {
|
||||
// Insert 5 items (max is 4) to trigger subdivision
|
||||
for (let i = 0; i < 5; i++) {
|
||||
quadTree.insert(
|
||||
`node${i}`,
|
||||
{
|
||||
x: i * 10,
|
||||
y: i * 10,
|
||||
width: 5,
|
||||
height: 5
|
||||
},
|
||||
`node${i}`
|
||||
)
|
||||
}
|
||||
|
||||
expect(quadTree.size).toBe(5)
|
||||
|
||||
// Verify all items can still be found
|
||||
const allResults = quadTree.query(worldBounds)
|
||||
expect(allResults.length).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('performance', () => {
|
||||
it('should handle 1000 nodes efficiently', () => {
|
||||
const insertStart = performance.now()
|
||||
|
||||
// Insert 1000 nodes
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const x = Math.random() * 900
|
||||
const y = Math.random() * 900
|
||||
quadTree.insert(
|
||||
`node${i}`,
|
||||
{
|
||||
x,
|
||||
y,
|
||||
width: 50,
|
||||
height: 50
|
||||
},
|
||||
`node${i}`
|
||||
)
|
||||
}
|
||||
|
||||
const insertTime = performance.now() - insertStart
|
||||
expect(insertTime).toBeLessThan(50) // Should be fast
|
||||
|
||||
// Query performance
|
||||
const queryStart = performance.now()
|
||||
const results = quadTree.query({
|
||||
x: 400,
|
||||
y: 400,
|
||||
width: 200,
|
||||
height: 200
|
||||
})
|
||||
const queryTime = performance.now() - queryStart
|
||||
|
||||
expect(queryTime).toBeLessThan(2) // Queries should be very fast
|
||||
expect(results.length).toBeGreaterThan(0)
|
||||
expect(results.length).toBeLessThan(1000) // Should cull most nodes
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle zero-sized bounds', () => {
|
||||
const success = quadTree.insert(
|
||||
'point',
|
||||
{ x: 100, y: 100, width: 0, height: 0 },
|
||||
'point'
|
||||
)
|
||||
expect(success).toBe(true)
|
||||
|
||||
const results = quadTree.query({ x: 99, y: 99, width: 2, height: 2 })
|
||||
expect(results).toContain('point')
|
||||
})
|
||||
|
||||
it('should handle items spanning multiple quadrants', () => {
|
||||
const success = quadTree.insert(
|
||||
'large',
|
||||
{
|
||||
x: 400,
|
||||
y: 400,
|
||||
width: 200,
|
||||
height: 200
|
||||
},
|
||||
'large'
|
||||
)
|
||||
expect(success).toBe(true)
|
||||
|
||||
// Should be found when querying any overlapping quadrant
|
||||
const topLeft = quadTree.query({ x: 0, y: 0, width: 500, height: 500 })
|
||||
const bottomRight = quadTree.query({
|
||||
x: 500,
|
||||
y: 500,
|
||||
width: 500,
|
||||
height: 500
|
||||
})
|
||||
|
||||
expect(topLeft).toContain('large')
|
||||
expect(bottomRight).toContain('large')
|
||||
})
|
||||
})
|
||||
})
|
||||
270
tests-ui/tests/utils/systemCompatibility.test.ts
Normal file
270
tests-ui/tests/utils/systemCompatibility.test.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type {
|
||||
RegistryAccelerator,
|
||||
RegistryOS
|
||||
} from '@/workbench/extensions/manager/types/compatibility.types'
|
||||
import {
|
||||
checkAcceleratorCompatibility,
|
||||
checkOSCompatibility,
|
||||
normalizeOSList
|
||||
} from '@/workbench/extensions/manager/utils/systemCompatibility'
|
||||
|
||||
describe('systemCompatibility', () => {
|
||||
describe('checkOSCompatibility', () => {
|
||||
it('should return null when supported OS list is null', () => {
|
||||
const result = checkOSCompatibility(null, 'darwin')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when supported OS list is undefined', () => {
|
||||
const result = checkOSCompatibility(undefined, 'darwin')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when supported OS list is empty', () => {
|
||||
const result = checkOSCompatibility([], 'darwin')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when OS is compatible (macOS)', () => {
|
||||
const supported: RegistryOS[] = ['macOS', 'Windows']
|
||||
const result = checkOSCompatibility(supported, 'darwin')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when OS is compatible (Windows)', () => {
|
||||
const supported: RegistryOS[] = ['Windows', 'Linux']
|
||||
const result = checkOSCompatibility(supported, 'win32')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when OS is compatible (Linux)', () => {
|
||||
const supported: RegistryOS[] = ['Linux', 'macOS']
|
||||
const result = checkOSCompatibility(supported, 'linux')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return conflict when OS is incompatible', () => {
|
||||
const supported: RegistryOS[] = ['Windows']
|
||||
const result = checkOSCompatibility(supported, 'darwin')
|
||||
expect(result).toEqual({
|
||||
type: 'os',
|
||||
current_value: 'macOS',
|
||||
required_value: 'Windows'
|
||||
})
|
||||
})
|
||||
|
||||
it('should return conflict with Unknown OS when current OS is unrecognized', () => {
|
||||
const supported: RegistryOS[] = ['Windows', 'Linux']
|
||||
const result = checkOSCompatibility(supported, 'freebsd')
|
||||
expect(result).toEqual({
|
||||
type: 'os',
|
||||
current_value: 'Unknown',
|
||||
required_value: 'Windows, Linux'
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle various OS string formats', () => {
|
||||
const supported: RegistryOS[] = ['Windows']
|
||||
|
||||
// Test Windows variations
|
||||
expect(checkOSCompatibility(supported, 'win32')).toBeNull()
|
||||
expect(checkOSCompatibility(supported, 'windows')).toBeNull()
|
||||
expect(checkOSCompatibility(supported, 'Windows_NT')).toBeNull()
|
||||
|
||||
// Test macOS variations
|
||||
const macSupported: RegistryOS[] = ['macOS']
|
||||
expect(checkOSCompatibility(macSupported, 'darwin')).toBeNull()
|
||||
expect(checkOSCompatibility(macSupported, 'Darwin')).toBeNull()
|
||||
expect(checkOSCompatibility(macSupported, 'macos')).toBeNull()
|
||||
expect(checkOSCompatibility(macSupported, 'mac')).toBeNull()
|
||||
})
|
||||
|
||||
it('should handle undefined current OS', () => {
|
||||
const supported: RegistryOS[] = ['Windows']
|
||||
const result = checkOSCompatibility(supported, undefined)
|
||||
expect(result).toEqual({
|
||||
type: 'os',
|
||||
current_value: 'Unknown',
|
||||
required_value: 'Windows'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkAcceleratorCompatibility', () => {
|
||||
it('should return null when supported accelerator list is null', () => {
|
||||
const result = checkAcceleratorCompatibility(null, 'cuda')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when supported accelerator list is undefined', () => {
|
||||
const result = checkAcceleratorCompatibility(undefined, 'cuda')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when supported accelerator list is empty', () => {
|
||||
const result = checkAcceleratorCompatibility([], 'cuda')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when accelerator is compatible (CUDA)', () => {
|
||||
const supported: RegistryAccelerator[] = ['CUDA', 'CPU']
|
||||
const result = checkAcceleratorCompatibility(supported, 'cuda')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when accelerator is compatible (Metal)', () => {
|
||||
const supported: RegistryAccelerator[] = ['Metal', 'CPU']
|
||||
const result = checkAcceleratorCompatibility(supported, 'mps')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when accelerator is compatible (ROCm)', () => {
|
||||
const supported: RegistryAccelerator[] = ['ROCm', 'CPU']
|
||||
const result = checkAcceleratorCompatibility(supported, 'rocm')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when accelerator is compatible (CPU)', () => {
|
||||
const supported: RegistryAccelerator[] = ['CPU']
|
||||
const result = checkAcceleratorCompatibility(supported, 'cpu')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return conflict when accelerator is incompatible', () => {
|
||||
const supported: RegistryAccelerator[] = ['CUDA']
|
||||
const result = checkAcceleratorCompatibility(supported, 'mps')
|
||||
expect(result).toEqual({
|
||||
type: 'accelerator',
|
||||
current_value: 'Metal',
|
||||
required_value: 'CUDA'
|
||||
})
|
||||
})
|
||||
|
||||
it('should default to CPU for unknown device types', () => {
|
||||
const supported: RegistryAccelerator[] = ['CUDA']
|
||||
const result = checkAcceleratorCompatibility(supported, 'unknown')
|
||||
expect(result).toEqual({
|
||||
type: 'accelerator',
|
||||
current_value: 'CPU',
|
||||
required_value: 'CUDA'
|
||||
})
|
||||
})
|
||||
|
||||
it('should default to CPU when device type is undefined', () => {
|
||||
const supported: RegistryAccelerator[] = ['CUDA']
|
||||
const result = checkAcceleratorCompatibility(supported, undefined)
|
||||
expect(result).toEqual({
|
||||
type: 'accelerator',
|
||||
current_value: 'CPU',
|
||||
required_value: 'CUDA'
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle case-insensitive device types', () => {
|
||||
const supported: RegistryAccelerator[] = ['CUDA']
|
||||
|
||||
// CUDA variations
|
||||
expect(checkAcceleratorCompatibility(supported, 'cuda')).toBeNull()
|
||||
expect(checkAcceleratorCompatibility(supported, 'CUDA')).toBeNull()
|
||||
expect(checkAcceleratorCompatibility(supported, 'Cuda')).toBeNull()
|
||||
|
||||
// Metal variations
|
||||
const metalSupported: RegistryAccelerator[] = ['Metal']
|
||||
expect(checkAcceleratorCompatibility(metalSupported, 'mps')).toBeNull()
|
||||
expect(checkAcceleratorCompatibility(metalSupported, 'MPS')).toBeNull()
|
||||
|
||||
// ROCm variations
|
||||
const rocmSupported: RegistryAccelerator[] = ['ROCm']
|
||||
expect(checkAcceleratorCompatibility(rocmSupported, 'rocm')).toBeNull()
|
||||
expect(checkAcceleratorCompatibility(rocmSupported, 'ROCM')).toBeNull()
|
||||
})
|
||||
|
||||
it('should handle multiple required accelerators', () => {
|
||||
const supported: RegistryAccelerator[] = ['CUDA', 'ROCm']
|
||||
const result = checkAcceleratorCompatibility(supported, 'mps')
|
||||
expect(result).toEqual({
|
||||
type: 'accelerator',
|
||||
current_value: 'Metal',
|
||||
required_value: 'CUDA, ROCm'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizeOSList', () => {
|
||||
it('should return undefined for null input', () => {
|
||||
const result = normalizeOSList(null)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for undefined input', () => {
|
||||
const result = normalizeOSList(undefined)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for empty array', () => {
|
||||
const result = normalizeOSList([])
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined when OS Independent is present', () => {
|
||||
const result = normalizeOSList(['OS Independent', 'Windows'])
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for case-insensitive OS Independent', () => {
|
||||
const result = normalizeOSList(['os independent'])
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should filter and return valid OS values', () => {
|
||||
const result = normalizeOSList(['Windows', 'Linux', 'macOS'])
|
||||
expect(result).toEqual(['Windows', 'Linux', 'macOS'])
|
||||
})
|
||||
|
||||
it('should filter out invalid OS values', () => {
|
||||
const result = normalizeOSList(['Windows', 'FreeBSD', 'Linux', 'Android'])
|
||||
expect(result).toEqual(['Windows', 'Linux'])
|
||||
})
|
||||
|
||||
it('should deduplicate OS values', () => {
|
||||
const result = normalizeOSList([
|
||||
'Windows',
|
||||
'Linux',
|
||||
'Windows',
|
||||
'macOS',
|
||||
'Linux'
|
||||
])
|
||||
expect(result).toEqual(['Windows', 'Linux', 'macOS'])
|
||||
})
|
||||
|
||||
it('should return undefined when no valid OS values remain', () => {
|
||||
const result = normalizeOSList(['FreeBSD', 'Android', 'iOS'])
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle mixed valid and invalid values', () => {
|
||||
const result = normalizeOSList([
|
||||
'windows',
|
||||
'Windows',
|
||||
'linux',
|
||||
'Linux',
|
||||
'macos'
|
||||
])
|
||||
// Only exact matches are valid
|
||||
expect(result).toEqual(['Windows', 'Linux'])
|
||||
})
|
||||
|
||||
it('should preserve order of first occurrence when deduplicating', () => {
|
||||
const result = normalizeOSList([
|
||||
'Linux',
|
||||
'Windows',
|
||||
'macOS',
|
||||
'Linux',
|
||||
'Windows'
|
||||
])
|
||||
expect(result).toEqual(['Linux', 'Windows', 'macOS'])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { TreeNode } from '@/types/treeExplorerTypes'
|
||||
import type { TreeNode } from '@/types/treeExplorerTypes'
|
||||
import { buildTree, sortedTree } from '@/utils/treeUtil'
|
||||
|
||||
describe('buildTree', () => {
|
||||
|
||||
346
tests-ui/tests/utils/versionUtil.test.ts
Normal file
346
tests-ui/tests/utils/versionUtil.test.ts
Normal file
@@ -0,0 +1,346 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
checkVersionCompatibility,
|
||||
getFrontendVersion
|
||||
} from '@/workbench/extensions/manager/utils/versionUtil'
|
||||
|
||||
// Mock config module
|
||||
vi.mock('@/config', () => ({
|
||||
default: {
|
||||
app_version: '1.24.0-1'
|
||||
}
|
||||
}))
|
||||
|
||||
describe('versionUtil', () => {
|
||||
describe('checkVersionCompatibility', () => {
|
||||
it('should return null when current version is undefined', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
undefined,
|
||||
'>=1.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when current version is null', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
null as any,
|
||||
'>=1.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when current version is empty string', () => {
|
||||
const result = checkVersionCompatibility('comfyui_version', '', '>=1.0.0')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when supported version is undefined', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.0.0',
|
||||
undefined
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when supported version is null', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.0.0',
|
||||
null as any
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when supported version is empty string', () => {
|
||||
const result = checkVersionCompatibility('comfyui_version', '1.0.0', '')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when supported version is whitespace only', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.0.0',
|
||||
' '
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
describe('version compatibility checks', () => {
|
||||
it('should return null when version satisfies >= requirement', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'2.0.0',
|
||||
'>=1.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when version exactly matches requirement', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.0.0',
|
||||
'1.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when version satisfies ^ requirement', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.2.3',
|
||||
'^1.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when version satisfies ~ requirement', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.0.5',
|
||||
'~1.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when version satisfies range requirement', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.5.0',
|
||||
'1.0.0 - 2.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return conflict when version does not satisfy >= requirement', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'0.9.0',
|
||||
'>=1.0.0'
|
||||
)
|
||||
expect(result).toEqual({
|
||||
type: 'comfyui_version',
|
||||
current_value: '0.9.0',
|
||||
required_value: '>=1.0.0'
|
||||
})
|
||||
})
|
||||
|
||||
it('should return conflict when version does not satisfy ^ requirement', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'2.0.0',
|
||||
'^1.0.0'
|
||||
)
|
||||
expect(result).toEqual({
|
||||
type: 'comfyui_version',
|
||||
current_value: '2.0.0',
|
||||
required_value: '^1.0.0'
|
||||
})
|
||||
})
|
||||
|
||||
it('should return conflict when version is outside range', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'3.0.0',
|
||||
'1.0.0 - 2.0.0'
|
||||
)
|
||||
expect(result).toEqual({
|
||||
type: 'comfyui_version',
|
||||
current_value: '3.0.0',
|
||||
required_value: '1.0.0 - 2.0.0'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('version cleaning', () => {
|
||||
it('should handle versions with v prefix', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'v1.0.0',
|
||||
'>=1.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should handle versions with pre-release tags', () => {
|
||||
// Pre-release versions have specific semver rules
|
||||
// 1.0.0-alpha satisfies >=1.0.0-alpha but not >=1.0.0
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.0.0-alpha',
|
||||
'>=1.0.0-alpha'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
|
||||
// This should fail because pre-release < stable
|
||||
const result2 = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.0.0-alpha',
|
||||
'>=1.0.0'
|
||||
)
|
||||
expect(result2).toEqual({
|
||||
type: 'comfyui_version',
|
||||
current_value: '1.0.0-alpha',
|
||||
required_value: '>=1.0.0'
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle versions with build metadata', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.0.0+build123',
|
||||
'>=1.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should handle malformed versions gracefully', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'not-a-version',
|
||||
'>=1.0.0'
|
||||
)
|
||||
expect(result).toEqual({
|
||||
type: 'comfyui_version',
|
||||
current_value: 'not-a-version',
|
||||
required_value: '>=1.0.0'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('different conflict types', () => {
|
||||
it('should handle comfyui_version type', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'0.5.0',
|
||||
'>=1.0.0'
|
||||
)
|
||||
expect(result?.type).toBe('comfyui_version')
|
||||
})
|
||||
|
||||
it('should handle frontend_version type', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'frontend_version',
|
||||
'0.5.0',
|
||||
'>=1.0.0'
|
||||
)
|
||||
expect(result?.type).toBe('frontend_version')
|
||||
})
|
||||
})
|
||||
|
||||
describe('complex version ranges', () => {
|
||||
it('should handle OR conditions with ||', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.5.0',
|
||||
'>=1.0.0 <2.0.0 || >=3.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should handle multiple constraints', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'1.5.0',
|
||||
'>=1.0.0 <2.0.0'
|
||||
)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return conflict when no constraints are met', () => {
|
||||
const result = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
'2.5.0',
|
||||
'>=1.0.0 <2.0.0 || >=3.0.0 <4.0.0'
|
||||
)
|
||||
expect(result).toEqual({
|
||||
type: 'comfyui_version',
|
||||
current_value: '2.5.0',
|
||||
required_value: '>=1.0.0 <2.0.0 || >=3.0.0 <4.0.0'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getFrontendVersion', () => {
|
||||
it('should return app_version from config when available', () => {
|
||||
const version = getFrontendVersion()
|
||||
expect(version).toBe('1.24.0-1')
|
||||
})
|
||||
|
||||
it('should fallback to VITE_APP_VERSION when app_version is not available', async () => {
|
||||
// Save original environment
|
||||
const originalEnv = import.meta.env.VITE_APP_VERSION
|
||||
|
||||
// Mock config without app_version
|
||||
vi.doMock('@/config', () => ({
|
||||
default: {}
|
||||
}))
|
||||
|
||||
// Set VITE_APP_VERSION
|
||||
import.meta.env.VITE_APP_VERSION = '2.0.0'
|
||||
|
||||
// Clear module cache to force re-import
|
||||
vi.resetModules()
|
||||
|
||||
// Import fresh module
|
||||
const versionUtil = await import(
|
||||
'@/workbench/extensions/manager/utils/versionUtil'
|
||||
)
|
||||
|
||||
const version = versionUtil.getFrontendVersion()
|
||||
expect(version).toBe('2.0.0')
|
||||
|
||||
// Restore original env
|
||||
import.meta.env.VITE_APP_VERSION = originalEnv
|
||||
|
||||
// Reset mocks for next test
|
||||
vi.resetModules()
|
||||
vi.doMock('@/config', () => ({
|
||||
default: {
|
||||
app_version: '1.24.0-1'
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
it('should return undefined when no version is available', async () => {
|
||||
// Save original environment
|
||||
const originalEnv = import.meta.env.VITE_APP_VERSION
|
||||
|
||||
// Mock config without app_version
|
||||
vi.doMock('@/config', () => ({
|
||||
default: {}
|
||||
}))
|
||||
|
||||
// Clear VITE_APP_VERSION
|
||||
delete import.meta.env.VITE_APP_VERSION
|
||||
|
||||
// Clear module cache to force re-import
|
||||
vi.resetModules()
|
||||
|
||||
// Import fresh module
|
||||
const versionUtil = await import(
|
||||
'@/workbench/extensions/manager/utils/versionUtil'
|
||||
)
|
||||
|
||||
const version = versionUtil.getFrontendVersion()
|
||||
expect(version).toBeUndefined()
|
||||
|
||||
// Restore original env
|
||||
if (originalEnv !== undefined) {
|
||||
import.meta.env.VITE_APP_VERSION = originalEnv
|
||||
}
|
||||
|
||||
// Reset mocks for next test
|
||||
vi.resetModules()
|
||||
vi.doMock('@/config', () => ({
|
||||
default: {
|
||||
app_version: '1.24.0-1'
|
||||
}
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user