feat(web): Einstellungen → Desktop-App zeigt in der App den verbundenen Server
- DesktopAppSettings rendert im Desktop-Client (useIsDesktopClient) einen Block "Verbunden mit: {origin}" plus Hinweis auf "Server-Adresse ändern…" im Infobereich-Menü; im Browser bleibt der Block weg
- Neue i18n-Schlüssel settings.desktop.connectedTo/changeHint in de.json und en.json
- Test 4 (Cookie tessera_desktop=1) und Test 5 (ohne Cookie) ergänzt; RED zuerst (Test 4 schlug auf der Zielbehauptung fehl), dann GREEN
- Alle 431 Web-Tests und type-check grün
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { cleanup, render, screen } from '@testing-library/react';
|
import { act, cleanup, render, screen } from '@testing-library/react';
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { DESKTOP_COOKIE_NAME } from '@/lib/desktop-client';
|
||||||
import { DesktopAppSettings } from './desktop-app-settings';
|
import { DesktopAppSettings } from './desktop-app-settings';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,9 +35,14 @@ vi.mock('@/lib/desktop', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function clearDesktopCookie() {
|
||||||
|
document.cookie = `${DESKTOP_COOKIE_NAME}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
||||||
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
cleanup();
|
cleanup();
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
clearDesktopCookie();
|
||||||
});
|
});
|
||||||
|
|
||||||
const windowsFile = {
|
const windowsFile = {
|
||||||
@@ -102,4 +108,24 @@ describe('DesktopAppSettings (18-03)', () => {
|
|||||||
),
|
),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Test 4 (im Desktop-Client): zeigt verbundenen Server und Änderungshinweis', async () => {
|
||||||
|
document.cookie = `${DESKTOP_COOKIE_NAME}=1; path=/`;
|
||||||
|
loadDesktopLatest.mockResolvedValue(null);
|
||||||
|
render(<DesktopAppSettings />);
|
||||||
|
|
||||||
|
expect(await screen.findByText('Verbunden mit: http://localhost:3000')).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText('Ändern über das Tessera-Symbol im Infobereich → „Server-Adresse ändern…“'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('desktop-connected')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 5 (im Browser): kein Block ohne Desktop-Cookie', async () => {
|
||||||
|
loadDesktopLatest.mockResolvedValue(null);
|
||||||
|
render(<DesktopAppSettings />);
|
||||||
|
await act(async () => {});
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('desktop-connected')).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useLocale, useTranslations } from 'next-intl';
|
import { useLocale, useTranslations } from 'next-intl';
|
||||||
import { desktopDownloadUrl, type DesktopFileInfo, type DesktopLatestInfo, formatFileSize, loadDesktopLatest } from '@/lib/desktop';
|
import { desktopDownloadUrl, type DesktopFileInfo, type DesktopLatestInfo, formatFileSize, loadDesktopLatest } from '@/lib/desktop';
|
||||||
|
import { useIsDesktopClient } from '@/lib/desktop-client';
|
||||||
|
|
||||||
const BUTTON_CLASS =
|
const BUTTON_CLASS =
|
||||||
'inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground hover:opacity-90';
|
'inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground hover:opacity-90';
|
||||||
@@ -59,11 +60,17 @@ function DownloadButton({
|
|||||||
/**
|
/**
|
||||||
* Inhalt der Einstellungsseite "Desktop-App" (D-12): vier erklaerende
|
* Inhalt der Einstellungsseite "Desktop-App" (D-12): vier erklaerende
|
||||||
* Saetze, dann Version/Downloads oder ein Hinweis, wenn der Server keine
|
* Saetze, dann Version/Downloads oder ein Hinweis, wenn der Server keine
|
||||||
* Pakete traegt (`loadDesktopLatest()` liefert `null`).
|
* Pakete traegt (`loadDesktopLatest()` liefert `null`). Im Desktop-Client
|
||||||
|
* (260917-jn2) zusaetzlich ein Block "Verbunden mit: {origin}" -- `isDesktop`
|
||||||
|
* ist im Server-HTML und im ersten Client-Render false (Hook mit
|
||||||
|
* `useEffect`), der Block rendert also erst nach der Hydration, weshalb
|
||||||
|
* `window.location.origin` hier direkt gelesen werden darf (kein
|
||||||
|
* Hydration-Fehler).
|
||||||
*/
|
*/
|
||||||
export function DesktopAppSettings() {
|
export function DesktopAppSettings() {
|
||||||
const t = useTranslations('settings');
|
const t = useTranslations('settings');
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
|
const isDesktop = useIsDesktopClient();
|
||||||
const [info, setInfo] = useState<DesktopLatestInfo | null | undefined>(undefined);
|
const [info, setInfo] = useState<DesktopLatestInfo | null | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -85,6 +92,16 @@ export function DesktopAppSettings() {
|
|||||||
<p className="text-sm text-muted-foreground">{t('desktop.tray')}</p>
|
<p className="text-sm text-muted-foreground">{t('desktop.tray')}</p>
|
||||||
<p className="text-sm text-muted-foreground">{t('desktop.update')}</p>
|
<p className="text-sm text-muted-foreground">{t('desktop.update')}</p>
|
||||||
|
|
||||||
|
{isDesktop && (
|
||||||
|
<div
|
||||||
|
data-testid="desktop-connected"
|
||||||
|
className="mt-4 space-y-1 rounded border border-border bg-muted/30 p-3 text-sm"
|
||||||
|
>
|
||||||
|
<p className="text-foreground">{t('desktop.connectedTo', { origin: window.location.origin })}</p>
|
||||||
|
<p className="text-muted-foreground">{t('desktop.changeHint')}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{info === null && <p className="mt-4 text-sm text-muted-foreground">{t('desktop.unavailable')}</p>}
|
{info === null && <p className="mt-4 text-sm text-muted-foreground">{t('desktop.unavailable')}</p>}
|
||||||
|
|
||||||
{info && (
|
{info && (
|
||||||
|
|||||||
@@ -169,7 +169,9 @@
|
|||||||
"downloadWindows": "Für Windows herunterladen",
|
"downloadWindows": "Für Windows herunterladen",
|
||||||
"downloadLinux": "Für Linux herunterladen",
|
"downloadLinux": "Für Linux herunterladen",
|
||||||
"fileInfo": "{name} · {size}",
|
"fileInfo": "{name} · {size}",
|
||||||
"unavailable": "Auf diesem Server sind derzeit keine Desktop-Pakete hinterlegt."
|
"unavailable": "Auf diesem Server sind derzeit keine Desktop-Pakete hinterlegt.",
|
||||||
|
"connectedTo": "Verbunden mit: {origin}",
|
||||||
|
"changeHint": "Ändern über das Tessera-Symbol im Infobereich → „Server-Adresse ändern…“"
|
||||||
},
|
},
|
||||||
"sourceDeleteConfirm": "Möchten Sie diese Kalenderquelle wirklich löschen?",
|
"sourceDeleteConfirm": "Möchten Sie diese Kalenderquelle wirklich löschen?",
|
||||||
"sourceDeleteCta": "Quelle löschen",
|
"sourceDeleteCta": "Quelle löschen",
|
||||||
|
|||||||
@@ -169,7 +169,9 @@
|
|||||||
"downloadWindows": "Download for Windows",
|
"downloadWindows": "Download for Windows",
|
||||||
"downloadLinux": "Download for Linux",
|
"downloadLinux": "Download for Linux",
|
||||||
"fileInfo": "{name} · {size}",
|
"fileInfo": "{name} · {size}",
|
||||||
"unavailable": "No desktop packages are available on this server yet."
|
"unavailable": "No desktop packages are available on this server yet.",
|
||||||
|
"connectedTo": "Connected to: {origin}",
|
||||||
|
"changeHint": "To change it, use the Tessera icon in the notification area → “Change server address…”"
|
||||||
},
|
},
|
||||||
"sourceDeleteConfirm": "Are you sure you want to delete this calendar source?",
|
"sourceDeleteConfirm": "Are you sure you want to delete this calendar source?",
|
||||||
"sourceDeleteCta": "Delete source",
|
"sourceDeleteCta": "Delete source",
|
||||||
|
|||||||
Reference in New Issue
Block a user