import { afterEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen } from '@testing-library/react';
import { ArtifactPanel } from '@/components/file-preview/ArtifactPanel';
import { ARTIFACT_PANEL_DEFAULT_WIDTH, useArtifactPanel } from '@/stores/artifact-panel';
import type { GeneratedFile } from '@/lib/generated-files';
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (_key: string, defaultValue?: string) => defaultValue ?? '',
}),
}));
vi.mock('@/components/file-preview/FilePreviewBody', () => ({
FilePreviewBody: ({ file, mode }: { file: { fileName: string; filePath: string }; mode: string }) => (
{mode}
{file.fileName}
{file.filePath}
),
}));
vi.mock('@/components/file-preview/WorkspaceBrowserBody', () => ({
WorkspaceBrowserBody: () => ,
}));
function makeGeneratedFile(overrides: Partial = {}): GeneratedFile {
return {
filePath: '/tmp/test_example.py',
fileName: 'test_example.py',
ext: '.py',
mimeType: 'text/x-python',
contentType: 'code',
action: 'modified',
fullContent: 'print("hello")\n',
lastSeenIndex: 1,
...overrides,
};
}
afterEach(() => {
useArtifactPanel.setState({
open: false,
tab: 'changes',
focusedFile: null,
widthPct: ARTIFACT_PANEL_DEFAULT_WIDTH,
});
});
describe('ArtifactPanel', () => {
it('keeps chat-opened SKILL.md focused when switching to changes', () => {
useArtifactPanel.setState({
open: true,
tab: 'changes',
focusedFile: {
filePath: '~/.openclaw/skills/open-baidu/SKILL.md',
fileName: 'SKILL.md',
ext: '.md',
mimeType: 'text/markdown',
contentType: 'document',
},
widthPct: ARTIFACT_PANEL_DEFAULT_WIDTH,
});
render(
,
);
const previewBodies = screen.getAllByTestId('file-preview-body');
expect(previewBodies[0]).toHaveTextContent('SKILL.md');
expect(previewBodies[0]).toHaveTextContent('~/.openclaw/skills/open-baidu/SKILL.md');
expect(screen.queryByText('test_example.py')).not.toBeInTheDocument();
});
it('keeps the selected preview file after visiting the workspace tab', () => {
useArtifactPanel.setState({
open: true,
tab: 'preview',
focusedFile: {
filePath: '~/.openclaw/skills/open-xueqiu/SKILL.md',
fileName: 'SKILL.md',
ext: '.md',
mimeType: 'text/markdown',
contentType: 'document',
},
widthPct: ARTIFACT_PANEL_DEFAULT_WIDTH,
});
render(
,
);
expect(screen.getAllByTestId('file-preview-body')[1]).toHaveTextContent('SKILL.md');
fireEvent.click(screen.getByRole('button', { name: 'Workspace' }));
expect(screen.getByTestId('workspace-browser')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Preview' }));
expect(screen.getAllByTestId('file-preview-body')[1]).toHaveTextContent('SKILL.md');
expect(screen.queryByText('No file selected')).not.toBeInTheDocument();
});
it('keeps panel tab buttons above iframe previews so changes stays clickable', () => {
useArtifactPanel.setState({
open: true,
tab: 'preview',
focusedFile: {
filePath: '/tmp/demo.html',
fileName: 'demo.html',
ext: '.html',
mimeType: 'text/html',
contentType: 'document',
},
widthPct: ARTIFACT_PANEL_DEFAULT_WIDTH,
});
render(
,
);
const changesButton = screen.getByTestId('artifact-panel-tab-changes');
expect(changesButton.className).toContain('z-40');
expect(changesButton.parentElement?.parentElement?.className).toContain('z-30');
fireEvent.pointerDown(changesButton, { button: 0 });
expect(screen.getAllByTestId('file-preview-body')[0]).toHaveTextContent('diff');
});
});