-
Notifications
You must be signed in to change notification settings - Fork 43
fix(web): премахване на hydration несъответствието на всяка страница (#274) #300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
bf8da41
7198cad
64edcb3
ebfd417
515dba0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| // The nonce mismatch is created by ServerRouter's FrameworkContext, but reproducing that | ||
| // server/client split needs Router internals (a manifest and data-router state) rather than the | ||
| // app's rendering harness. Keep this regression test structural: it protects the two exact | ||
| // Layout props that make the server and client markup agree without coupling to those internals. | ||
| import ts from 'typescript'; | ||
| import { describe, expect, it } from 'vitest'; | ||
| // Read root.tsx as a raw string via Vite's `?raw` import (typed by vite/client) rather than node:fs — | ||
| // apps/web test files are typechecked under the Workers config (tsconfig.cloudflare.json), which has no | ||
| // Node types, so `node:fs`/`node:url` would not resolve. | ||
| import rootRaw from './root.tsx?raw'; | ||
|
|
||
| const rootSource = ts.createSourceFile( | ||
| 'root.tsx', | ||
| rootRaw, | ||
| ts.ScriptTarget.Latest, | ||
| true, | ||
| ts.ScriptKind.TSX, | ||
| ); | ||
|
|
||
| const layout = rootSource.statements.find( | ||
| (statement): statement is ts.FunctionDeclaration => | ||
| ts.isFunctionDeclaration(statement) && statement.name?.text === 'Layout', | ||
| ); | ||
|
|
||
| function layoutElements(tagName: string): ts.JsxOpeningLikeElement[] { | ||
| const elements: ts.JsxOpeningLikeElement[] = []; | ||
| const visit = (node: ts.Node) => { | ||
| const element = ts.isJsxElement(node) | ||
| ? node.openingElement | ||
| : ts.isJsxSelfClosingElement(node) | ||
| ? node | ||
| : undefined; | ||
| if (element?.tagName.getText(rootSource) === tagName) { | ||
| elements.push(element); | ||
| } | ||
| ts.forEachChild(node, visit); | ||
| }; | ||
| ts.forEachChild(layout!, visit); | ||
| return elements; | ||
| } | ||
|
|
||
| function attribute(element: ts.JsxOpeningLikeElement, name: string): ts.JsxAttribute | undefined { | ||
| return element.attributes.properties.find( | ||
| (property): property is ts.JsxAttribute => | ||
| ts.isJsxAttribute(property) && property.name.getText(rootSource) === name, | ||
| ); | ||
| } | ||
|
|
||
| describe('root Layout hydration guards', () => { | ||
| it('passes an explicit empty nonce to Links', () => { | ||
| expect(layout).toBeDefined(); | ||
| const links = layoutElements('Links'); | ||
| expect(links[0]).toBeDefined(); | ||
| const nonce = attribute(links[0]!, 'nonce'); | ||
| expect( | ||
| nonce?.initializer && ts.isStringLiteral(nonce.initializer) && nonce.initializer.text, | ||
| ).toBe(''); | ||
| }); | ||
|
|
||
| it('suppresses hydration warnings on body attributes', () => { | ||
| expect(layout).toBeDefined(); | ||
| const bodies = layoutElements('body'); | ||
| expect(bodies[0]).toBeDefined(); | ||
| const suppressHydrationWarning = attribute(bodies[0]!, 'suppressHydrationWarning'); | ||
| expect(suppressHydrationWarning).toBeDefined(); | ||
| expect(suppressHydrationWarning?.initializer).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('keeps body hydration suppression scoped to its only attribute', () => { | ||
| expect(layout).toBeDefined(); | ||
| const bodies = layoutElements('body'); | ||
| expect(bodies[0]).toBeDefined(); | ||
|
|
||
| const attributes = bodies[0]!.attributes.properties; | ||
| expect(attributes).toHaveLength(1); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Тази проверка |
||
| expect(ts.isJsxAttribute(attributes[0]!)).toBe(true); | ||
| expect((attributes[0]! as ts.JsxAttribute).name.getText(rootSource)).toBe( | ||
| 'suppressHydrationWarning', | ||
| ); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Тестът валидира само първия
<Links>елемент (links[0]). Ако намерението е инвариантата да важи за всеки<Links>вLayout, обмислете проверка върху всички намерени елементи (напр. итерация презlinks). В текущия код има само един, така че това е само превантивна бележка за устойчивост при бъдещи промени.