Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Security Notes
# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/learn/settings/actions)
# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions.
# REVIEWERS, please always double-check security practices before merging a PR that contains workflow changes!!
# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags.

name: Playwright Tests

# Runs end-to-end tests against the Vercel preview of each pull request, so the
# site is exercised exactly as Vercel builds it (VERCEL_ENV=preview), not just
# as it builds locally. Ported from nodejs/nodejs.org's playwright.yml.
#
# Unlike nodejs.org, Dependabot PRs are NOT skipped here: dependency bumps
# (e.g. doc-kit, #138) are one of the likeliest ways for Learn to break.

on:
pull_request:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read
actions: read
Comment thread
bmuenzenmeyer marked this conversation as resolved.

jobs:
playwright:
name: Playwright Tests
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Capture Vercel Preview
id: deployment
uses: patrickedqvist/wait-for-vercel-preview@d7982701e6fcd3ae073bff929e408e004404d38d # v1.3.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_timeout: 300 # timeout after 5 minutes
check_interval: 10 # check every 10 seconds

- name: Get Playwright version
id: playwright-version
run: echo "version=$(node_modules/.bin/playwright --version | awk '{print $2}')" >> "$GITHUB_OUTPUT"

- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}

- name: Install Playwright Browsers
run: node_modules/.bin/playwright install --with-deps chromium

- name: Run Playwright tests
run: node --run test:e2e
env:
PLAYWRIGHT_BASE_URL: ${{ steps.deployment.outputs.url }}

- name: Upload Playwright test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report
path: playwright-report/
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
node_modules
out

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
18 changes: 17 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,28 @@ npm run build
The output is written to `out/`. Start a local server from a separate terminal:

```bash
npx serve out
npm run serve
```

Open <http://localhost:3000/learn> to preview the site. Run `npm run build`
again after making changes to update the generated output.

## Running the end-to-end tests

