fix(cards): Sitzung, Entwurf und Datei-Aufräumen beim Öffnen und Speichern
- Gäste/abgelaufene Sitzung: Appwrite antwortet mit 404; withLogin und openSavedCard melden dann an (Dialog, einmal wiederholen) statt „nicht gefunden“ zu zeigen. - Öffnen einer Karte fragt vor dem Verwerfen eines ungespeicherten Entwurfs nach; ein fehlgeschlagenes Öffnen setzt einen gefüllten Editor nicht zurück. - Update ohne Bildwechsel schickt kein imageFileId mehr (veralteter Stand zeigte sonst auf eine gelöschte Datei). - Uploads werden nur nach eindeutiger Ablehnung (4xx ohne 408/429) gelöscht, nicht bei Netzfehler, 5xx oder 429. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
09de06e797
commit
0c51689dfd
7 files changed
+546
-57
No files matched your search
@@ -279,6 +279,75 @@ describe('Meine Karten: Öffnen und Herunterladen', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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' });
|
||||
/** Beim Laden der Seite ist der Nutzer noch da; danach (Nutzerprüfung nach dem 404) nicht mehr. */
|
||||
const sessionEndsAfterLoad = () => mocks.getCurrentUser.mockResolvedValueOnce(user).mockResolvedValue(null);
|
||||
|
||||
it('Vorschau: erst anmelden, dann wird die Vorschau geladen (statt „Keine Vorschau“)', async () => {
|
||||
sessionEndsAfterLoad();
|
||||
mocks.list.mockResolvedValue([card('a', 'Alpha')]);
|
||||
mocks.loadPreview.mockReset();
|
||||
mocks.loadPreview
|
||||
.mockRejectedValueOnce(fileGone())
|
||||
.mockImplementation(async () => new Blob(['p'], { type: 'image/webp' }));
|
||||
mount();
|
||||
await vi.waitFor(() => expect(tileFor('a').querySelector('img')).not.toBeNull());
|
||||
expect(mocks.requireLogin).toHaveBeenCalledOnce();
|
||||
expect(mocks.loadPreview).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('Herunterladen: erst anmelden, dann wird das Bild geladen (statt „Das Bild dieser Karte fehlt.“)', async () => {
|
||||
const png = new Blob(['png'], { type: 'image/png' });
|
||||
const image = { source: {}, w: 3000, h: 2000 };
|
||||
sessionEndsAfterLoad();
|
||||
mocks.list.mockResolvedValue([card('a', 'Alpha')]);
|
||||
mocks.loadImage.mockRejectedValueOnce(fileGone()).mockResolvedValueOnce(new Blob(['i'], { type: 'image/jpeg' }));
|
||||
mocks.decodeImage.mockResolvedValue(image);
|
||||
mocks.loadCardAssets.mockResolvedValue({ image, network: null, brand: null });
|
||||
mocks.exportCard.mockResolvedValue(png);
|
||||
mount();
|
||||
await vi.waitFor(() => expect(tiles()).toHaveLength(1));
|
||||
button(tileFor('a'), 'Herunterladen').click();
|
||||
await vi.waitFor(() => expect(mocks.saveBlobAs).toHaveBeenCalledWith(png, 'aircard-alpha.png'));
|
||||
expect(mocks.requireLogin).toHaveBeenCalledOnce();
|
||||
expect(tileFor('a').querySelector('.card-msg')!.textContent).toBe('');
|
||||
});
|
||||
|
||||
it('Löschen: die Karte gilt nicht als „schon gelöscht“, sondern wird nach dem Anmelden wirklich gelöscht', async () => {
|
||||
sessionEndsAfterLoad();
|
||||
mocks.list.mockResolvedValue([card('a', 'Alpha'), card('b', 'Beta')]);
|
||||
mocks.remove.mockRejectedValueOnce(rowGone()).mockResolvedValueOnce(undefined);
|
||||
mount();
|
||||
await vi.waitFor(() => expect(tiles()).toHaveLength(2));
|
||||
await vi.waitFor(() => expect(button(tileFor('b'), 'Löschen').disabled).toBe(false));
|
||||
button(tileFor('b'), 'Löschen').click();
|
||||
await vi.waitFor(() => expect(dialog()?.open).toBe(true));
|
||||
button(dialog()!, 'Löschen').click();
|
||||
await vi.waitFor(() => expect(tiles()).toHaveLength(1));
|
||||
expect(mocks.requireLogin).toHaveBeenCalledOnce();
|
||||
expect(mocks.remove).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('Löschen, Anmeldung abgebrochen: die Karte bleibt, Hinweis an der Kachel', async () => {
|
||||
sessionEndsAfterLoad();
|
||||
mocks.requireLogin.mockResolvedValue(false);
|
||||
mocks.list.mockResolvedValue([card('a', 'Alpha')]);
|
||||
mocks.remove.mockRejectedValue(rowGone());
|
||||
mount();
|
||||
await vi.waitFor(() => expect(tiles()).toHaveLength(1));
|
||||
await vi.waitFor(() => expect(button(tileFor('a'), 'Löschen').disabled).toBe(false));
|
||||
button(tileFor('a'), 'Löschen').click();
|
||||
await vi.waitFor(() => expect(dialog()?.open).toBe(true));
|
||||
button(dialog()!, 'Löschen').click();
|
||||
await vi.waitFor(() =>
|
||||
expect(tileFor('a').querySelector('.card-msg')!.textContent).toBe('Bitte melde dich an, um fortzufahren.'),
|
||||
);
|
||||
expect(tiles()).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Meine Karten: Umbenennen', () => {
|
||||
const startRename = async () => {
|
||||
mocks.list.mockResolvedValue([card('a', 'Alpha'), card('b', 'Beta')]);
|
||||
|
||||
Reference in new issue
Block a user