feat(shell): Routen, Kopfzeile, Hell/Dunkel, Spenden-Banner
- Router: Routen home, gestalten, cards, community/<id>, reset; Umleitung /?karte=<id> nach /gestalten?karte=<id>; Titel je Route (titleFor); Hash-Ziele (/#community) scrollen den Anker ins Bild, nur-Hash-Wechsel bauen die Seite nicht neu auf. - Kopfzeile: Navigation, Suche (Enter -> /?q=...#community), Spenden-Knopf, Hell/Dunkel-Schalter, Menue unter 900 px, Konto als Avatar-Kreis mit Menue. - Theme: data-theme auf <html> (Skript im <head> gegen Aufblitzen), helle Tokens, danger-/warn-/ok-text-Tokens statt fester Farben. - Spenden-Banner (schliessbar); ohne DONATE_URL bleiben Banner und Knopf weg. - User.labels und isAdmin; Platzhalter fuer Startseite und Detailseite. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1a64e12bc5
commit
25ff496548
30 files changed
+1783
-189
No files matched your search
@@ -0,0 +1,75 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mountDonateBanner } from './donate-banner';
|
||||
|
||||
const KEY = 'aircard.donateBannerClosed';
|
||||
const URL_ = 'https://buymeacoffee.com/aircard';
|
||||
const TEXT = '☕ Gefällt dir AirCard-IOS? Spendier uns einen Kaffee – damit das Projekt weiterläuft.';
|
||||
|
||||
let slot: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
document.body.innerHTML = '<div id="donate-banner"></div>';
|
||||
slot = document.getElementById('donate-banner') as HTMLElement;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('mountDonateBanner', () => {
|
||||
it('leere URL: nichts wird gerendert', () => {
|
||||
mountDonateBanner(slot, '');
|
||||
expect(slot.childElementCount).toBe(0);
|
||||
mountDonateBanner(slot, ' ');
|
||||
expect(slot.childElementCount).toBe(0);
|
||||
});
|
||||
|
||||
it('zeigt Text, Link „Unterstützen →“ (neuer Tab) und Schließen-Knopf', () => {
|
||||
mountDonateBanner(slot, URL_);
|
||||
expect(slot.querySelector('.donate-banner-text')?.textContent).toBe(TEXT);
|
||||
const link = slot.querySelector<HTMLAnchorElement>('a.donate-banner-link')!;
|
||||
expect(link.textContent).toBe('Unterstützen →');
|
||||
expect(link.getAttribute('href')).toBe(URL_);
|
||||
expect(link.target).toBe('_blank');
|
||||
expect(link.rel).toBe('noopener noreferrer');
|
||||
expect(link.hasAttribute('data-link')).toBe(false);
|
||||
expect(slot.querySelector('button')?.getAttribute('aria-label')).toBe('Banner schließen');
|
||||
});
|
||||
|
||||
it('Schließen setzt das Flag und entfernt das Banner', () => {
|
||||
mountDonateBanner(slot, URL_);
|
||||
slot.querySelector('button')!.click();
|
||||
expect(localStorage.getItem(KEY)).toBe('1');
|
||||
expect(slot.childElementCount).toBe(0);
|
||||
});
|
||||
|
||||
it('Flag gesetzt: nichts wird gerendert', () => {
|
||||
localStorage.setItem(KEY, '1');
|
||||
mountDonateBanner(slot, URL_);
|
||||
expect(slot.childElementCount).toBe(0);
|
||||
});
|
||||
|
||||
it('zweimal eingehängt gibt es nur ein Banner', () => {
|
||||
mountDonateBanner(slot, URL_);
|
||||
mountDonateBanner(slot, URL_);
|
||||
expect(slot.querySelectorAll('.donate-banner')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('localStorage wirft beim Lesen: Banner wird gezeigt', () => {
|
||||
vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
|
||||
throw new DOMException('blockiert', 'SecurityError');
|
||||
});
|
||||
expect(() => mountDonateBanner(slot, URL_)).not.toThrow();
|
||||
expect(slot.querySelectorAll('.donate-banner')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('localStorage wirft beim Schreiben: Banner verschwindet trotzdem, kein Fehler', () => {
|
||||
vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
|
||||
throw new DOMException('voll', 'QuotaExceededError');
|
||||
});
|
||||
mountDonateBanner(slot, URL_);
|
||||
expect(() => slot.querySelector('button')!.click()).not.toThrow();
|
||||
expect(slot.childElementCount).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user