fix(card): fehlendes Logo bricht nur ab, wenn die Vorlage es zeichnet; Download aus „Meine Karten“ meldet es
Entscheidung des Controllers zu Task 5: Ein fehlendes Community-Logo ist beim Rendern einer gespeicherten Karte nur dann ein Problem, wenn die Vorlage es zeichnet (layoutFor(template).logo). Bei „Nur Bild“ wird ein Logo nie gezeichnet, dort fehlt nichts. Der Editor-Hinweis (Auswahlzustand) und der Download im Editor bleiben unverändert. - community/publish.ts: neue gemeinsame Prüfung assertLogoAvailable(config, assets); sie wirft LOGO_MISSING_PUBLISH, wenn assets.logoMissing gilt und die Vorlage das Logo zeichnet (minimal, classic, signature). renderForGallery nutzt sie: bei einer zeichnenden Vorlage Abbruch vor dem Rendern (nichts hochgeladen), bei „Nur Bild“ wird normal gerendert und veröffentlicht. - pages/cards-page.ts: der Download einer gespeicherten Karte ruft dieselbe Prüfung. Statt stillschweigend ohne Logo herunterzuladen, zeigt die Kachel den Text LOGO_MISSING_PUBLISH über den vorhandenen Fehlermechanismus (run() und failureText() schreiben in .card-msg), übersetzt mit t(); es wird nichts exportiert oder gespeichert. Bei „Nur Bild“ läuft der Download normal. - Tests: publish.test.ts (classic/minimal/signature mit fehlendem Logo brechen ab, „Nur Bild“ veröffentlicht normal, Wahrheitstabelle für assertLogoAvailable) und cards-page.test.ts (Download mit fehlendem Logo: Meldung, kein Download; „Nur Bild“ und vorhandenes Logo: Download wie sonst; zweiter Versuch; Englisch). Vitest 45 Dateien, 1338 Tests grün, tsc und Build sauber. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0be5abd954
commit
8205f7c37f
4 files changed
+147
-14
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { User } from '../account/auth';
|
||||
import type { SavedCard } from '../account/cards';
|
||||
import { DEFAULT_CONFIG } from '../card/config';
|
||||
import { DEFAULT_CONFIG, type TemplateId } from '../card/config';
|
||||
import { createStore, initialEditorState } from '../editor/state';
|
||||
import type { GalleryCard } from '../community/gallery';
|
||||
|
||||
@@ -67,6 +67,7 @@ vi.mock('../community/api', async (importOriginal) => ({
|
||||
vi.mock('../community/gallery', () => ({ myPublications: mocks.myPublications }));
|
||||
|
||||
import { ApiError } from '../community/api';
|
||||
import { LOGO_MISSING_PUBLISH } from '../community/publish';
|
||||
import { mountCardsPage } from './cards-page';
|
||||
|
||||
const user: User = { $id: 'u1', email: 'a@b.de' };
|
||||
@@ -301,6 +302,80 @@ describe('Meine Karten: Öffnen und Herunterladen', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Meine Karten: Herunterladen mit fehlendem Logo', () => {
|
||||
const image = { source: {}, w: 3000, h: 2000 };
|
||||
const png = new Blob(['png'], { type: 'image/png' });
|
||||
/** Eine Karte, die ein Logo wählt, das es nicht mehr gibt (loadCardAssets meldet logoMissing). */
|
||||
const onTemplate = (template: TemplateId) =>
|
||||
card('a', 'Alpha', { config: { ...structuredClone(DEFAULT_CONFIG), template, logo: 'gone' } });
|
||||
const msg = () => tileFor('a').querySelector('.card-msg')!;
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.loadImage.mockResolvedValue(new Blob(['i'], { type: 'image/jpeg' }));
|
||||
mocks.decodeImage.mockResolvedValue(image);
|
||||
mocks.loadCardAssets.mockResolvedValue({ image, logo: null, brand: null, logoMissing: true });
|
||||
mocks.exportCard.mockResolvedValue(png);
|
||||
});
|
||||
|
||||
async function download(template: TemplateId): Promise<void> {
|
||||
mocks.list.mockResolvedValue([onTemplate(template)]);
|
||||
mount();
|
||||
await vi.waitFor(() => expect(tiles()).toHaveLength(1));
|
||||
button(tileFor('a'), 'Herunterladen').click();
|
||||
}
|
||||
|
||||
it.each(['minimal', 'classic', 'signature'] as const)(
|
||||
'Vorlage %s zeichnet das Logo: die Meldung steht an der Kachel, es wird nichts heruntergeladen',
|
||||
async (template) => {
|
||||
await download(template);
|
||||
await vi.waitFor(() => expect(msg().textContent).toBe(LOGO_MISSING_PUBLISH));
|
||||
expect(msg().textContent).toBe(
|
||||
'Das Logo dieser Karte ist nicht mehr verfügbar. Öffne die Karte im Editor und wähle ein anderes.',
|
||||
);
|
||||
expect(msg().classList.contains('is-warning')).toBe(false);
|
||||
expect(mocks.exportCard).not.toHaveBeenCalled();
|
||||
expect(mocks.saveBlobAs).not.toHaveBeenCalled();
|
||||
// Der Knopf ist danach wieder bedienbar und trägt seine Beschriftung.
|
||||
const again = button(tileFor('a'), 'Herunterladen');
|
||||
expect(again.disabled).toBe(false);
|
||||
expect(tileFor('a').getAttribute('aria-busy')).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it('„Nur Bild“ (image) zeichnet nie ein Logo: der Download läuft ganz normal, ohne Meldung', async () => {
|
||||
await download('image');
|
||||
await vi.waitFor(() => expect(mocks.saveBlobAs).toHaveBeenCalledWith(png, 'aircard-alpha.png'));
|
||||
expect(mocks.exportCard).toHaveBeenCalledTimes(1);
|
||||
expect(msg().textContent).toBe('');
|
||||
});
|
||||
|
||||
it('nach der Meldung geht ein zweiter Versuch (Logo wieder da) durch, die Meldung ist dann weg', async () => {
|
||||
await download('classic');
|
||||
await vi.waitFor(() => expect(msg().textContent).toBe(LOGO_MISSING_PUBLISH));
|
||||
mocks.loadCardAssets.mockResolvedValue({ image, logo: { source: {}, w: 200, h: 100 }, brand: null, logoMissing: false });
|
||||
button(tileFor('a'), 'Herunterladen').click();
|
||||
await vi.waitFor(() => expect(mocks.saveBlobAs).toHaveBeenCalledTimes(1));
|
||||
expect(msg().textContent).toBe('');
|
||||
});
|
||||
|
||||
it('Englisch: dieselbe Meldung, übersetzt', async () => {
|
||||
const { setLang } = await import('../i18n/i18n');
|
||||
setLang('en', { persist: false });
|
||||
await download('signature');
|
||||
await vi.waitFor(() =>
|
||||
expect(msg().textContent).toBe('This card’s logo is no longer available. Open the card in the editor and choose another one.'),
|
||||
);
|
||||
expect(mocks.saveBlobAs).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('eine Karte ohne gewähltes Logo wird normal heruntergeladen', async () => {
|
||||
mocks.loadCardAssets.mockResolvedValue({ image, logo: null, brand: null, logoMissing: false });
|
||||
await download('classic');
|
||||
await vi.waitFor(() => expect(mocks.saveBlobAs).toHaveBeenCalledWith(png, 'aircard-alpha.png'));
|
||||
expect(msg().textContent).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Meine Karten: Sitzung weg, Appwrite antwortet mit 404 statt 401', () => {
|
||||
const fileGone = () => Object.assign(new Error('File not found'), { code: 404, type: 'storage_file_not_found' });
|
||||
const rowGone = () => Object.assign(new Error('Row not found'), { code: 404, type: 'row_not_found' });
|
||||
|
||||
Reference in new issue
Block a user