fix(community): Label-ID im Veröffentlichen-Dialog, Status nie in „lädt“, Nachprüfung robust

Die Überschrift des Dialogs hatte dieselbe ID wie das Titelfeld (publish-title), das Label des
Pflichtfelds zeigte damit auf die Überschrift; jetzt publish-heading. Schließt der Browser den Dialog
mitten im Senden, wird er wieder geöffnet statt als abgebrochen aufgelöst. Die Status-Abfrage der Seite
wird bei einer lokalen Änderung während der Anfrage wiederholt (nie in „lädt“ hängen), die Nachprüfung nach
offenem Ausgang ist eine eigene Abfrage. Der Löschen-Dialog nennt bei unbekanntem Status einen allgemeinen
Hinweis und sagt, dass sich eine Veröffentlichung nur auf der Community-Seite zurückziehen lässt.
publishCard heißt lokal publishWithCheck.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 15:56:21 +00:00
1 parent f6b9ee61f4
commit 87b71ec71f
4 files changed
+216 -33

No files matched your search

+92 -6
View File
@@ -768,16 +768,77 @@ describe('Meine Karten: Veröffentlichung (Status an der Kachel)', () => {
expect(mocks.myPublications).toHaveBeenCalledTimes(1);
});
it('Löschen einer veröffentlichten Karte weist darauf hin, dass die Veröffentlichung bleibt', async () => {
await mountWith(pubMap(published('g1', 'a')));
button(tileFor('a'), 'Löschen').click();
const GENERIC_HINT =
'Falls diese Karte veröffentlicht ist, bleibt die Veröffentlichung bestehen – zurückziehen kannst du sie auf ihrer Community-Seite.';
const openDelete = async (id: string) => {
button(tileFor(id), 'Löschen').click();
await vi.waitFor(() => expect(dialog()?.open).toBe(true));
};
it('Löschen einer veröffentlichten Karte (Status bekannt) nennt, dass die Veröffentlichung bleibt und wo man sie zurückzieht', async () => {
await mountWith(pubMap(published('g1', 'a')));
await openDelete('a');
expect(dialog()!.textContent).toContain('bleibt in der Community-Galerie');
expect(dialog()!.textContent).toContain('Community-Seite');
expect(dialog()!.textContent).not.toContain('Falls diese Karte');
button(dialog()!, 'Abbrechen').click();
await vi.waitFor(() => expect(dialog()).toBeNull());
button(tileFor('b'), 'Löschen').click();
// Bekannt unveröffentlicht: kein Hinweis.
await vi.waitFor(() => expect(button(tileFor('b'), 'Löschen').disabled).toBe(false));
await openDelete('b');
expect(dialog()!.textContent).not.toContain('Community');
});
it('Löschen, solange der Status noch lädt: allgemeiner Hinweis', async () => {
mocks.list.mockResolvedValue([card('a', 'Alpha')]);
mocks.myPublications.mockReturnValue(new Promise(() => {}));
mount();
await vi.waitFor(() => expect(tiles()).toHaveLength(1));
await openDelete('a');
expect(dialog()!.textContent).toContain(GENERIC_HINT);
});
it('Löschen, wenn der Status nicht geladen werden konnte: allgemeiner Hinweis', async () => {
mocks.list.mockResolvedValue([card('a', 'Alpha')]);
mocks.myPublications.mockRejectedValue(new TypeError('offline'));
mount();
await vi.waitFor(() => expect(text()).toContain('konnte nicht geladen werden'));
await openDelete('a');
expect(dialog()!.textContent).toContain(GENERIC_HINT);
});
it('Status-Abfrage, die ein gleichzeitiges Zurückziehen überholt, lässt den Status nie in „lädt“ hängen', async () => {
mocks.list.mockResolvedValue([card('a', 'Alpha'), card('b', 'Beta')]);
mocks.myPublications.mockRejectedValueOnce(new TypeError('offline'));
mount();
await vi.waitFor(() => expect(text()).toContain('konnte nicht geladen werden'));
await vi.waitFor(() => expect(labels(tileFor('a'))).toContain('Veröffentlichen'));
// Im Fehlerzustand lässt sich veröffentlichen; danach gibt es „Zurückziehen“.
mocks.publishSavedCard.mockResolvedValue({ id: 'g9' });
await openPublish('a');
fillDialog();
sendForm();
await vi.waitFor(() => expect(badge('a')).toBe('Veröffentlicht'));
// Zurückziehen läuft (Anfrage unterwegs) ...
let finishWithdraw!: () => void;
mocks.withdrawCard.mockReturnValue(new Promise<void>((r) => (finishWithdraw = r)));
await vi.waitFor(() => expect(button(tileFor('a'), 'Zurückziehen').disabled).toBe(false));
button(tileFor('a'), 'Zurückziehen').click();
await vi.waitFor(() => expect(dialog()?.open).toBe(true));
expect(dialog()!.textContent).not.toContain('Community-Galerie');
button(dialog()!, 'Zurückziehen').click();
await vi.waitFor(() => expect(mocks.withdrawCard).toHaveBeenCalledWith('g9'));
// ... währenddessen „Nochmal versuchen“ (die Abfrage hängt noch) ...
let finishFetch!: (m: Map<string, GalleryCard>) => void;
mocks.myPublications.mockReturnValueOnce(new Promise((r) => (finishFetch = r)));
button(root, 'Nochmal versuchen').click();
expect(labels(tileFor('b'))).not.toContain('Veröffentlichen'); // Status gilt wieder als unbekannt
// ... und das Zurückziehen wird fertig, bevor die Abfrage antwortet.
finishWithdraw();
await vi.waitFor(() => expect(badge('a')).toBe(''));
finishFetch(new Map());
await vi.waitFor(() => expect(labels(tileFor('b'))).toContain('Veröffentlichen'));
expect(labels(tileFor('a'))).toContain('Veröffentlichen');
expect(text()).not.toContain('konnte nicht geladen werden');
});
});
@@ -941,6 +1002,31 @@ describe('Meine Karten: Veröffentlichen', () => {
expect(badge('a')).toBe('');
});
it('die Nachprüfung geht nicht verloren, wenn währenddessen eine andere Kachel ihren Status ändert', async () => {
mocks.publishSavedCard.mockRejectedValue(new ApiError(503, 'Der Dienst ist gerade nicht erreichbar.'));
mocks.withdrawCard.mockResolvedValue(undefined);
mocks.list.mockResolvedValue([card('a', 'Alpha'), card('b', 'Beta')]);
mocks.myPublications.mockResolvedValue(new Map([['b', published('g2', 'b')]]));
mount();
await vi.waitFor(() => expect(tiles()).toHaveLength(2));
await vi.waitFor(() => expect(labels(tileFor('b'))).toContain('Zurückziehen'));
await openPublish('a');
fillDialog();
let finishCheck!: (m: Map<string, GalleryCard>) => void;
mocks.myPublications.mockReturnValueOnce(new Promise((r) => (finishCheck = r)));
sendForm();
await vi.waitFor(() => expect(mocks.myPublications).toHaveBeenCalledTimes(2));
// Während die Nachprüfung läuft, zieht der Nutzer in einer anderen Kachel eine Karte zurück.
button(tileFor('b'), 'Zurückziehen').click();
await vi.waitFor(() => expect(dialog()?.open).toBe(true));
button(dialog()!, 'Zurückziehen').click();
await vi.waitFor(() => expect(labels(tileFor('b'))).toContain('Veröffentlichen'));
finishCheck(new Map([['a', published('g1', 'a')]]));
await vi.waitFor(() => expect(badge('a')).toBe('Veröffentlicht'));
expect(publishDialogEl()).toBeNull();
expect(tileFor('a').querySelector('.card-msg')!.textContent).toBe('');
});
it('die Nachprüfung findet nur die Veröffentlichung dieser Karte (eine andere zählt nicht)', async () => {
mocks.publishSavedCard.mockRejectedValue(new ApiError(503, 'Der Dienst ist gerade nicht erreichbar.'));
await start();
@@ -950,7 +1036,7 @@ describe('Meine Karten: Veröffentlichen', () => {
sendForm();
await vi.waitFor(() => expect(dlgError()).toContain('Der Dienst ist gerade nicht erreichbar.'));
expect(badge('a')).toBe('');
expect(badge('b')).toBe('Veröffentlicht');
expect(dlgError()).toContain('Der Dienst ist gerade nicht erreichbar.');
});
});