211 lines
5.9 KiB
TypeScript
211 lines
5.9 KiB
TypeScript
import { createElement, type ComponentType, type ReactNode } from 'react';
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { SceneProvider, type SceneDataController } from '@/lib/contexts/scene-context';
|
|
import { MediaStageProvider } from '@/lib/contexts/media-stage-context';
|
|
import { PlaybackScreenCanvas } from '@/components/slide-renderer/Editor/ScreenCanvas';
|
|
import { getPlaybackImageState } from '@/components/slide-renderer/Editor/RendererScreenCanvas';
|
|
import type { SlideContent } from '@/lib/types/stage';
|
|
import { useMediaGenerationStore } from '@/lib/store/media-generation';
|
|
|
|
vi.mock('@/lib/hooks/use-i18n', () => ({
|
|
useI18n: () => ({ t: (key: string) => key }),
|
|
}));
|
|
|
|
const content: SlideContent = {
|
|
type: 'slide',
|
|
canvas: {
|
|
id: 'slide-1',
|
|
viewportSize: 1000,
|
|
viewportRatio: 0.5625,
|
|
background: { type: 'solid', color: '#ffffff' },
|
|
theme: {
|
|
fontName: 'Arial',
|
|
fontColor: '#111111',
|
|
backgroundColor: '#ffffff',
|
|
themeColors: ['#111111'],
|
|
},
|
|
elements: [
|
|
{
|
|
id: 'title-1',
|
|
type: 'text',
|
|
left: 24,
|
|
top: 32,
|
|
width: 120,
|
|
height: 48,
|
|
rotate: 0,
|
|
content: '<p>Hello</p>',
|
|
defaultFontName: 'Arial',
|
|
defaultColor: '#111111',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const controller: SceneDataController<SlideContent> = {
|
|
sceneId: 'scene-1',
|
|
sceneType: 'slide',
|
|
getSnapshot: () => content,
|
|
updateSceneData: () => {},
|
|
};
|
|
|
|
const TestSceneProvider = SceneProvider as ComponentType<{
|
|
controller?: SceneDataController;
|
|
children?: ReactNode;
|
|
}>;
|
|
|
|
describe('PlaybackScreenCanvas', () => {
|
|
const flag = 'NEXT_PUBLIC_MAIC_PLAYBACK_RENDERER_ENABLED';
|
|
let original: string | undefined;
|
|
|
|
beforeEach(() => {
|
|
original = process.env[flag];
|
|
useMediaGenerationStore.setState({ tasks: {} });
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (original === undefined) {
|
|
delete process.env[flag];
|
|
} else {
|
|
process.env[flag] = original;
|
|
}
|
|
});
|
|
|
|
it('uses the legacy playback renderer by default', () => {
|
|
delete process.env[flag];
|
|
|
|
const html = renderToStaticMarkup(
|
|
createElement(
|
|
TestSceneProvider,
|
|
{ controller: controller as SceneDataController },
|
|
createElement(PlaybackScreenCanvas),
|
|
),
|
|
);
|
|
|
|
expect(html).toContain('class="screen-element"');
|
|
expect(html).toContain('id="screen-element-title-1"');
|
|
expect(html).not.toContain('class="slide-element"');
|
|
});
|
|
|
|
it('uses @openmaic/renderer when the playback renderer flag is enabled', () => {
|
|
process.env[flag] = 'true';
|
|
const html = renderToStaticMarkup(
|
|
createElement(
|
|
TestSceneProvider,
|
|
{ controller: controller as SceneDataController },
|
|
createElement(PlaybackScreenCanvas),
|
|
),
|
|
);
|
|
|
|
expect(html).toContain('class="slide-element"');
|
|
expect(html).toContain('id="screen-element-title-1"');
|
|
});
|
|
|
|
it('renders an untracked generated placeholder as pending in renderer playback mode', () => {
|
|
process.env[flag] = 'true';
|
|
const imageContent: SlideContent = {
|
|
...content,
|
|
canvas: {
|
|
...content.canvas,
|
|
elements: [
|
|
{
|
|
id: 'image-1',
|
|
type: 'image',
|
|
left: 0,
|
|
top: 0,
|
|
width: 100,
|
|
height: 100,
|
|
rotate: 0,
|
|
fixedRatio: true,
|
|
src: 'gen_img_1',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const imageController: SceneDataController<SlideContent> = {
|
|
...controller,
|
|
getSnapshot: () => imageContent,
|
|
};
|
|
|
|
const html = renderToStaticMarkup(
|
|
createElement(
|
|
MediaStageProvider,
|
|
{ value: 'stage-1' },
|
|
createElement(
|
|
TestSceneProvider,
|
|
{ controller: imageController as SceneDataController },
|
|
createElement(PlaybackScreenCanvas),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(html).toContain('data-media-state="pending"');
|
|
expect(html).not.toContain('src="gen_img_1"');
|
|
});
|
|
|
|
it('renders a completed generated image with the renderer default image treatment', () => {
|
|
process.env[flag] = 'true';
|
|
useMediaGenerationStore.setState({
|
|
tasks: {
|
|
gen_img_1: {
|
|
elementId: 'gen_img_1',
|
|
type: 'image',
|
|
status: 'done',
|
|
prompt: '',
|
|
params: {},
|
|
objectUrl: 'blob:generated-image',
|
|
retryCount: 0,
|
|
stageId: 'stage-1',
|
|
},
|
|
},
|
|
});
|
|
const imageContent: SlideContent = {
|
|
...content,
|
|
canvas: {
|
|
...content.canvas,
|
|
elements: [
|
|
{
|
|
id: 'image-1',
|
|
type: 'image',
|
|
left: 0,
|
|
top: 0,
|
|
width: 100,
|
|
height: 100,
|
|
rotate: 0,
|
|
fixedRatio: true,
|
|
src: 'gen_img_1',
|
|
colorMask: '#ff0000',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const imageController: SceneDataController<SlideContent> = {
|
|
...controller,
|
|
getSnapshot: () => imageContent,
|
|
};
|
|
|
|
const html = renderToStaticMarkup(
|
|
createElement(
|
|
MediaStageProvider,
|
|
{ value: 'stage-1' },
|
|
createElement(
|
|
TestSceneProvider,
|
|
{ controller: imageController as SceneDataController },
|
|
createElement(PlaybackScreenCanvas),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(html).toContain('src="blob:generated-image"');
|
|
expect(html).toContain('background-color:#ff0000');
|
|
});
|
|
|
|
it('classifies generated-image pending, failed, and retryable states', () => {
|
|
expect(getPlaybackImageState({ kind: 'pending' })).toBe('pending');
|
|
expect(getPlaybackImageState({ kind: 'placeholder' })).toBe('pending');
|
|
expect(getPlaybackImageState({ kind: 'failed', retryable: true })).toBe('failed');
|
|
expect(getPlaybackImageState({ kind: 'raw', value: 'logo.png' })).toBe('ready');
|
|
expect(getPlaybackImageState({ kind: 'url', url: 'blob:image' })).toBe('ready');
|
|
});
|
|
});
|