Task 11: alle sichtbaren Texte von community/, pages/home-page.ts, pages/community-page.ts, pages/cards-page.ts und pages/format.ts
(Texte, aria-label, title, Platzhalter, Alt-Texte, Rückfragen, Meldungen) gehen durch t()/msg(); en.ts bekommt 82 Einträge (214 → 296).
- Konstanten bleiben deutsche Strings per msg() und gehen beim Anzeigen durch t() (publish.ts, publish-dialog.ts, gallery.ts und die Seiten);
geworfene Fehler tragen weiter den deutschen Text, damit die Vergleiche gehen. Funktionen, die Text für Nutzer liefern, übersetzen beim
Zurückgeben (validatePublishForm, publishFailureText, formatDate, reportsLabel). Template-Literale sind Platzhalter geworden ({max},
{reason}, {name}, {title}, {query}, {count}); der deutsche Text bleibt Zeichen für Zeichen derselbe.
- API-Fehler: jeder ApiError wird mit apiErrorText() angezeigt (Veröffentlichen-Dialog, Detailseite, Meine Karten), nach dem Code des Dienstes
statt nach dem deutschen Servertext. Logik, die err.message vergleicht (publishCard, 404 beim Zurückziehen), bleibt unverändert.
- Bekannte Reste aus Task 10 behoben: LOGIN_NEEDED ging in publish-dialog.ts, community-page.ts und cards-page.ts ohne t() an die Oberfläche;
die Meldungen zu Bild lesen/Pixelgrenze/fehlendem Logo (cards-page.ts, GERMAN_MESSAGES in publish-dialog.ts) erscheinen jetzt übersetzt;
die Zahl der Meldungen in der Logo-Infozeile („3 reports“).
- pages/format.ts: formatDate liefert Deutsch dd.mm.yyyy und Englisch „Oct 4, 2026“ (Intl.DateTimeFormat en-US), reportsLabel „1 report“ /
„3 reports“. „Herausgeber“ heißt als Bank „Issuer / bank“.
- logos/prepare-logo.ts: der Helfer problem() ist weg, die sechs Stellen schreiben new LogoError(t(MELDUNG)) selbst (gleiches Verhalten);
so sieht der neue Prüfer unten jede Verwendung einer Konstante.
- i18n/coverage.test.ts: Die Heuristik für deutsche Literale deckt jetzt die ganze Oberfläche ab (account/, card/, community/, editor/, logos/,
pages/, shell/, main.ts, i18n/api-messages.ts). Sie erkennt zusätzlich Wörter mit Klammer („Ausgeblendet (gemeldet)“) und nimmt rel und
Namen in Klammerschreibweise (headers['X-Aircard-Jwt']) aus. Acht begründete Ausnahmen kommen dazu (zwei AppwriteException-Meldungen,
vier interne Logo-Fehler, der Sprachknopf in der Zielsprache). Neu: ein Prüfer über den Syntaxbaum, der verlangt, dass jede Konstante
mit msg() nur in t(), new Error(), Vergleichen, case und Listen/Mengen vorkommt (auch über Importe und Umbenennungen); ohne Ausnahmen.
- Tests: format.test.ts (beide Sprachen), Englisch-Blöcke in publish-dialog, gallery-grid, home-page, community-page, cards-page und
logo-info (Texte, Fehlerwege, „Publishing failed – This card is already published.“, kein deutscher Wörterbuchtext auf der Seite). Die
Hilfen texts()/germanLeft() liegen jetzt in i18n/english-check.ts (vorher in english-ui.test.ts). Der bestehende Englisch-Test in
cards-page.test.ts sucht den Knopf „Download“ statt „Herunterladen“.
- Vitest 53 Dateien, 1812 Tests grün (vorher 1779), tsc und Build sauber.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
347 lines
18 KiB
TypeScript
347 lines
18 KiB
TypeScript
// Stichproben der englischen Oberfläche von Konto, Editor und Passwort-Seite (Task 10): Die Texte erscheinen bei Englisch, und was an
|
||
// Texten hängt (Abgleich der Passwörter, übersetzte Fehler, Beschriftung des wiederverwendeten Login-Dialogs) arbeitet in beiden
|
||
// Sprachen gleich. Wer Module mit `vi.resetModules()` frisch lädt, stellt die Sprache an der frischen Kopie von i18n.ts ein.
|
||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||
import { germanLeft, texts } from './english-check';
|
||
|
||
const mocks = vi.hoisted(() => ({
|
||
acct: {
|
||
get: vi.fn(),
|
||
create: vi.fn(),
|
||
createEmailPasswordSession: vi.fn(),
|
||
deleteSession: vi.fn(),
|
||
createRecovery: vi.fn(),
|
||
updateRecovery: vi.fn(),
|
||
},
|
||
getCard: vi.fn(),
|
||
saveCard: vi.fn(),
|
||
getCurrentUser: vi.fn(),
|
||
requireLogin: vi.fn(),
|
||
decodeImage: vi.fn(),
|
||
loadCardAssets: vi.fn(),
|
||
exportCard: vi.fn(),
|
||
getGalleryCard: vi.fn(),
|
||
}));
|
||
|
||
vi.mock('../account/appwrite', () => ({ account: mocks.acct, client: {}, tables: {}, storage: {}, IDS: {}, APPWRITE_ENDPOINT: '' }));
|
||
|
||
beforeEach(() => {
|
||
vi.resetAllMocks();
|
||
});
|
||
|
||
const fail = (code: number, type: string, message = '') => Object.assign(new Error(message), { code, type });
|
||
const submit = (form: HTMLFormElement | null) => form!.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
||
|
||
/** Frische Kopie von i18n.ts in der gewünschten Sprache (die Module danach laden dieselbe Kopie). */
|
||
async function freshI18n(lang: 'de' | 'en') {
|
||
vi.resetModules();
|
||
const i18n = await import('./i18n');
|
||
i18n.setLang(lang, { persist: false });
|
||
return i18n;
|
||
}
|
||
|
||
describe('Fehlertexte und Standardname auf Englisch', () => {
|
||
it('errorMessage und validatePassword übersetzen beim Zurückgeben', async () => {
|
||
await freshI18n('en');
|
||
const { errorMessage } = await import('../account/errors');
|
||
const { validatePassword } = await import('../account/auth');
|
||
expect(errorMessage(fail(401, 'user_invalid_credentials'), 'login')).toBe('Email or password is incorrect.');
|
||
expect(errorMessage(fail(404, 'user_not_found'), 'reset')).toBe('The link is invalid or has expired.');
|
||
expect(errorMessage(fail(429, ''), 'login')).toBe('Too many attempts – please wait a moment.');
|
||
expect(errorMessage(new TypeError('x'), 'load')).toBe('Can’t reach the server.');
|
||
expect(errorMessage(new Error('x'), 'load')).toBe('Something went wrong. Please try again.');
|
||
expect(validatePassword('kurz')).toBe('The password must be 8 to 256 characters long.');
|
||
});
|
||
|
||
it('der Standardname einer neuen Karte folgt der Sprache, die Konstante bleibt der deutsche Schlüssel', async () => {
|
||
const i18n = await freshI18n('en');
|
||
const { DEFAULT_CARD_NAME, normalizeName } = await import('../account/cards');
|
||
const { initialEditorState } = await import('../editor/state');
|
||
expect(normalizeName(' ')).toBe('My card');
|
||
expect(initialEditorState().cardName).toBe('My card');
|
||
expect(DEFAULT_CARD_NAME).toBe('Meine Karte');
|
||
i18n.setLang('de', { persist: false });
|
||
expect(normalizeName(' ')).toBe('Meine Karte');
|
||
});
|
||
});
|
||
|
||
describe('Konto-Menü und Login-Dialog auf Englisch', () => {
|
||
beforeEach(() => {
|
||
document.body.innerHTML = '<div id="account-slot"></div>';
|
||
});
|
||
|
||
it('das Menü zeigt „Sign in“ bzw. Konto-Menü mit englischen Texten und Fehlern', async () => {
|
||
await freshI18n('en');
|
||
const { mountAccountMenu } = await import('../account/account-menu');
|
||
const auth = await import('../account/auth');
|
||
const slot = document.getElementById('account-slot')!;
|
||
const menu = mountAccountMenu(slot);
|
||
menu.ready();
|
||
expect(texts(slot)).toEqual(['Sign in']);
|
||
mocks.acct.get.mockResolvedValue({ $id: 'u1', email: 'kenso@example.com' });
|
||
await auth.getCurrentUser();
|
||
expect(texts(slot)).toEqual(expect.arrayContaining(['Account menu', 'Account', 'My cards', 'Sign out']));
|
||
expect(germanLeft(slot)).toEqual([]);
|
||
mocks.acct.deleteSession.mockRejectedValue(new TypeError('Failed to fetch'));
|
||
[...slot.querySelectorAll('button')].find((b) => b.textContent === 'Sign out')!.click();
|
||
await vi.waitFor(() => expect(slot.querySelector('.account-error')!.textContent).toBe('Can’t reach the server.'));
|
||
menu.dispose();
|
||
});
|
||
|
||
it('alle drei Ansichten, der Abgleich der Passwörter (Fokus am Feld) und die Beschriftung nach dem Sprachwechsel', async () => {
|
||
const i18n = await freshI18n('de');
|
||
const { requireLogin } = await import('../account/login-dialog');
|
||
const dialog = () => document.querySelector<HTMLDialogElement>('dialog.auth-dialog')!;
|
||
const button = (text: string) => [...dialog().querySelectorAll('button')].find((b) => b.textContent === text)!;
|
||
const input = (name: string) => dialog().querySelector<HTMLInputElement>(`input[name="${name}"]`)!;
|
||
const alert = () => dialog().querySelector('[role="alert"]')!.textContent;
|
||
|
||
// Der Dialog wird einmal gebaut: Deutsch öffnen und schließen, dann auf Englisch wieder öffnen.
|
||
const first = requireLogin('login');
|
||
expect(dialog().querySelector('.auth-close')!.getAttribute('aria-label')).toBe('Schließen');
|
||
dialog().querySelector<HTMLButtonElement>('.auth-close')!.click();
|
||
await first;
|
||
i18n.setLang('en', { persist: false });
|
||
void requireLogin('login');
|
||
expect(dialog().querySelector('h2')!.textContent).toBe('Sign in');
|
||
expect(dialog().querySelector('.auth-close')!.getAttribute('aria-label')).toBe('Close');
|
||
expect(dialog().querySelector('[role="tablist"]')!.getAttribute('aria-label')).toBe('Account');
|
||
expect(texts(dialog())).toEqual(expect.arrayContaining(['Sign in', 'Sign up', 'Email', 'Password', 'Forgot password?']));
|
||
expect(germanLeft(dialog())).toEqual([]);
|
||
|
||
button('Sign up').click();
|
||
expect(dialog().querySelector('h2')!.textContent).toBe('Sign up');
|
||
expect(texts(dialog())).toEqual(expect.arrayContaining(['At least 8 characters.', 'Confirm password']));
|
||
input('email').value = 'a@b.de';
|
||
input('password').value = 'passwort123';
|
||
input('password-repeat').value = 'passwort124';
|
||
submit(dialog().querySelector('form'));
|
||
expect(alert()).toBe('The passwords don’t match.');
|
||
expect(document.activeElement).toBe(input('password-repeat'));
|
||
input('password').value = 'kurz';
|
||
input('password-repeat').value = 'kurz';
|
||
submit(dialog().querySelector('form'));
|
||
expect(alert()).toBe('The password must be 8 to 256 characters long.');
|
||
expect(document.activeElement).toBe(input('password'));
|
||
expect(germanLeft(dialog())).toEqual([]);
|
||
|
||
// vorhandene E-Mail: Meldung mit dem Link „Sign in“
|
||
mocks.acct.create.mockRejectedValue(fail(409, 'user_already_exists'));
|
||
input('password').value = 'passwort123';
|
||
input('password-repeat').value = 'passwort123';
|
||
submit(dialog().querySelector('form'));
|
||
await vi.waitFor(() => expect(alert()).toBe('This email is already registered. Please sign in. Sign in'));
|
||
|
||
button('Sign in').click();
|
||
button('Forgot password?').click();
|
||
expect(dialog().querySelector('h2')!.textContent).toBe('Forgot password');
|
||
expect(texts(dialog())).toEqual(expect.arrayContaining(['Send link', 'Back to sign in']));
|
||
mocks.acct.createRecovery.mockResolvedValue({});
|
||
submit(dialog().querySelector('form'));
|
||
await vi.waitFor(() => expect(dialog().querySelector('[role="status"]')!.textContent).toBe('We’ve sent you a link. It’s valid for one hour.'));
|
||
expect(germanLeft(dialog())).toEqual([]);
|
||
});
|
||
});
|
||
|
||
describe('Seite „Passwort neu setzen“ auf Englisch', () => {
|
||
beforeEach(() => {
|
||
document.body.innerHTML = '<main id="app-root"></main>';
|
||
});
|
||
|
||
it('ohne gültigen Link', async () => {
|
||
await freshI18n('en');
|
||
history.replaceState(null, '', '/passwort-neu');
|
||
const { mountResetPage } = await import('../pages/reset-page');
|
||
const root = document.getElementById('app-root')!;
|
||
mountResetPage(root);
|
||
expect(root.querySelector('h1')!.textContent).toBe('Set new password');
|
||
expect(root.querySelector('[role="alert"]')!.textContent).toBe('The link is invalid or has expired.');
|
||
expect(texts(root)).toContain('Request a new link');
|
||
expect(germanLeft(root)).toEqual([]);
|
||
});
|
||
|
||
it('mit Link: Formular, Abgleich der Passwörter (Fokus am Feld) und Erfolg', async () => {
|
||
await freshI18n('en');
|
||
history.replaceState(null, '', '/passwort-neu?userId=u1&secret=s3cret');
|
||
const { mountResetPage } = await import('../pages/reset-page');
|
||
const root = document.getElementById('app-root')!;
|
||
mountResetPage(root);
|
||
expect(texts(root)).toEqual(expect.arrayContaining(['New password', 'Confirm new password', 'At least 8 characters.', 'Save password']));
|
||
expect(germanLeft(root)).toEqual([]);
|
||
const input = (name: string) => root.querySelector<HTMLInputElement>(`input[name="${name}"]`)!;
|
||
input('password').value = 'passwort123';
|
||
input('password-repeat').value = 'passwort124';
|
||
submit(root.querySelector('form'));
|
||
expect(root.querySelector('[role="alert"]')!.textContent).toBe('The passwords don’t match.');
|
||
expect(document.activeElement).toBe(input('password-repeat'));
|
||
input('password').value = 'kurz';
|
||
input('password-repeat').value = 'kurz';
|
||
submit(root.querySelector('form'));
|
||
expect(root.querySelector('[role="alert"]')!.textContent).toBe('The password must be 8 to 256 characters long.');
|
||
expect(document.activeElement).toBe(input('password'));
|
||
mocks.acct.updateRecovery.mockResolvedValue({});
|
||
input('password').value = 'passwort123';
|
||
input('password-repeat').value = 'passwort123';
|
||
submit(root.querySelector('form'));
|
||
await vi.waitFor(() => expect(root.querySelector('[role="status"]')!.textContent).toBe('Password changed. You can now sign in.'));
|
||
expect(germanLeft(root)).toEqual([]);
|
||
history.replaceState(null, '', '/');
|
||
});
|
||
});
|
||
|
||
describe('Editor auf Englisch', () => {
|
||
let root: HTMLElement;
|
||
|
||
/** Lädt den Editor frisch in der Sprache `en`; Karten-Dienst, Anmeldung, Renderer und Logo-Auswahl sind ersetzt. */
|
||
async function mountEnglishEditor(url = '/gestalten') {
|
||
vi.resetModules();
|
||
vi.doMock('../account/auth', () => ({
|
||
getCurrentUser: mocks.getCurrentUser,
|
||
currentUserId: () => 'u1',
|
||
onAuthChange: () => () => {},
|
||
onLogout: () => () => {},
|
||
}));
|
||
vi.doMock('../account/login-dialog', () => ({ requireLogin: mocks.requireLogin }));
|
||
vi.doMock('../account/cards', () => ({ cardsService: () => ({ get: mocks.getCard, save: mocks.saveCard, loadImage: vi.fn() }) }));
|
||
vi.doMock('../card/render', () => ({ drawCard: vi.fn(), loadCardAssets: mocks.loadCardAssets, exportCard: mocks.exportCard }));
|
||
vi.doMock('../community/gallery', () => ({ getGalleryCard: mocks.getGalleryCard, loadGalleryImage: vi.fn() }));
|
||
vi.doMock('../logos/logo-picker', () => ({
|
||
createLogoPicker: () => ({ el: document.createElement('div'), select: vi.fn(), selected: vi.fn(), dispose: vi.fn() }),
|
||
}));
|
||
vi.doMock('../editor/download', async (importOriginal) => ({
|
||
...(await importOriginal<typeof import('../editor/download')>()),
|
||
saveBlobAs: vi.fn(),
|
||
}));
|
||
vi.doMock('../editor/upload', async (importOriginal) => ({
|
||
...(await importOriginal<typeof import('../editor/upload')>()),
|
||
decodeImage: mocks.decodeImage,
|
||
}));
|
||
(await import('./i18n')).setLang('en', { persist: false });
|
||
history.replaceState(null, '', url);
|
||
document.body.innerHTML = '<main id="app-root"></main>';
|
||
root = document.getElementById('app-root')!;
|
||
const { mountEditor } = await import('../editor/editor-page');
|
||
const { createStore, initialEditorState } = await import('../editor/state');
|
||
const store = createStore(initialEditorState());
|
||
return { store, unmount: mountEditor(root, store) };
|
||
}
|
||
|
||
beforeEach(() => {
|
||
mocks.getCurrentUser.mockResolvedValue({ $id: 'u1', email: 'a@b.de' });
|
||
mocks.requireLogin.mockResolvedValue(true);
|
||
mocks.loadCardAssets.mockResolvedValue({ image: null, logo: null, brand: {}, logoMissing: false });
|
||
vi.stubGlobal('requestAnimationFrame', () => 0);
|
||
vi.stubGlobal('cancelAnimationFrame', () => {});
|
||
});
|
||
|
||
afterEach(() => {
|
||
for (const id of ['auth', 'login-dialog', 'cards']) vi.doUnmock(`../account/${id}`);
|
||
vi.doUnmock('../card/render');
|
||
vi.doUnmock('../community/gallery');
|
||
vi.doUnmock('../logos/logo-picker');
|
||
vi.doUnmock('../editor/download');
|
||
vi.doUnmock('../editor/upload');
|
||
vi.unstubAllGlobals();
|
||
history.replaceState(null, '', '/');
|
||
});
|
||
|
||
const errorText = () => root.querySelector('.msg-error')!.textContent;
|
||
const button = (text: string) => [...root.querySelectorAll('button')].find((b) => b.textContent === text)!;
|
||
const pick = (file: File) => {
|
||
const input = root.querySelector<HTMLInputElement>('input[type="file"]')!;
|
||
Object.defineProperty(input, 'files', { configurable: true, value: [file] });
|
||
input.dispatchEvent(new Event('change'));
|
||
};
|
||
|
||
it('die Seite ist englisch; Dateifehler, Speichern, Download und Rückfrage ebenso', async () => {
|
||
const { store, unmount } = await mountEnglishEditor();
|
||
expect(store.get().cardName).toBe('My card');
|
||
expect(germanLeft(root)).toEqual([]);
|
||
expect(texts(root)).toEqual(
|
||
expect.arrayContaining([
|
||
'Template',
|
||
'Image only',
|
||
'Card preview',
|
||
'Choose image',
|
||
'Drag your idea here',
|
||
'Save',
|
||
'Customize',
|
||
'Light',
|
||
'Dark',
|
||
'No logo',
|
||
'Logo – AirCard-IOS · Symbol',
|
||
]),
|
||
);
|
||
|
||
// Typ und Größe prüft validateImageFile (übersetzt beim Zurückgeben); die Fehler von decodeImage tragen den deutschen Schlüssel
|
||
pick(new File(['x'], 'a.gif', { type: 'image/gif' }));
|
||
expect(errorText()).toBe('Please choose a PNG, JPG or WebP image.');
|
||
const { PIXELS_MESSAGE, READ_MESSAGE } = await import('../editor/upload');
|
||
mocks.decodeImage.mockRejectedValueOnce(new Error(PIXELS_MESSAGE));
|
||
pick(new File(['x'], 'a.png', { type: 'image/png' }));
|
||
await vi.waitFor(() => expect(errorText()).toBe('The image is too large (max. 40 megapixels).'));
|
||
mocks.decodeImage.mockRejectedValueOnce(new Error(READ_MESSAGE));
|
||
pick(new File(['x'], 'a.png', { type: 'image/png' }));
|
||
await vi.waitFor(() => expect(errorText()).toBe('The image couldn’t be read.'));
|
||
|
||
// Mit Bild: Zoom in der englischen Schreibweise, Speichern scheitert, dann klappt es
|
||
const image = { source: {} as CanvasImageSource, w: 3000, h: 2000 };
|
||
store.set({ image, imageBlob: new Blob(['png'], { type: 'image/png' }), imageChanged: true });
|
||
expect(root.querySelector('.zoom-value')!.textContent).toBe('100%');
|
||
mocks.saveCard.mockRejectedValueOnce(new TypeError('Failed to fetch'));
|
||
button('Save').click();
|
||
await vi.waitFor(() => expect(errorText()).toBe('Saving failed – Can’t reach the server.'));
|
||
mocks.saveCard.mockResolvedValueOnce({ id: 'k1', name: 'Urlaub', imageFileId: 'i', previewFileId: 'p', updatedAt: 'x', config: store.get().config });
|
||
button('Save').click();
|
||
await vi.waitFor(() => expect(button('Saved')).toBeDefined());
|
||
expect(root.querySelector('.bound-label')!.textContent).toBe('Editing: Urlaub');
|
||
|
||
// Download: Logos nicht ladbar, dann ein PNG über der Wallet-Grenze, dann ein Export-Fehler mit deutscher Meldung aus card/render.ts
|
||
mocks.loadCardAssets.mockRejectedValueOnce(new Error('Failed to fetch'));
|
||
button('Download').click();
|
||
await vi.waitFor(() => expect(errorText()).toBe('The logos couldn’t be loaded.'));
|
||
mocks.loadCardAssets.mockResolvedValue({ image, logo: null, brand: {}, logoMissing: false });
|
||
mocks.exportCard.mockResolvedValueOnce(new Blob([new Uint8Array(4_000_100)], { type: 'image/png' }));
|
||
button('Download').click();
|
||
const warning = 'The PNG is larger than 4 MB – Wallet may only accept smaller images.';
|
||
await vi.waitFor(() => expect(root.querySelector('.msg-warning')!.textContent).toBe(warning));
|
||
mocks.exportCard.mockRejectedValueOnce(new Error('Das Bild konnte nicht erzeugt werden.'));
|
||
button('Download').click();
|
||
await vi.waitFor(() => expect(errorText()).toBe('The image couldn’t be created.'));
|
||
|
||
// „Neue Karte“ mit ungespeicherten Änderungen fragt nach
|
||
store.set({ imageChanged: true });
|
||
button('New card').click();
|
||
await vi.waitFor(() => expect(document.querySelector('dialog.confirm-dialog')).not.toBeNull());
|
||
const dialog = document.querySelector('dialog.confirm-dialog')!;
|
||
expect(texts(dialog)).toEqual(['Discard unsaved changes?', 'Everything you’ve changed since the last save will be lost.', 'Cancel', 'Discard']);
|
||
unmount();
|
||
dialog.remove();
|
||
});
|
||
|
||
it('Karte oder Vorlage öffnen: Ladehinweis, abgebrochene Anmeldung, fehlende Karte, fehlende Vorlage', async () => {
|
||
let known!: (user: null) => void;
|
||
mocks.getCurrentUser.mockReturnValue(new Promise<null>((resolve) => (known = resolve)));
|
||
mocks.requireLogin.mockResolvedValue(false);
|
||
let editor = await mountEnglishEditor('/gestalten?karte=abc');
|
||
expect(root.querySelector('.msg-notice')!.textContent).toBe('Loading card …');
|
||
known(null);
|
||
await vi.waitFor(() => expect(root.querySelector('.msg-warning')!.textContent).toBe('Sign in to open this card.'));
|
||
editor.unmount();
|
||
|
||
mocks.getCurrentUser.mockResolvedValue({ $id: 'u1', email: 'a@b.de' });
|
||
mocks.getCard.mockRejectedValue(fail(404, 'row_not_found'));
|
||
editor = await mountEnglishEditor('/gestalten?karte=abc');
|
||
await vi.waitFor(() => expect(errorText()).toBe('This card no longer exists or doesn’t belong to your account.'));
|
||
editor.unmount();
|
||
|
||
let answer!: (card: null) => void;
|
||
mocks.getGalleryCard.mockReturnValue(new Promise<null>((resolve) => (answer = resolve)));
|
||
editor = await mountEnglishEditor('/gestalten?vorlage=abc');
|
||
expect(root.querySelector('.msg-notice')!.textContent).toBe('Loading template …');
|
||
answer(null);
|
||
await vi.waitFor(() => expect(errorText()).toBe('This template is no longer available.'));
|
||
editor.unmount();
|
||
});
|
||
});
|