Skip to content
3 changes: 3 additions & 0 deletions .lycheeignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ dist/(.*/)?404((/index)?\.html|/)$
# Oracle pages return 403 when connected
^https://www\.mysql\.com/
^https://www\.oracle\.com/

# NVD pages time out when requested from CI runners
^https://nvd\.nist\.gov/
10 changes: 10 additions & 0 deletions docs/design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,13 @@ Colors are defined using OKLCH color space with `light-dark()` for theme switchi
| `xs` | < 768px | `--xs-only` |
| `md` | 768px – 1439px | `--md-only`, `--md-up` |
| `lg` | >= 1440px | `--lg-up`, `--lg-down` |

## Doc Bottom Navigation

`<DocBottomNav>` closes a doc page with links to the previous and next page in a single
flex row. The row uses `flex-wrap: wrap-reverse`, so when the two titles cannot sit side
by side the _Next_ link wraps onto its own line above _Previous_ instead of overflowing
the page. On wider screens both links stay side by side on one line.

The DOM order stays previous-then-next, so keyboard and screen reader order is unchanged
when the row wraps.
10 changes: 10 additions & 0 deletions src/components/patterns/DocBottomNav/DocBottomNav.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
margin-top: var(--space-8);
padding-top: var(--space-6);
border-top: 1px solid var(--color-border-secondary);
flex-wrap: wrap;
}

.doc-nav__link--prev,
.doc-nav__link--next {
flex-grow: 1;

svg {
flex-shrink: 0;
}
}

