-
Notifications
You must be signed in to change notification settings - Fork 979
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Handle focus on route change (#2321)
* feat: focus RouteFocus on route change * feat: handle elements that aren't focusable * feat: add some tests for getFocus * style: split up tests
- Loading branch information
Showing
5 changed files
with
189 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import { render, waitFor } from '@testing-library/react' | ||
import '@testing-library/jest-dom/extend-expect' | ||
|
||
import { Router, Route, routes, getFocus } from '../internal' | ||
import RouteFocus from '../route-focus' | ||
|
||
// SETUP | ||
const RouteFocusPage = () => ( | ||
<> | ||
<RouteFocus> | ||
<a>a link is a focusable element</a> | ||
</RouteFocus> | ||
<h1>Route Focus Page</h1> | ||
<p></p> | ||
</> | ||
) | ||
|
||
const NoRouteFocusPage = () => <h1>No Route Focus Page</h1> | ||
|
||
const RouteFocusNoChildren = () => ( | ||
<> | ||
<RouteFocus></RouteFocus> | ||
<h1>Route Focus No Children Page</h1> | ||
<p></p> | ||
</> | ||
) | ||
|
||
const RouteFocusTextNodePage = () => ( | ||
<> | ||
<RouteFocus>some text</RouteFocus> | ||
<h1>Route Focus Text Node Page </h1> | ||
<p></p> | ||
</> | ||
) | ||
|
||
const RouteFocusNegativeTabIndexPage = () => ( | ||
<> | ||
<RouteFocus> | ||
<p>my tabindex is -1</p> | ||
</RouteFocus> | ||
<h1>Route Focus Negative Tab Index Page </h1> | ||
<p></p> | ||
</> | ||
) | ||
|
||
beforeEach(() => { | ||
window.history.pushState({}, null, '/') | ||
Object.keys(routes).forEach((key) => delete routes[key]) | ||
}) | ||
|
||
test('getFocus returns a focusable element if RouteFocus has one', async () => { | ||
const TestRouter = () => ( | ||
<Router> | ||
<Route path="/" page={RouteFocusPage} name="routeFocus" /> | ||
</Router> | ||
) | ||
|
||
const screen = render(<TestRouter />) | ||
|
||
await waitFor(() => { | ||
screen.getByText(/Route Focus Page/i) | ||
const routeFocus = getFocus() | ||
expect(routeFocus).toHaveTextContent('a link is a focusable element') | ||
}) | ||
}) | ||
|
||
test("getFocus returns null if there's no RouteFocus", async () => { | ||
const TestRouter = () => ( | ||
<Router> | ||
<Route path="/" page={NoRouteFocusPage} name="noRouteFocus" /> | ||
</Router> | ||
) | ||
|
||
const screen = render(<TestRouter />) | ||
|
||
await waitFor(() => { | ||
screen.getByText(/No Route Focus Page/i) | ||
const routeFocus = getFocus() | ||
expect(routeFocus).toBeNull() | ||
}) | ||
}) | ||
|
||
test("getFocus returns null if RouteFocus doesn't have any children", async () => { | ||
const TestRouter = () => ( | ||
<Router> | ||
<Route path="/" page={RouteFocusNoChildren} name="routeFocusNoChildren" /> | ||
</Router> | ||
) | ||
|
||
const screen = render(<TestRouter />) | ||
|
||
await waitFor(() => { | ||
screen.getByText(/Route Focus No Children Page/i) | ||
const routeFocus = getFocus() | ||
expect(routeFocus).toBeNull() | ||
}) | ||
}) | ||
|
||
test('getFocus returns null if RouteFocus has just a Text node', async () => { | ||
const TestRouter = () => ( | ||
<Router> | ||
<Route path="/" page={RouteFocusTextNodePage} name="routeFocusTextNode" /> | ||
</Router> | ||
) | ||
|
||
const screen = render(<TestRouter />) | ||
|
||
await waitFor(() => { | ||
screen.getByText(/Route Focus Text Node Page/i) | ||
const routeFocus = getFocus() | ||
expect(routeFocus).toBeNull() | ||
}) | ||
}) | ||
|
||
test("getFocus returns null if RouteFocus's child isn't focusable", async () => { | ||
const TestRouter = () => ( | ||
<Router> | ||
<Route | ||
path="/" | ||
page={RouteFocusNegativeTabIndexPage} | ||
name="routeFocusNegativeTabIndex" | ||
/> | ||
</Router> | ||
) | ||
|
||
const screen = render(<TestRouter />) | ||
|
||
await waitFor(() => { | ||
screen.getByText(/Route Focus Negative Tab Index Page/i) | ||
const routeFocus = getFocus() | ||
expect(routeFocus).toBeNull() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import React from 'react' | ||
|
||
/** | ||
* this initial implementation borrows (heavily!) from madalyn's great work at gatsby: | ||
* - issue: https://github.com/gatsbyjs/gatsby/issues/21059 | ||
* - PR: https://github.com/gatsbyjs/gatsby/pull/26376 | ||
*/ | ||
const RouteFocus: React.FC<RouteFocusProps> = ({ children, ...props }) => ( | ||
<div {...props} data-redwood-route-focus={true}> | ||
{children} | ||
</div> | ||
) | ||
|
||
export interface RouteFocusProps { | ||
children: React.ReactNode | ||
} | ||
|
||
export default RouteFocus |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters