Files
deepseek-harness/scripts/cordis-entry-commit.spec.ts
T

120 lines
5.3 KiB
TypeScript

/** Tree mutation reports and detached patch inputs across the Loader/Include upgrade. */
import { Context, FiberState } from '@deepseek-ai/cordis'
import Loader, { Group, type EntryChange, type EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
import { applyEntryPatches } from '@deepseek-ai/cordis-plugin-include'
import { describe, expect, it, onTestFinished, vi } from 'vitest'
async function fixture() {
const ctx = new Context()
onTestFinished(() => ctx.fiber.dispose())
await ctx.plugin(Loader)
ctx.loader.builtins.group = Group
ctx.loader.builtins.example = () => {}
const changes: EntryChange[] = []
vi.spyOn(ctx.loader, 'commit').mockImplementation((change) => { changes.push(change) })
return { ctx, loader: ctx.loader, changes }
}
describe('Loader tree mutation reports', () => {
it('reports assigned ids and applied options for create, update, move and remove', async () => {
const { loader, changes } = await fixture()
const id = await loader.create({ name: 'cordis:example', config: { value: 1 } })
await loader.await()
expect(changes).toHaveLength(1)
expect(changes[0]).toEqual({ id, group: loader.root, options: loader.resolve(id).options })
expect(id).not.toBe('')
const groupId = await loader.create({ name: 'cordis:group', group: true, config: [] })
await loader.await()
changes.length = 0
await loader.update(id, { config: { value: 2 } }, groupId)
expect(changes).toHaveLength(1)
expect(changes[0]).toMatchObject({ id, from: loader.root, group: loader.resolveGroup(groupId),
legacy: { config: { value: 1 } }, options: { config: { value: 2 } } })
expect(changes[0]!.options).toBe(loader.resolve(id).options)
changes.length = 0
await loader.update(id, { config: { value: 3 } })
expect(changes).toHaveLength(1)
expect(changes[0]!.from).toBeUndefined()
changes.length = 0
const legacy = loader.resolve(id).options
loader.remove(id)
await loader.await()
expect(changes).toEqual([{ id, group: loader.resolveGroup(groupId), legacy }])
expect(legacy.disabled).toBeUndefined()
})
it('reports explicit Fiber saves and self-disposal but ignores child disposal', async () => {
const { loader, changes } = await fixture()
const id = await loader.create({ name: 'cordis:example', config: { value: 1 } })
await loader.await()
const fiber = loader.resolve(id).fiber!
changes.length = 0
await fiber.update({ value: 2 })
expect(changes).toHaveLength(1)
expect(changes[0]).toMatchObject({ legacy: { config: { value: 1 } }, options: { config: { value: 2 } } })
changes.length = 0
const child = fiber.ctx.plugin(() => {})
await child
await child.dispose()
expect(changes).toEqual([])
await fiber.dispose()
expect(changes).toHaveLength(1)
expect(changes[0]!.options!.disabled).toBe(true)
expect(changes[0]!.legacy!.disabled).toBeUndefined()
})
it('keeps adopted entries and does not report file-driven removal or owner teardown', async () => {
const { ctx, loader, changes } = await fixture()
await loader.root.update([
{ id: 'g', name: 'cordis:group', group: true, config: [] },
{ id: 'x', name: 'cordis:example' },
])
await loader.await()
const target = loader.resolveGroup('g')
const options = loader.resolve('x').options
target.data.push(options)
await target.create(options)
loader.root.remove('x')
expect(loader.resolve('x').parent).toBe(target)
expect(loader.resolve('x').fiber!.state).toBe(FiberState.ACTIVE)
await target.update([])
await loader.await()
expect(loader.store.x).toBeUndefined()
expect(changes).toEqual([])
await ctx.fiber.dispose()
expect(changes).toEqual([])
})
it('mounts explicit ids containing the separator without nested-path resolution', async () => {
const { loader, changes } = await fixture()
const options: EntryOptions = { id: 'a:b', name: 'cordis:example' }
await expect(loader.create(options)).resolves.toBe('a:b')
await loader.await()
expect(loader.store['a:b']!.fiber!.state).toBe(FiberState.ACTIVE)
expect(changes).toEqual([{ id: 'a:b', group: loader.root, options }])
expect(() => loader.resolve('a:b')).toThrow('cannot resolve entry a:b')
})
})
describe('Include patch data ownership', () => {
it('detaches file entries even with no patches', () => {
const data: EntryOptions[] = [{ id: 'x', name: 'example', config: { value: 1 } }]
const result = applyEntryPatches(data, undefined, vi.fn())
result[0]!.disabled = true
;(result[0]!.config as { value: number }).value = 2
expect(data).toEqual([{ id: 'x', name: 'example', config: { value: 1 } }])
})
it('clones inserted groups and lets later patches address their descendants', () => {
const insert: EntryOptions[] = [{ id: 'g', name: 'group', group: true,
config: [{ id: 'x', name: 'example', config: { value: 1 } }] }]
const warn = vi.fn()
const result = applyEntryPatches([], [{ insert }, { id: 'x', disabled: true }], warn)
const children = result[0]!.config as Array<{ disabled?: boolean; config: { value: number } }>
expect(children[0]!.disabled).toBe(true)
children[0]!.config.value = 2
expect(insert[0]!.config).toEqual([{ id: 'x', name: 'example', config: { value: 1 } }])
expect(warn).not.toHaveBeenCalled()
})
})