Themenskript aus dem Hydrierungsvergleich nehmen
Der Browser leert das Nonce-Attribut, sobald er das Element geparst hat — so schreibt es die CSP-Spezifikation vor, damit kein Skript den Wert auslesen und sich damit selbst nachladen kann. React vergleicht beim Hydrieren trotzdem das Attribut, liest clientseitig den leeren Wert und meldet einen Unterschied, den es nicht gibt. suppressHydrationWarning an diesem einen Element sagt React, dass hier nichts zu vergleichen ist. Das Nonce selbst bleibt: ohne es blockiert die Richtlinie das Skript, und ohne das Skript blitzt beim Laden das falsche Thema auf. Nachgewiesen gegen einen Entwicklungsserver: ohne das Attribut erscheint genau die gemeldete Konsolenmeldung, mit ihm nicht. Ein erster Versuch, das per Konsolenprüfung im e2e abzusichern, war auch ohne den Fix grün — React meldet den Unterschied nur im Entwicklungsmodus, der Testlauf nutzt den Produktionsbau. Die Prüfung ist wieder entfernt; eine, die nicht anschlägt, ist schlimmer als keine. Stattdessen prüft tests/thema-vorab.test.ts die drei Dinge am Skript, die zusammenpassen müssen: Nonce, Hydrierungsausnahme und derselbe Speicherschlüssel wie im Umschalter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
73b6dc820e
commit
2fb120dd77
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
/**
|
||||
* Das Skript, das das Thema vor dem ersten Malen setzt, hängt an drei Dingen,
|
||||
* die zusammenpassen müssen. Fällt eines weg, merkt es niemand sofort:
|
||||
*
|
||||
* 1. **Der Nonce.** Ohne ihn blockiert die Inhaltsrichtlinie das Skript, und
|
||||
* beim Laden blitzt das falsche Thema auf. Sichtbar, aber leicht als
|
||||
* Kleinigkeit abgetan.
|
||||
* 2. **`suppressHydrationWarning`.** Der Browser leert das Nonce-Attribut
|
||||
* nach dem Parsen, React vergleicht es beim Hydrieren trotzdem und meldet
|
||||
* einen Unterschied, den es nicht gibt. Die Meldung steht nur in der
|
||||
* Konsole des Entwicklungsservers — im Produktionsbau meldet React sie
|
||||
* nicht, ein e2e-Lauf kann sie also nicht auffangen. Deshalb hier auf der
|
||||
* Ebene der Quelle.
|
||||
* 3. **Der Schlüssel im Speicher**, derselbe wie im Umschalter. Weichen sie
|
||||
* ab, wird die Wahl bei jedem Laden vergessen.
|
||||
*/
|
||||
const layout = readFileSync(join(process.cwd(), 'app/layout.tsx'), 'utf8')
|
||||
|
||||
describe('Thema vor dem ersten Malen', () => {
|
||||
it('gibt dem Skript einen Nonce und nimmt es aus dem Hydrierungsvergleich', () => {
|
||||
const tag = layout.slice(layout.indexOf('<script'), layout.indexOf('THEME_SCRIPT }} />'))
|
||||
|
||||
expect(tag).toContain('nonce={nonce}')
|
||||
expect(tag).toContain('suppressHydrationWarning')
|
||||
})
|
||||
|
||||
it('benutzt denselben Speicherschlüssel wie der Umschalter', () => {
|
||||
const toggle = readFileSync(join(process.cwd(), 'components/ThemeToggle.tsx'), 'utf8')
|
||||
|
||||
// Der Schlüssel wird importiert, nicht abgeschrieben — genau das prüft das
|
||||
// hier: eine zweite, abgeschriebene Zeichenkette wäre der Fehler.
|
||||
expect(toggle).toMatch(/export const THEME_KEY = '([^']+)'/)
|
||||
expect(layout).toContain("import { THEME_KEY } from '@/components/ThemeToggle'")
|
||||
expect(layout).toContain('JSON.stringify(THEME_KEY)')
|
||||
})
|
||||
|
||||
it('setzt nur die beiden ausdrücklichen Themen, nie „wie das System"', () => {
|
||||
// "system" darf kein data-theme setzen, sonst gewinnt es über die
|
||||
// Medienabfrage und der Nutzer sitzt im falschen Thema.
|
||||
expect(layout).toContain("t==='dark'||t==='light'")
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user