feat(18-03): Task 2 — Einstellungsseite Desktop-App mit Knoepfen, Groesse und Erklaerung

- Neue Route /settings/general/desktop (DesktopSettingsPage)
- DesktopAppSettings: Version, zwei Primaerknoepfe mit Plattform-Symbol,
  Dateiname/Groesse je Knopf, Beta-Kanal-Hinweis, vier erklaerende Saetze,
  Hinweistext wenn keine Pakete hinterlegt sind
- Seitenleiste: Eintrag "Desktop-App" unter "Allgemein"
- de/en: settings.categoryDesktopApp, settings.desktop.*
- umlaut-dictionary.ts: "neuere" zur Allowlist ergaenzt (Rule 1 —
  Waechter-Test brach an einer bereits korrekten Umlautschreibung, die
  zufaellig die Buchstabenfolge "ue" enthaelt)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-16 16:30:34 +02:00
parent 026d9c39af
commit e96d460b8e
7 changed files with 290 additions and 0 deletions
@@ -0,0 +1,105 @@
import { cleanup, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { DesktopAppSettings } from './desktop-app-settings';
/**
* desktop-app-settings.test — Einstellungsseite "Desktop-App" (18-03).
* next-intl-Mock nach dem Muster in widget-settings-panel.test.tsx
* (de.json-gestuetzt), `@/lib/desktop` gemockt.
*/
vi.mock('next-intl', async () => {
const messages = (await import('@/messages/de.json')).default as Record<string, unknown>;
const lookup = (path: string): string | undefined =>
path.split('.').reduce<unknown>((o, k) => (o && typeof o === 'object' ? (o as any)[k] : undefined), messages) as
| string
| undefined;
return {
useTranslations:
(ns?: string) =>
(key: string, values?: Record<string, unknown>) => {
const raw = lookup(ns ? `${ns}.${key}` : key) ?? key;
return values ? raw.replace(/\{(\w+)\}/g, (_: string, n: string) => String(values[n] ?? '')) : raw;
},
useLocale: () => 'de',
};
});
const { loadDesktopLatest } = vi.hoisted(() => ({ loadDesktopLatest: vi.fn() }));
vi.mock('@/lib/desktop', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/lib/desktop')>();
return {
...actual,
loadDesktopLatest,
};
});
afterEach(() => {
cleanup();
vi.clearAllMocks();
});
const windowsFile = {
name: 'Tessera-Setup-1.1.0.exe',
size: 6123456,
sha256: 'x',
url: '/desktop/download/windows',
};
const linuxFile = {
name: 'Tessera-1.1.0.AppImage',
size: 106461688,
sha256: 'y',
url: '/desktop/download/linux',
};
describe('DesktopAppSettings (18-03)', () => {
it('Test 1 (beide Plattformen): Version, zwei Links, Dateiname und Groesse', async () => {
loadDesktopLatest.mockResolvedValue({
version: '1.1.0',
channel: 'live',
commit: 'abc1234',
buildTime: 'x',
files: { windows: windowsFile, linux: linuxFile },
});
render(<DesktopAppSettings />);
expect(await screen.findByText('Aktuelle Version: 1.1.0')).toBeInTheDocument();
const windowsLink = screen.getByTestId('desktop-download-windows');
expect(windowsLink).toHaveAttribute('href', 'http://localhost:3001/desktop/download/windows');
const linuxLink = screen.getByTestId('desktop-download-linux');
expect(linuxLink).toHaveAttribute('href', 'http://localhost:3001/desktop/download/linux');
expect(screen.getByText('Tessera-Setup-1.1.0.exe · 5,8 MB')).toBeInTheDocument();
expect(screen.getByText('Tessera-1.1.0.AppImage · 101,5 MB')).toBeInTheDocument();
});
it('Test 2 (Beta-Kanal): Hinweis mit Commit', async () => {
loadDesktopLatest.mockResolvedValue({
version: '1.1.0',
channel: 'beta',
commit: 'abc1234',
buildTime: 'x',
files: { windows: windowsFile, linux: linuxFile },
});
render(<DesktopAppSettings />);
expect(await screen.findByText('Beta-Ausgabe, Stand abc1234')).toBeInTheDocument();
});
it('Test 3 (keine Pakete): Hinweistext statt Knoepfe, Saetze bleiben', async () => {
loadDesktopLatest.mockResolvedValue(null);
render(<DesktopAppSettings />);
expect(
await screen.findByText('Auf diesem Server sind derzeit keine Desktop-Pakete hinterlegt.'),
).toBeInTheDocument();
expect(screen.queryByTestId('desktop-download-windows')).toBeNull();
expect(screen.queryByTestId('desktop-download-linux')).toBeNull();
expect(
screen.getByText(
'Die Desktop-App öffnet Tessera in einem eigenen Fenster – ohne Browser, mit Symbol im Infobereich der Taskleiste.',
),
).toBeInTheDocument();
});
});