mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-03 05:38:26 +00:00
Compare commits
1 Commits
codex/crit
...
shihchi/co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2fe1c3f26 |
141
src/scripts/metadata/parser.test.ts
Normal file
141
src/scripts/metadata/parser.test.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { getFromWebmFile } from '@/scripts/metadata/ebml'
|
||||
import { getGltfBinaryMetadata } from '@/scripts/metadata/gltf'
|
||||
import { getFromIsobmffFile } from '@/scripts/metadata/isobmff'
|
||||
import { getDataFromJSON } from '@/scripts/metadata/json'
|
||||
import { getMp3Metadata } from '@/scripts/metadata/mp3'
|
||||
import { getOggMetadata } from '@/scripts/metadata/ogg'
|
||||
import { getWorkflowDataFromFile } from '@/scripts/metadata/parser'
|
||||
import { getSvgMetadata } from '@/scripts/metadata/svg'
|
||||
import {
|
||||
getAvifMetadata,
|
||||
getFlacMetadata,
|
||||
getLatentMetadata,
|
||||
getPngMetadata,
|
||||
getWebpMetadata
|
||||
} from '@/scripts/pnginfo'
|
||||
|
||||
vi.mock('@/scripts/metadata/ebml', () => ({ getFromWebmFile: vi.fn() }))
|
||||
vi.mock('@/scripts/metadata/gltf', () => ({ getGltfBinaryMetadata: vi.fn() }))
|
||||
vi.mock('@/scripts/metadata/isobmff', () => ({ getFromIsobmffFile: vi.fn() }))
|
||||
vi.mock('@/scripts/metadata/json', () => ({ getDataFromJSON: vi.fn() }))
|
||||
vi.mock('@/scripts/metadata/mp3', () => ({ getMp3Metadata: vi.fn() }))
|
||||
vi.mock('@/scripts/metadata/ogg', () => ({ getOggMetadata: vi.fn() }))
|
||||
vi.mock('@/scripts/metadata/svg', () => ({ getSvgMetadata: vi.fn() }))
|
||||
vi.mock('@/scripts/pnginfo', () => ({
|
||||
getAvifMetadata: vi.fn(),
|
||||
getFlacMetadata: vi.fn(),
|
||||
getLatentMetadata: vi.fn(),
|
||||
getPngMetadata: vi.fn(),
|
||||
getWebpMetadata: vi.fn()
|
||||
}))
|
||||
|
||||
function file(type: string, name = 'file') {
|
||||
return new File(['data'], name, { type })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('getWorkflowDataFromFile', () => {
|
||||
it('routes png/avif/mp3/ogg/webm to their parsers and returns the result', async () => {
|
||||
vi.mocked(getPngMetadata).mockResolvedValue({ a: 1 } as never)
|
||||
expect(await getWorkflowDataFromFile(file('image/png'))).toEqual({ a: 1 })
|
||||
expect(getPngMetadata).toHaveBeenCalled()
|
||||
|
||||
await getWorkflowDataFromFile(file('image/avif'))
|
||||
expect(getAvifMetadata).toHaveBeenCalled()
|
||||
|
||||
await getWorkflowDataFromFile(file('audio/mpeg'))
|
||||
expect(getMp3Metadata).toHaveBeenCalled()
|
||||
|
||||
await getWorkflowDataFromFile(file('audio/ogg'))
|
||||
expect(getOggMetadata).toHaveBeenCalled()
|
||||
|
||||
await getWorkflowDataFromFile(file('video/webm'))
|
||||
expect(getFromWebmFile).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('extracts workflow/prompt from webp, preferring lowercase keys', async () => {
|
||||
vi.mocked(getWebpMetadata).mockResolvedValue({
|
||||
workflow: 'wf',
|
||||
prompt: 'pr'
|
||||
} as never)
|
||||
expect(await getWorkflowDataFromFile(file('image/webp'))).toEqual({
|
||||
workflow: 'wf',
|
||||
prompt: 'pr'
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to capitalized webp keys when lowercase are absent', async () => {
|
||||
vi.mocked(getWebpMetadata).mockResolvedValue({
|
||||
Workflow: 'WF',
|
||||
Prompt: 'PR'
|
||||
} as never)
|
||||
expect(await getWorkflowDataFromFile(file('image/webp'))).toEqual({
|
||||
workflow: 'WF',
|
||||
prompt: 'PR'
|
||||
})
|
||||
})
|
||||
|
||||
it('handles both flac mime types and extracts workflow/prompt', async () => {
|
||||
vi.mocked(getFlacMetadata).mockResolvedValue({ workflow: 'w' } as never)
|
||||
expect(await getWorkflowDataFromFile(file('audio/flac'))).toEqual({
|
||||
workflow: 'w',
|
||||
prompt: undefined
|
||||
})
|
||||
expect(await getWorkflowDataFromFile(file('audio/x-flac'))).toEqual({
|
||||
workflow: 'w',
|
||||
prompt: undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to capitalized flac keys when lowercase are absent', async () => {
|
||||
vi.mocked(getFlacMetadata).mockResolvedValue({
|
||||
Workflow: 'WF',
|
||||
Prompt: 'PR'
|
||||
} as never)
|
||||
expect(await getWorkflowDataFromFile(file('audio/flac'))).toEqual({
|
||||
workflow: 'WF',
|
||||
prompt: 'PR'
|
||||
})
|
||||
})
|
||||
|
||||
it('routes isobmff by mime type and by file extension', async () => {
|
||||
await getWorkflowDataFromFile(file('video/mp4'))
|
||||
await getWorkflowDataFromFile(file('video/quicktime'))
|
||||
await getWorkflowDataFromFile(file('video/x-m4v'))
|
||||
await getWorkflowDataFromFile(file('', 'clip.mp4'))
|
||||
await getWorkflowDataFromFile(file('', 'clip.mov'))
|
||||
await getWorkflowDataFromFile(file('', 'clip.m4v'))
|
||||
expect(getFromIsobmffFile).toHaveBeenCalledTimes(6)
|
||||
})
|
||||
|
||||
it('routes svg and gltf by mime type or extension', async () => {
|
||||
await getWorkflowDataFromFile(file('image/svg+xml'))
|
||||
await getWorkflowDataFromFile(file('', 'icon.svg'))
|
||||
expect(getSvgMetadata).toHaveBeenCalledTimes(2)
|
||||
|
||||
await getWorkflowDataFromFile(file('model/gltf-binary'))
|
||||
await getWorkflowDataFromFile(file('', 'model.glb'))
|
||||
expect(getGltfBinaryMetadata).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('routes latent/safetensors and json by extension or mime type', async () => {
|
||||
await getWorkflowDataFromFile(file('', 'x.latent'))
|
||||
await getWorkflowDataFromFile(file('', 'x.safetensors'))
|
||||
expect(getLatentMetadata).toHaveBeenCalledTimes(2)
|
||||
|
||||
await getWorkflowDataFromFile(file('application/json'))
|
||||
await getWorkflowDataFromFile(file('', 'x.json'))
|
||||
expect(getDataFromJSON).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('returns undefined for an unrecognized file', async () => {
|
||||
expect(
|
||||
await getWorkflowDataFromFile(file('application/zip', 'a.zip'))
|
||||
).toBe(undefined)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,277 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { usePackInstall } from '@/workbench/extensions/manager/composables/nodePack/usePackInstall'
|
||||
import type { ConflictDetail } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
||||
|
||||
type NodePack = components['schemas']['Node']
|
||||
type CompatibilityCheck = {
|
||||
hasConflict: boolean
|
||||
conflicts: ConflictDetail[]
|
||||
}
|
||||
|
||||
const { managerStore, showDialog, checkNodeCompatibility } = vi.hoisted(() => ({
|
||||
managerStore: {
|
||||
installPack: { call: vi.fn(), clear: vi.fn() },
|
||||
isPackInstalling: vi.fn((_id?: string) => false),
|
||||
isPackInstalled: vi.fn((_id?: string) => false)
|
||||
},
|
||||
showDialog: vi.fn(),
|
||||
checkNodeCompatibility: vi.fn(
|
||||
(): CompatibilityCheck => ({ hasConflict: false, conflicts: [] })
|
||||
)
|
||||
}))
|
||||
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key }) }))
|
||||
|
||||
vi.mock('@/workbench/extensions/manager/stores/comfyManagerStore', () => ({
|
||||
useComfyManagerStore: () => managerStore
|
||||
}))
|
||||
|
||||
vi.mock(
|
||||
'@/workbench/extensions/manager/composables/useNodeConflictDialog',
|
||||
() => ({
|
||||
useNodeConflictDialog: () => ({ show: showDialog })
|
||||
})
|
||||
)
|
||||
|
||||
vi.mock(
|
||||
'@/workbench/extensions/manager/composables/useConflictDetection',
|
||||
() => ({
|
||||
useConflictDetection: () => ({ checkNodeCompatibility })
|
||||
})
|
||||
)
|
||||
|
||||
function pack(over: Partial<NodePack> = {}): NodePack {
|
||||
return { id: 'pack-a', name: 'Pack A', ...over } as NodePack
|
||||
}
|
||||
|
||||
function conflict(overrides: Partial<ConflictDetail> = {}): ConflictDetail {
|
||||
return {
|
||||
type: 'os',
|
||||
current_value: 'linux',
|
||||
required_value: 'darwin',
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
managerStore.installPack.call.mockReset().mockResolvedValue(undefined)
|
||||
managerStore.installPack.clear.mockReset()
|
||||
managerStore.isPackInstalling.mockReset().mockReturnValue(false)
|
||||
managerStore.isPackInstalled.mockReset().mockReturnValue(false)
|
||||
showDialog.mockReset()
|
||||
checkNodeCompatibility.mockReset().mockReturnValue({
|
||||
hasConflict: false,
|
||||
conflicts: []
|
||||
})
|
||||
})
|
||||
|
||||
describe('usePackInstall', () => {
|
||||
it('reports isInstalling when any pack is installing', () => {
|
||||
managerStore.isPackInstalling.mockImplementation(
|
||||
(id?: string) => id === 'pack-b'
|
||||
)
|
||||
const { isInstalling } = usePackInstall(() => [
|
||||
pack(),
|
||||
pack({ id: 'pack-b' })
|
||||
])
|
||||
expect(isInstalling.value).toBe(true)
|
||||
})
|
||||
|
||||
it('reports not installing for an empty or idle pack list', () => {
|
||||
expect(usePackInstall(() => []).isInstalling.value).toBe(false)
|
||||
expect(
|
||||
usePackInstall(() => undefined as unknown as NodePack[]).isInstalling
|
||||
.value
|
||||
).toBe(false)
|
||||
expect(usePackInstall(() => [pack()]).isInstalling.value).toBe(false)
|
||||
})
|
||||
|
||||
it('installs each pack and clears the command afterward', async () => {
|
||||
const { performInstallation } = usePackInstall(() => [])
|
||||
await performInstallation([
|
||||
pack({
|
||||
id: 'a',
|
||||
latest_version: { version: '1.2.0' }
|
||||
} as Partial<NodePack>),
|
||||
pack({ id: 'b', publisher: { name: 'Unclaimed' } } as Partial<NodePack>)
|
||||
])
|
||||
|
||||
expect(managerStore.installPack.call).toHaveBeenCalledTimes(2)
|
||||
expect(managerStore.installPack.call).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: 'a', selected_version: '1.2.0' })
|
||||
)
|
||||
expect(managerStore.installPack.call).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: 'b', selected_version: 'nightly' })
|
||||
)
|
||||
expect(managerStore.installPack.clear).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('installAllPacks installs only the not-yet-installed packs', async () => {
|
||||
managerStore.isPackInstalled.mockImplementation(
|
||||
(id?: string) => id === 'installed'
|
||||
)
|
||||
const { installAllPacks } = usePackInstall(() => [
|
||||
pack({ id: 'installed' }),
|
||||
pack({ id: 'fresh' })
|
||||
])
|
||||
|
||||
await installAllPacks()
|
||||
|
||||
expect(managerStore.installPack.call).toHaveBeenCalledTimes(1)
|
||||
expect(managerStore.installPack.call).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: 'fresh' })
|
||||
)
|
||||
})
|
||||
|
||||
it('installAllPacks returns early for empty or already installed packs', async () => {
|
||||
await usePackInstall(() => []).installAllPacks()
|
||||
|
||||
managerStore.isPackInstalled.mockReturnValue(true)
|
||||
await usePackInstall(() => [pack({ id: 'installed' })]).installAllPacks()
|
||||
|
||||
expect(managerStore.installPack.call).not.toHaveBeenCalled()
|
||||
expect(managerStore.installPack.clear).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('installAllPacks opens the conflict dialog instead of installing when conflicted', async () => {
|
||||
const osConflict = conflict()
|
||||
checkNodeCompatibility.mockReturnValue({
|
||||
hasConflict: true,
|
||||
conflicts: [osConflict]
|
||||
})
|
||||
const { installAllPacks } = usePackInstall(
|
||||
() => [pack({ id: 'x' })],
|
||||
() => true,
|
||||
() => [osConflict]
|
||||
)
|
||||
|
||||
await installAllPacks()
|
||||
|
||||
expect(showDialog).toHaveBeenCalledTimes(1)
|
||||
expect(showDialog).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
conflictedPackages: [
|
||||
expect.objectContaining({
|
||||
package_id: 'x',
|
||||
package_name: 'Pack A',
|
||||
has_conflict: true,
|
||||
conflicts: [osConflict],
|
||||
is_compatible: false
|
||||
})
|
||||
]
|
||||
})
|
||||
)
|
||||
expect(managerStore.installPack.call).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('installAllPacks stops when conflict details are unavailable', async () => {
|
||||
const { installAllPacks } = usePackInstall(
|
||||
() => [pack({ id: 'x' })],
|
||||
() => true
|
||||
)
|
||||
|
||||
await installAllPacks()
|
||||
|
||||
expect(showDialog).not.toHaveBeenCalled()
|
||||
expect(managerStore.installPack.call).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('conflict dialog payload falls back for unnamed package data', async () => {
|
||||
checkNodeCompatibility.mockReturnValue({
|
||||
hasConflict: true,
|
||||
conflicts: [conflict()]
|
||||
})
|
||||
const { installAllPacks } = usePackInstall(
|
||||
() => [pack({ id: undefined, name: undefined })],
|
||||
() => true,
|
||||
() => [conflict()]
|
||||
)
|
||||
|
||||
await installAllPacks()
|
||||
|
||||
expect(showDialog).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
conflictedPackages: [
|
||||
expect.objectContaining({
|
||||
package_id: '',
|
||||
package_name: ''
|
||||
})
|
||||
]
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('conflict dialog action installs only packs still missing', async () => {
|
||||
checkNodeCompatibility.mockReturnValue({
|
||||
hasConflict: false,
|
||||
conflicts: []
|
||||
})
|
||||
managerStore.isPackInstalled.mockImplementation(
|
||||
(id?: string) => id === 'installed'
|
||||
)
|
||||
const { installAllPacks } = usePackInstall(
|
||||
() => [pack({ id: 'installed' }), pack({ id: 'fresh' })],
|
||||
() => true,
|
||||
() => [conflict()]
|
||||
)
|
||||
|
||||
await installAllPacks()
|
||||
const [{ onButtonClick }] = showDialog.mock.calls[0]
|
||||
await onButtonClick()
|
||||
|
||||
expect(managerStore.installPack.call).toHaveBeenCalledTimes(1)
|
||||
expect(managerStore.installPack.call).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: 'fresh' })
|
||||
)
|
||||
expect(managerStore.installPack.clear).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('conflict dialog action returns when every pack is already installed', async () => {
|
||||
managerStore.isPackInstalled.mockReturnValue(true)
|
||||
const { installAllPacks } = usePackInstall(
|
||||
() => [pack({ id: 'installed' })],
|
||||
() => true,
|
||||
() => [conflict()]
|
||||
)
|
||||
|
||||
await installAllPacks()
|
||||
const [{ onButtonClick }] = showDialog.mock.calls[0]
|
||||
await onButtonClick()
|
||||
|
||||
expect(managerStore.installPack.call).not.toHaveBeenCalled()
|
||||
expect(managerStore.installPack.clear).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('clears the command when payload validation rejects', async () => {
|
||||
const { performInstallation } = usePackInstall(() => [])
|
||||
|
||||
await expect(
|
||||
performInstallation([pack({ id: undefined })])
|
||||
).rejects.toThrow('manager.packInstall.nodeIdRequired')
|
||||
|
||||
expect(managerStore.installPack.call).not.toHaveBeenCalled()
|
||||
expect(managerStore.installPack.clear).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('leaves command cleanup in finally when one install fails', async () => {
|
||||
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
try {
|
||||
managerStore.installPack.call
|
||||
.mockResolvedValueOnce(undefined)
|
||||
.mockRejectedValueOnce(new Error('failed'))
|
||||
const { performInstallation } = usePackInstall(() => [])
|
||||
|
||||
await performInstallation([pack({ id: 'a' }), pack({ id: 'b' })])
|
||||
|
||||
expect(consoleError).toHaveBeenCalledWith(
|
||||
'[usePackInstall] Some installations failed:',
|
||||
[expect.any(Error)]
|
||||
)
|
||||
expect(managerStore.installPack.clear).toHaveBeenCalledTimes(1)
|
||||
} finally {
|
||||
consoleError.mockRestore()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,279 @@
|
||||
import type * as VueUse from '@vueuse/core'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
|
||||
import { useManagerDisplayPacks } from '@/workbench/extensions/manager/composables/useManagerDisplayPacks'
|
||||
|
||||
type NodePack = components['schemas']['Node']
|
||||
|
||||
const { state } = vi.hoisted(() => ({
|
||||
state: {
|
||||
installed: [] as NodePack[],
|
||||
workflow: [] as NodePack[],
|
||||
installedLoading: false,
|
||||
workflowLoading: false,
|
||||
installedReady: true,
|
||||
workflowReady: true,
|
||||
startFetchInstalled: vi.fn(),
|
||||
startFetchWorkflowPacks: vi.fn(),
|
||||
installedIds: new Set<string>(),
|
||||
installedVersions: {} as Record<string, string>,
|
||||
conflicts: [] as { package_id: string }[]
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@vueuse/core', async (orig) => ({
|
||||
...(await orig<typeof VueUse>()),
|
||||
whenever: (source: unknown, callback?: () => void) => {
|
||||
if (typeof source === 'function' && source() && callback) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/services/gateway/registrySearchGateway', () => ({
|
||||
useRegistrySearchGateway: () => ({
|
||||
getSortValue: (pack: NodePack, field: string) =>
|
||||
(pack as Record<string, unknown>)[field],
|
||||
getSortableFields: () => [{ id: 'name', direction: 'asc' }]
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock(
|
||||
'@/workbench/extensions/manager/composables/nodePack/useInstalledPacks',
|
||||
() => ({
|
||||
useInstalledPacks: () => ({
|
||||
startFetchInstalled: state.startFetchInstalled,
|
||||
filterInstalledPack: (packs: NodePack[]) =>
|
||||
packs.filter((p) => state.installedIds.has(p.id ?? '')),
|
||||
installedPacks: ref(state.installed),
|
||||
isLoading: ref(state.installedLoading),
|
||||
isReady: ref(state.installedReady)
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
vi.mock(
|
||||
'@/workbench/extensions/manager/composables/nodePack/useWorkflowPacks',
|
||||
() => ({
|
||||
useWorkflowPacks: () => ({
|
||||
startFetchWorkflowPacks: state.startFetchWorkflowPacks,
|
||||
filterWorkflowPack: (packs: NodePack[]) => packs,
|
||||
workflowPacks: ref(state.workflow),
|
||||
isLoading: ref(state.workflowLoading),
|
||||
isReady: ref(state.workflowReady)
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
vi.mock('@/workbench/extensions/manager/stores/comfyManagerStore', () => ({
|
||||
useComfyManagerStore: () => ({
|
||||
isPackInstalled: (id: string | undefined) =>
|
||||
state.installedIds.has(id ?? ''),
|
||||
getInstalledPackVersion: (id: string) => state.installedVersions[id]
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/workbench/extensions/manager/stores/conflictDetectionStore', () => ({
|
||||
useConflictDetectionStore: () => ({
|
||||
get conflictedPackages() {
|
||||
return state.conflicts
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
function pack(id: string, latestVersion?: string): NodePack {
|
||||
return {
|
||||
id,
|
||||
name: id,
|
||||
latest_version: latestVersion ? { version: latestVersion } : undefined
|
||||
} as NodePack
|
||||
}
|
||||
|
||||
function display(
|
||||
tab: ManagerTab,
|
||||
searchResults: NodePack[] = [],
|
||||
query = '',
|
||||
sortField = ''
|
||||
) {
|
||||
return useManagerDisplayPacks(
|
||||
ref(tab),
|
||||
ref(searchResults),
|
||||
ref(query),
|
||||
ref(sortField)
|
||||
)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
state.installed = []
|
||||
state.workflow = []
|
||||
state.installedLoading = false
|
||||
state.workflowLoading = false
|
||||
state.installedReady = true
|
||||
state.workflowReady = true
|
||||
state.startFetchInstalled.mockReset()
|
||||
state.startFetchWorkflowPacks.mockReset()
|
||||
state.installedIds = new Set()
|
||||
state.installedVersions = {}
|
||||
state.conflicts = []
|
||||
})
|
||||
|
||||
describe('useManagerDisplayPacks', () => {
|
||||
it('All tab returns the raw search results', () => {
|
||||
const results = [pack('a'), pack('b')]
|
||||
expect(display(ManagerTab.All, results).displayPacks.value).toEqual(results)
|
||||
})
|
||||
|
||||
it('NotInstalled tab excludes installed packs', () => {
|
||||
state.installedIds = new Set(['a'])
|
||||
const { displayPacks } = display(ManagerTab.NotInstalled, [
|
||||
pack('a'),
|
||||
pack('b')
|
||||
])
|
||||
expect(displayPacks.value.map((p) => p.id)).toEqual(['b'])
|
||||
})
|
||||
|
||||
it('AllInstalled tab shows installed packs when not searching', () => {
|
||||
state.installed = [pack('x'), pack('y')]
|
||||
const { displayPacks } = display(ManagerTab.AllInstalled)
|
||||
expect(displayPacks.value.map((p) => p.id)).toEqual(['x', 'y'])
|
||||
})
|
||||
|
||||
it('UpdateAvailable tab keeps only installed packs with a newer latest version', () => {
|
||||
state.installedIds = new Set(['old', 'current', 'nightly'])
|
||||
state.installedVersions = {
|
||||
old: '1.0.0',
|
||||
current: '2.0.0',
|
||||
nightly: 'not-semver'
|
||||
}
|
||||
state.installed = [
|
||||
pack('old', '1.2.0'),
|
||||
pack('current', '2.0.0'),
|
||||
pack('nightly', '9.9.9'),
|
||||
pack('uninstalled', '5.0.0')
|
||||
]
|
||||
const { displayPacks } = display(ManagerTab.UpdateAvailable)
|
||||
expect(displayPacks.value.map((p) => p.id)).toEqual(['old'])
|
||||
})
|
||||
|
||||
it('Conflicting tab keeps packs flagged by the conflict store', () => {
|
||||
state.installed = [pack('a'), pack('b')]
|
||||
state.conflicts = [{ package_id: 'b' }]
|
||||
const { displayPacks } = display(ManagerTab.Conflicting)
|
||||
expect(displayPacks.value.map((p) => p.id)).toEqual(['b'])
|
||||
})
|
||||
|
||||
it('Missing tab returns workflow packs that are not installed', () => {
|
||||
state.workflow = [pack('a'), pack('b')]
|
||||
state.installedIds = new Set(['a'])
|
||||
const { displayPacks, missingNodePacks } = display(ManagerTab.Missing)
|
||||
expect(displayPacks.value.map((p) => p.id)).toEqual(['b'])
|
||||
expect(missingNodePacks.value.map((p) => p.id)).toEqual(['b'])
|
||||
})
|
||||
|
||||
it('Unresolved tab is always empty', () => {
|
||||
expect(
|
||||
display(ManagerTab.Unresolved, [pack('a')]).displayPacks.value
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('reports loading state scoped to the active tab group', () => {
|
||||
state.installedLoading = true
|
||||
state.workflowLoading = false
|
||||
expect(display(ManagerTab.AllInstalled).isLoading.value).toBe(true)
|
||||
expect(display(ManagerTab.All).isLoading.value).toBe(false)
|
||||
|
||||
state.installedLoading = false
|
||||
state.workflowLoading = true
|
||||
expect(display(ManagerTab.Workflow).isLoading.value).toBe(true)
|
||||
expect(display(ManagerTab.Missing).isLoading.value).toBe(true)
|
||||
})
|
||||
|
||||
it('fetches installed packs when an installed tab is selected and not ready', () => {
|
||||
state.installedReady = false
|
||||
display(ManagerTab.AllInstalled)
|
||||
|
||||
expect(state.startFetchInstalled).toHaveBeenCalledTimes(1)
|
||||
expect(state.startFetchWorkflowPacks).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('fetches workflow and installed packs for missing workflow dependencies', () => {
|
||||
state.installedReady = false
|
||||
state.workflowReady = false
|
||||
display(ManagerTab.Missing)
|
||||
|
||||
expect(state.startFetchInstalled).toHaveBeenCalledTimes(1)
|
||||
expect(state.startFetchWorkflowPacks).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('filters search results to installed packs on the AllInstalled tab while searching', () => {
|
||||
state.installedIds = new Set(['a'])
|
||||
const { displayPacks } = display(
|
||||
ManagerTab.AllInstalled,
|
||||
[pack('a'), pack('b')],
|
||||
'query'
|
||||
)
|
||||
expect(displayPacks.value.map((p) => p.id)).toEqual(['a'])
|
||||
})
|
||||
|
||||
it('filters searched update and conflict tabs before applying tab rules', () => {
|
||||
state.installedIds = new Set(['old', 'conflict'])
|
||||
state.installedVersions = {
|
||||
old: '1.0.0',
|
||||
conflict: '1.0.0'
|
||||
}
|
||||
state.conflicts = [{ package_id: 'conflict' }]
|
||||
const results = [
|
||||
pack('old', '2.0.0'),
|
||||
pack('current', '1.0.0'),
|
||||
pack('conflict', '1.0.0')
|
||||
]
|
||||
|
||||
expect(
|
||||
display(
|
||||
ManagerTab.UpdateAvailable,
|
||||
results,
|
||||
'query'
|
||||
).displayPacks.value.map((p) => p.id)
|
||||
).toEqual(['old'])
|
||||
expect(
|
||||
display(ManagerTab.Conflicting, results, 'query').displayPacks.value.map(
|
||||
(p) => p.id
|
||||
)
|
||||
).toEqual(['conflict'])
|
||||
})
|
||||
|
||||
it('filters workflow search results on the Workflow tab while searching', () => {
|
||||
const { displayPacks } = display(
|
||||
ManagerTab.Workflow,
|
||||
[pack('a'), pack('b')],
|
||||
'query'
|
||||
)
|
||||
expect(displayPacks.value.map((p) => p.id)).toEqual(['a', 'b'])
|
||||
})
|
||||
|
||||
it('filters searched missing workflow packs to not-installed packs', () => {
|
||||
state.installedIds = new Set(['a'])
|
||||
const { displayPacks } = display(
|
||||
ManagerTab.Missing,
|
||||
[pack('a'), pack('b')],
|
||||
'query'
|
||||
)
|
||||
expect(displayPacks.value.map((p) => p.id)).toEqual(['b'])
|
||||
})
|
||||
|
||||
it('falls back to search results for unknown tabs', () => {
|
||||
const results = [pack('a')]
|
||||
expect(
|
||||
display('unknown' as ManagerTab, results).displayPacks.value
|
||||
).toEqual(results)
|
||||
})
|
||||
|
||||
it('sorts installed packs by the configured field', () => {
|
||||
state.installed = [pack('b'), pack('a'), pack('c')]
|
||||
const { displayPacks } = display(ManagerTab.AllInstalled, [], '', 'name')
|
||||
expect(displayPacks.value.map((p) => p.id)).toEqual(['a', 'b', 'c'])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user