import { buildImageActionTarget, formatLogTime, ImageOutput } from './image-output';
import { I18nProvider } from '@/lib/i18n';
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { renderToStaticMarkup } from 'react-dom/server';
const noop = () => {};
const buttonContentPattern = '[\\s\\S]*?';
function renderImageOutput(viewMode: 'grid' | number): string {
return renderToStaticMarkup(
);
}
describe('ImageOutput result actions', () => {
it('does not show fixed preview timing or dimensions before real image metadata is known', () => {
const html = renderImageOutput(0);
assert.match(html, /第 1 张 \/ 共 2 张/);
assert.doesNotMatch(html, /预计 8-12 秒/);
assert.doesNotMatch(html, /1024 x 768/);
});
it('uses the localized generated-image alt text when a caller does not override it', () => {
const html = renderImageOutput(0);
assert.match(html, /alt="生成图片输出"/);
assert.doesNotMatch(html, /Generated image output/);
});
it('formats activity timestamps with the selected app locale and preserves invalid diagnostic values', () => {
const timestamp = '2026-05-31T12:15:30.000Z';
assert.equal(formatLogTime(timestamp, 'zh-CN'), new Date(timestamp).toLocaleTimeString('zh-CN'));
assert.equal(formatLogTime(timestamp, 'en-US'), new Date(timestamp).toLocaleTimeString('en-US'));
assert.equal(formatLogTime('not-a-timestamp', 'zh-CN'), 'not-a-timestamp');
});
it('uses the no-image state in the preview header before generation', () => {
const html = renderToStaticMarkup(
);
assert.match(html, /还没有生成图像/);
assert.match(html, /写下灵感后点击生成/);
assert.match(html, /workbench-panel[^"]*h-fit xl:h-full[\s\S]*preview-gallery-board/);
assert.match(html, /min-h-\[220px\] shrink-0 sm:min-h-\[248px\] lg:min-h-\[272px\] xl:min-h-0 xl:flex-1/);
assert.doesNotMatch(html, /min-h-\[20rem\]/);
assert.match(html, /max-w-\[30rem\]/);
assert.match(html, /items-center justify-center/);
assert.match(html, /text-center/);
assert.doesNotMatch(html, /2xl:max-w-\[34rem\]/);
assert.doesNotMatch(html, /photo-paper relative flex aspect-\[8\/5\]/);
assert.doesNotMatch(html, /max-w-\[780px\] flex-col justify-between/);
assert.doesNotMatch(html, /
]*disabled=""[^>]*>${buttonContentPattern}${action}${buttonContentPattern}`
)
);
}
});
it('keeps the full preview stage height after images exist', () => {
const html = renderImageOutput(0);
assert.match(html, /workbench-panel[^"]*h-full[\s\S]*preview-gallery-board/);
assert.match(html, /min-h-\[300px\] flex-1 sm:min-h-\[420px\] lg:min-h-\[520px\]/);
assert.doesNotMatch(html, /min-h-\[220px\] shrink-0 sm:min-h-\[248px\] lg:min-h-\[272px\]/);
});
it('uses an edit reference placeholder instead of the sample image in edit mode', () => {
const html = renderToStaticMarkup(
);
assert.match(html, /先放入参考图/);
assert.match(html, /等待参考图/);
assert.doesNotMatch(html, /min-h-\[18rem\]/);
assert.match(html, /max-w-\[30rem\]/);
assert.doesNotMatch(html, /photo-paper relative flex aspect-\[4\/3\]/);
assert.doesNotMatch(html, /灵感样张/);
});
it('shows the edit reference image before an edit result exists', () => {
const html = renderToStaticMarkup(
);
assert.match(html, /参考图已就绪/);
assert.match(html, /图生图参考图预览/);
assert.match(html, /参考图/);
assert.doesNotMatch(html, /灵感样张/);
});
it('requires an explicit selection before single-image actions in the multi-image grid view', () => {
const html = renderImageOutput('grid');
assert.match(html, /aria-label="选择第 1 张图片"/);
assert.match(html, /aria-label="选择第 2 张图片"/);
assert.match(html, /请选择一张图片/);
assert.doesNotMatch(html, /第 1 张 \/ 共 2 张/);
for (const action of ['继续编辑', '对比', '下载', '分享']) {
assert.match(
html,
new RegExp(
``
)
);
}
});
it('keeps single-image actions available after selecting a multi-image result', () => {
const html = renderImageOutput(1);
assert.match(html, /第 2 张 \/ 共 2 张/);
for (const action of ['继续编辑', '对比', '下载', '分享']) {
assert.match(
html,
new RegExp(``)
);
assert.doesNotMatch(
html,
new RegExp(
``
)
);
}
});
it('builds selected image action targets with storage mode', () => {
assert.deepEqual(
buildImageActionTarget({
path: 'data:image/png;base64,selected',
filename: 'selected.png',
storageMode: 'indexeddb'
}),
{ filename: 'selected.png', storageMode: 'indexeddb' }
);
assert.deepEqual(
buildImageActionTarget({
path: '/api/image/fs.png',
filename: 'fs.png'
}),
{ filename: 'fs.png' }
);
assert.equal(buildImageActionTarget(null), null);
});
it('keeps compare disabled for single-image results', () => {
const html = renderToStaticMarkup(
);
assert.match(
html,
new RegExp(``)
);
});
it('enables compare for single-image results with a previous history image', () => {
const html = renderToStaticMarkup(
);
assert.match(
html,
new RegExp(``)
);
});
it('uses user-facing generation activity copy for the activity entry', () => {
const html = renderToStaticMarkup(
);
assert.match(html, /查看动态/);
assert.doesNotMatch(html, /查看日志/);
});
it('shows an explicit failure state with retry near the canvas', () => {
const html = renderToStaticMarkup(
);
assert.match(html, /生成失败/);
assert.match(html, /上游或 API 中转站异常。请稍后重试。/);
assert.match(html, new RegExp(``));
assert.doesNotMatch(html, /灵感样张/);
});
});