From cd1396e7b800f2adaccb439cb7ee1a8112c1b09b Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sat, 19 Sep 2026 13:56:24 -0400 Subject: [PATCH 1/4] perf(homepage): prepare code examples in the route loader Generate highlighted markup and the compressed playground URL during prerendering with an isolated highlighter. Keep the hero presentational, preserve existing screenshots, verify hydration and navigation, and ratchet the initial JavaScript byte budget down. --- .../__tests__/LandingPageBehavior_.test.res | 37 ++++----- .../LandingPagePlaygroundLoader_.test.res | 31 ++++++++ .../__tests__/visual/LandingPage_.test.res | 2 +- apps/docs/app/routes/LandingPage.res | 4 +- apps/docs/app/routes/LandingPage.resi | 2 +- apps/docs/app/routes/LandingPageRoute.res | 12 ++- apps/docs/app/routes/LandingPageRoute.resi | 6 ++ .../homepage-highlighting.spec.mjs | 76 +++++++++++++++++++ .../src/components/LandingPagePlayground.res | 57 ++++---------- .../src/components/LandingPagePlayground.resi | 9 ++- .../src/data/LandingPagePlaygroundLoader.res | 73 ++++++++++++++++++ .../src/data/LandingPagePlaygroundLoader.resi | 2 + apps/docs/test-utils/LandingPageFixture.res | 36 +++++++++ 13 files changed, 275 insertions(+), 72 deletions(-) create mode 100644 apps/docs/__tests__/LandingPagePlaygroundLoader_.test.res create mode 100644 apps/docs/e2e-playwright/homepage-highlighting.spec.mjs create mode 100644 apps/docs/src/data/LandingPagePlaygroundLoader.res create mode 100644 apps/docs/src/data/LandingPagePlaygroundLoader.resi create mode 100644 apps/docs/test-utils/LandingPageFixture.res diff --git a/apps/docs/__tests__/LandingPageBehavior_.test.res b/apps/docs/__tests__/LandingPageBehavior_.test.res index b159b7c93..1d7b5bb20 100644 --- a/apps/docs/__tests__/LandingPageBehavior_.test.res +++ b/apps/docs/__tests__/LandingPageBehavior_.test.res @@ -1,20 +1,6 @@ open ReactRouter open Vitest -let expectedExample = `module Button = { - @react.component - let make = (~count) => { - let times = switch count { - | 1 => "once" - | 2 => "twice" - | n => n->Int.toString ++ " times" - } - let text = \`Click me $\{times\}\` - - - } -}` - let getOpenGraphImageUrl = () => { switch document->WebAPI.Document.querySelector("meta[property='og:image']") { | Value(meta) => @@ -29,7 +15,7 @@ let getOpenGraphImageUrl = () => { test("landing page Open Graph image targets its absolute page URL", async () => { let _screen = await render( - + , ) let pageUrl = WebAPI.URL.make(~url=Env.root_url) @@ -52,7 +38,7 @@ test( async () => { let screen = await render( - + , ) @@ -82,14 +68,14 @@ test( ->Nullable.toOption expect(decodedCode->Option.isSome)->toBe(true) - expect(decodedCode->Option.getOrThrow)->toBe(expectedExample) + expect(decodedCode->Option.getOrThrow)->toBe(LandingPageFixture.expectedExample) }, ) test("landing page playground hero renders highlighted code tokens", async () => { let screen = await render( - + , ) @@ -111,12 +97,21 @@ test("landing page playground hero renders highlighted code tokens", async () => expect(rescriptCodeBlock.innerHTML->String.includes("toBe(true) expect(javascriptCodeBlock.innerHTML->String.includes("toBe(true) + expect(rescriptCodeBlock.textContent->Null.toOption)->toEqual( + Some(LandingPageFixture.expectedExample), + ) + expect(javascriptCodeBlock.textContent->Null.toOption)->toEqual( + Some(LandingPageFixture.expectedJavascript), + ) + expect(rescriptCodeBlock.className)->toBe("hljs lang-res dark") + expect(javascriptCodeBlock.className)->toBe("hljs lang-js dark") + expect(rescriptCodeBlock->WebAPI.Element.querySelector("button")->Null.toOption)->toEqual(None) }) test("landing page renders its critical sections", async () => { let screen = await render( - + , ) @@ -142,7 +137,7 @@ test("landing page renders its critical sections", async () => { test("landing page copy button shows success feedback", async () => { let screen = await render( - + , ) @@ -160,7 +155,7 @@ test( let screen = await render( - + , ) diff --git a/apps/docs/__tests__/LandingPagePlaygroundLoader_.test.res b/apps/docs/__tests__/LandingPagePlaygroundLoader_.test.res new file mode 100644 index 000000000..b9c1389f8 --- /dev/null +++ b/apps/docs/__tests__/LandingPagePlaygroundLoader_.test.res @@ -0,0 +1,31 @@ +open Vitest + +test( + "homepage example loader produces only serializable highlighted code and its URL", + async () => { + let data = LandingPagePlaygroundLoader.build() + let serialized = data->JSON.stringifyAny->Option.getOrThrow + let decoded = JSON.parseOrThrow(serialized) + let expected = JSON.Object( + Dict.fromArray([ + ("rescriptHtml", JSON.String(data.rescriptHtml)), + ("javascriptHtml", JSON.String(data.javascriptHtml)), + ("playgroundHref", JSON.String(data.playgroundHref)), + ]), + ) + + expect(decoded)->toEqual(expected) + expect(data.rescriptHtml->String.includes("toBe(true) + expect(data.javascriptHtml->String.includes("toBe(true) + let {pathname, searchParams} = WebAPI.URL.make( + ~url=data.playgroundHref, + ~base="https://rescript-lang.org", + ) + let code = searchParams->WebAPI.URLSearchParams.get("code") + + expect(pathname)->toBe("/try") + expect(LzString.lzString.decompressFromEncodedURIComponent(code))->toBe( + LandingPageFixture.expectedExample, + ) + }, +) diff --git a/apps/docs/__tests__/visual/LandingPage_.test.res b/apps/docs/__tests__/visual/LandingPage_.test.res index 3dc6b6e44..cbab54d93 100644 --- a/apps/docs/__tests__/visual/LandingPage_.test.res +++ b/apps/docs/__tests__/visual/LandingPage_.test.res @@ -6,7 +6,7 @@ let snapshotSection = async (~width, ~height, ~sectionTestId, ~screenshotName) = let screen = await render( - + , ) diff --git a/apps/docs/app/routes/LandingPage.res b/apps/docs/app/routes/LandingPage.res index b9a63f28f..2cae95441 100644 --- a/apps/docs/app/routes/LandingPage.res +++ b/apps/docs/app/routes/LandingPage.res @@ -1,5 +1,5 @@ @react.component -let make = () => { +let make = (~playgroundData) => { <> {
- + diff --git a/apps/docs/app/routes/LandingPage.resi b/apps/docs/app/routes/LandingPage.resi index 54ccfceef..8430d7604 100644 --- a/apps/docs/app/routes/LandingPage.resi +++ b/apps/docs/app/routes/LandingPage.resi @@ -1,3 +1,3 @@ /** Use hosted Inter faces within the homepage without changing other routes. */ @react.component -let make: unit => React.element +let make: (~playgroundData: LandingPagePlayground.playgroundData) => React.element diff --git a/apps/docs/app/routes/LandingPageRoute.res b/apps/docs/app/routes/LandingPageRoute.res index 6cd64e3df..fd83445f4 100644 --- a/apps/docs/app/routes/LandingPageRoute.res +++ b/apps/docs/app/routes/LandingPageRoute.res @@ -23,8 +23,14 @@ let links = () => [ fontPreload("/fonts/red-hat-mono-700.woff2"), ] +type loaderData = {playgroundData: LandingPagePlayground.playgroundData} + +let loader: ReactRouter.Loader.t = async _ => { + playgroundData: LandingPagePlaygroundLoader.build(), +} + +@react.component let default = () => { - <> - - + let {playgroundData}: loaderData = ReactRouter.useLoaderData() + } diff --git a/apps/docs/app/routes/LandingPageRoute.resi b/apps/docs/app/routes/LandingPageRoute.resi index 90c8cc322..1c6062553 100644 --- a/apps/docs/app/routes/LandingPageRoute.resi +++ b/apps/docs/app/routes/LandingPageRoute.resi @@ -10,4 +10,10 @@ type fontPreload = { /** Preload above-the-fold faces, including Inter 700 only at the desktop breakpoint. */ let links: unit => array +type loaderData = {playgroundData: LandingPagePlayground.playgroundData} + +/** Prerender the fixed example so highlighting and URL compression stay off the client. */ +let loader: ReactRouter.Loader.t + +@react.component let default: unit => React.element diff --git a/apps/docs/e2e-playwright/homepage-highlighting.spec.mjs b/apps/docs/e2e-playwright/homepage-highlighting.spec.mjs new file mode 100644 index 000000000..2b7af30fa --- /dev/null +++ b/apps/docs/e2e-playwright/homepage-highlighting.spec.mjs @@ -0,0 +1,76 @@ +import { expect, test } from "playwright/test"; +import { JSDOM } from "jsdom"; + +test("initial homepage scripts do not include example preparation or compression", async ({ + request, +}) => { + const response = await request.get("/"); + const { document } = new JSDOM(await response.text()).window; + const scripts = [ + ...document.querySelectorAll('link[rel="modulepreload"][href]'), + ...document.querySelectorAll("script[src]"), + ].map( + (element) => element.getAttribute("href") ?? element.getAttribute("src"), + ); + + expect(response.ok()).toBe(true); + expect(scripts.length).toBeGreaterThan(0); + for (const asset of scripts) { + const script = await request.get(asset); + expect(script.ok()).toBe(true); + const source = await script.text(); + expect(source.includes("compressToEncodedURIComponent"), asset).toBe(false); + expect(source.includes("function Playground$Button(props)"), asset).toBe( + false, + ); + } +}); + +test("prepared examples survive hydration and navigation back from documentation", async ({ + page, + request, +}) => { + const response = await request.get("/"); + const { document } = new JSDOM(await response.text()).window; + const examples = ["res", "js"].map((language) => ({ + selector: `code.lang-${language}`, + html: document.querySelector(`code.lang-${language}`)?.innerHTML, + })); + const playgroundHref = document + .querySelector('a[href^="/try?code="]') + ?.getAttribute("href"); + const runtimeErrors = []; + page.on("pageerror", (error) => runtimeErrors.push(error.message)); + page.on("console", (message) => { + if (message.type() === "error") runtimeErrors.push(message.text()); + }); + + expect(response.ok()).toBe(true); + expect(playgroundHref).toMatch(/^\/try\?code=.+/); + for (const example of examples) { + expect(example.html).toContain(' { - let times = switch count { - | 1 => "once" - | 2 => "twice" - | n => n->Int.toString ++ " times" - } - let text = \`Click me $\{times\}\` - - - } -}`, - js: `import * as JsxRuntime from "react/jsx-runtime"; - -function Playground$Button(props) { - let count = props.count; - let times = count !== 1 ? ( - count !== 2 ? count.toString() + " times" : "twice" - ) : "once"; - let text = "Click me " + times; - return JsxRuntime.jsx("button", { - children: text - }); -} - -let Button = { - make: Playground$Button -}; - -export { - Button, -}`, +type playgroundData = { + rescriptHtml: string, + javascriptHtml: string, + playgroundHref: string, } @react.component -let make = () => { +let make = (~playgroundData: playgroundData) => {
{ {React.string("Write in ReScript")}
-            {HighlightJs.renderHLJS(~darkmode=true, ~code=example.res, ~lang="res", ())}
+            
           
@@ -63,12 +31,15 @@ let make = () => { {React.string("Compile to JavaScript")}
-            {HighlightJs.renderHLJS(~darkmode=true, ~code=example.js, ~lang="js", ())}
+            
           
{React.string("Edit this example in Playground")} diff --git a/apps/docs/src/components/LandingPagePlayground.resi b/apps/docs/src/components/LandingPagePlayground.resi index 1ca44ce26..fa01d2021 100644 --- a/apps/docs/src/components/LandingPagePlayground.resi +++ b/apps/docs/src/components/LandingPagePlayground.resi @@ -1,2 +1,9 @@ +type playgroundData = { + rescriptHtml: string, + javascriptHtml: string, + playgroundHref: string, +} + +/** Render only trusted highlighted markup produced by the homepage loader. */ @react.component -let make: unit => React.element +let make: (~playgroundData: playgroundData) => React.element diff --git a/apps/docs/src/data/LandingPagePlaygroundLoader.res b/apps/docs/src/data/LandingPagePlaygroundLoader.res new file mode 100644 index 000000000..880df806f --- /dev/null +++ b/apps/docs/src/data/LandingPagePlaygroundLoader.res @@ -0,0 +1,73 @@ +type highlighter +type language +type highlightOptions = {language: string} +type highlightResult = {value: string} + +@module("highlight.js/lib/core") @scope("default") +external createHighlighter: unit => highlighter = "newInstance" + +@module("highlight.js/lib/languages/javascript") +external javascript: language = "default" + +@module("highlightjs-rescript") +external rescript: language = "default" + +@send +external registerLanguage: (highlighter, string, language) => unit = "registerLanguage" + +@send +external highlight: (highlighter, string, highlightOptions) => highlightResult = "highlight" + +type example = { + res: string, + js: string, +} + +let example = { + res: `module Button = { + @react.component + let make = (~count) => { + let times = switch count { + | 1 => "once" + | 2 => "twice" + | n => n->Int.toString ++ " times" + } + let text = \`Click me $\{times\}\` + + + } +}`, + js: `import * as JsxRuntime from "react/jsx-runtime"; + +function Playground$Button(props) { + let count = props.count; + let times = count !== 1 ? ( + count !== 2 ? count.toString() + " times" : "twice" + ) : "once"; + let text = "Click me " + times; + return JsxRuntime.jsx("button", { + children: text + }); +} + +let Button = { + make: Playground$Button +}; + +export { + Button, +}`, +} + +let build = (): LandingPagePlayground.playgroundData => { + // Keep prerendering independent of global language registration and other routes. + let highlighter = createHighlighter() + highlighter->registerLanguage("rescript", rescript) + highlighter->registerLanguage("javascript", javascript) + + { + rescriptHtml: (highlighter->highlight(example.res, {language: "rescript"})).value, + javascriptHtml: (highlighter->highlight(example.js, {language: "javascript"})).value, + playgroundHref: `/try?code=${LzString.lzString.compressToEncodedURIComponent(example.res)}`, + } +} diff --git a/apps/docs/src/data/LandingPagePlaygroundLoader.resi b/apps/docs/src/data/LandingPagePlaygroundLoader.resi new file mode 100644 index 000000000..407a5e468 --- /dev/null +++ b/apps/docs/src/data/LandingPagePlaygroundLoader.resi @@ -0,0 +1,2 @@ +/** Build trusted highlighted markup and the share URL for the fixed homepage example. */ +let build: unit => LandingPagePlayground.playgroundData diff --git a/apps/docs/test-utils/LandingPageFixture.res b/apps/docs/test-utils/LandingPageFixture.res new file mode 100644 index 000000000..b2a5ce3e4 --- /dev/null +++ b/apps/docs/test-utils/LandingPageFixture.res @@ -0,0 +1,36 @@ +let expectedExample = `module Button = { + @react.component + let make = (~count) => { + let times = switch count { + | 1 => "once" + | 2 => "twice" + | n => n->Int.toString ++ " times" + } + let text = \`Click me $\{times\}\` + + + } +}` + +let expectedJavascript = `import * as JsxRuntime from "react/jsx-runtime"; + +function Playground$Button(props) { + let count = props.count; + let times = count !== 1 ? ( + count !== 2 ? count.toString() + " times" : "twice" + ) : "once"; + let text = "Click me " + times; + return JsxRuntime.jsx("button", { + children: text + }); +} + +let Button = { + make: Playground$Button +}; + +export { + Button, +}` + +let playgroundData = LandingPagePlaygroundLoader.build() From b55ca730c83285d729b2d3b43810009f98c67c33 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sat, 19 Sep 2026 18:13:31 -0400 Subject: [PATCH 2/4] test(highlighting): port prepared homepage checks to Cypress Retain initial-script exclusions and exact prerender/hydration/navigation markup checks for PR #1355 comment 4054821928. --- .../homepage-highlighting.spec.mjs | 76 ------------------- .../e2e/homepage/homepage-highlighting.cy.js | 47 ++++++++++++ 2 files changed, 47 insertions(+), 76 deletions(-) delete mode 100644 apps/docs/e2e-playwright/homepage-highlighting.spec.mjs create mode 100644 apps/docs/e2e/homepage/homepage-highlighting.cy.js diff --git a/apps/docs/e2e-playwright/homepage-highlighting.spec.mjs b/apps/docs/e2e-playwright/homepage-highlighting.spec.mjs deleted file mode 100644 index 2b7af30fa..000000000 --- a/apps/docs/e2e-playwright/homepage-highlighting.spec.mjs +++ /dev/null @@ -1,76 +0,0 @@ -import { expect, test } from "playwright/test"; -import { JSDOM } from "jsdom"; - -test("initial homepage scripts do not include example preparation or compression", async ({ - request, -}) => { - const response = await request.get("/"); - const { document } = new JSDOM(await response.text()).window; - const scripts = [ - ...document.querySelectorAll('link[rel="modulepreload"][href]'), - ...document.querySelectorAll("script[src]"), - ].map( - (element) => element.getAttribute("href") ?? element.getAttribute("src"), - ); - - expect(response.ok()).toBe(true); - expect(scripts.length).toBeGreaterThan(0); - for (const asset of scripts) { - const script = await request.get(asset); - expect(script.ok()).toBe(true); - const source = await script.text(); - expect(source.includes("compressToEncodedURIComponent"), asset).toBe(false); - expect(source.includes("function Playground$Button(props)"), asset).toBe( - false, - ); - } -}); - -test("prepared examples survive hydration and navigation back from documentation", async ({ - page, - request, -}) => { - const response = await request.get("/"); - const { document } = new JSDOM(await response.text()).window; - const examples = ["res", "js"].map((language) => ({ - selector: `code.lang-${language}`, - html: document.querySelector(`code.lang-${language}`)?.innerHTML, - })); - const playgroundHref = document - .querySelector('a[href^="/try?code="]') - ?.getAttribute("href"); - const runtimeErrors = []; - page.on("pageerror", (error) => runtimeErrors.push(error.message)); - page.on("console", (message) => { - if (message.type() === "error") runtimeErrors.push(message.text()); - }); - - expect(response.ok()).toBe(true); - expect(playgroundHref).toMatch(/^\/try\?code=.+/); - for (const example of examples) { - expect(example.html).toContain(' { + homepageDocument().then((document) => { + const scripts = initialScriptUrls(document); + expect(scripts.length).to.be.greaterThan(0); + for (const asset of scripts) { + cy.request(asset).then(({ status, body }) => { + expect(status, asset).to.equal(200); + expect(body, asset).not.to.include("compressToEncodedURIComponent"); + expect(body, asset).not.to.include("function Playground$Button(props)"); + }); + } + }); +}); + +it("prepared examples survive hydration and navigation back from documentation", () => { + homepageDocument().then((document) => { + const examples = ["res", "js"].map((language) => ({ + selector: `code.lang-${language}`, + html: document.querySelector(`code.lang-${language}`)?.innerHTML, + })); + const playgroundHref = document + .querySelector('a[href^="/try?code="]') + ?.getAttribute("href"); + expect(playgroundHref).to.match(/^\/try\?code=.+/); + for (const example of examples) + expect(example.html).to.include(' Date: Sat, 19 Sep 2026 23:52:40 -0400 Subject: [PATCH 3/4] refactor(highlighting): centralize loader bindings Move the Highlight.js core and homepage language interop into a dedicated bindings module while leaving loader behavior unchanged. --- .../docs/src/bindings/HighlightJsBindings.res | 19 +++++++++++ .../src/data/LandingPagePlaygroundLoader.res | 34 +++++-------------- 2 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 apps/docs/src/bindings/HighlightJsBindings.res diff --git a/apps/docs/src/bindings/HighlightJsBindings.res b/apps/docs/src/bindings/HighlightJsBindings.res new file mode 100644 index 000000000..cbfe747c0 --- /dev/null +++ b/apps/docs/src/bindings/HighlightJsBindings.res @@ -0,0 +1,19 @@ +type t +type language +type highlightOptions = {language: string} +type highlightResult = {value: string} + +@module("highlight.js/lib/core") @scope("default") +external make: unit => t = "newInstance" + +@module("highlight.js/lib/languages/javascript") +external javascript: language = "default" + +@module("highlightjs-rescript") +external rescript: language = "default" + +@send +external registerLanguage: (t, string, language) => unit = "registerLanguage" + +@send +external highlight: (t, string, highlightOptions) => highlightResult = "highlight" diff --git a/apps/docs/src/data/LandingPagePlaygroundLoader.res b/apps/docs/src/data/LandingPagePlaygroundLoader.res index 880df806f..c8f946591 100644 --- a/apps/docs/src/data/LandingPagePlaygroundLoader.res +++ b/apps/docs/src/data/LandingPagePlaygroundLoader.res @@ -1,23 +1,3 @@ -type highlighter -type language -type highlightOptions = {language: string} -type highlightResult = {value: string} - -@module("highlight.js/lib/core") @scope("default") -external createHighlighter: unit => highlighter = "newInstance" - -@module("highlight.js/lib/languages/javascript") -external javascript: language = "default" - -@module("highlightjs-rescript") -external rescript: language = "default" - -@send -external registerLanguage: (highlighter, string, language) => unit = "registerLanguage" - -@send -external highlight: (highlighter, string, highlightOptions) => highlightResult = "highlight" - type example = { res: string, js: string, @@ -61,13 +41,17 @@ export { let build = (): LandingPagePlayground.playgroundData => { // Keep prerendering independent of global language registration and other routes. - let highlighter = createHighlighter() - highlighter->registerLanguage("rescript", rescript) - highlighter->registerLanguage("javascript", javascript) + let highlighter = HighlightJsBindings.make() + highlighter->HighlightJsBindings.registerLanguage("rescript", HighlightJsBindings.rescript) + highlighter->HighlightJsBindings.registerLanguage("javascript", HighlightJsBindings.javascript) { - rescriptHtml: (highlighter->highlight(example.res, {language: "rescript"})).value, - javascriptHtml: (highlighter->highlight(example.js, {language: "javascript"})).value, + rescriptHtml: ( + highlighter->HighlightJsBindings.highlight(example.res, {language: "rescript"}) + ).value, + javascriptHtml: ( + highlighter->HighlightJsBindings.highlight(example.js, {language: "javascript"}) + ).value, playgroundHref: `/try?code=${LzString.lzString.compressToEncodedURIComponent(example.res)}`, } } From 149251f20e53362935268d8dee1dfe44a3bc15be Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sun, 20 Sep 2026 10:16:09 -0400 Subject: [PATCH 4/4] test(homepage): port highlighting Cypress spec to ReScript Keep prerendered highlighting and navigation coverage in the typed Cypress test suite. --- apps/docs/e2e/bindings/Cypress.res | 4 ++ .../e2e/homepage/HomepageHighlighting.cy.res | 61 +++++++++++++++++++ .../e2e/homepage/homepage-highlighting.cy.js | 47 -------------- 3 files changed, 65 insertions(+), 47 deletions(-) create mode 100644 apps/docs/e2e/homepage/HomepageHighlighting.cy.res delete mode 100644 apps/docs/e2e/homepage/homepage-highlighting.cy.js diff --git a/apps/docs/e2e/bindings/Cypress.res b/apps/docs/e2e/bindings/Cypress.res index f306de74d..c104ed58c 100644 --- a/apps/docs/e2e/bindings/Cypress.res +++ b/apps/docs/e2e/bindings/Cypress.res @@ -88,6 +88,9 @@ external shouldValue: (chain, @as("have.value") _, string) => chain, @as("have.attr") _, string, string) => chain = "should" +@send +external shouldProperty: (chain, @as("have.prop") _, string, string) => chain = + "should" @send external attribute: (chain, @as("attr") _, string) => chain = "invoke" @send external propertyInt: (chain<'a>, string) => chain = "its" @send external shouldSatisfy: (chain<'a>, 'a => unit) => chain<'a> = "should" @@ -125,6 +128,7 @@ type parser @get external documentElement: Dom.document => Dom.element = "documentElement" @get external currentScript: Dom.document => Null.t = "currentScript" @get external textContent: Dom.element => Null.t = "textContent" +@get external innerHTML: Dom.element => string = "innerHTML" @get external outerHTML: Dom.element => string = "outerHTML" @send external getAttribute: (Dom.element, string) => Null.t = "getAttribute" diff --git a/apps/docs/e2e/homepage/HomepageHighlighting.cy.res b/apps/docs/e2e/homepage/HomepageHighlighting.cy.res new file mode 100644 index 000000000..fa6a82b36 --- /dev/null +++ b/apps/docs/e2e/homepage/HomepageHighlighting.cy.res @@ -0,0 +1,61 @@ +open Cypress +open HomepageHelpers + +it("initial homepage scripts do not include example preparation or compression", () => { + homepageDocument(document => { + let scripts = document->initialScriptUrls + expect(scripts->Array.length)->greaterThan(0) + scripts->Array.forEach( + asset => + request(asset) + ->then( + response => { + expect(response.status, ~message=asset)->equal(200) + expect(response.body, ~message=asset)->notInclude("compressToEncodedURIComponent") + expect(response.body, ~message=asset)->notInclude("function Playground$Button(props)") + }, + ) + ->ignore, + ) + }) +}) + +it("prepared examples survive hydration and navigation back from documentation", () => { + homepageDocument(document => { + let examples = ["res", "js"]->Array.map( + language => { + let selector = `code.lang-${language}` + let html = + document + ->querySelector(selector) + ->Null.toOption + ->Option.map(element => element->innerHTML) + ->Option.getOrThrow + (selector, html) + }, + ) + let playgroundHref = + document + ->querySelector(`a[href^="/try?code="]`) + ->Null.toOption + ->Option.flatMap(element => element->getAttribute("href")->Null.toOption) + ->Option.getOrThrow + expect(playgroundHref)->match_(/^\/try\?code=.+/) + examples->Array.forEach(((_, html)) => expect(html)->include_(`Array.forEach( + ((selector, html)) => get(selector)->shouldProperty("innerHTML", html)->ignore, + ) + containsInRegex("a", /^Docs$/)->click->ignore + cyLocation("pathname")->shouldEqual("/docs/manual/introduction")->ignore + get(`a[aria-label="homepage"]`)->click->ignore + cyLocation("pathname")->shouldEqual("/")->ignore + examples->Array.forEach( + ((selector, html)) => + get(selector)->should("be.visible")->shouldProperty("innerHTML", html)->ignore, + ) + containsIn("a", "Edit this example in Playground") + ->shouldAttribute("href", playgroundHref) + ->ignore + }) +}) diff --git a/apps/docs/e2e/homepage/homepage-highlighting.cy.js b/apps/docs/e2e/homepage/homepage-highlighting.cy.js deleted file mode 100644 index 3cefcc547..000000000 --- a/apps/docs/e2e/homepage/homepage-highlighting.cy.js +++ /dev/null @@ -1,47 +0,0 @@ -import { homepageDocument, initialScriptUrls } from "./helpers.js"; - -it("initial homepage scripts do not include example preparation or compression", () => { - homepageDocument().then((document) => { - const scripts = initialScriptUrls(document); - expect(scripts.length).to.be.greaterThan(0); - for (const asset of scripts) { - cy.request(asset).then(({ status, body }) => { - expect(status, asset).to.equal(200); - expect(body, asset).not.to.include("compressToEncodedURIComponent"); - expect(body, asset).not.to.include("function Playground$Button(props)"); - }); - } - }); -}); - -it("prepared examples survive hydration and navigation back from documentation", () => { - homepageDocument().then((document) => { - const examples = ["res", "js"].map((language) => ({ - selector: `code.lang-${language}`, - html: document.querySelector(`code.lang-${language}`)?.innerHTML, - })); - const playgroundHref = document - .querySelector('a[href^="/try?code="]') - ?.getAttribute("href"); - expect(playgroundHref).to.match(/^\/try\?code=.+/); - for (const example of examples) - expect(example.html).to.include('