mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
fix(web): send annotations without the screenshot when preview capture fails (#4080)
The srcDoc foreignObject snapshot bridge legitimately fails on real-world artifacts (Chromium often refuses to rasterize <foreignObject> HTML loaded via <img>, surfacing as empty-render / snapshot-image-failed). The markup composer treated every capture failure as a dead end: it blocked the send with 'Could not capture the preview. Please try again.' even though retrying replays the same failure, so annotations on affected previews could never be sent at all. Only block when the annotation has no meaning without pixels (ink/box-only marks). A typed note or attached images still carry the user's intent, so those now send without the screenshot and the toolbar shows an honest status: the note went out, the pixels did not. New i18n key chat.annotationSentWithoutScreenshot across all 19 locales.
This commit is contained in:
@@ -642,7 +642,16 @@ export function PreviewDrawOverlay({
|
||||
let blob: Blob | null = null;
|
||||
const snap = await requestSnapshot();
|
||||
if (snap) blob = await compositeWithBackground(snap);
|
||||
if (!blob) {
|
||||
if (blob) {
|
||||
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
file = new File([blob], `drawing-${ts}.png`, { type: 'image/png' });
|
||||
} else if (!note.trim() && extraFiles.length === 0) {
|
||||
// The snapshot pipeline is best-effort — the srcDoc foreignObject
|
||||
// rasterizer legitimately fails on real-world artifacts (issue
|
||||
// #4064), and retrying replays the same failure. Only block when
|
||||
// the annotation has no meaning without pixels: ink/box-only marks
|
||||
// are pure bitmap. A typed note or attached images still carry the
|
||||
// user's intent, so those fall through and send without the shot.
|
||||
setCaptureWarning({
|
||||
action,
|
||||
message: captureViewport && !hasInk && !hasBox && !hasTarget
|
||||
@@ -651,9 +660,8 @@ export function PreviewDrawOverlay({
|
||||
});
|
||||
return;
|
||||
}
|
||||
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
file = new File([blob], `drawing-${ts}.png`, { type: 'image/png' });
|
||||
}
|
||||
const sentWithoutScreenshot = shouldCapture && !file;
|
||||
const kind = markKind();
|
||||
const result = await new Promise<{ ok: boolean; message?: string }>((resolve) => {
|
||||
let settled = false;
|
||||
@@ -686,7 +694,13 @@ export function PreviewDrawOverlay({
|
||||
return;
|
||||
}
|
||||
clearInk();
|
||||
setCaptureWarning(null);
|
||||
// Degraded sends keep the user honest about what the agent received:
|
||||
// the note went out, the pixels did not.
|
||||
setCaptureWarning(
|
||||
sentWithoutScreenshot
|
||||
? { action, message: t('chat.annotationSentWithoutScreenshot') }
|
||||
: null,
|
||||
);
|
||||
setNote('');
|
||||
setExtraFiles([]);
|
||||
setPreviewIndex(null);
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const ar: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'هناك مهمة قيد التشغيل حاليًا',
|
||||
'chat.annotationPreviewMissing': 'تعذّر التقاط المعاينة. يُرجى المحاولة مرة أخرى.',
|
||||
'chat.annotationPreviewMissingInk': 'تعذّر التقاط المعاينة. حاول مرة أخرى لتجنّب إرسال الحبر فقط.',
|
||||
'chat.annotationSentWithoutScreenshot': 'تعذّر التقاط المعاينة. تم إرسال التعليق التوضيحي بدون لقطة شاشة.',
|
||||
'chat.annotationTimeout': 'انتهت مهلة إرسال التعليق التوضيحي. يُرجى المحاولة مرة أخرى.',
|
||||
'chat.annotationFailed': 'فشل إرسال التعليق التوضيحي. يُرجى المحاولة مرة أخرى.',
|
||||
'chat.annotationProjectCreateFailed': 'تعذّر إنشاء مشروع، لذا لم يتم إرسال التعليق التوضيحي.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const de: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'Eine Aufgabe wird gerade ausgeführt',
|
||||
'chat.annotationPreviewMissing': 'Vorschau konnte nicht erfasst werden. Bitte versuche es erneut.',
|
||||
'chat.annotationPreviewMissingInk': 'Vorschau konnte nicht erfasst werden. Versuche es erneut, um nicht nur die Tinte zu senden.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Vorschau konnte nicht erfasst werden. Die Anmerkung wurde ohne Screenshot gesendet.',
|
||||
'chat.annotationTimeout': 'Zeitüberschreitung beim Senden der Annotation. Bitte versuche es erneut.',
|
||||
'chat.annotationFailed': 'Senden der Anmerkung fehlgeschlagen. Bitte versuche es erneut.',
|
||||
'chat.annotationProjectCreateFailed': 'Es konnte kein Projekt erstellt werden, daher wurde die Anmerkung nicht gesendet.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const en: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'A task is currently running',
|
||||
'chat.annotationPreviewMissing': 'Could not capture the preview. Please try again.',
|
||||
'chat.annotationPreviewMissingInk': 'Could not capture the preview. Try again to avoid sending only ink.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Could not capture the preview. The annotation was sent without a screenshot.',
|
||||
'chat.annotationTimeout': 'Annotation send timed out. Please try again.',
|
||||
'chat.annotationFailed': 'Annotation send failed. Please try again.',
|
||||
'chat.annotationProjectCreateFailed': 'Could not create a project, so the annotation was not sent.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const esES: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'Hay una tarea en ejecución',
|
||||
'chat.annotationPreviewMissing': 'No se pudo capturar la vista previa. Inténtalo de nuevo.',
|
||||
'chat.annotationPreviewMissingInk': 'No se pudo capturar la vista previa. Inténtalo de nuevo para evitar enviar solo la anotación.',
|
||||
'chat.annotationSentWithoutScreenshot': 'No se pudo capturar la vista previa. La anotación se envió sin captura de pantalla.',
|
||||
'chat.annotationTimeout': 'Se agotó el tiempo de envío de la anotación. Inténtalo de nuevo.',
|
||||
'chat.annotationFailed': 'Error al enviar la anotación. Inténtalo de nuevo.',
|
||||
'chat.annotationProjectCreateFailed': 'No se pudo crear un proyecto, por lo que la anotación no se envió.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const fa: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'در حال حاضر یک وظیفه در حال اجراست',
|
||||
'chat.annotationPreviewMissing': 'امکان ثبت پیشنمایش وجود نداشت. لطفاً دوباره تلاش کنید.',
|
||||
'chat.annotationPreviewMissingInk': 'امکان ثبت پیشنمایش وجود نداشت. برای جلوگیری از ارسال صرفِ ترسیم، دوباره تلاش کنید.',
|
||||
'chat.annotationSentWithoutScreenshot': 'پیشنمایش قابل ثبت نبود. حاشیهنویسی بدون اسکرینشات ارسال شد.',
|
||||
'chat.annotationTimeout': 'زمان ارسال حاشیهنویسی به پایان رسید. لطفاً دوباره تلاش کنید.',
|
||||
'chat.annotationFailed': 'ارسال حاشیهنویسی ناموفق بود. لطفاً دوباره تلاش کنید.',
|
||||
'chat.annotationProjectCreateFailed': 'امکان ایجاد پروژه وجود نداشت، بنابراین حاشیهنویسی ارسال نشد.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const fr: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'Une tâche est en cours d\'exécution',
|
||||
'chat.annotationPreviewMissing': 'Impossible de capturer l\'aperçu. Veuillez réessayer.',
|
||||
'chat.annotationPreviewMissingInk': 'Impossible de capturer l\'aperçu. Réessayez pour éviter de n\'envoyer que l\'annotation.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Impossible de capturer l\'aperçu. L\'annotation a été envoyée sans capture d\'écran.',
|
||||
'chat.annotationTimeout': 'L\'envoi de l\'annotation a expiré. Veuillez réessayer.',
|
||||
'chat.annotationFailed': 'L\'envoi de l\'annotation a échoué. Veuillez réessayer.',
|
||||
'chat.annotationProjectCreateFailed': 'Impossible de créer un projet, l\'annotation n\'a donc pas été envoyée.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const hu: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'Jelenleg fut egy feladat',
|
||||
'chat.annotationPreviewMissing': 'Nem sikerült rögzíteni az előnézetet. Kérjük, próbálja újra.',
|
||||
'chat.annotationPreviewMissingInk': 'Nem sikerült rögzíteni az előnézetet. Próbálja újra, hogy ne csak a tinta legyen elküldve.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Nem sikerült rögzíteni az előnézetet. A jegyzet képernyőkép nélkül lett elküldve.',
|
||||
'chat.annotationTimeout': 'Az annotáció küldése időtúllépés miatt megszakadt. Kérjük, próbálja újra.',
|
||||
'chat.annotationFailed': 'Az annotáció küldése nem sikerült. Kérjük, próbálja újra.',
|
||||
'chat.annotationProjectCreateFailed': 'Nem sikerült projektet létrehozni, ezért az annotáció nem lett elküldve.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const id: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'A task is currently running',
|
||||
'chat.annotationPreviewMissing': 'Could not capture the preview. Please try again.',
|
||||
'chat.annotationPreviewMissingInk': 'Could not capture the preview. Try again to avoid sending only ink.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Tidak dapat menangkap pratinjau. Anotasi dikirim tanpa tangkapan layar.',
|
||||
'chat.annotationTimeout': 'Annotation send timed out. Please try again.',
|
||||
'chat.annotationFailed': 'Annotation send failed. Please try again.',
|
||||
'chat.annotationProjectCreateFailed': 'Could not create a project, so the annotation was not sent.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const it: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'È in esecuzione un\'attività',
|
||||
'chat.annotationPreviewMissing': 'Impossibile acquisire l\'anteprima. Riprova.',
|
||||
'chat.annotationPreviewMissingInk': 'Impossibile acquisire l\'anteprima. Riprova per evitare di inviare solo l\'inchiostro.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Impossibile catturare l\'anteprima. L\'annotazione è stata inviata senza screenshot.',
|
||||
'chat.annotationTimeout': 'Invio dell\'annotazione scaduto. Riprova.',
|
||||
'chat.annotationFailed': 'Invio dell\'annotazione non riuscito. Riprova.',
|
||||
'chat.annotationProjectCreateFailed': 'Impossibile creare un progetto, quindi l\'annotazione non è stata inviata.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const ja: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'タスクが実行中です',
|
||||
'chat.annotationPreviewMissing': 'プレビューをキャプチャできませんでした。もう一度お試しください。',
|
||||
'chat.annotationPreviewMissingInk': 'プレビューをキャプチャできませんでした。インクのみの送信を避けるため、もう一度お試しください。',
|
||||
'chat.annotationSentWithoutScreenshot': 'プレビューをキャプチャできませんでした。注釈はスクリーンショットなしで送信されました。',
|
||||
'chat.annotationTimeout': '注釈の送信がタイムアウトしました。もう一度お試しください。',
|
||||
'chat.annotationFailed': '注釈の送信に失敗しました。もう一度お試しください。',
|
||||
'chat.annotationProjectCreateFailed': 'プロジェクトを作成できなかったため、注釈は送信されませんでした。',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const ko: Dict = {
|
||||
'chat.annotationSendDisabledReason': '현재 작업이 실행 중입니다',
|
||||
'chat.annotationPreviewMissing': '미리보기를 캡처할 수 없습니다. 다시 시도해 주세요.',
|
||||
'chat.annotationPreviewMissingInk': '미리보기를 캡처할 수 없습니다. 잉크만 전송되지 않도록 다시 시도해 주세요.',
|
||||
'chat.annotationSentWithoutScreenshot': '미리보기를 캡처하지 못했습니다. 주석은 스크린샷 없이 전송되었습니다.',
|
||||
'chat.annotationTimeout': '주석 전송 시간이 초과되었습니다. 다시 시도해 주세요.',
|
||||
'chat.annotationFailed': '주석 전송에 실패했습니다. 다시 시도해 주세요.',
|
||||
'chat.annotationProjectCreateFailed': '프로젝트를 생성할 수 없어 주석이 전송되지 않았습니다.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const pl: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'Zadanie jest obecnie w toku',
|
||||
'chat.annotationPreviewMissing': 'Nie udało się przechwycić podglądu. Spróbuj ponownie.',
|
||||
'chat.annotationPreviewMissingInk': 'Nie udało się przechwycić podglądu. Spróbuj ponownie, aby nie wysłać samego rysunku.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Nie udało się przechwycić podglądu. Adnotacja została wysłana bez zrzutu ekranu.',
|
||||
'chat.annotationTimeout': 'Przekroczono limit czasu wysyłania adnotacji. Spróbuj ponownie.',
|
||||
'chat.annotationFailed': 'Nie udało się wysłać adnotacji. Spróbuj ponownie.',
|
||||
'chat.annotationProjectCreateFailed': 'Nie udało się utworzyć projektu, więc adnotacja nie została wysłana.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const ptBR: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'Uma tarefa está em execução no momento',
|
||||
'chat.annotationPreviewMissing': 'Não foi possível capturar a prévia. Tente novamente.',
|
||||
'chat.annotationPreviewMissingInk': 'Não foi possível capturar a prévia. Tente novamente para evitar enviar apenas o traçado.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Não foi possível capturar a pré-visualização. A anotação foi enviada sem captura de tela.',
|
||||
'chat.annotationTimeout': 'O envio da anotação expirou. Tente novamente.',
|
||||
'chat.annotationFailed': 'Falha ao enviar a anotação. Tente novamente.',
|
||||
'chat.annotationProjectCreateFailed': 'Não foi possível criar um projeto, então a anotação não foi enviada.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const ru: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'Сейчас выполняется задача',
|
||||
'chat.annotationPreviewMissing': 'Не удалось сделать предпросмотр. Попробуйте ещё раз.',
|
||||
'chat.annotationPreviewMissingInk': 'Не удалось сделать предпросмотр. Попробуйте ещё раз, чтобы не отправить только рукописные пометки.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Не удалось захватить предпросмотр. Аннотация отправлена без скриншота.',
|
||||
'chat.annotationTimeout': 'Истекло время отправки аннотации. Попробуйте ещё раз.',
|
||||
'chat.annotationFailed': 'Не удалось отправить аннотацию. Попробуйте ещё раз.',
|
||||
'chat.annotationProjectCreateFailed': 'Не удалось создать проект, поэтому аннотация не была отправлена.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const th: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'มีงานกำลังทำงานอยู่',
|
||||
'chat.annotationPreviewMissing': 'ไม่สามารถจับภาพตัวอย่างได้ โปรดลองอีกครั้ง',
|
||||
'chat.annotationPreviewMissingInk': 'ไม่สามารถจับภาพตัวอย่างได้ ลองอีกครั้งเพื่อหลีกเลี่ยงการส่งเฉพาะลายเส้น',
|
||||
'chat.annotationSentWithoutScreenshot': 'ไม่สามารถจับภาพตัวอย่างได้ ส่งคำอธิบายประกอบโดยไม่มีภาพหน้าจอแล้ว',
|
||||
'chat.annotationTimeout': 'การส่งคำอธิบายประกอบหมดเวลา โปรดลองอีกครั้ง',
|
||||
'chat.annotationFailed': 'การส่งคำอธิบายประกอบล้มเหลว โปรดลองอีกครั้ง',
|
||||
'chat.annotationProjectCreateFailed': 'ไม่สามารถสร้างโปรเจกต์ได้ จึงไม่ได้ส่งคำอธิบายประกอบ',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const tr: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'Şu anda bir görev çalışıyor',
|
||||
'chat.annotationPreviewMissing': 'Önizleme yakalanamadı. Lütfen tekrar deneyin.',
|
||||
'chat.annotationPreviewMissingInk': 'Önizleme yakalanamadı. Yalnızca çizim göndermemek için tekrar deneyin.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Önizleme yakalanamadı. Açıklama ekran görüntüsü olmadan gönderildi.',
|
||||
'chat.annotationTimeout': 'Açıklama gönderimi zaman aşımına uğradı. Lütfen tekrar deneyin.',
|
||||
'chat.annotationFailed': 'Açıklama gönderimi başarısız oldu. Lütfen tekrar deneyin.',
|
||||
'chat.annotationProjectCreateFailed': 'Bir proje oluşturulamadığından açıklama gönderilmedi.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const uk: Dict = {
|
||||
'chat.annotationSendDisabledReason': 'Наразі виконується завдання',
|
||||
'chat.annotationPreviewMissing': 'Не вдалося захопити попередній перегляд. Спробуйте ще раз.',
|
||||
'chat.annotationPreviewMissingInk': 'Не вдалося захопити попередній перегляд. Спробуйте ще раз, щоб не надіслати лише рукописні позначки.',
|
||||
'chat.annotationSentWithoutScreenshot': 'Не вдалося захопити попередній перегляд. Анотацію надіслано без знімка екрана.',
|
||||
'chat.annotationTimeout': 'Час очікування надсилання анотації вичерпано. Спробуйте ще раз.',
|
||||
'chat.annotationFailed': 'Не вдалося надіслати анотацію. Спробуйте ще раз.',
|
||||
'chat.annotationProjectCreateFailed': 'Не вдалося створити проєкт, тому анотацію не надіслано.',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const zhCN: Dict = {
|
||||
'chat.annotationSendDisabledReason': '当前正有任务在执行',
|
||||
'chat.annotationPreviewMissing': '无法截到下面的预览,请重试',
|
||||
'chat.annotationPreviewMissingInk': '无法截到下面的预览,请重试,避免只附带笔迹',
|
||||
'chat.annotationSentWithoutScreenshot': '未能截取预览,已发送标注(不含截图)。',
|
||||
'chat.annotationTimeout': '标注发送超时,请重试',
|
||||
'chat.annotationFailed': '标注发送失败,请重试',
|
||||
'chat.annotationProjectCreateFailed': '无法创建项目,标注未发送',
|
||||
|
||||
@@ -1505,6 +1505,7 @@ export const zhTW: Dict = {
|
||||
'chat.annotationSendDisabledReason': '目前正有任務執行中',
|
||||
'chat.annotationPreviewMissing': '無法截到下方預覽,請重試',
|
||||
'chat.annotationPreviewMissingInk': '無法截到下方預覽,請重試,避免只附帶筆跡',
|
||||
'chat.annotationSentWithoutScreenshot': '未能擷取預覽,已傳送標註(不含截圖)。',
|
||||
'chat.annotationTimeout': '標註傳送逾時,請重試',
|
||||
'chat.annotationFailed': '標註傳送失敗,請重試',
|
||||
'chat.annotationProjectCreateFailed': '無法建立專案,因此未送出標註。',
|
||||
|
||||
@@ -2052,6 +2052,7 @@ export interface Dict {
|
||||
'chat.annotationSendDisabledReason': string;
|
||||
'chat.annotationPreviewMissing': string;
|
||||
'chat.annotationPreviewMissingInk': string;
|
||||
'chat.annotationSentWithoutScreenshot': string;
|
||||
'chat.annotationTimeout': string;
|
||||
'chat.annotationFailed': string;
|
||||
'chat.annotationProjectCreateFailed': string;
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
// Issue #4064: the srcDoc foreignObject snapshot bridge legitimately fails on
|
||||
// real-world artifacts (Chromium often refuses to rasterize <foreignObject>
|
||||
// HTML loaded via <img>). A failed screenshot must not dead-end an annotation
|
||||
// that carries its own meaning without pixels (typed note / attached images) —
|
||||
// the retry warning is a dead end because retrying the same pipeline fails the
|
||||
// same way. Ink/box-only annotations still block: without the bitmap there is
|
||||
// nothing to send.
|
||||
|
||||
import { cleanup, fireEvent, render, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { PreviewDrawOverlay } from '../../src/components/PreviewDrawOverlay';
|
||||
import { requestPreviewSnapshot } from '../../src/runtime/exports';
|
||||
|
||||
vi.mock('../../src/runtime/exports', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../../src/runtime/exports')>();
|
||||
return {
|
||||
...actual,
|
||||
// The snapshot bridge fails — the shape every foreignObject failure mode
|
||||
// (empty-render, snapshot image failed, timeout) collapses into.
|
||||
requestPreviewSnapshot: vi.fn(async () => null),
|
||||
};
|
||||
});
|
||||
|
||||
let restoreRect: (() => void) | null = null;
|
||||
|
||||
beforeEach(() => {
|
||||
// jsdom reports 0x0 client rects, which would collapse the drawn selection
|
||||
// box into nothing. Give the ink canvas a real-looking rect so pointer
|
||||
// events produce a valid normalized box, like in a browser.
|
||||
const rectSpy = vi
|
||||
.spyOn(HTMLCanvasElement.prototype, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
x: 0,
|
||||
y: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 320,
|
||||
height: 200,
|
||||
right: 320,
|
||||
bottom: 200,
|
||||
toJSON: () => ({}),
|
||||
} as DOMRect);
|
||||
restoreRect = () => rectSpy.mockRestore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
restoreRect?.();
|
||||
restoreRect = null;
|
||||
vi.mocked(requestPreviewSnapshot).mockClear();
|
||||
});
|
||||
|
||||
function drawSelectionBox(canvas: HTMLCanvasElement) {
|
||||
fireEvent.pointerDown(canvas, { clientX: 40, clientY: 30, pointerId: 1 });
|
||||
fireEvent.pointerMove(canvas, { clientX: 220, clientY: 150, pointerId: 1 });
|
||||
fireEvent.pointerUp(canvas, { clientX: 220, clientY: 150, pointerId: 1 });
|
||||
}
|
||||
|
||||
describe('PreviewDrawOverlay capture fallback (issue #4064)', () => {
|
||||
it('sends a box annotation with a typed note even when the snapshot fails', async () => {
|
||||
const annotation = vi.fn((event: Event) => {
|
||||
const detail = (event as CustomEvent<{ ack?: (result: { ok: boolean }) => void }>).detail;
|
||||
detail.ack?.({ ok: true });
|
||||
});
|
||||
window.addEventListener('opendesign:annotation', annotation);
|
||||
|
||||
try {
|
||||
const { container, getByRole, getByText } = render(
|
||||
<PreviewDrawOverlay active>
|
||||
<iframe title="srcdoc" data-od-render-mode="srcdoc" />
|
||||
</PreviewDrawOverlay>,
|
||||
);
|
||||
|
||||
const canvas = container.querySelector<HTMLCanvasElement>('canvas');
|
||||
expect(canvas).toBeTruthy();
|
||||
drawSelectionBox(canvas!);
|
||||
|
||||
const input = container.querySelector<HTMLInputElement>('.preview-draw-note-input');
|
||||
expect(input).toBeTruthy();
|
||||
fireEvent.change(input!, { target: { value: 'This section is missing its bar chart.' } });
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'Send' }));
|
||||
|
||||
await waitFor(() => expect(annotation).toHaveBeenCalledTimes(1));
|
||||
expect(annotation.mock.calls[0]?.[0]).toMatchObject({
|
||||
detail: expect.objectContaining({
|
||||
action: 'send',
|
||||
note: 'This section is missing its bar chart.',
|
||||
file: null,
|
||||
}),
|
||||
});
|
||||
// The user is told the annotation went out without its screenshot.
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getByText('Could not capture the preview. The annotation was sent without a screenshot.'),
|
||||
).toBeTruthy(),
|
||||
);
|
||||
} finally {
|
||||
window.removeEventListener('opendesign:annotation', annotation);
|
||||
}
|
||||
});
|
||||
|
||||
it('still blocks a box-only annotation with no note when the snapshot fails', async () => {
|
||||
const annotation = vi.fn();
|
||||
window.addEventListener('opendesign:annotation', annotation);
|
||||
|
||||
try {
|
||||
const { container, getByRole, getByText } = render(
|
||||
<PreviewDrawOverlay active>
|
||||
<iframe title="srcdoc" data-od-render-mode="srcdoc" />
|
||||
</PreviewDrawOverlay>,
|
||||
);
|
||||
|
||||
const canvas = container.querySelector<HTMLCanvasElement>('canvas');
|
||||
expect(canvas).toBeTruthy();
|
||||
drawSelectionBox(canvas!);
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'Send' }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getByText('Could not capture the preview. Try again to avoid sending only ink.'),
|
||||
).toBeTruthy(),
|
||||
);
|
||||
expect(annotation).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
window.removeEventListener('opendesign:annotation', annotation);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user