-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(gatsby): fix not focusing router wrapper after navigation #13197
Changes from 22 commits
c783437
4379722
571aa10
d4147e4
39a0e10
d7d1e80
b0818f2
ac4f22e
335b3e6
b69e3df
8af2517
15740ee
06b3114
3ad203a
30df94c
2d17c03
6d62524
00116f6
f84e41e
5996d80
ef89046
931c911
2868637
43bc8b4
dfacf88
498337e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
describe(`focus managment`, () => { | ||
it(`Focus router wrapper after navigation to regular page (from index)`, () => { | ||
cy.visit(`/`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/page-2/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus router wrapper after navigation to regular page (to index)`, () => { | ||
cy.visit(`/page-2/`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
// Navigating to 404 and from 404 doesn't properly trigger focusing | ||
// router wrapper. That's because dev 404 is special case and new router | ||
// is being used when transitioning to/from 404 page and @reach/router | ||
// doesn't meddle with focus on initial router component mount. | ||
|
||
it.skip(`Focus router wrapper after navigation to 404`, () => { | ||
cy.visit(`/`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/broken-path/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it.skip(`Focus router wrapper after navigation from 404`, () => { | ||
cy.visit(`/broken-path`, { failOnStatusCode: false }).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus router wrapper after navigation from one 404 path to another 404 path`, () => { | ||
cy.visit(`/broken-path`, { failOnStatusCode: false }).waitForRouteChange() | ||
|
||
// navigating to different not existing page should also trigger | ||
// router wrapper focus as this is different page | ||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/another-broken-path/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus router wrapper after navigation to client-only page`, () => { | ||
cy.visit(`/`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/client-only-paths/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus router wrapper after navigation from client-only page`, () => { | ||
cy.visit(`/client-only-paths/`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus subrouter inside client-only page`, () => { | ||
cy.visit(`/client-only-paths`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/client-only-paths/profile`) | ||
|
||
// inner paths are handled by router instance defined in client-only-paths page | ||
// which means that navigating inside those should be handled by that router | ||
// (not main router provided by gatsby) | ||
cy.assertRouterWrapperFocus(false) | ||
cy.focused().should(`have.attr`, `id`, `client-only-paths-sub-router`) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React from "react" | ||
import { Router } from "@reach/router" | ||
import { Link } from "gatsby" | ||
|
||
import Layout from "../components/layout" | ||
import InstrumentPage from "../utils/instrument-page" | ||
|
||
const Page = props => ( | ||
<pre data-testid="dom-marker">[client-only-path] {props.page}</pre> | ||
) | ||
|
||
const routes = [`/`, `/profile`, `/dashboard`] | ||
|
||
const basePath = `/client-only-paths` | ||
|
||
const ClientOnlyPathPage = props => ( | ||
<Layout> | ||
<Router | ||
location={props.location} | ||
basepath={basePath} | ||
id="client-only-paths-sub-router" | ||
> | ||
<Page path="/" page="index" /> | ||
<Page path="/:page" /> | ||
</Router> | ||
<ul> | ||
{routes.map(route => ( | ||
<li key={route}> | ||
<Link to={`${basePath}${route}`} data-testid={route}> | ||
{route} | ||
</Link> | ||
</li> | ||
))} | ||
</ul> | ||
</Layout> | ||
) | ||
|
||
export default InstrumentPage(ClientOnlyPathPage) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
describe(`focus managment`, () => { | ||
it(`Focus router wrapper after navigation to regular page (from index)`, () => { | ||
cy.visit(`/`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/page-2/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus router wrapper after navigation to regular page (to index)`, () => { | ||
cy.visit(`/page-2/`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus router wrapper after navigation to 404`, () => { | ||
cy.visit(`/`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/broken-path/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus router wrapper after navigation from 404`, () => { | ||
cy.visit(`/broken-path`, { failOnStatusCode: false }).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus router wrapper after navigation from one 404 path to another 404 path`, () => { | ||
cy.visit(`/broken-path`, { failOnStatusCode: false }).waitForRouteChange() | ||
|
||
// navigating to different not existing page should also trigger | ||
// router wrapper focus as this is different page | ||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/another-broken-path/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus router wrapper after navigation to client-only page`, () => { | ||
cy.visit(`/`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/client-only-paths/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus router wrapper after navigation from client-only page`, () => { | ||
cy.visit(`/client-only-paths/`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/`) | ||
cy.assertRouterWrapperFocus(true) | ||
}) | ||
|
||
it(`Focus subrouter inside client-only page`, () => { | ||
cy.visit(`/client-only-paths`).waitForRouteChange() | ||
|
||
cy.changeFocus() | ||
cy.assertRouterWrapperFocus(false) | ||
cy.navigateAndWaitForRouteChange(`/client-only-paths/profile`) | ||
|
||
// inner paths are handled by router instance defined in client-only-paths page | ||
// which means that navigating inside those should be handled by that router | ||
// (not main router provided by gatsby) | ||
cy.assertRouterWrapperFocus(false) | ||
cy.focused().should(`have.attr`, `id`, `client-only-paths-sub-router`) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ const NotFound = () => <h1>Not Found in App</h1> | |
|
||
const AppPage = () => ( | ||
<Router> | ||
<App path="/paths/app" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sweet! |
||
<App path="/app" /> | ||
<NotFound default /> | ||
</Router> | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -169,8 +169,17 @@ class EnsureResources extends React.Component { | |
if (!this.hasResources(this.state.pageResources) && isInitialRender) { | ||
window.___failedResources = true | ||
|
||
// prevent hydrating | ||
throw new Error(`Missing resources for ${this.state.location.pathname}`) | ||
console.error(`Missing resources for ${this.state.location.pathname}`) | ||
// Code below cause partial hydration. Anything above will be properly hydrated, | ||
// but there's really not much other than LocationHandler and any wrappers | ||
// added by wrapRootElement. <div> will mount in place of @reach/router's wrapper | ||
// and preserve DOM elements from static html. | ||
return ( | ||
<div | ||
dangerouslySetInnerHTML={{ __html: `` }} | ||
suppressHydrationWarning | ||
/> | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @davidbailey00 I would like sanity check here: was always there coming from above . My change moves below so no DOM element is above, which was causing issues.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this means we will do a full client rehydration if something goes wrong, right? maybe add a comment about it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's partial hydration - things above the actual page will be hydrated - so if users use This is a neat trick that you can do when using:
during hydration, as it won't try to actually change DOM nodes under it. But yeah, I should add more context there for sure |
||
} | ||
|
||
isInitialRender = false | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are so awesome!! 🌮