diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 30934211c..e6a8c4a08 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -47,8 +47,3 @@ jobs: run: yarn playwright install --with-deps - name: Vitest run: yarn ci:test - - name: Commit and Push changes - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: "chore: update vitest screenshots [skip ci]" - file_pattern: "**/__screenshots__/**/*.png" diff --git a/AGENTS.md b/AGENTS.md index eef55bc62..539a7ea1e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,6 +52,7 @@ __tests__/ → Vitest browser-mode tests (Playwright) - `.res` files must always be capitalized (PascalCase), matching ReScript module conventions. - Use the pipe-first operator (`->`) for chaining, which is idiomatic ReScript. - Resolve all warnings and treat them as errors. The project has `"error": "+8"` in `rescript.json`. +- Never directly edit an `.jsx` files, you must edit the corresponding `.res` file. The `.jsx` files are generated by the ReScript compiler and will be overwritten on the next compile. ## ReScript Rules @@ -64,6 +65,9 @@ __tests__/ → Vitest browser-mode tests (Playwright) - Output format is ES modules with `.jsx` suffix, compiled in-source (`.jsx` files sit alongside `.res` files). - Reference the abridged documentation for clarification on how ReScript's APIs work: https://rescript-lang.org/llms/manual/llm-small.txt - If you need more information you can access the full documentation, but do this only when needed as the docs are very large: https://rescript-lang.org/llms/manual/llm-full.txt +- Never use `%raw` unless you are specifically asked to +- Never use `Object.magic` +- Don't add type annotations unless necessary for clarity or to resolve an error. ReScript's type inference is powerful, and often explicit annotations are not needed. ### ReScript Dependencies @@ -178,8 +182,10 @@ let default: unit => React.element }) ``` -- Components requiring React Router context must be wrapped in ``. -- Run tests with `yarn vitest`. +- Components requiring React Router context must be wrapped in ``. +- Do not use `` in tests. +- Run tests with `yarn vitest --browser.headless --run`. +- Do not update snapshots without asking for confirmation. ## MDX Content diff --git a/README.md b/README.md index 692d71a94..5cc3edbbe 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,27 @@ yarn dev ## Run Tests +### Unit Tests (Vitest) + +We use [Vitest](https://vitest.dev/) with browser mode (Playwright) for component-level unit tests. Test files live in `__tests__/` and are written in ReScript. + +```sh +# Run tests in watch mode (headed browser) +yarn vitest + +# Run tests once in headless mode (same as CI) +yarn ci:test +``` + +**Updating screenshots:** Screenshot baselines should only be updated in headless mode so they match CI and stay consistent across devices. Use the dedicated command: + +```sh +yarn vitest:update +``` + +This runs the full suite headlessly with the `--update` flag, regenerating any screenshot baselines that have changed. Commit the updated `.png` files alongside your code changes. +Please be selective in pushing up changes to screenshots and only update files that you have added or expected to change. Pushing up all changes can make it hard to review PRs with small image differences based on different devices or environments that wouldn't trigger failures in CI. + ### Markdown Codeblock Tests We check the validity of our code examples marked with: diff --git a/__tests__/MarkdownComponents_.test.res b/__tests__/MarkdownComponents_.test.res index 2d7a21efd..62ffc75c1 100644 --- a/__tests__/MarkdownComponents_.test.res +++ b/__tests__/MarkdownComponents_.test.res @@ -203,12 +203,14 @@ test("renders Image with caption", async () => { let screen = await render(
, ) - let caption = await screen->getByText("The ReScript logo") + let caption = await screen->getByText("A sample image caption") await element(caption)->toBeVisible let wrapper = await screen->getByTestId("image-wrapper") @@ -226,9 +228,6 @@ test("renders Video with caption", async () => { let caption = await screen->getByText("A sample video") await element(caption)->toBeVisible - - let wrapper = await screen->getByTestId("video-wrapper") - await element(wrapper)->toMatchScreenshot("markdown-video") }) test("renders horizontal rule", async () => { diff --git a/__tests__/NavbarSecondary_.test.res b/__tests__/NavbarSecondary_.test.res index 4746c83ca..796b30a99 100644 --- a/__tests__/NavbarSecondary_.test.res +++ b/__tests__/NavbarSecondary_.test.res @@ -5,9 +5,9 @@ test("desktop secondary navbar shows all doc section links", async () => { await viewport(1440, 500) let screen = await render( - + - , +
, ) let navbar = await screen->getByTestId("navbar-secondary") @@ -24,9 +24,9 @@ test("mobile secondary navbar shows all links", async () => { await viewport(600, 500) let screen = await render( - + - , + , ) let navbar = await screen->getByTestId("navbar-secondary") diff --git a/__tests__/__screenshots__/ApiLayout_.test.jsx/desktop-API-layout-shows-sidebar-categories-and-version-select-1.png b/__tests__/__screenshots__/ApiLayout_.test.jsx/desktop-API-layout-shows-sidebar-categories-and-version-select-1.png deleted file mode 100644 index b751321f8..000000000 Binary files a/__tests__/__screenshots__/ApiLayout_.test.jsx/desktop-API-layout-shows-sidebar-categories-and-version-select-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/ApiLayout_.test.jsx/mobile-API-layout-hides-sidebar-1.png b/__tests__/__screenshots__/ApiLayout_.test.jsx/mobile-API-layout-hides-sidebar-1.png deleted file mode 100644 index 80c3ddc0c..000000000 Binary files a/__tests__/__screenshots__/ApiLayout_.test.jsx/mobile-API-layout-hides-sidebar-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/ApiLayout_.test.jsx/old-docs-warning-shows-version-info-1.png b/__tests__/__screenshots__/ApiLayout_.test.jsx/old-docs-warning-shows-version-info-1.png deleted file mode 100644 index f993aad86..000000000 Binary files a/__tests__/__screenshots__/ApiLayout_.test.jsx/old-docs-warning-shows-version-info-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-API-overview-shows-all-category-items-1.png b/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-API-overview-shows-all-category-items-1.png deleted file mode 100644 index e83429f07..000000000 Binary files a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-API-overview-shows-all-category-items-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-API-overview-shows-sidebar-categories-and-content-1.png b/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-API-overview-shows-sidebar-categories-and-content-1.png deleted file mode 100644 index bd9d90404..000000000 Binary files a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-API-overview-shows-sidebar-categories-and-content-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/mobile-API-overview-hides-sidebar-1.png b/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/mobile-API-overview-hides-sidebar-1.png deleted file mode 100644 index 3f1b3f67b..000000000 Binary files a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/mobile-API-overview-hides-sidebar-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/tablet-API-overview-1.png b/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/tablet-API-overview-1.png deleted file mode 100644 index 72019c22f..000000000 Binary files a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/tablet-API-overview-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Banner_.test.jsx/mobile-banner-1.png b/__tests__/__screenshots__/Banner_.test.jsx/mobile-banner-1.png deleted file mode 100644 index 70886689f..000000000 Binary files a/__tests__/__screenshots__/Banner_.test.jsx/mobile-banner-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Banner_.test.jsx/renders-banner-with-content-1.png b/__tests__/__screenshots__/Banner_.test.jsx/renders-banner-with-content-1.png deleted file mode 100644 index ac79539d8..000000000 Binary files a/__tests__/__screenshots__/Banner_.test.jsx/renders-banner-with-content-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-archived-blog-article-shows-warning-banner-1.png b/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-archived-blog-article-shows-warning-banner-1.png deleted file mode 100644 index 22cb38495..000000000 Binary files a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-archived-blog-article-shows-warning-banner-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-renders-header--author--date--and-body-1.png b/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-renders-header--author--date--and-body-1.png deleted file mode 100644 index 4eda750d1..000000000 Binary files a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-renders-header--author--date--and-body-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-article-image-shows-image-1.png b/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-article-image-shows-image-1.png deleted file mode 100644 index 7af51cf47..000000000 Binary files a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-article-image-shows-image-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-co-authors-shows-all-authors-1.png b/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-co-authors-shows-all-authors-1.png deleted file mode 100644 index 185153fcb..000000000 Binary files a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-co-authors-shows-all-authors-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-without-description-1.png b/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-without-description-1.png deleted file mode 100644 index 4c2cc165d..000000000 Binary files a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-without-description-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/mobile-blog-article-1.png b/__tests__/__screenshots__/BlogArticle_.test.jsx/mobile-blog-article-1.png deleted file mode 100644 index 5912562dd..000000000 Binary files a/__tests__/__screenshots__/BlogArticle_.test.jsx/mobile-blog-article-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-shows-archived-posts-1.png b/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-shows-archived-posts-1.png deleted file mode 100644 index 1e1455afe..000000000 Binary files a/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-shows-archived-posts-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-with-single-post-1.png b/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-with-single-post-1.png deleted file mode 100644 index c0fe2ebc1..000000000 Binary files a/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-with-single-post-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Button_.test.jsx/renders-PrimaryBlue-button-1.png b/__tests__/__screenshots__/Button_.test.jsx/renders-PrimaryBlue-button-1.png deleted file mode 100644 index f96acbe35..000000000 Binary files a/__tests__/__screenshots__/Button_.test.jsx/renders-PrimaryBlue-button-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Button_.test.jsx/renders-PrimaryRed-button-1.png b/__tests__/__screenshots__/Button_.test.jsx/renders-PrimaryRed-button-1.png deleted file mode 100644 index 23ce58ce2..000000000 Binary files a/__tests__/__screenshots__/Button_.test.jsx/renders-PrimaryRed-button-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Button_.test.jsx/renders-SecondaryRed-button-1.png b/__tests__/__screenshots__/Button_.test.jsx/renders-SecondaryRed-button-1.png deleted file mode 100644 index f022fb79b..000000000 Binary files a/__tests__/__screenshots__/Button_.test.jsx/renders-SecondaryRed-button-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Button_.test.jsx/renders-Small-button-1.png b/__tests__/__screenshots__/Button_.test.jsx/renders-Small-button-1.png deleted file mode 100644 index 370ea8fff..000000000 Binary files a/__tests__/__screenshots__/Button_.test.jsx/renders-Small-button-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/CodeExample_.test.jsx/renders-code-block-with-highlighted-lines-1.png b/__tests__/__screenshots__/CodeExample_.test.jsx/renders-code-block-with-highlighted-lines-1.png deleted file mode 100644 index d9ac8de5d..000000000 Binary files a/__tests__/__screenshots__/CodeExample_.test.jsx/renders-code-block-with-highlighted-lines-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/CodeExample_.test.jsx/renders-code-block-with-language-label-1.png b/__tests__/__screenshots__/CodeExample_.test.jsx/renders-code-block-with-language-label-1.png deleted file mode 100644 index 7a34b7c9c..000000000 Binary files a/__tests__/__screenshots__/CodeExample_.test.jsx/renders-code-block-with-language-label-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/CodeExample_.test.jsx/renders-code-block-without-label-when-showLabel-is-false-1.png b/__tests__/__screenshots__/CodeExample_.test.jsx/renders-code-block-without-label-when-showLabel-is-false-1.png deleted file mode 100644 index 52833d29e..000000000 Binary files a/__tests__/__screenshots__/CodeExample_.test.jsx/renders-code-block-without-label-when-showLabel-is-false-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/CodeExample_.test.jsx/renders-toggle-with-multiple-tabs-1.png b/__tests__/__screenshots__/CodeExample_.test.jsx/renders-toggle-with-multiple-tabs-1.png deleted file mode 100644 index c53aad15f..000000000 Binary files a/__tests__/__screenshots__/CodeExample_.test.jsx/renders-toggle-with-multiple-tabs-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/CodeExample_.test.jsx/toggle-switches-between-tabs-on-click-1.png b/__tests__/__screenshots__/CodeExample_.test.jsx/toggle-switches-between-tabs-on-click-1.png deleted file mode 100644 index 3d0be472f..000000000 Binary files a/__tests__/__screenshots__/CodeExample_.test.jsx/toggle-switches-between-tabs-on-click-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-shows-sidebar-and-content-1.png b/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-shows-sidebar-and-content-1.png deleted file mode 100644 index c400e4c93..000000000 Binary files a/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-shows-sidebar-and-content-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-with-multiple-categories-1.png b/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-with-multiple-categories-1.png deleted file mode 100644 index 404ed6794..000000000 Binary files a/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-with-multiple-categories-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/CommunityLayout_.test.jsx/mobile-community-layout-hides-sidebar-1.png b/__tests__/__screenshots__/CommunityLayout_.test.jsx/mobile-community-layout-hides-sidebar-1.png deleted file mode 100644 index 462bb857f..000000000 Binary files a/__tests__/__screenshots__/CommunityLayout_.test.jsx/mobile-community-layout-hides-sidebar-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/CommunityLayout_.test.jsx/tablet-community-layout-1.png b/__tests__/__screenshots__/CommunityLayout_.test.jsx/tablet-community-layout-1.png deleted file mode 100644 index aebf5cda8..000000000 Binary files a/__tests__/__screenshots__/CommunityLayout_.test.jsx/tablet-community-layout-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-highlights-active-nav-item-1.png b/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-highlights-active-nav-item-1.png deleted file mode 100644 index e5b21d679..000000000 Binary files a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-highlights-active-nav-item-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-shows-pagination--prev-next--1.png b/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-shows-pagination--prev-next--1.png deleted file mode 100644 index 8515a1585..000000000 Binary files a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-shows-pagination--prev-next--1.png and /dev/null differ diff --git a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-shows-sidebar-with-categories-1.png b/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-shows-sidebar-with-categories-1.png deleted file mode 100644 index 1fe3ddeb5..000000000 Binary files a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-shows-sidebar-with-categories-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-shows-table-of-contents-entries-1.png b/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-shows-table-of-contents-entries-1.png deleted file mode 100644 index 1fe3ddeb5..000000000 Binary files a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-shows-table-of-contents-entries-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/DocsLayout_.test.jsx/mobile-docs-layout-hides-sidebar-by-default-1.png b/__tests__/__screenshots__/DocsLayout_.test.jsx/mobile-docs-layout-hides-sidebar-by-default-1.png deleted file mode 100644 index fa7845427..000000000 Binary files a/__tests__/__screenshots__/DocsLayout_.test.jsx/mobile-docs-layout-hides-sidebar-by-default-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-shows-all-section-cards-1.png b/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-shows-all-section-cards-1.png deleted file mode 100644 index a6394633f..000000000 Binary files a/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-shows-all-section-cards-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-shows-ecosystem-links-1.png b/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-shows-ecosystem-links-1.png deleted file mode 100644 index a6394633f..000000000 Binary files a/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-shows-ecosystem-links-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/DocsOverview_.test.jsx/mobile-docs-overview-1.png b/__tests__/__screenshots__/DocsOverview_.test.jsx/mobile-docs-overview-1.png deleted file mode 100644 index bf6897b24..000000000 Binary files a/__tests__/__screenshots__/DocsOverview_.test.jsx/mobile-docs-overview-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Footer_.test.jsx/desktop-footer-shows-all-sections-and-links-1.png b/__tests__/__screenshots__/Footer_.test.jsx/desktop-footer-shows-all-sections-and-links-1.png deleted file mode 100644 index 110fcffb2..000000000 Binary files a/__tests__/__screenshots__/Footer_.test.jsx/desktop-footer-shows-all-sections-and-links-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Footer_.test.jsx/mobile-footer-stacks-sections-vertically-1.png b/__tests__/__screenshots__/Footer_.test.jsx/mobile-footer-stacks-sections-vertically-1.png deleted file mode 100644 index 57e8570d1..000000000 Binary files a/__tests__/__screenshots__/Footer_.test.jsx/mobile-footer-stacks-sections-vertically-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/MainLayout_.test.jsx/desktop-main-layout-renders-children-and-footer-1.png b/__tests__/__screenshots__/MainLayout_.test.jsx/desktop-main-layout-renders-children-and-footer-1.png deleted file mode 100644 index 64ce5a766..000000000 Binary files a/__tests__/__screenshots__/MainLayout_.test.jsx/desktop-main-layout-renders-children-and-footer-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/MainLayout_.test.jsx/mobile-main-layout-1.png b/__tests__/__screenshots__/MainLayout_.test.jsx/mobile-main-layout-1.png deleted file mode 100644 index 6d5674b79..000000000 Binary files a/__tests__/__screenshots__/MainLayout_.test.jsx/mobile-main-layout-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-chromium-linux.png b/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-chromium-linux.png index cea55eff3..6d361a284 100644 Binary files a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-chromium-linux.png and b/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-chromium-linux.png differ diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-video-chromium-linux.png b/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-video-chromium-linux.png deleted file mode 100644 index 067090d24..000000000 Binary files a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-video-chromium-linux.png and /dev/null differ diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Anchor-with-link-icon-1.png b/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Anchor-with-link-icon-1.png deleted file mode 100644 index fc4b67bdf..000000000 Binary files a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Anchor-with-link-icon-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Cite-without-author-1.png b/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Cite-without-author-1.png deleted file mode 100644 index 924936093..000000000 Binary files a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Cite-without-author-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Image-with-small-size-1.png b/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Image-with-small-size-1.png deleted file mode 100644 index 7b4b2a8fc..000000000 Binary files a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Image-with-small-size-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Strong-text-1.png b/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Strong-text-1.png deleted file mode 100644 index 964b64ac3..000000000 Binary files a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-Strong-text-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-inline-code-1.png b/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-inline-code-1.png deleted file mode 100644 index 574d95c4e..000000000 Binary files a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-inline-code-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-lists-1.png b/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-lists-1.png deleted file mode 100644 index a59fef67a..000000000 Binary files a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-lists-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-nested-list--ul-inside-li--1.png b/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-nested-list--ul-inside-li--1.png deleted file mode 100644 index 4f4afe78f..000000000 Binary files a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/renders-nested-list--ul-inside-li--1.png and /dev/null differ diff --git a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-chromium-linux.png b/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-chromium-linux.png index bc51442ee..ceeb81806 100644 Binary files a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-chromium-linux.png and b/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-chromium-linux.png differ diff --git a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-secondary-navbar-shows-all-doc-section-links-1.png b/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-secondary-navbar-shows-all-doc-section-links-1.png deleted file mode 100644 index 68e650f4f..000000000 Binary files a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-secondary-navbar-shows-all-doc-section-links-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-navbar-secondary-chromium-linux.png b/__tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-navbar-secondary-chromium-linux.png index 5ba05892c..870cc995f 100644 Binary files a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-navbar-secondary-chromium-linux.png and b/__tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-navbar-secondary-chromium-linux.png differ diff --git a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-secondary-navbar-shows-all-links-1.png b/__tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-secondary-navbar-shows-all-links-1.png deleted file mode 100644 index b68ce07da..000000000 Binary files a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-secondary-navbar-shows-all-links-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/secondary-navbar-highlights-active-section-1.png b/__tests__/__screenshots__/NavbarSecondary_.test.jsx/secondary-navbar-highlights-active-section-1.png deleted file mode 100644 index 89bc240df..000000000 Binary files a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/secondary-navbar-highlights-active-section-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SearchBox_.test.jsx/renders-with-a-value-1.png b/__tests__/__screenshots__/SearchBox_.test.jsx/renders-with-a-value-1.png deleted file mode 100644 index 210666bc7..000000000 Binary files a/__tests__/__screenshots__/SearchBox_.test.jsx/renders-with-a-value-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SearchBox_.test.jsx/renders-with-placeholder-text-1.png b/__tests__/__screenshots__/SearchBox_.test.jsx/renders-with-placeholder-text-1.png deleted file mode 100644 index 345e0e81e..000000000 Binary files a/__tests__/__screenshots__/SearchBox_.test.jsx/renders-with-placeholder-text-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/breadcrumbs-render-path-segments-1.png b/__tests__/__screenshots__/SidebarLayout_.test.jsx/breadcrumbs-render-path-segments-1.png deleted file mode 100644 index 6bbd23878..000000000 Binary files a/__tests__/__screenshots__/SidebarLayout_.test.jsx/breadcrumbs-render-path-segments-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/breadcrumbs-with-deep-path-1.png b/__tests__/__screenshots__/SidebarLayout_.test.jsx/breadcrumbs-with-deep-path-1.png deleted file mode 100644 index a3e7c46aa..000000000 Binary files a/__tests__/__screenshots__/SidebarLayout_.test.jsx/breadcrumbs-with-deep-path-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-highlights-active-item-1.png b/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-highlights-active-item-1.png deleted file mode 100644 index b5d9f7416..000000000 Binary files a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-highlights-active-item-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-renders-title-and-nav-items-1.png b/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-renders-title-and-nav-items-1.png deleted file mode 100644 index 76d010528..000000000 Binary files a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-renders-title-and-nav-items-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-active-TOC-renders-entries-1.png b/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-active-TOC-renders-entries-1.png deleted file mode 100644 index 2ac0bb615..000000000 Binary files a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-active-TOC-renders-entries-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-many-items-1.png b/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-many-items-1.png deleted file mode 100644 index 7999cec54..000000000 Binary files a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-many-items-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/deprecated-items-show-with-line-through-styling-1.png b/__tests__/__screenshots__/SyntaxLookup_.test.jsx/deprecated-items-show-with-line-through-styling-1.png deleted file mode 100644 index 5cf8af1c0..000000000 Binary files a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/deprecated-items-show-with-line-through-styling-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-renders-categories-and-tags-1.png b/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-renders-categories-and-tags-1.png deleted file mode 100644 index 5cf8af1c0..000000000 Binary files a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-renders-categories-and-tags-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-with-active-item-shows-detail-box-1.png b/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-with-active-item-shows-detail-box-1.png deleted file mode 100644 index 049ff22ce..000000000 Binary files a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-with-active-item-shows-detail-box-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-1.png b/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-1.png deleted file mode 100644 index d5c7ce17a..000000000 Binary files a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-with-active-item-1.png b/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-with-active-item-1.png deleted file mode 100644 index 8a6257e68..000000000 Binary files a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-with-active-item-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/syntax-lookup-detail-box-shows-summary-1.png b/__tests__/__screenshots__/SyntaxLookup_.test.jsx/syntax-lookup-detail-box-shows-summary-1.png deleted file mode 100644 index 0642aa1fd..000000000 Binary files a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/syntax-lookup-detail-box-shows-summary-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Tag_.test.jsx/renders-multiple-tags-side-by-side-1.png b/__tests__/__screenshots__/Tag_.test.jsx/renders-multiple-tags-side-by-side-1.png deleted file mode 100644 index 17f02a83b..000000000 Binary files a/__tests__/__screenshots__/Tag_.test.jsx/renders-multiple-tags-side-by-side-1.png and /dev/null differ diff --git a/__tests__/__screenshots__/Tag_.test.jsx/renders-subtle-tag-with-text-1.png b/__tests__/__screenshots__/Tag_.test.jsx/renders-subtle-tag-with-text-1.png deleted file mode 100644 index a052ecada..000000000 Binary files a/__tests__/__screenshots__/Tag_.test.jsx/renders-subtle-tag-with-text-1.png and /dev/null differ diff --git a/app/routes.res b/app/routes.res index 656db708c..83637584b 100644 --- a/app/routes.res +++ b/app/routes.res @@ -28,7 +28,18 @@ let stdlibRoutes = let beltRoutes = beltPaths->Array.map(path => route(path, "./routes/ApiRoute.jsx", ~options={id: path})) -let mdxRoutes = mdxRoutes("./routes/MdxRoute.jsx") +let blogArticleRoutes = + MdxFile.scanPaths(~dir="markdown-pages/blog", ~alias="blog")->Array.map(path => + route(path, "./routes/BlogArticleRoute.jsx", ~options={id: path}) + ) + +let mdxRoutes = + mdxRoutes("./routes/MdxRoute.jsx")->Array.filter(r => + !(r.path + ->Option.map(path => path === "blog" || String.startsWith(path, "blog/")) + ->Option.getOr(false) + ) + ) let default = [ index("./routes/LandingPageRoute.jsx"), @@ -44,6 +55,7 @@ let default = [ route("docs/manual/api/dom", "./routes/ApiRoute.jsx", ~options={id: "api-dom"}), ...stdlibRoutes, ...beltRoutes, + ...blogArticleRoutes, ...mdxRoutes, route("*", "./routes/NotFoundRoute.jsx"), ] diff --git a/app/routes/BlogArticleRoute.res b/app/routes/BlogArticleRoute.res new file mode 100644 index 000000000..6644a1509 --- /dev/null +++ b/app/routes/BlogArticleRoute.res @@ -0,0 +1,54 @@ +type loaderData = { + compiledMdx: CompiledMdx.t, + blogPost: BlogApi.post, + title: string, +} + +let loader: ReactRouter.Loader.t = async ({request}) => { + let {pathname} = WebAPI.URL.make(~url=request.url) + let filePath = MdxFile.resolveFilePath( + (pathname :> string), + ~dir="markdown-pages/blog", + ~alias="blog", + ) + + let raw = await Node.Fs.readFile(filePath, "utf-8") + let {frontmatter}: MarkdownParser.result = MarkdownParser.parseSync(raw) + + let frontmatter = switch BlogFrontmatter.decode(frontmatter) { + | Ok(fm) => fm + | Error(msg) => JsError.throwWithMessage(msg) + } + + let compiledMdx = await MdxFile.compileMdx(raw, ~filePath, ~remarkPlugins=Mdx.plugins) + + let archived = filePath->String.includes("/archived/") + + let slug = + filePath + ->Node.Path.basename + ->String.replace(".mdx", "") + ->String.replaceRegExp(/^\d\d\d\d-\d\d-\d\d-/, "") + + let path = archived ? "archived/" ++ slug : slug + + let blogPost: BlogApi.post = { + path, + archived, + frontmatter, + } + + { + compiledMdx, + blogPost, + title: `${frontmatter.title} | ReScript Blog`, + } +} + +let default = () => { + let {compiledMdx, blogPost: {frontmatter, archived, path}} = ReactRouter.useLoaderData() + + + + +} diff --git a/app/routes/BlogArticleRoute.resi b/app/routes/BlogArticleRoute.resi new file mode 100644 index 000000000..40ba9580c --- /dev/null +++ b/app/routes/BlogArticleRoute.resi @@ -0,0 +1,9 @@ +type loaderData = { + compiledMdx: CompiledMdx.t, + blogPost: BlogApi.post, + title: string, +} + +let loader: ReactRouter.Loader.t + +let default: unit => React.element diff --git a/app/routes/MdxRoute.res b/app/routes/MdxRoute.res index 55f1bf4fb..f61e9db29 100644 --- a/app/routes/MdxRoute.res +++ b/app/routes/MdxRoute.res @@ -4,7 +4,6 @@ type loaderData = { ...Mdx.t, categories: array, entries: array, - blogPost?: BlogApi.post, mdxSources?: array, activeSyntaxItem?: SyntaxLookup.item, breadcrumbs?: list, @@ -134,18 +133,7 @@ let loader: ReactRouter.Loader.t = async ({request}) => { let mdx = await loadMdx(request, ~options={remarkPlugins: Mdx.plugins}) - if pathname->String.includes("blog") { - let res: loaderData = { - __raw: mdx.__raw, - attributes: mdx.attributes, - entries: [], - categories: [], - blogPost: mdx.attributes->BlogLoader.transform, - title: `${mdx.attributes.title} | ReScript Blog`, - filePath: None, - } - res - } else if pathname->String.includes("syntax-lookup") { + if pathname->String.includes("syntax-lookup") { let mdxSources = (await allMdx(~filterByPaths=["markdown-pages/syntax-lookup"])) ->Array.filter(page => @@ -418,12 +406,6 @@ let default = () => {
{component()}
- } else if (pathname :> string)->String.includes("blog") { - switch loaderData.blogPost { - | Some({frontmatter, archived, path}) => - {component()} - | None => React.null // TODO: Post RR7 show an error? - } } else { switch loaderData.mdxSources { | Some(mdxSources) => diff --git a/app/routes/MdxRoute.resi b/app/routes/MdxRoute.resi index b6a26c12c..8e4a827fb 100644 --- a/app/routes/MdxRoute.resi +++ b/app/routes/MdxRoute.resi @@ -2,7 +2,6 @@ type loaderData = { ...Mdx.t, categories: array, entries: array, - blogPost?: BlogApi.post, mdxSources?: array, activeSyntaxItem?: SyntaxLookup.item, breadcrumbs?: list, diff --git a/package.json b/package.json index b232590b1..8f43a6852 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "build:vite": "react-router build", "build": "yarn build:res && yarn build:scripts && yarn build:update-index && yarn build:vite", "ci:format": "prettier . --check --experimental-cli", - "ci:test": "yarn vitest --run --browser.headless --update", + "ci:test": "yarn vitest --run --browser.headless", "clean:res": "rescript clean", "convert-images": "auto-convert-images", "dev:res": "rescript watch", @@ -30,7 +30,8 @@ "preview": "yarn build && static-server build/client", "reanalyze": "rescript-tools reanalyze -all-cmt .", "test": "node scripts/test-examples.mjs && node scripts/test-hrefs.mjs", - "vitest": "vitest" + "vitest": "vitest", + "vitest:update": "vitest --run --browser.headless --update" }, "dependencies": { "@babel/generator": "^7.24.7", @@ -47,6 +48,7 @@ "@docsearch/react": "^4.3.1", "@headlessui/react": "^2.2.4", "@lezer/highlight": "^1.2.1", + "@mdx-js/mdx": "^3.1.1", "@node-cli/static-server": "^3.1.4", "@react-router/node": "^7.8.1", "@replit/codemirror-vim": "^6.3.0", diff --git a/src/MdxFile.res b/src/MdxFile.res new file mode 100644 index 000000000..a9bbf9d9f --- /dev/null +++ b/src/MdxFile.res @@ -0,0 +1,77 @@ +type fileData = { + content: string, + frontmatter: JSON.t, +} + +type compileInput = {value: string, path: string} +type compileOptions = { + outputFormat: string, + remarkPlugins: array, +} +@module("@mdx-js/mdx") +external compile: (compileInput, compileOptions) => promise = "compile" + +@module("remark-frontmatter") external remarkFrontmatter: Mdx.remarkPlugin = "default" + +let compileMdx = async (content, ~filePath, ~remarkPlugins=[]) => { + let compiled = await compile( + {value: content, path: filePath}, + { + outputFormat: "function-body", + remarkPlugins: [remarkFrontmatter, ...remarkPlugins], + }, + ) + compiled->CompiledMdx.fromCompileResult +} + +let resolveFilePath = (pathname, ~dir, ~alias) => { + let path = if pathname->String.startsWith("/") { + pathname->String.slice(~start=1, ~end=String.length(pathname)) + } else { + pathname + } + let relativePath = + if path->String.startsWith(alias ++ "/") { + let rest = path->String.slice(~start=String.length(alias) + 1, ~end=String.length(path)) + Node.Path.join2(dir, rest) + } else if path->String.startsWith(alias) { + let rest = path->String.slice(~start=String.length(alias), ~end=String.length(path)) + Node.Path.join2(dir, rest) + } else { + path + } + relativePath ++ ".mdx" +} + +let loadFile = async filePath => { + let raw = await Node.Fs.readFile(filePath, "utf-8") + let {frontmatter, content}: MarkdownParser.result = MarkdownParser.parseSync(raw) + {content, frontmatter} +} + +// Recursively scan a directory for .mdx files +let rec scanDir = (baseDir, currentDir) => { + let entries = Node.Fs.readdirSync(currentDir) + entries->Array.flatMap(entry => { + let fullPath = Node.Path.join2(currentDir, entry) + if Node.Fs.statSync(fullPath)["isDirectory"]() { + scanDir(baseDir, fullPath) + } else if Node.Path.extname(entry) === ".mdx" { + // Get the relative path from baseDir + let relativePath = + fullPath + ->String.replaceAll("\\", "/") + ->String.replace(baseDir->String.replaceAll("\\", "/") ++ "/", "") + ->String.replace(".mdx", "") + [relativePath] + } else { + [] + } + }) +} + +let scanPaths = (~dir, ~alias) => { + scanDir(dir, dir)->Array.map(relativePath => { + alias ++ "/" ++ relativePath + }) +} diff --git a/src/MdxFile.resi b/src/MdxFile.resi new file mode 100644 index 000000000..9ca94395e --- /dev/null +++ b/src/MdxFile.resi @@ -0,0 +1,26 @@ +type fileData = { + content: string, + frontmatter: JSON.t, +} + +/** Maps a URL pathname to an .mdx file path on disk. + * e.g. `/blog/release-12-0-0` with ~dir="markdown-pages/blog" ~alias="blog" + * → `markdown-pages/blog/release-12-0-0.mdx` + */ +let resolveFilePath: (string, ~dir: string, ~alias: string) => string + +/** Read a file from disk and parse its frontmatter using MarkdownParser. */ +let loadFile: string => promise + +/** Scan a directory recursively for .mdx files and return URL paths. + * e.g. scanPaths(~dir="markdown-pages/blog", ~alias="blog") + * → ["blog/release-12-0-0", "blog/archived/some-post", ...] + */ +let scanPaths: (~dir: string, ~alias: string) => array + +/** Compile raw MDX content into a function-body string using @mdx-js/mdx. */ +let compileMdx: ( + string, + ~filePath: string, + ~remarkPlugins: array=?, +) => promise diff --git a/src/common/CompiledMdx.res b/src/common/CompiledMdx.res new file mode 100644 index 000000000..840c4dde2 --- /dev/null +++ b/src/common/CompiledMdx.res @@ -0,0 +1,5 @@ +type t = string + +type compileResult + +@send external fromCompileResult: compileResult => t = "toString" diff --git a/src/common/CompiledMdx.resi b/src/common/CompiledMdx.resi new file mode 100644 index 000000000..3e9eee388 --- /dev/null +++ b/src/common/CompiledMdx.resi @@ -0,0 +1,5 @@ +type t + +type compileResult + +@send external fromCompileResult: compileResult => t = "toString" diff --git a/src/common/MarkdownParser.res b/src/common/MarkdownParser.res index 3a0a756ab..1974c75a5 100644 --- a/src/common/MarkdownParser.res +++ b/src/common/MarkdownParser.res @@ -27,6 +27,15 @@ type result = { let vfileMatterPlugin = makePlugin(_options => (_tree, vfile) => vfileMatter(vfile)) +type remarkNode = {@as("type") type_: string} +type remarkTree = {mutable children: array} + +let stripFrontmatterPlugin = makePlugin(_options => + (tree, _vfile) => { + tree.children = tree.children->Array.filter(node => node.type_ !== "yaml") + } +) + let parser = make() ->use(remarkParse) @@ -35,6 +44,7 @@ let parser = ->use(remarkComment) ->useOptions(remarkFrontmatter, [{"type": "yaml", "marker": "-"}]) ->use(vfileMatterPlugin) + ->use(stripFrontmatterPlugin) let parseSync = content => { let vfile = parser->processSync(content) diff --git a/src/components/MdxContent.res b/src/components/MdxContent.res new file mode 100644 index 000000000..9b51cc393 --- /dev/null +++ b/src/components/MdxContent.res @@ -0,0 +1,100 @@ +// --------------------------------------------------------------------------- +// JSX runtime values needed by runSync +// --------------------------------------------------------------------------- + +// We re-import the jsx-runtime exports as opaque values so we can pass them +// through to runSync without running into ReScript's monomorphisation of +// the polymorphic `React.jsx` / `React.jsxs` signatures. +/** + * MdxContent — renders compiled MDX content as a React component. + * + * Uses `runSync` from `@mdx-js/mdx` to evaluate compiled MDX (produced by + * `MdxFile.compileMdx`) and renders the result with a shared component map. + */ +type jsxRuntimeValue + +@module("react/jsx-runtime") external fragment: jsxRuntimeValue = "Fragment" +@module("react/jsx-runtime") external jsx: jsxRuntimeValue = "jsx" +@module("react/jsx-runtime") external jsxs: jsxRuntimeValue = "jsxs" + +@val @scope(("import", "meta")) external importMetaUrl: string = "url" + +// --------------------------------------------------------------------------- +// @mdx-js/mdx runSync binding +// --------------------------------------------------------------------------- + +type runOptions = { + @as("Fragment") fragment: jsxRuntimeValue, + jsx: jsxRuntimeValue, + jsxs: jsxRuntimeValue, + baseUrl: string, +} + +type mdxModule + +@module("@mdx-js/mdx") +external runSync: (CompiledMdx.t, runOptions) => mdxModule = "runSync" + +@get external getDefault: mdxModule => React.component<{..}> = "default" + +let runOptions = { + fragment, + jsx, + jsxs, + baseUrl: importMetaUrl, +} + +// --------------------------------------------------------------------------- +// Shared MDX component map +// --------------------------------------------------------------------------- + +let components = { + // Standard HTML element overrides + "a": Markdown.A.make, + "blockquote": Markdown.Blockquote.make, + "code": Markdown.Code.make, + "h1": Markdown.H1.make, + "h2": Markdown.H2.make, + "h3": Markdown.H3.make, + "h4": Markdown.H4.make, + "h5": Markdown.H5.make, + "hr": Markdown.Hr.make, + "li": Markdown.Li.make, + "ol": Markdown.Ol.make, + "p": Markdown.P.make, + "pre": Markdown.Pre.make, + "strong": Markdown.Strong.make, + "table": Markdown.Table.make, + "th": Markdown.Th.make, + "thead": Markdown.Thead.make, + "td": Markdown.Td.make, + "ul": Markdown.Ul.make, + // Custom MDX components + "Cite": Markdown.Cite.make, + "CodeTab": Markdown.CodeTab.make, + "Image": Markdown.Image.make, + "Info": Markdown.Info.make, + "Intro": Markdown.Intro.make, + "UrlBox": Markdown.UrlBox.make, + "Video": Markdown.Video.make, + "Warn": Markdown.Warn.make, + "CommunityContent": CommunityContent.make, + "WarningTable": WarningTable.make, + "Docson": DocsonLazy.make, + "Suspense": React.Suspense.make, +} + +// --------------------------------------------------------------------------- +// React component +// --------------------------------------------------------------------------- + +@react.component +let make = (~compiledMdx: CompiledMdx.t) => { + let element = React.useMemo(() => { + let mdxModule = runSync(compiledMdx, runOptions) + let content = getDefault(mdxModule) + React.jsx(content, {"components": components}) + }, [compiledMdx]) + + element +} diff --git a/src/components/MdxContent.resi b/src/components/MdxContent.resi new file mode 100644 index 000000000..882a3530b --- /dev/null +++ b/src/components/MdxContent.resi @@ -0,0 +1,2 @@ +@react.component +let make: (~compiledMdx: CompiledMdx.t) => React.element diff --git a/styles/test-overrides.css b/styles/test-overrides.css new file mode 100644 index 000000000..b4631ed3b --- /dev/null +++ b/styles/test-overrides.css @@ -0,0 +1,20 @@ +/* + * Test overrides for consistent screenshots. + * This file is only imported in vitest.setup.mjs. + */ + +/* Disable all CSS animations and transitions */ +*, +*::before, +*::after { + animation-duration: 0s !important; + animation-delay: 0s !important; + transition-duration: 0s !important; + transition-delay: 0s !important; +} + +/* Force overlay scrollbars so scrollbar width is 0 in both headless and headed mode */ +* { + scrollbar-width: none !important; + scrollbar-gutter: auto !important; +} diff --git a/vitest.config.mjs b/vitest.config.mjs index 49417630f..dbaeaa05f 100644 --- a/vitest.config.mjs +++ b/vitest.config.mjs @@ -10,14 +10,31 @@ export default defineConfig({ setupFiles: ["./vitest.setup.mjs"], browser: { enabled: true, - provider: playwright(), + provider: playwright({ + contextOptions: { + deviceScaleFactor: 1, + }, + }), + ui: false, // https://vitest.dev/config/browser/playwright + provider: playwright(), instances: [ { browser: "chromium", viewport: { width: 1440, height: 900 }, }, ], + expect: { + toMatchScreenshot: { + screenshotOptions: { + scale: "css", + }, + comparatorOptions: { + threshold: 0.2, + allowedMismatchedPixelRatio: 0.05, + }, + }, + }, }, }, }); diff --git a/vitest.setup.mjs b/vitest.setup.mjs index 6d48aafc1..3ba70e746 100644 --- a/vitest.setup.mjs +++ b/vitest.setup.mjs @@ -1,2 +1,3 @@ import "./styles/main.css"; import "./styles/utils.css"; +import "./styles/test-overrides.css"; diff --git a/yarn.lock b/yarn.lock index 02ae82c71..6ccb0d181 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2003,7 +2003,7 @@ __metadata: languageName: node linkType: hard -"@mdx-js/mdx@npm:^3.1.0": +"@mdx-js/mdx@npm:^3.1.0, @mdx-js/mdx@npm:^3.1.1": version: 3.1.1 resolution: "@mdx-js/mdx@npm:3.1.1" dependencies: @@ -9489,6 +9489,7 @@ __metadata: "@docsearch/react": "npm:^4.3.1" "@headlessui/react": "npm:^2.2.4" "@lezer/highlight": "npm:^1.2.1" + "@mdx-js/mdx": "npm:^3.1.1" "@node-cli/static-server": "npm:^3.1.4" "@prettier/plugin-oxc": "npm:^0.0.4" "@react-router/dev": "npm:^7.8.1"