fix(core): revert wrong anchor link implementation change (#10311)

This commit is contained in:
Sébastien Lorber 2024-07-19 17:13:28 +02:00 committed by GitHub
parent 61d6858864
commit 868d72fe4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 371 additions and 6 deletions

View file

@ -46,9 +46,11 @@ jobs:
uses: preactjs/compressed-size-action@f780fd104362cfce9e118f9198df2ee37d12946c # v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
build-script: build:website:en
build-script: build:website:fast
clean-script: clear:website # see https://github.com/facebook/docusaurus/pull/6838
pattern: '{website/build/assets/js/main*js,website/build/assets/css/styles*css,website/.docusaurus/globalData.json,website/.docusaurus/registry.js,website/.docusaurus/routes.js,website/.docusaurus/routesChunkNames.json,website/.docusaurus/site-metadata.json,website/.docusaurus/codeTranslations.json,website/.docusaurus/i18n.json,website/.docusaurus/docusaurus.config.mjs,website/build/index.html,website/build/blog/index.html,website/build/blog/**/introducing-docusaurus/*,website/build/docs/index.html,website/build/docs/installation/index.html,website/build/tests/docs/index.html,website/build/tests/docs/standalone/index.html}'
pattern: '{website/build/assets/js/main*js,website/build/assets/css/styles*css,website/.docusaurus/globalData.json,website/.docusaurus/registry.js,website/.docusaurus/routes.js,website/.docusaurus/routesChunkNames.json,website/.docusaurus/site-metadata.json,website/.docusaurus/codeTranslations.json,website/.docusaurus/i18n.json,website/.docusaurus/docusaurus.config.mjs,website/build/index.html,website/build/docs.html,website/build/docs/**/*.html,website/build/blog.html,website/build/blog/**/*.html}'
# HTML files: exclude versioned docs pages, tags pages, html redirect files
exclude: '{website/build/docs/?.?.?/**/*.html,website/build/docs/next/**/*.html,website/build/blog/tags/**/*.html,**/*.html.html}'
strip-hash: '\.([^;]\w{7})\.'
minimum-change-threshold: 30
compression: none
@ -68,11 +70,11 @@ jobs:
# Ensure build with a cold cache does not increase too much
- name: Build (cold cache)
run: yarn workspace website build --locale en
run: yarn build:website:fast
timeout-minutes: 8
# Ensure build with a warm cache does not increase too much
- name: Build (warm cache)
run: yarn workspace website build --locale en
run: yarn build:website:fast
timeout-minutes: 2
# TODO post a GitHub comment with build with perf warnings?

View file

@ -135,7 +135,7 @@ function Link(
useEffect(() => {
// If IO is not supported. We prefetch by default (only once).
if (!IOSupported && isInternal) {
if (!IOSupported && isInternal && ExecutionEnvironment.canUseDOM) {
if (targetLink != null) {
window.docusaurus.prefetch(targetLink);
}
@ -157,7 +157,15 @@ function Link(
const hasInternalTarget = !props.target || props.target === '_self';
// Should we use a regular <a> tag instead of React-Router Link component?
const isRegularHtmlLink = !targetLink || !isInternal || !hasInternalTarget;
const isRegularHtmlLink =
!targetLink ||
!isInternal ||
!hasInternalTarget ||
// When using the hash router, we can't use the regular <a> link for anchors
// We need to use React Router to navigate to /#/pathname/#anchor
// And not /#anchor
// See also https://github.com/facebook/docusaurus/pull/10311
(isAnchorLink && router !== 'hash');
if (!noBrokenLinkCheck && (isAnchorLink || !isRegularHtmlLink)) {
brokenLinks.collectLink(targetLink!);
@ -167,6 +175,12 @@ function Link(
brokenLinks.collectAnchor(props.id);
}
// These props are only added in unit tests to assert/capture the type of link
const testOnlyProps =
process.env.NODE_ENV === 'test'
? {'data-test-link-type': isRegularHtmlLink ? 'regular' : 'react-router'}
: {};
return isRegularHtmlLink ? (
// eslint-disable-next-line jsx-a11y/anchor-has-content, @docusaurus/no-html-links
<a
@ -175,6 +189,7 @@ function Link(
{...(targetLinkUnprefixed &&
!isInternal && {target: '_blank', rel: 'noopener noreferrer'})}
{...props}
{...testOnlyProps}
/>
) : (
<LinkComponent
@ -186,6 +201,7 @@ function Link(
// Avoid "React does not recognize the `activeClassName` prop on a DOM
// element"
{...(isNavLink && {isActive, activeClassName})}
{...testOnlyProps}
/>
);
}

View file

@ -0,0 +1,347 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/* eslint-disable jsx-a11y/anchor-is-valid */
import React, {type ReactNode} from 'react';
import renderer from 'react-test-renderer';
import {fromPartial} from '@total-typescript/shoehorn';
import {StaticRouter} from 'react-router-dom';
import Link from '../Link';
import {Context} from '../../docusaurusContext';
import type {DocusaurusContext} from '@docusaurus/types';
type Options = {
trailingSlash: boolean | undefined;
baseUrl: string;
router: DocusaurusContext['siteConfig']['future']['experimental_router'];
currentLocation: string;
};
const defaultOptions: Options = {
trailingSlash: undefined,
baseUrl: '/',
router: 'browser',
// currentLocation is nested on purpose, shows relative link resolution
currentLocation: '/sub/category/currentPathname',
};
function createDocusaurusContext(
partialOptions: Partial<Options>,
): DocusaurusContext {
const options: Options = {...defaultOptions, ...partialOptions};
return fromPartial({
siteConfig: {
baseUrl: options.baseUrl,
trailingSlash: options.trailingSlash,
future: {
experimental_router: options.router,
},
},
});
}
function createLinkRenderer(defaultRendererOptions: Partial<Options> = {}) {
return (linkJsx: ReactNode, testOptions: Partial<Options> = {}) => {
const options: Options = {
...defaultOptions,
...defaultRendererOptions,
...testOptions,
};
const docusaurusContext = createDocusaurusContext(options);
return renderer
.create(
<StaticRouter location={options.currentLocation} context={{}}>
<Context.Provider value={docusaurusContext}>
{linkJsx}
</Context.Provider>
</StaticRouter>,
)
.toJSON();
};
}
describe('<Link>', () => {
describe('using "browser" router', () => {
const render = createLinkRenderer({router: 'browser'});
it("can render '/docs/intro'", () => {
expect(render(<Link to="/docs/intro" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/docs/intro"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render '/docs/intro' with baseUrl /baseUrl/", () => {
expect(render(<Link to="/docs/intro" />, {baseUrl: '/baseUrl/'}))
.toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/baseUrl/docs/intro"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render '/docs/intro' with baseUrl /docs/", () => {
// TODO Docusaurus v4 ?
// Change weird historical baseUrl behavior
// we should link to /docs/docs/intro, not /docs/intro
// see https://github.com/facebook/docusaurus/issues/6294
expect(render(<Link to="/docs/intro" />, {baseUrl: '/docs/'}))
.toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/docs/intro"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render '/docs/intro' with trailingSlash true", () => {
expect(render(<Link to="/docs/intro" />, {trailingSlash: true}))
.toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/docs/intro/"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render '/docs/intro/' with trailingSlash false", () => {
expect(render(<Link to="/docs/intro/" />, {trailingSlash: false}))
.toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/docs/intro"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render '#anchor'", () => {
expect(render(<Link to="#anchor" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="regular"
href="#anchor"
/>
`);
});
it("can render '/docs/intro#anchor'", () => {
expect(render(<Link to="/docs/intro#anchor" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/docs/intro#anchor"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render '/docs/intro/#anchor'", () => {
expect(render(<Link to="/docs/intro/#anchor" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/docs/intro/#anchor"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render '/pathname?qs#anchor'", () => {
expect(render(<Link to="/pathname?qs#anchor" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/pathname?qs#anchor"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render ''", () => {
expect(render(<Link to="" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="regular"
/>
`);
});
it("can render 'relativeDoc'", () => {
expect(render(<Link to="relativeDoc" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/sub/category/relativeDoc"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render './relativeDoc'", () => {
expect(render(<Link to="./relativeDoc" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/sub/category/relativeDoc"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render './../relativeDoc?qs#anchor'", () => {
expect(render(<Link to="./../relativeDoc?qs#anchor" />))
.toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/sub/relativeDoc?qs#anchor"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render 'https://example.com/xyz'", () => {
expect(render(<Link to="https://example.com/xyz" />))
.toMatchInlineSnapshot(`
<a
data-test-link-type="regular"
href="https://example.com/xyz"
rel="noopener noreferrer"
target="_blank"
/>
`);
});
it("can render 'pathname:///docs/intro'", () => {
expect(render(<Link to="pathname:///docs/intro" />))
.toMatchInlineSnapshot(`
<a
data-test-link-type="regular"
href="/docs/intro"
rel="noopener noreferrer"
target="_blank"
/>
`);
});
it("can render 'pathname://docs/intro'", () => {
expect(render(<Link to="pathname://docs/intro" />))
.toMatchInlineSnapshot(`
<a
data-test-link-type="regular"
href="docs/intro"
rel="noopener noreferrer"
target="_blank"
/>
`);
});
it("can render 'pathname:///docs/intro' with baseUrl /baseUrl/", () => {
expect(
render(<Link to="pathname:///docs/intro" />, {baseUrl: '/baseUrl/'}),
).toMatchInlineSnapshot(`
<a
data-test-link-type="regular"
href="/baseUrl/docs/intro"
rel="noopener noreferrer"
target="_blank"
/>
`);
});
it("can render 'pathname:///docs/intro' with target _self", () => {
expect(render(<Link to="pathname:///docs/intro" target="_self" />))
.toMatchInlineSnapshot(`
<a
data-test-link-type="regular"
href="/docs/intro"
rel="noopener noreferrer"
target="_self"
/>
`);
});
it("can render 'pathname:///docs/intro with trailingSlash: true", () => {
expect(
render(<Link to="pathname:///docs/intro" />, {trailingSlash: true}),
).toMatchInlineSnapshot(`
<a
data-test-link-type="regular"
href="/docs/intro"
rel="noopener noreferrer"
target="_blank"
/>
`);
});
});
describe('using "hash" router', () => {
const render = createLinkRenderer({router: 'hash'});
it("can render '/docs/intro'", () => {
expect(render(<Link to="/docs/intro" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/docs/intro"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render '#anchor'", () => {
// It's important to use React Router link for hash router anchors
// See https://github.com/facebook/docusaurus/pull/10311
expect(render(<Link to="#anchor" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/sub/category/currentPathname#anchor"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
it("can render './relativeDoc'", () => {
// Not sure to remember exactly what's this edge case about
// still worth it to capture behavior in tests
expect(render(<Link to="./relativeDoc" />)).toMatchInlineSnapshot(`
<a
data-test-link-type="react-router"
href="/relativeDoc"
onClick={[Function]}
onMouseEnter={[Function]}
onTouchStart={[Function]}
/>
`);
});
});
});