Files
open-design/apps/web/tests/components/export-failure-toast.test.ts
lefarcenandGitHub 7eb1e217be fix(web): align client error copy with the product error-copy doc (OPEND-2849) (#8620)
* fix(web): align export and update-check error copy with product (OPEND-2849)

- S26a: a generic export failure now shows the product title + body
  ("导出失败" / "本次导出未完成,请重新尝试。") through the same Toast
  message + details shape as S26c. fileViewer.exportFailed is split into
  fileViewer.exportFailedTitle / fileViewer.exportFailedDescription in all
  19 locales; the old key is removed.
- S31b: drop the sentence-final period from the title line of
  updater.dialogCheckFailed (zh-CN and 17 other locales; th had none).

* fix(web): never surface raw exporter errors on export failure (OPEND-2849)

Product copy S26: an export failure shows the S26a title + body by default;
only an already-localized, user-facing reason may replace it. Raw exporter
strings (HTTP status lines, renderer/daemon errors, internal exceptions)
no longer reach the toast.

- New export-failure-toast module: LocalizedExportError marks errors whose
  message comes from an i18n key; exportFailureToast passes only those
  through and falls back to S26a for everything else.
- Localized reasons now thrown as LocalizedExportError: PPTX daemon
  unreachable / PPTX not available, version image capture failed.
- Image export catch-all uses the same rule instead of echoing err.message.
- The original error object is unchanged, so console.warn and
  exportErrorCode analytics still see the raw message.

* fix(web): align disk-write failure and browser-open hint copy with product (OPEND-2849)

- chat.runError.localStorageFailureMessage now matches the product supplement
  row 13 verbatim in zh-CN ("…请确认有足够的剩余空间后,再重新尝试。"); the
  old "permission to save to the current folder" clause is dropped and all
  19 locales are retranslated to the new meaning.
- settings.amrActivationBrowserFailed (S32 browser not opened): half-width
  comma replaced by a full-width one in zh-CN and zh-TW; wording unchanged.

* fix(web): align S03/S04/S06/S09a/S15/S24/S28a/S31c/S32a error copy with product (OPEND-2849)

zh-CN copied verbatim from the product error-copy doc; all 19 locales translated.

- S04 pre-send signed out (balance gate dialog): "Open Design 尚未登录" + body.
- S06 pre-send insufficient allowance: owner dialog "可用额度不足" + body (no
  balance figure); member dialog "团队额度不足" + body. Benefits list and
  buttons unchanged.
- S15 mid-run allowance exhausted: the turn-anchored upgrade card's $0 state
  shows title + body, personal vs. member chosen by the billing audience
  ProjectView already resolves. Low-balance tier, amount and Upgrade button
  unchanged; the pre-send tail card keeps "现在无法开始新任务".
- S24a unfinished todos: "任务尚未完成" + "本次任务仍有 {n} 项内容未完成。" with the
  real unfinished count; S24c user stop: "任务已停止" + body. Buttons kept.
- S03 / S28a Home send failures: the optimistic create's rollback toast (the
  surface users actually see) and HomeView's inline block now map
  AMR_AUTH_REQUIRED to S03 and transport failures to S28a, as title + body.
- S31c update download failed (updater error code download-failed): update
  dialog shows "更新下载未完成" + body; Settings status shows the title.
- S32a browser not opened during sign-in: title + body via one shared
  component for the three sign-in surfaces; the sign-in page link is kept.
- S09a rate-limit auto retry: run_retry_attempted now carries
  failure_category in the SSE contract; a rate_limit retry row reads
  "模型服务请求繁忙" + body, other retries keep "正在重试".
2026-10-09 10:00:10 +00:00

46 lines
1.9 KiB
TypeScript

// OPEND-2849 / product copy S26: which export errors may replace the generic
// S26a "导出失败 / 本次导出未完成,请重新尝试。" copy. Only errors explicitly
// marked as carrying a localized, user-facing reason pass through.
import { describe, expect, it } from 'vitest';
import {
LocalizedExportError,
exportFailureToast,
genericExportFailedToast,
} from '../../src/components/export-failure-toast';
import { zhCN } from '../../src/i18n/locales/zh-CN';
import type { Dict } from '../../src/i18n/types';
const t = (key: keyof Dict) => zhCN[key];
const S26A = { message: '导出失败', details: '本次导出未完成,请重新尝试。', tone: 'error' };
describe('exportFailureToast', () => {
it('uses the S26a title + body for the generic case', () => {
expect(genericExportFailedToast(t)).toEqual(S26A);
});
it.each([
['an HTTP status line', new Error('html export request failed (500)')],
['an archive failure', new Error('archive request failed (502)')],
['an internal exception', new Error('Canvas is not available')],
['a desktop renderer failure', new Error('desktop PDF export unavailable (404)')],
['an error without a message', new Error('')],
['a non-Error rejection', 'boom'],
['undefined', undefined],
])('hides %s behind S26a', (_label, err) => {
expect(exportFailureToast(err, t)).toEqual(S26A);
});
it('passes through an already-localized, user-facing reason', () => {
const reason = zhCN['fileViewer.exportDaemonUnreachable'];
expect(exportFailureToast(new LocalizedExportError(reason), t)).toEqual({ message: reason, tone: 'error' });
});
it('keeps the original error object intact for diagnostics', () => {
const err = new LocalizedExportError(zhCN['fileViewer.exportPptxNa']);
expect(err).toBeInstanceOf(Error);
expect(err.message).toBe(zhCN['fileViewer.exportPptxNa']);
});
});