import { beforeEach, describe, expect, it, vi } from 'vitest'
import { canvasState, resetCanvasState } from '../../mocks/meta-register'
vi.mock('@opentiny/tiny-engine-utils', () => ({
utils: {
deepClone: (value: unknown) => JSON.parse(JSON.stringify(value))
}
}))
vi.mock('@opentiny/vue-icon', () => ({
default: {
IconWarning: {}
}
}))
vi.mock('../../../src/composables/core/useConfig', () => ({
default: () => ({
getSelectedModelInfo: () => ({
config: {
chatMode: 'agent'
}
})
})
}))
const getBaseSchema = () => ({
componentName: 'Page',
props: {},
state: {},
methods: {},
css: '',
children: []
})
const addButtonPatch = () =>
JSON.stringify([
{
op: 'add',
path: '/children/0',
value: {
componentName: 'TinyButton',
props: {
text: 'Submit'
},
children: []
}
}
])
describe('pageUpdater', () => {
beforeEach(async () => {
resetCanvasState()
canvasState.pageSchema = getBaseSchema()
const { resetPageSchemaUpdateState } = await import('../../../src/composables/core/pageUpdater')
resetPageSchemaUpdateState()
})
it('applies streaming updates without importing the whole schema', async () => {
const { updatePageSchema } = await import('../../../src/composables/core/pageUpdater')
const initialSchema = getBaseSchema()
const result = await updatePageSchema(addButtonPatch(), initialSchema, false)
expect(result?.schema?.children).toHaveLength(1)
expect(canvasState.pageSchema.children).toHaveLength(1)
expect(canvasState.pageSchema.children[0].componentName).toBe('TinyButton')
expect(canvasState.imported).toHaveLength(0)
expect(canvasState.history).toHaveLength(0)
expect(canvasState.saved).toBe(false)
expect(canvasState.published).toEqual([{ topic: 'schemaChange', data: {} }])
})
it('imports and records history for final updates', async () => {
const { updatePageSchema } = await import('../../../src/composables/core/pageUpdater')
const initialSchema = getBaseSchema()
const result = await updatePageSchema(addButtonPatch(), initialSchema, true)
expect(result?.schema?.children).toHaveLength(1)
expect(canvasState.imported).toHaveLength(1)
expect(canvasState.imported[0].children[0].componentName).toBe('TinyButton')
expect(canvasState.history).toHaveLength(1)
expect(canvasState.published).toHaveLength(0)
expect(canvasState.saved).toBe(false)
})
it('stores the last successful streaming schema as a final fallback', async () => {
const { getLastSuccessfulPageSchema, updatePageSchema } = await import('../../../src/composables/core/pageUpdater')
const initialSchema = getBaseSchema()
await updatePageSchema(addButtonPatch(), initialSchema, false)
const fallbackBeforeFinal = getLastSuccessfulPageSchema()
const finalResult = await updatePageSchema('not json', initialSchema, true)
expect(finalResult?.isError).toBe(true)
expect(getLastSuccessfulPageSchema()).toBe(fallbackBeforeFinal)
expect(getLastSuccessfulPageSchema()?.children[0].componentName).toBe('TinyButton')
})
it('clears the fallback only when a new agent turn starts', async () => {
const { getLastSuccessfulPageSchema, resetPageSchemaUpdateState, updatePageSchema } = await import(
'../../../src/composables/core/pageUpdater'
)
const initialSchema = getBaseSchema()
await updatePageSchema(addButtonPatch(), initialSchema, false)
expect(getLastSuccessfulPageSchema()).toBeTruthy()
resetPageSchemaUpdateState()
expect(getLastSuccessfulPageSchema()).toBeNull()
})
it('keeps the final schema after an earlier streaming update has completed', async () => {
const { updatePageSchema } = await import('../../../src/composables/core/pageUpdater')
const initialSchema = getBaseSchema()
const streamingResult = await updatePageSchema(addButtonPatch(), initialSchema, false)
const finalResult = await updatePageSchema(
JSON.stringify([
{
op: 'add',
path: '/children/0',
value: {
componentName: 'TinyForm',
props: {},
children: []
}
}
]),
initialSchema,
true
)
expect(streamingResult?.schema?.children[0].componentName).toBe('TinyButton')
expect(finalResult?.schema?.children[0].componentName).toBe('TinyForm')
expect(canvasState.pageSchema.children[0].componentName).toBe('TinyForm')
})
it('rejects schemas containing invalid children nodes before touching canvas', async () => {
const { updatePageSchema } = await import('../../../src/composables/core/pageUpdater')
const initialSchema = getBaseSchema()
const result = await updatePageSchema(
JSON.stringify([
{
op: 'add',
path: '/children/0',
value: 'broken child'
}
]),
initialSchema,
true
)
expect(result?.isError).toBe(true)
expect(canvasState.imported).toHaveLength(0)
expect(canvasState.pageSchema.children).toHaveLength(0)
})
it('normalizes invalid methods before updating canvas', async () => {
const { updatePageSchema } = await import('../../../src/composables/core/pageUpdater')
const initialSchema = getBaseSchema()
const result = await updatePageSchema(
JSON.stringify([
{
op: 'add',
path: '/methods/submit',
value: {
type: 'JSExpression',
value: 'this.submit()'
}
}
]),
initialSchema,
true
)
expect(result?.schema?.methods.submit.type).toBe('JSFunction')
expect(result?.schema?.methods.submit.value).toContain('function submit()')
expect(canvasState.pageSchema.methods.submit.type).toBe('JSFunction')
})
})