Files
simstudioai--sim/apps/sim/hooks/queries/utils/folder-tree.test.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

206 lines
7.2 KiB
TypeScript

/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import {
findLockedAncestorFolder,
getFolderPath,
isFolderEffectivelyLocked,
isFolderOrAncestorLocked,
isWorkflowEffectivelyLocked,
} from '@/hooks/queries/utils/folder-tree'
import type { WorkflowFolder } from '@/stores/folders/types'
function makeFolder(overrides: Partial<WorkflowFolder> & { id: string }): WorkflowFolder {
return {
id: overrides.id,
name: overrides.name ?? overrides.id,
userId: 'user-1',
workspaceId: 'ws-1',
parentId: overrides.parentId ?? null,
color: '#000000',
isExpanded: false,
locked: overrides.locked ?? false,
sortOrder: 0,
createdAt: new Date(0),
updatedAt: new Date(0),
archivedAt: null,
}
}
describe('isFolderOrAncestorLocked', () => {
it('returns false for root', () => {
expect(isFolderOrAncestorLocked(null, {})).toBe(false)
expect(isFolderOrAncestorLocked(undefined, {})).toBe(false)
})
it('returns true when the folder itself is locked', () => {
const folders = { f1: makeFolder({ id: 'f1', locked: true }) }
expect(isFolderOrAncestorLocked('f1', folders)).toBe(true)
})
it('returns true when an ancestor is locked', () => {
const folders = {
f1: makeFolder({ id: 'f1', locked: true }),
f2: makeFolder({ id: 'f2', parentId: 'f1' }),
f3: makeFolder({ id: 'f3', parentId: 'f2' }),
}
expect(isFolderOrAncestorLocked('f3', folders)).toBe(true)
})
it('returns false when nothing in the chain is locked', () => {
const folders = {
f1: makeFolder({ id: 'f1' }),
f2: makeFolder({ id: 'f2', parentId: 'f1' }),
}
expect(isFolderOrAncestorLocked('f2', folders)).toBe(false)
})
it('short-circuits on cycles instead of looping forever', () => {
const folders = {
f1: makeFolder({ id: 'f1', parentId: 'f2' }),
f2: makeFolder({ id: 'f2', parentId: 'f1' }),
}
expect(isFolderOrAncestorLocked('f1', folders)).toBe(false)
})
})
describe('getFolderPath', () => {
it('returns null for root or unknown folders', () => {
expect(getFolderPath(null, {})).toBeNull()
expect(getFolderPath(undefined, {})).toBeNull()
expect(getFolderPath('missing', {})).toBeNull()
})
it('returns the leaf folder name when at top level', () => {
const folders = { f1: makeFolder({ id: 'f1', name: 'Engineering' }) }
expect(getFolderPath('f1', folders)).toBe('Engineering')
})
it('joins ancestor names from root to leaf', () => {
const folders = {
eng: makeFolder({ id: 'eng', name: 'Engineering' }),
be: makeFolder({ id: 'be', name: 'Backend', parentId: 'eng' }),
api: makeFolder({ id: 'api', name: 'API', parentId: 'be' }),
}
expect(getFolderPath('api', folders)).toBe('Engineering / Backend / API')
})
it('respects custom separators', () => {
const folders = {
eng: makeFolder({ id: 'eng', name: 'Engineering' }),
be: makeFolder({ id: 'be', name: 'Backend', parentId: 'eng' }),
}
expect(getFolderPath('be', folders, ' > ')).toBe('Engineering > Backend')
})
it('returns the partial path resolved before a missing ancestor', () => {
const folders = {
be: makeFolder({ id: 'be', name: 'Backend', parentId: 'missing' }),
}
expect(getFolderPath('be', folders)).toBe('Backend')
})
it('short-circuits on cycles instead of looping forever', () => {
const folders = {
f1: makeFolder({ id: 'f1', name: 'A', parentId: 'f2' }),
f2: makeFolder({ id: 'f2', name: 'B', parentId: 'f1' }),
}
expect(getFolderPath('f1', folders)).toBe('B / A')
})
})
describe('findLockedAncestorFolder', () => {
it('returns null for root or unknown folders', () => {
expect(findLockedAncestorFolder(null, {})).toBeNull()
expect(findLockedAncestorFolder(undefined, {})).toBeNull()
expect(findLockedAncestorFolder('missing', {})).toBeNull()
})
it('returns the folder itself when it is the locked one', () => {
const folders = { f1: makeFolder({ id: 'f1', name: 'Engineering', locked: true }) }
expect(findLockedAncestorFolder('f1', folders)?.name).toBe('Engineering')
})
it('returns the closest locked ancestor, not the root', () => {
const folders = {
root: makeFolder({ id: 'root', name: 'Root', locked: true }),
mid: makeFolder({ id: 'mid', name: 'Mid', parentId: 'root', locked: true }),
leaf: makeFolder({ id: 'leaf', name: 'Leaf', parentId: 'mid' }),
}
expect(findLockedAncestorFolder('leaf', folders)?.id).toBe('mid')
})
it('returns null when no folder in the chain is locked', () => {
const folders = {
f1: makeFolder({ id: 'f1', name: 'A' }),
f2: makeFolder({ id: 'f2', name: 'B', parentId: 'f1' }),
}
expect(findLockedAncestorFolder('f2', folders)).toBeNull()
})
it('short-circuits on cycles instead of looping forever', () => {
const folders = {
f1: makeFolder({ id: 'f1', name: 'A', parentId: 'f2' }),
f2: makeFolder({ id: 'f2', name: 'B', parentId: 'f1' }),
}
expect(findLockedAncestorFolder('f1', folders)).toBeNull()
})
})
describe('isWorkflowEffectivelyLocked', () => {
it('treats undefined or null workflows as unlocked', () => {
expect(isWorkflowEffectivelyLocked(undefined, {})).toBe(false)
expect(isWorkflowEffectivelyLocked(null, {})).toBe(false)
})
it('returns true when the workflow row itself is locked', () => {
expect(isWorkflowEffectivelyLocked({ locked: true, folderId: null }, {})).toBe(true)
})
it('returns true when an ancestor folder is locked', () => {
const folders = {
eng: makeFolder({ id: 'eng', locked: true }),
be: makeFolder({ id: 'be', parentId: 'eng' }),
}
expect(isWorkflowEffectivelyLocked({ locked: false, folderId: 'be' }, folders)).toBe(true)
})
it('returns false when neither row nor any ancestor is locked', () => {
const folders = { eng: makeFolder({ id: 'eng' }) }
expect(isWorkflowEffectivelyLocked({ locked: false, folderId: 'eng' }, folders)).toBe(false)
})
it('returns false for a workflow at workspace root with no row lock', () => {
expect(isWorkflowEffectivelyLocked({ locked: false, folderId: null }, {})).toBe(false)
})
})
describe('isFolderEffectivelyLocked', () => {
it('treats undefined or null folders as unlocked', () => {
expect(isFolderEffectivelyLocked(undefined, {})).toBe(false)
expect(isFolderEffectivelyLocked(null, {})).toBe(false)
})
it('returns true when the folder itself is locked', () => {
expect(isFolderEffectivelyLocked({ locked: true, parentId: null }, {})).toBe(true)
})
it('returns true when an ancestor folder is locked', () => {
const folders = {
eng: makeFolder({ id: 'eng', locked: true }),
be: makeFolder({ id: 'be', parentId: 'eng' }),
}
expect(isFolderEffectivelyLocked({ locked: false, parentId: 'eng' }, folders)).toBe(true)
})
it('returns false when neither the folder nor any ancestor is locked', () => {
const folders = { eng: makeFolder({ id: 'eng' }) }
expect(isFolderEffectivelyLocked({ locked: false, parentId: 'eng' }, folders)).toBe(false)
})
it('returns false for a root-level folder with no own lock', () => {
expect(isFolderEffectivelyLocked({ locked: false, parentId: null }, {})).toBe(false)
})
})