mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-04 01:09:20 +02:00
fix(core): revert wrong anchor link implementation change (#10311)
This commit is contained in:
parent
61d6858864
commit
868d72fe4f
3 changed files with 371 additions and 6 deletions
10
.github/workflows/build-perf.yml
vendored
10
.github/workflows/build-perf.yml
vendored
|
@ -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?
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
347
packages/docusaurus/src/client/exports/__tests__/Link.test.tsx
Normal file
347
packages/docusaurus/src/client/exports/__tests__/Link.test.tsx
Normal 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]}
|
||||
/>
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue