144 lines
4.6 KiB
TypeScript
144 lines
4.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { generateSceneActions, generateSceneContent } from '@openmaic/generation';
|
|
import type { AICallFn } from '@openmaic/generation';
|
|
import type { GeneratedInteractiveContent, SceneOutline } from '@openmaic/generation';
|
|
|
|
function baseInteractiveOutline(overrides: Partial<SceneOutline> = {}): SceneOutline {
|
|
return {
|
|
id: 'scene-widget',
|
|
type: 'interactive',
|
|
title: 'Energy Widget',
|
|
description: 'Explore how energy changes with the slider.',
|
|
keyPoints: ['Observe the slider', 'Set a higher energy value', 'Reveal the formula'],
|
|
order: 0,
|
|
widgetType: 'simulation',
|
|
widgetOutline: { concept: 'Energy transfer', keyVariables: ['energy'] },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('widget actions direct pipeline', () => {
|
|
it('generates widget content without a second teacherActions call', async () => {
|
|
const capturedUsers: string[] = [];
|
|
const aiCall: AICallFn = async (_system, user) => {
|
|
capturedUsers.push(user);
|
|
return '<!DOCTYPE html><html><body><div id="energy-slider"></div></body></html>';
|
|
};
|
|
|
|
const content = await generateSceneContent(baseInteractiveOutline(), aiCall, {
|
|
languageDirective: 'Teach in English.',
|
|
});
|
|
|
|
expect(capturedUsers).toHaveLength(1);
|
|
expect(content).toMatchObject({
|
|
html: expect.stringContaining('energy-slider'),
|
|
widgetType: 'simulation',
|
|
});
|
|
expect(content && 'teacherActions' in content).toBe(false);
|
|
});
|
|
|
|
it('uses interactive action generation directly and preserves all four widget action types', async () => {
|
|
const capturedUsers: string[] = [];
|
|
const aiCall: AICallFn = async (_system, user) => {
|
|
capturedUsers.push(user);
|
|
return JSON.stringify([
|
|
{
|
|
type: 'action',
|
|
name: 'widget_highlight',
|
|
params: { target: '#energy-slider', content: 'Watch this control first.' },
|
|
},
|
|
{
|
|
type: 'action',
|
|
name: 'widget_setState',
|
|
params: { state: { energy: 82 }, content: 'Set energy to a high value.' },
|
|
},
|
|
{
|
|
type: 'action',
|
|
name: 'widget_annotation',
|
|
params: { target: '#result-card', content: 'This result reflects the new state.' },
|
|
},
|
|
{
|
|
type: 'action',
|
|
name: 'widget_reveal',
|
|
params: { target: '#hidden-formula', content: 'Reveal the formula behind it.' },
|
|
},
|
|
]);
|
|
};
|
|
const legacyContent = {
|
|
html: '<!DOCTYPE html><html><body><div id="energy-slider"></div></body></html>',
|
|
widgetType: 'simulation',
|
|
teacherActions: [
|
|
{
|
|
id: 'legacy-highlight',
|
|
type: 'highlight',
|
|
target: '#legacy',
|
|
content: 'Legacy action should be ignored.',
|
|
},
|
|
],
|
|
} as unknown as GeneratedInteractiveContent;
|
|
|
|
const actions = await generateSceneActions(baseInteractiveOutline(), legacyContent, aiCall, {
|
|
languageDirective: 'Teach in English.',
|
|
});
|
|
|
|
expect(capturedUsers).toHaveLength(1);
|
|
expect(capturedUsers[0]).toContain('widget_highlight');
|
|
expect(actions.map((action) => action.type)).toEqual([
|
|
'widget_highlight',
|
|
'widget_setState',
|
|
'widget_annotation',
|
|
'widget_reveal',
|
|
]);
|
|
expect(actions).toEqual([
|
|
expect.objectContaining({
|
|
type: 'widget_highlight',
|
|
target: '#energy-slider',
|
|
content: 'Watch this control first.',
|
|
}),
|
|
expect.objectContaining({
|
|
type: 'widget_setState',
|
|
state: { energy: 82 },
|
|
content: 'Set energy to a high value.',
|
|
}),
|
|
expect.objectContaining({
|
|
type: 'widget_annotation',
|
|
target: '#result-card',
|
|
content: 'This result reflects the new state.',
|
|
}),
|
|
expect.objectContaining({
|
|
type: 'widget_reveal',
|
|
target: '#hidden-formula',
|
|
content: 'Reveal the formula behind it.',
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it('defaults widget_setState.state to {} when the LLM omits it', async () => {
|
|
const aiCall: AICallFn = async () =>
|
|
JSON.stringify([
|
|
{
|
|
type: 'action',
|
|
name: 'widget_setState',
|
|
params: { content: 'Update the widget.' },
|
|
},
|
|
]);
|
|
const content = {
|
|
html: '<!DOCTYPE html><html><body></body></html>',
|
|
widgetType: 'simulation',
|
|
} as unknown as GeneratedInteractiveContent;
|
|
|
|
const actions = await generateSceneActions(baseInteractiveOutline(), content, aiCall, {
|
|
languageDirective: 'Teach in English.',
|
|
});
|
|
|
|
expect(actions).toEqual([
|
|
expect.objectContaining({
|
|
type: 'widget_setState',
|
|
state: {},
|
|
content: 'Update the widget.',
|
|
}),
|
|
]);
|
|
});
|
|
});
|