The [Playwright](https://playwright.dev) tests in `tests/e2e/` check that the
built site loads its assets, and that navigation, search and the theme toggle
work. They run against the Vercel preview of every pull request. To run them
locally against your own build:

```bash
npm run build
npx playwright install chromium # first time only
npm run test:e2e
```

The tests start `npm run serve` for you, or reuse a server already running on
port 3000. Set `PLAYWRIGHT_BASE_URL` to test a deployed copy instead.

## Code of Conduct

This project follows the
Expand Down
2 changes: 1 addition & 1 deletion components/Layout/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default ({ metadata, headings, readingTime, children }) => (
<SpeedInsights basePath="/learn/_vercel" />
<NavBar metadata={metadata} />
<Article>
<SideBar metadata={metadata} />
<SideBar pathname={`/learn${metadata.path.replace('/index', '')}`} />
<div>
<div>
<TableOfContents headings={headings} summaryTitle="On this page" />
Expand Down
45 changes: 20 additions & 25 deletions components/Navigation/index.jsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,32 @@
import ThemeToggle from '@node-core/ui-components/Common/ThemeToggle';
import NavBar from '@node-core/ui-components/Containers/NavBar';
import styles from '@node-core/ui-components/Containers/NavBar/index.module.css';
import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub';

import SearchBox from '@doc-kit/generator-react/html/ui/components/SearchBox/index.jsx';
import { useTheme } from '@doc-kit/generator-react/html/ui/hooks/useTheme.mjs';
// doc-kit's ThemeToggle is registered as an island, so it hydrates on the
// client. Rendering `@node-core/ui-components`' toggle directly here leaves a
// static button that never opens.
import ThemeToggle from '@doc-kit/generator-react/html/ui/components/ThemeToggle.jsx';
import { navigation } from '../../site.json' with { type: 'json' };
import Logo from '#theme/Logo';

/**
* NavBar component that displays the headings, search, etc.
*/
export default ({ metadata }) => {
const [themePreference, setThemePreference] = useTheme();

return (
<NavBar
Logo={Logo}
pathname="/learn"
sidebarItemTogglerAriaLabel="Toggle navigation menu"
navItems={navigation}
export default ({ metadata }) => (
<NavBar
Logo={Logo}
pathname="/learn"
sidebarItemTogglerAriaLabel="Toggle navigation menu"
navItems={navigation}
>
<SearchBox pathname={metadata.path} />
<ThemeToggle />
<a
href={`https://github.com/nodejs/learn`}
className={styles.ghIconWrapper}
>
<SearchBox pathname={metadata.path} />
<ThemeToggle
onChange={setThemePreference}
currentTheme={themePreference}
/>
<a
href={`https://github.com/nodejs/learn`}
className={styles.ghIconWrapper}
>
<GitHubIcon />
</a>
</NavBar>
);
};
<GitHubIcon />
</a>
</NavBar>
);
15 changes: 12 additions & 3 deletions components/Sidebar/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SideBar from '@node-core/ui-components/Containers/Sidebar';
import withIsland from '@doc-kit/generator-react/html/ui/islands/withIsland.jsx';
import { sidebar } from '../../site.json' with { type: 'json' };

/** @param {string} url */
Expand All @@ -7,14 +8,22 @@ const redirect = url => (window.location.href = url);
const PrefetchLink = props => <a {...props} rel="prefetch" />;

/**
* Sidebar component for MDX documentation with page navigation
* Sidebar component for MDX documentation with page navigation.
*
* On small screens the sidebar collapses into a dropdown, which only opens
* once hydrated. doc-kit 2.x hydrates islands and nothing else, so the sidebar
* is registered as one (see `components` in doc-kit.config.mjs).
*
* @param {{ pathname: string }} props
*/
export default ({ metadata }) => (
const Sidebar = ({ pathname }) => (
<SideBar
pathname={`/learn${metadata.path.replace('/index', '')}`}
pathname={pathname}
groups={sidebar}
onSelect={redirect}
as={PrefetchLink}
title="Navigation"
/>
);

export default withIsland(Sidebar, { name: 'Sidebar', on: { idle: true } });
14 changes: 9 additions & 5 deletions doc-kit.config.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { join } from 'node:path';

// The full origin (scheme included) lives here, and only here, so `baseURL`
// below never has to guess the protocol. Local builds are served over plain
// HTTP (see CONTRIBUTING.md), previews and production over HTTPS.
const origin =
process.env.VERCEL_ENV === 'preview'
? process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: process.env.VERCEL_ENV === 'production'
? 'nodejs.org'
: 'localhost:3000';
? 'https://nodejs.org'
: 'http://localhost:3000';

/** @type {import('@doc-kit/core/utils/configuration/types.d.ts').Configuration} */
export default {
Expand All @@ -15,7 +18,7 @@ export default {
global: {
output: 'out/learn',
input: ['pages/**/*.md'],
baseURL: `https://${origin}/learn`,
baseURL: `${origin}/learn`,

// The preset documents the runtime itself, so it points these at
// nodejs/node. Learn is its own repository, and it has no use for the
Expand All @@ -39,9 +42,10 @@ export default {
templatePath: join(import.meta.dirname, 'template.html'),
generateAllPage: false,

// Registers the component as an island, so it hydrates client-side
// Registers the components as islands, so they hydrate client-side
components: {
Authors: join(import.meta.dirname, 'components/Authors/index.jsx'),
Sidebar: join(import.meta.dirname, 'components/Sidebar/index.jsx'),
},

// Imports
Expand Down
9 changes: 8 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { defineConfig } from 'eslint/config';
import * as mdx from 'eslint-plugin-mdx';

export default defineConfig(
{ ignores: ['out'] },
{ ignores: ['out', 'playwright-report', 'test-results'] },
eslint.configs.recommended,
tseslint.configs.recommended,
mdx.flatCodeBlocks,
Expand All @@ -14,6 +14,13 @@ export default defineConfig(
globals: globals.nodeBuiltin,
},
},
{
// Callbacks passed to `page.evaluate()` run in the browser
files: ['tests/e2e/**/*.mjs'],
languageOptions: {
globals: { ...globals.nodeBuiltin, ...globals.browser },
},
},
{
files: ['**/*.{md,mdx}/*.cjs'],
rules: {
Expand Down
Loading
Loading