fix(quick-260921-jt4): verschwendete Kalender-Abrufe abgestellt, t-Identitaet gemessen statt behauptet
Restposten 3a (doppeltes Ladefenster): lastFetchWindowRef merkt sich das
zuletzt TATSAECHLICH geholte from/to-Paar; ein Monatswechsel, dessen
berechnetes Fenster damit uebereinstimmt, ueberspringt fetchEvents. Der
5-Minuten-Auffrischer (force=true) umgeht den Sperrgriff immer, sonst
friert die Anzeige ein. computeFetchWindow und die Tagesgrenzen-Rundung
bleiben unangetastet.
Restposten 3b (Quellenliste je Monatswechsel): hasSourcesRef merkt sich
das Ergebnis; fetchSources laeuft nur beim Aufbau (Merkung leer) oder
erzwungen (Auffrischer) — eine neu eingerichtete Quelle wird weiterhin
binnen fuenf Minuten bemerkt.
Restposten 4 (t-Identitaet): neue translations-identity.test.tsx rendert
eine Testkomponente unter dem ECHTEN NextIntlClientProvider und beweist
per Referenzgleichheit, dass t bei einem lokalen Zustandswechsel
dasselbe Funktionsobjekt bleibt — bestaetigt durch den use-intl-4.13.0-
Quelltext (translate entsteht in einem useMemo, dessen Abhaengigkeiten
ausschliesslich aus dem root-staendigen Intl-Kontext stammen). Die
Faustregel aus 260921-gof ("t gehoert in keine Abhaengigkeitsliste")
bleibt als Konvention in Ordnung; die zugrunde liegende Annahme ("t ist
bei jedem Render frisch") ist damit ausdruecklich WIDERLEGT statt ein
drittes Mal weitergetragen. Die vier verbliebenen Stellen
(marketplace/page.tsx, admin/users/page.tsx,
calendar-settings-panel.tsx, calendar-source-form.tsx) bleiben deshalb
unveraendert.
Vier neue zaehlende Testfaelle in calendar-widget.test.tsx (Aufbau je 1,
abweichendes Fenster +1/+0, identisches Fenster +0/+0, erzwungener Lauf
+1/+1) plus ein Test, dass ein uebersprungener Lauf den Ladezustand
sauber beendet und geladene Termine nicht leert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { act, cleanup, render } from '@testing-library/react';
|
||||
import { NextIntlClientProvider, useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
/**
|
||||
* translations-identity.test.tsx (Restposten 4, quick-260921-jt4)
|
||||
*
|
||||
* Prueft am ECHTEN `NextIntlClientProvider` — nicht behauptet, sondern
|
||||
* gemessen —, ob `t` aus `useTranslations` bei einem Zustandswechsel EINER
|
||||
* Komponente seine Identitaet behaelt. Der Quelltext der eingesetzten
|
||||
* `use-intl`-Fassung (4.13.0, `react.js`) zeigt: `translate` entsteht in
|
||||
* einem `useMemo`, dessen Abhaengigkeiten ausschliesslich aus dem
|
||||
* Intl-Kontext stammen (`cache, formatters, getMessageFallback, allMessages,
|
||||
* namespace, onError, globalFormats, locale, timeZone`) — keine davon haengt
|
||||
* vom lokalen Zustand einer Komponente ab. Der Anbieter (`NextIntlClientProvider`)
|
||||
* steht in `app/layout.tsx`, also oberhalb aller Komponenten dieses Projekts;
|
||||
* ein Zustandswechsel in einer Kindkomponente rendert den Anbieter nicht neu.
|
||||
*
|
||||
* Dieser eine Test entscheidet die Annahme aus quick-260921-gof
|
||||
* ("`t` ist bei jedem Render eine frische Funktion") an der Wurzel statt an
|
||||
* vier einzelnen Symptomstellen.
|
||||
*/
|
||||
|
||||
const messages = {
|
||||
widgets: {
|
||||
calendar: {
|
||||
name: 'Kalender',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function ProbeComponent({ onRender }: { onRender: (t: unknown) => void }) {
|
||||
const t = useTranslations('widgets');
|
||||
const [, forceRerender] = useState(0);
|
||||
onRender(t);
|
||||
|
||||
return (
|
||||
<button type="button" onClick={() => forceRerender((n) => n + 1)}>
|
||||
{t('calendar.name')}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe('t aus useTranslations — Identitaet ueber einen Zustandswechsel (Restposten 4)', () => {
|
||||
it('bleibt beim erneuten Render derselben Komponente dasselbe Funktionsobjekt, wenn NUR lokaler Zustand wechselt', () => {
|
||||
const seenTranslators: unknown[] = [];
|
||||
const { getByRole } = render(
|
||||
<NextIntlClientProvider locale="de" messages={messages} timeZone="Europe/Berlin">
|
||||
<ProbeComponent onRender={(t) => seenTranslators.push(t)} />
|
||||
</NextIntlClientProvider>,
|
||||
);
|
||||
|
||||
expect(seenTranslators).toHaveLength(1);
|
||||
const tBeforeRerender = seenTranslators[0];
|
||||
|
||||
act(() => {
|
||||
getByRole('button').click();
|
||||
});
|
||||
|
||||
expect(seenTranslators).toHaveLength(2);
|
||||
const tAfterRerender = seenTranslators[1];
|
||||
|
||||
// Der gemessene Kern der Annahme: dasselbe Objekt, nicht nur denselben
|
||||
// Rueckgabewert bei Aufruf — ein `toBe` (Referenzgleichheit), kein `toEqual`.
|
||||
expect(tAfterRerender).toBe(tBeforeRerender);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user