.doc-nav__link--next {
Expand Down
54 changes: 54 additions & 0 deletions tests/e2e/doc-bottom-nav.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { test, expect } from '@playwright/test';

// Regression coverage for https://github.com/expressjs/expressjs.com/issues/2486
const DOC_PATH = '/en/guide/migrating-4/';

test.describe('Doc bottom navigation', () => {
test('should not overflow horizontally on a narrow viewport', async ({ page }) => {
await page.setViewportSize({ width: 400, height: 900 });
await page.goto(DOC_PATH);

const nav = page.locator('.doc-nav');
await expect(nav).toBeVisible();

const overflow = await nav.evaluate((el) => el.scrollWidth - el.clientWidth);
expect(overflow).toBeLessThanOrEqual(1);

Check failure on line 15 in tests/e2e/doc-bottom-nav.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Tests

[chromium] › tests/e2e/doc-bottom-nav.spec.ts:7:3 › Doc bottom navigation › should not overflow horizontally on a narrow viewport

1) [chromium] › tests/e2e/doc-bottom-nav.spec.ts:7:3 › Doc bottom navigation › should not overflow horizontally on a narrow viewport Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBeLessThanOrEqual(expected) Expected: <= 1 Received: 89 13 | 14 | const overflow = await nav.evaluate((el) => el.scrollWidth - el.clientWidth); > 15 | expect(overflow).toBeLessThanOrEqual(1); | ^ 16 | }); 17 | 18 | test('should move the next link above the previous link when they cannot share a line', async ({ at /home/runner/work/expressjs.com/expressjs.com/tests/e2e/doc-bottom-nav.spec.ts:15:22

Check failure on line 15 in tests/e2e/doc-bottom-nav.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Tests

[chromium] › tests/e2e/doc-bottom-nav.spec.ts:7:3 › Doc bottom navigation › should not overflow horizontally on a narrow viewport

1) [chromium] › tests/e2e/doc-bottom-nav.spec.ts:7:3 › Doc bottom navigation › should not overflow horizontally on a narrow viewport Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBeLessThanOrEqual(expected) Expected: <= 1 Received: 89 13 | 14 | const overflow = await nav.evaluate((el) => el.scrollWidth - el.clientWidth); > 15 | expect(overflow).toBeLessThanOrEqual(1); | ^ 16 | }); 17 | 18 | test('should move the next link above the previous link when they cannot share a line', async ({ at /home/runner/work/expressjs.com/expressjs.com/tests/e2e/doc-bottom-nav.spec.ts:15:22

Check failure on line 15 in tests/e2e/doc-bottom-nav.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Tests

[chromium] › tests/e2e/doc-bottom-nav.spec.ts:7:3 › Doc bottom navigation › should not overflow horizontally on a narrow viewport

1) [chromium] › tests/e2e/doc-bottom-nav.spec.ts:7:3 › Doc bottom navigation › should not overflow horizontally on a narrow viewport Error: expect(received).toBeLessThanOrEqual(expected) Expected: <= 1 Received: 89 13 | 14 | const overflow = await nav.evaluate((el) => el.scrollWidth - el.clientWidth); > 15 | expect(overflow).toBeLessThanOrEqual(1); | ^ 16 | }); 17 | 18 | test('should move the next link above the previous link when they cannot share a line', async ({ at /home/runner/work/expressjs.com/expressjs.com/tests/e2e/doc-bottom-nav.spec.ts:15:22
});

test('should move the next link above the previous link when they cannot share a line', async ({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think these tests are particularly useful, as we generally don’t test layout behaviours such as “the next link should move above the previous link when they can’t fit on the same line” or “the content should not overflow horizontally on a narrow viewport.”

These are more like responsive/layout implementation details rather than behaviours we need to explicitly test. I’d suggest removing these tests to keep the test suite focused on meaningful and maintainable behaviours.

page,
}) => {
await page.setViewportSize({ width: 400, height: 900 });
await page.goto(DOC_PATH);

const prev = page.locator('.doc-nav__link--prev');
const next = page.locator('.doc-nav__link--next');

await expect(prev).toBeVisible();
await expect(next).toBeVisible();

const prevBox = await prev.boundingBox();
const nextBox = await next.boundingBox();

if (!prevBox || !nextBox) {
throw new Error('expected both doc nav links to be laid out');
}

expect(nextBox.y + nextBox.height).toBeLessThanOrEqual(prevBox.y);

Check failure on line 37 in tests/e2e/doc-bottom-nav.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Tests

[webkit] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line

4) [webkit] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line Error: expect(received).toBeLessThanOrEqual(expected) Expected: <= 15434.9375 Received: 15594.9375 35 | } 36 | > 37 | expect(nextBox.y + nextBox.height).toBeLessThanOrEqual(prevBox.y); | ^ 38 | }); 39 | 40 | test('should keep both links on a single line on a wide viewport', async ({ page }) => { at /home/runner/work/expressjs.com/expressjs.com/tests/e2e/doc-bottom-nav.spec.ts:37:40

Check failure on line 37 in tests/e2e/doc-bottom-nav.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Tests

[firefox] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line

3) [firefox] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBeLessThanOrEqual(expected) Expected: <= 15445.18359375 Received: 15605.18359375 35 | } 36 | > 37 | expect(nextBox.y + nextBox.height).toBeLessThanOrEqual(prevBox.y); | ^ 38 | }); 39 | 40 | test('should keep both links on a single line on a wide viewport', async ({ page }) => { at /home/runner/work/expressjs.com/expressjs.com/tests/e2e/doc-bottom-nav.spec.ts:37:40

Check failure on line 37 in tests/e2e/doc-bottom-nav.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Tests

[firefox] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line

3) [firefox] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBeLessThanOrEqual(expected) Expected: <= 15445.18359375 Received: 15605.18359375 35 | } 36 | > 37 | expect(nextBox.y + nextBox.height).toBeLessThanOrEqual(prevBox.y); | ^ 38 | }); 39 | 40 | test('should keep both links on a single line on a wide viewport', async ({ page }) => { at /home/runner/work/expressjs.com/expressjs.com/tests/e2e/doc-bottom-nav.spec.ts:37:40

Check failure on line 37 in tests/e2e/doc-bottom-nav.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Tests

[firefox] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line

3) [firefox] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line Error: expect(received).toBeLessThanOrEqual(expected) Expected: <= 15445.18359375 Received: 15605.18359375 35 | } 36 | > 37 | expect(nextBox.y + nextBox.height).toBeLessThanOrEqual(prevBox.y); | ^ 38 | }); 39 | 40 | test('should keep both links on a single line on a wide viewport', async ({ page }) => { at /home/runner/work/expressjs.com/expressjs.com/tests/e2e/doc-bottom-nav.spec.ts:37:40

Check failure on line 37 in tests/e2e/doc-bottom-nav.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Tests

[chromium] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line

2) [chromium] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBeLessThanOrEqual(expected) Expected: <= 15386.9375 Received: 15458.9375 35 | } 36 | > 37 | expect(nextBox.y + nextBox.height).toBeLessThanOrEqual(prevBox.y); | ^ 38 | }); 39 | 40 | test('should keep both links on a single line on a wide viewport', async ({ page }) => { at /home/runner/work/expressjs.com/expressjs.com/tests/e2e/doc-bottom-nav.spec.ts:37:40

Check failure on line 37 in tests/e2e/doc-bottom-nav.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Tests

[chromium] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line

2) [chromium] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBeLessThanOrEqual(expected) Expected: <= 15386.9375 Received: 15458.9375 35 | } 36 | > 37 | expect(nextBox.y + nextBox.height).toBeLessThanOrEqual(prevBox.y); | ^ 38 | }); 39 | 40 | test('should keep both links on a single line on a wide viewport', async ({ page }) => { at /home/runner/work/expressjs.com/expressjs.com/tests/e2e/doc-bottom-nav.spec.ts:37:40

Check failure on line 37 in tests/e2e/doc-bottom-nav.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Tests

[chromium] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line

2) [chromium] › tests/e2e/doc-bottom-nav.spec.ts:18:3 › Doc bottom navigation › should move the next link above the previous link when they cannot share a line Error: expect(received).toBeLessThanOrEqual(expected) Expected: <= 15386.9375 Received: 15458.9375 35 | } 36 | > 37 | expect(nextBox.y + nextBox.height).toBeLessThanOrEqual(prevBox.y); | ^ 38 | }); 39 | 40 | test('should keep both links on a single line on a wide viewport', async ({ page }) => { at /home/runner/work/expressjs.com/expressjs.com/tests/e2e/doc-bottom-nav.spec.ts:37:40
});

test('should keep both links on a single line on a wide viewport', async ({ page }) => {
await page.setViewportSize({ width: 1400, height: 900 });
await page.goto(DOC_PATH);

const prevBox = await page.locator('.doc-nav__link--prev').boundingBox();
const nextBox = await page.locator('.doc-nav__link--next').boundingBox();

if (!prevBox || !nextBox) {
throw new Error('expected both doc nav links to be laid out');
}

expect(nextBox.y).toBeCloseTo(prevBox.y, 0);
expect(nextBox.x).toBeGreaterThan(prevBox.x);
});
});
Loading