mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 00:01:55 +00:00
568dbbee85
* fix(frontend): show friendly draft path for draft-only items in pickers * fix(frontend): dedupe current draft item and scope tab picker by friendly path * fix(frontend): key live draft picker entries by storage path * fix(frontend): fall back to the current leaf when the picker highlight key vanishes * fix(frontend): remount session tab picker when the friendly scope arrives * fix(frontend): stamp staged tab path for deployed items with undeployed renames * fix(frontend): expose staged flow/raw-app renames through the live draft registration
534 lines
19 KiB
TypeScript
534 lines
19 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { buildWorkspaceTree, legacyScopeToPath, relativizeWorkspacePath } from './workspaceTree'
|
|
import {
|
|
dirKey,
|
|
kindKey,
|
|
leafKeyFor,
|
|
type WorkspaceItem,
|
|
type WorkspaceItemKind
|
|
} from './workspacePicker'
|
|
import type { DrillBranch, DrillLeaf, DrillNode } from './drillPicker'
|
|
|
|
const item = (
|
|
kind: WorkspaceItemKind,
|
|
path: string,
|
|
summary?: string,
|
|
raw_app?: boolean
|
|
): WorkspaceItem => ({ kind, path, summary: summary ?? '', raw_app })
|
|
|
|
const isBranch = <L>(n: DrillNode<L> | undefined): n is DrillBranch<L> => !!n && n.type === 'branch'
|
|
const isLeaf = <L>(n: DrillNode<L> | undefined): n is DrillLeaf<L> => !!n && n.type === 'leaf'
|
|
|
|
const childKeys = <L>(b: DrillBranch<L>) => b.children.map((c) => c.key)
|
|
const findBranch = <L>(nodes: DrillNode<L>[], key: string): DrillBranch<L> => {
|
|
const n = nodes.find((x) => x.key === key)
|
|
if (!isBranch(n)) throw new Error(`expected branch ${key} in [${nodes.map((x) => x.key)}]`)
|
|
return n
|
|
}
|
|
|
|
describe('buildWorkspaceTree', () => {
|
|
describe('shape', () => {
|
|
it('returns an empty tree when kinds is empty', () => {
|
|
expect(buildWorkspaceTree({ loaded: {}, kinds: [], loadingKind: {} })).toEqual([])
|
|
})
|
|
|
|
it('multi-kind: prepends an All branch then per-kind branches', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: {
|
|
flow: [item('flow', 'f/demo/a')],
|
|
script: [item('script', 'f/demo/b')]
|
|
},
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: {}
|
|
})
|
|
expect(tree.map((n) => n.key)).toEqual([kindKey('all'), kindKey('flow'), kindKey('script')])
|
|
})
|
|
|
|
it('All branch is omitFromSearch and labeled "All"', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a')], script: [] },
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: {}
|
|
})
|
|
const all = findBranch(tree, kindKey('all'))
|
|
expect(all.omitFromSearch).toBe(true)
|
|
expect(all.label).toBe('All')
|
|
})
|
|
|
|
it('per-kind branches are searchGroup anchors', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a')], script: [] },
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: {}
|
|
})
|
|
const flow = findBranch(tree, kindKey('flow'))
|
|
expect(flow.searchGroup).toBe(true)
|
|
})
|
|
|
|
it("single-kind: returns that kind branch's children directly (no kind-level)", () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a'), item('flow', 'u/alice/b')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {}
|
|
})
|
|
// At the top we should see the scope dirs (u/alice, f/demo) directly,
|
|
// not a single 'kind:flow' branch wrapping them.
|
|
expect(tree.every((n) => isBranch(n) && n.key.startsWith('dir:flow:'))).toBe(true)
|
|
// u-scopes come before f-scopes
|
|
expect(tree.map((n) => n.key)).toEqual([dirKey('flow', 'u/alice'), dirKey('flow', 'f/demo')])
|
|
})
|
|
})
|
|
|
|
describe('loading state', () => {
|
|
it('per-kind branch is loading=true when loaded[k] is undefined and loadingKind[k] is true', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: {},
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: { flow: true }
|
|
})
|
|
const flow = findBranch(tree, kindKey('flow'))
|
|
expect(flow.loading).toBe(true)
|
|
})
|
|
|
|
it('per-kind branch is not loading once loaded[k] is set, even mid-refetch', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [] },
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: { flow: true }
|
|
})
|
|
const flow = findBranch(tree, kindKey('flow'))
|
|
expect(flow.loading).toBeFalsy()
|
|
})
|
|
|
|
it('All branch is loading when any kind is loading', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [] },
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: { script: true }
|
|
})
|
|
const all = findBranch(tree, kindKey('all'))
|
|
expect(all.loading).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('dir forest', () => {
|
|
it('groups leaves under their scope, then nested folders', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: {
|
|
flow: [
|
|
item('flow', 'f/demo/a'),
|
|
item('flow', 'f/demo/sub/b'),
|
|
item('flow', 'f/demo/sub/c'),
|
|
item('flow', 'u/alice/d')
|
|
]
|
|
},
|
|
kinds: ['flow'],
|
|
loadingKind: {}
|
|
})
|
|
// Top-level: u/alice (user scope), f/demo (folder scope)
|
|
expect(tree.map((n) => n.key)).toEqual([dirKey('flow', 'u/alice'), dirKey('flow', 'f/demo')])
|
|
const demo = findBranch(tree, dirKey('flow', 'f/demo'))
|
|
// Children: nested folder `sub` first, then leaf `a`
|
|
expect(childKeys(demo)).toEqual([
|
|
dirKey('flow', 'f/demo/sub'),
|
|
leafKeyFor('flow', 'f/demo/a')
|
|
])
|
|
const sub = findBranch(demo.children, dirKey('flow', 'f/demo/sub'))
|
|
expect(childKeys(sub)).toEqual([
|
|
leafKeyFor('flow', 'f/demo/sub/b'),
|
|
leafKeyFor('flow', 'f/demo/sub/c')
|
|
])
|
|
})
|
|
|
|
it('skips items with paths shorter than 3 segments', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo'), item('flow', 'f/demo/a')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {}
|
|
})
|
|
const demo = findBranch(tree, dirKey('flow', 'f/demo'))
|
|
expect(childKeys(demo)).toEqual([leafKeyFor('flow', 'f/demo/a')])
|
|
})
|
|
})
|
|
|
|
describe('leaf shape', () => {
|
|
it('uses summary as label and path as secondary when summary is present', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a', 'Hello')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {}
|
|
})
|
|
const demo = findBranch(tree, dirKey('flow', 'f/demo'))
|
|
const leaf = demo.children[0]
|
|
if (!isLeaf(leaf)) throw new Error('expected leaf')
|
|
expect(leaf.label).toBe('Hello')
|
|
expect(leaf.secondary).toBe('f/demo/a')
|
|
})
|
|
|
|
it('falls back to path as label when summary is empty', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {}
|
|
})
|
|
const demo = findBranch(tree, dirKey('flow', 'f/demo'))
|
|
const leaf = demo.children[0]
|
|
if (!isLeaf(leaf)) throw new Error('expected leaf')
|
|
expect(leaf.label).toBe('f/demo/a')
|
|
expect(leaf.secondary).toBeUndefined()
|
|
})
|
|
|
|
it('labels and groups a draft-only item by its friendly draftPath, keyed by storage path', () => {
|
|
const draft = { ...item('app', 'u/admin/draft_abc123'), draftPath: 'f/marketing/dashboard' }
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { app: [draft] },
|
|
kinds: ['app'],
|
|
loadingKind: {}
|
|
})
|
|
// Grouped under the friendly folder, not u/admin.
|
|
expect(tree.map((n) => n.key)).toEqual([dirKey('app', 'f/marketing')])
|
|
const marketing = findBranch(tree, dirKey('app', 'f/marketing'))
|
|
const leaf = marketing.children[0]
|
|
if (!isLeaf(leaf)) throw new Error('expected leaf')
|
|
// Displayed by the friendly path; keyed (and navigated) by storage path.
|
|
expect(leaf.label).toBe('f/marketing/dashboard')
|
|
expect(leaf.key).toBe(leafKeyFor('app', 'u/admin/draft_abc123'))
|
|
expect(leaf.data.path).toBe('u/admin/draft_abc123')
|
|
})
|
|
|
|
it('uses the friendly draftPath as secondary when a summary is present', () => {
|
|
const draft = {
|
|
...item('script', 'u/admin/draft_xyz', 'My Script'),
|
|
draftPath: 'u/admin/my_script'
|
|
}
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { script: [draft] },
|
|
kinds: ['script'],
|
|
loadingKind: {}
|
|
})
|
|
const admin = findBranch(tree, dirKey('script', 'u/admin'))
|
|
const leaf = admin.children[0]
|
|
if (!isLeaf(leaf)) throw new Error('expected leaf')
|
|
expect(leaf.label).toBe('My Script')
|
|
expect(leaf.secondary).toBe('u/admin/my_script')
|
|
})
|
|
|
|
it('marks the currentItem leaf with current=true', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a'), item('flow', 'f/demo/b')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {},
|
|
currentItem: item('flow', 'f/demo/a')
|
|
})
|
|
const demo = findBranch(tree, dirKey('flow', 'f/demo'))
|
|
const [a, b] = demo.children
|
|
if (!isLeaf(a) || !isLeaf(b)) throw new Error('expected leaves')
|
|
expect(a.current).toBe(true)
|
|
expect(b.current).toBeFalsy()
|
|
})
|
|
})
|
|
|
|
describe('withCurrent: rename suppression', () => {
|
|
it('injects currentItem at its live path when not already in the list', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [] },
|
|
kinds: ['flow'],
|
|
loadingKind: {},
|
|
currentItem: { ...item('flow', 'f/demo/new'), summary: 'My Flow' }
|
|
})
|
|
const demo = findBranch(tree, dirKey('flow', 'f/demo'))
|
|
expect(demo.children.map((c) => c.key)).toEqual([leafKeyFor('flow', 'f/demo/new')])
|
|
})
|
|
|
|
it('drops the savedPath entry during a mid-rename so only the live one shows', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/old', 'My Flow')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {},
|
|
currentItem: { ...item('flow', 'f/demo/new', 'My Flow'), savedPath: 'f/demo/old' }
|
|
})
|
|
const demo = findBranch(tree, dirKey('flow', 'f/demo'))
|
|
const paths = demo.children.map((c) => c.key)
|
|
expect(paths).toContain(leafKeyFor('flow', 'f/demo/new'))
|
|
expect(paths).not.toContain(leafKeyFor('flow', 'f/demo/old'))
|
|
})
|
|
|
|
it('does not duplicate a draft-only row whose friendly draftPath is the current live path', () => {
|
|
// Editor open on a renamed draft-only script: currentItem.path is the
|
|
// friendly path while listScripts returns the storage-path row carrying
|
|
// the same friendly path as draftPath. One leaf, marked current.
|
|
const loadedDraft = {
|
|
...item('script', 'u/admin/draft_abc'),
|
|
draftPath: 'u/admin/my_script'
|
|
}
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { script: [loadedDraft] },
|
|
kinds: ['script'],
|
|
loadingKind: {},
|
|
currentItem: item('script', 'u/admin/my_script')
|
|
})
|
|
const admin = findBranch(tree, dirKey('script', 'u/admin'))
|
|
expect(admin.children.map((c) => c.key)).toEqual([leafKeyFor('script', 'u/admin/draft_abc')])
|
|
const leaf = admin.children[0]
|
|
if (!isLeaf(leaf)) throw new Error('expected leaf')
|
|
expect(leaf.current).toBe(true)
|
|
})
|
|
|
|
it('drops the storage-path row via draftPath during a mid-rename', () => {
|
|
// Renaming a draft-only item: savedPath is the old friendly path, which
|
|
// the loaded row only knows as its draftPath. The stale row must go so
|
|
// only the live (typed) entry shows.
|
|
const loadedDraft = {
|
|
...item('script', 'u/admin/draft_abc'),
|
|
draftPath: 'u/admin/old_name'
|
|
}
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { script: [loadedDraft] },
|
|
kinds: ['script'],
|
|
loadingKind: {},
|
|
currentItem: { ...item('script', 'u/admin/new_name'), savedPath: 'u/admin/old_name' }
|
|
})
|
|
const admin = findBranch(tree, dirKey('script', 'u/admin'))
|
|
expect(admin.children.map((c) => c.key)).toEqual([leafKeyFor('script', 'u/admin/new_name')])
|
|
})
|
|
|
|
it('does not re-inject when the live entry already exists in loaded', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a', 'Original')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {},
|
|
currentItem: item('flow', 'f/demo/a', 'Original')
|
|
})
|
|
const demo = findBranch(tree, dirKey('flow', 'f/demo'))
|
|
expect(demo.children.length).toBe(1)
|
|
})
|
|
|
|
it('passes other-kind items through untouched', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a')], script: [item('script', 'f/demo/b')] },
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: {},
|
|
currentItem: { ...item('flow', 'f/demo/new'), savedPath: 'f/demo/old' }
|
|
})
|
|
const script = findBranch(tree, kindKey('script'))
|
|
const demo = findBranch(script.children, dirKey('script', 'f/demo'))
|
|
expect(demo.children.map((c) => c.key)).toEqual([leafKeyFor('script', 'f/demo/b')])
|
|
})
|
|
})
|
|
|
|
describe('extraItemsByKind (drafts)', () => {
|
|
it('merges extras alongside loaded items', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {},
|
|
extraItemsByKind: { flow: [item('flow', 'f/demo/draft')] }
|
|
})
|
|
const demo = findBranch(tree, dirKey('flow', 'f/demo'))
|
|
expect(demo.children.map((c) => c.key).sort()).toEqual(
|
|
[leafKeyFor('flow', 'f/demo/a'), leafKeyFor('flow', 'f/demo/draft')].sort()
|
|
)
|
|
})
|
|
|
|
it('drops extras whose path collides with a loaded item (loaded wins)', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a', 'Backend summary')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {},
|
|
extraItemsByKind: { flow: [item('flow', 'f/demo/a', 'Draft summary')] }
|
|
})
|
|
const demo = findBranch(tree, dirKey('flow', 'f/demo'))
|
|
expect(demo.children.length).toBe(1)
|
|
const leaf = demo.children[0]
|
|
if (!isLeaf(leaf)) throw new Error('expected leaf')
|
|
expect(leaf.label).toBe('Backend summary')
|
|
})
|
|
|
|
it('extras flow into the cross-kind All branch too', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [], script: [item('script', 'f/demo/b')] },
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: {},
|
|
extraItemsByKind: { flow: [item('flow', 'f/demo/draft')] }
|
|
})
|
|
const all = findBranch(tree, kindKey('all'))
|
|
const demo = findBranch(all.children, dirKey('all', 'f/demo'))
|
|
const keys = demo.children.map((c) => c.key)
|
|
expect(keys).toContain(leafKeyFor('flow', 'f/demo/draft'))
|
|
expect(keys).toContain(leafKeyFor('script', 'f/demo/b'))
|
|
})
|
|
|
|
it('drops a live-cell extra at the friendly path when a loaded row carries it as draftPath', () => {
|
|
// listApps returns the draft-only row at its storage path with the
|
|
// friendly path in draftPath; the live editor cell surfaces the same
|
|
// draft as an extra keyed by the friendly path. One leaf, not two.
|
|
const loadedDraft = { ...item('app', 'u/admin/draft_abc'), draftPath: 'u/admin/dashboard' }
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { app: [loadedDraft] },
|
|
kinds: ['app'],
|
|
loadingKind: {},
|
|
extraItemsByKind: { app: [item('app', 'u/admin/dashboard')] }
|
|
})
|
|
const admin = findBranch(tree, dirKey('app', 'u/admin'))
|
|
expect(admin.children.map((c) => c.key)).toEqual([leafKeyFor('app', 'u/admin/draft_abc')])
|
|
})
|
|
|
|
it('folds a mid-rename live extra into the stale loaded row: one storage-keyed leaf under the typed folder', () => {
|
|
// Session picker while a rename's autosave is pending: listApps still
|
|
// carries the pre-rename friendly path, the live cell extra (re-keyed to
|
|
// the storage path by the picker) carries the typed one, and the tab's
|
|
// currentItem is the storage path. The typed name must win, on a single
|
|
// leaf that navigates via the storage path — never the display path.
|
|
const staleLoaded = { ...item('app', 'u/admin/draft_abc'), draftPath: 'u/admin/old_name' }
|
|
const liveExtra = { ...item('app', 'u/admin/draft_abc'), draftPath: 'f/marketing/new_name' }
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { app: [staleLoaded] },
|
|
kinds: ['app'],
|
|
loadingKind: {},
|
|
extraItemsByKind: { app: [liveExtra] },
|
|
currentItem: item('app', 'u/admin/draft_abc')
|
|
})
|
|
expect(tree.map((n) => n.key)).toEqual([dirKey('app', 'f/marketing')])
|
|
const marketing = findBranch(tree, dirKey('app', 'f/marketing'))
|
|
expect(marketing.children.map((c) => c.key)).toEqual([leafKeyFor('app', 'u/admin/draft_abc')])
|
|
const leaf = marketing.children[0]
|
|
if (!isLeaf(leaf)) throw new Error('expected leaf')
|
|
expect(leaf.current).toBe(true)
|
|
expect(leaf.data.path).toBe('u/admin/draft_abc')
|
|
expect(leaf.label).toBe('f/marketing/new_name')
|
|
})
|
|
|
|
it('is a no-op when extras are absent or empty', () => {
|
|
const noOpts = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {}
|
|
})
|
|
const emptyExtras = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a')] },
|
|
kinds: ['flow'],
|
|
loadingKind: {},
|
|
extraItemsByKind: { flow: [] }
|
|
})
|
|
expect(JSON.stringify(noOpts)).toEqual(JSON.stringify(emptyExtras))
|
|
})
|
|
})
|
|
|
|
describe('flat layout', () => {
|
|
it('roots the cross-kind scope dirs directly (no All / kind branches)', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: {
|
|
flow: [item('flow', 'f/demo/a'), item('flow', 'u/alice/b')],
|
|
script: [item('script', 'f/demo/c')]
|
|
},
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: {},
|
|
layout: 'flat'
|
|
})
|
|
// u-scopes before f-scopes, keyed under the 'all' namespace.
|
|
expect(tree.map((n) => n.key)).toEqual([dirKey('all', 'u/alice'), dirKey('all', 'f/demo')])
|
|
})
|
|
|
|
it('mixes every kind inside the same scope dir', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: {
|
|
flow: [item('flow', 'f/demo/a')],
|
|
script: [item('script', 'f/demo/b')]
|
|
},
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: {},
|
|
layout: 'flat'
|
|
})
|
|
const demo = findBranch(tree, dirKey('all', 'f/demo'))
|
|
expect(childKeys(demo)).toEqual([
|
|
leafKeyFor('flow', 'f/demo/a'),
|
|
leafKeyFor('script', 'f/demo/b')
|
|
])
|
|
})
|
|
|
|
it('applies extras and currentItem like the by-kind layout', () => {
|
|
const tree = buildWorkspaceTree({
|
|
loaded: { flow: [item('flow', 'f/demo/a')], script: [] },
|
|
kinds: ['flow', 'script'],
|
|
loadingKind: {},
|
|
extraItemsByKind: { script: [item('script', 'f/demo/draft')] },
|
|
currentItem: item('flow', 'f/demo/a'),
|
|
layout: 'flat'
|
|
})
|
|
const demo = findBranch(tree, dirKey('all', 'f/demo'))
|
|
const leaves = demo.children.filter(isLeaf)
|
|
expect(leaves.map((l) => l.key)).toEqual([
|
|
leafKeyFor('flow', 'f/demo/a'),
|
|
leafKeyFor('script', 'f/demo/draft')
|
|
])
|
|
expect(leaves[0].current).toBe(true)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('legacyScopeToPath', () => {
|
|
it('returns [] for undefined scope', () => {
|
|
expect(legacyScopeToPath(undefined, ['flow', 'script'])).toEqual([])
|
|
})
|
|
|
|
it('multi-kind: returns [kindKey] for a kind-only scope', () => {
|
|
expect(legacyScopeToPath({ kind: 'flow' }, ['flow', 'script'])).toEqual([kindKey('flow')])
|
|
})
|
|
|
|
it('multi-kind: returns [kindKey, dirKey] for a kind+dir scope', () => {
|
|
expect(legacyScopeToPath({ kind: 'flow', dir: 'f/demo' }, ['flow', 'script'])).toEqual([
|
|
kindKey('flow'),
|
|
dirKey('flow', 'f/demo')
|
|
])
|
|
})
|
|
|
|
it('multi-kind: handles `all` as a kind', () => {
|
|
expect(legacyScopeToPath({ kind: 'all', dir: 'f/demo' }, ['flow', 'script'])).toEqual([
|
|
kindKey('all'),
|
|
dirKey('all', 'f/demo')
|
|
])
|
|
})
|
|
|
|
it('single-kind: returns [] for a kind-only scope (no kind level in tree)', () => {
|
|
expect(legacyScopeToPath({ kind: 'flow' }, ['flow'])).toEqual([])
|
|
})
|
|
|
|
it('single-kind: returns [dirKey] for a kind+dir scope', () => {
|
|
expect(legacyScopeToPath({ kind: 'flow', dir: 'f/demo' }, ['flow'])).toEqual([
|
|
dirKey('flow', 'f/demo')
|
|
])
|
|
})
|
|
|
|
it('flat: returns [dirKey under all] for a dir scope, ignoring the kind', () => {
|
|
expect(legacyScopeToPath({ kind: 'flow', dir: 'f/demo' }, ['flow', 'script'], 'flat')).toEqual([
|
|
dirKey('all', 'f/demo')
|
|
])
|
|
})
|
|
|
|
it('flat: returns [] without a dir', () => {
|
|
expect(legacyScopeToPath({ kind: 'all' }, ['flow', 'script'], 'flat')).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('relativizeWorkspacePath', () => {
|
|
it('returns the absolute path when scope has no dir segment', () => {
|
|
expect(relativizeWorkspacePath('f/demo/a', [])).toBe('f/demo/a')
|
|
expect(relativizeWorkspacePath('f/demo/a', [kindKey('flow')])).toBe('f/demo/a')
|
|
})
|
|
|
|
it('shortens to the path relative to the deepest dir scope', () => {
|
|
const scope = [kindKey('flow'), dirKey('flow', 'f/demo')]
|
|
expect(relativizeWorkspacePath('f/demo/a', scope)).toBe('a')
|
|
})
|
|
|
|
it('uses the DEEPEST dir scope when there are nested ones', () => {
|
|
const scope = [kindKey('flow'), dirKey('flow', 'f/demo'), dirKey('flow', 'f/demo/sub')]
|
|
expect(relativizeWorkspacePath('f/demo/sub/b', scope)).toBe('b')
|
|
})
|
|
|
|
it('falls back to absolute path when the leaf is not under the dir scope', () => {
|
|
const scope = [kindKey('flow'), dirKey('flow', 'f/demo')]
|
|
expect(relativizeWorkspacePath('f/other/a', scope)).toBe('f/other/a')
|
|
})
|
|
})
|