mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
Flagged behind `experimental.appNewScrollHandler` `focus()` on Fragment refs works different than `findDOMNode(this).focus()`. `findDOMNode` will return an actual host element so it's effectively `HTMLElement.focus()`. If the element isn't focuseable, `focus()` will do nothing. By spec, there's also no way to determine if an element is focusable. It's up to user agents (e.g. sometimes scrollable containers are considered focusable). `FragmentInstance.focus` will focus the first focusable descendant. That could be a focusable element deep within the fragment e.g. `<Fragment><section><p>A lot of text</p><a href="">Next page</a></Fragment>`. Focusing such a deep element may sometimes be counter productive. Now we just blur the active element. This is how a hard navigation already works today and ensures we don't skip past content of the new segment. This means you potentially stay a long way before the actual Segment (e.g. in a preceding nav) with the focus cursor which could be annoying. We could be clever and temporarily insert a focusable element at the start of the Segment, focus it, and then immediately remove. That would leave the focus cursor right before the Segment. However, this requires knowledge about the parent content (can we just insert an element of our choosing?) and relies on knowing what's focusable (a button would be a good bet). That's a bit too clever for a fix without concrete motivation so I'm waiting somebody to complain about the new behavior.
113 lines
3.5 KiB
TypeScript
113 lines
3.5 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import { retry } from 'next-test-utils'
|
|
|
|
const enableNewScrollHandler =
|
|
process.env.__NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER === 'true'
|
|
|
|
describe('navigation-focus', () => {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
it('navigation to an interactive segment', async () => {
|
|
const browser = await next.browser('/')
|
|
await browser.elementByCss('a[href="/interactive-segment"]').click()
|
|
|
|
await retry(async () => {
|
|
// Good debug info is a moving target. Use Playwright traces to find out
|
|
// what was focused if this fails
|
|
if (enableNewScrollHandler) {
|
|
expect(await browser.eval(() => document.activeElement.localName)).toBe(
|
|
'body'
|
|
)
|
|
} else {
|
|
expect(
|
|
await browser.eval(() =>
|
|
document.activeElement.getAttribute('data-testid')
|
|
)
|
|
).toBe('segment-container')
|
|
}
|
|
})
|
|
})
|
|
|
|
it('navigation to a scrollable segment', async () => {
|
|
const browser = await next.browser('/')
|
|
await browser.elementByCss('a[href="/scrollable-segment"]').click()
|
|
|
|
await retry(async () => {
|
|
if (enableNewScrollHandler) {
|
|
expect(await browser.eval(() => document.activeElement.localName)).toBe(
|
|
'body'
|
|
)
|
|
} else {
|
|
expect(
|
|
await browser.eval(() =>
|
|
document.activeElement.getAttribute('data-testid')
|
|
)
|
|
).toBe('segment-container')
|
|
}
|
|
})
|
|
})
|
|
|
|
it('navigation to a segment with a focusable descendant', async () => {
|
|
const browser = await next.browser('/')
|
|
await browser
|
|
.elementByCss('a[href="/segment-with-focusable-descendant"]')
|
|
.click()
|
|
|
|
await retry(async () => {
|
|
if (enableNewScrollHandler) {
|
|
// Focus goes to the focusable descendant, not the segment itself
|
|
expect(await browser.eval(() => document.activeElement.localName)).toBe(
|
|
'body'
|
|
)
|
|
} else {
|
|
// Focus stays on the original link
|
|
expect(
|
|
await browser.eval(() => document.activeElement.getAttribute('href'))
|
|
).toBe('/segment-with-focusable-descendant')
|
|
}
|
|
})
|
|
})
|
|
|
|
it('navigation to a fragment within a page', async () => {
|
|
const browser = await next.browser('/')
|
|
await browser.elementByCss('a[href="/uri-fragments#section-2"]').click()
|
|
|
|
await retry(async () => {
|
|
if (enableNewScrollHandler) {
|
|
expect(await browser.eval(() => document.activeElement.localName)).toBe(
|
|
'body'
|
|
)
|
|
} else {
|
|
// Focus stays on the anchor unlike native behavior
|
|
expect(
|
|
await browser.eval(() => document.activeElement.getAttribute('href'))
|
|
).toEqual('/uri-fragments#section-2')
|
|
}
|
|
})
|
|
// Fragment URI not targetted unlike native behavior
|
|
expect(await browser.locator(':target').isVisible()).toEqual(false)
|
|
})
|
|
|
|
it('navigation within a page to fragments', async () => {
|
|
const browser = await next.browser('/uri-fragments')
|
|
await browser.elementByCss('a[href="#section-1"]').click()
|
|
|
|
await retry(async () => {
|
|
if (enableNewScrollHandler) {
|
|
expect(await browser.eval(() => document.activeElement.localName)).toBe(
|
|
'body'
|
|
)
|
|
} else {
|
|
// Focus stays on the anchor unlike native behavior
|
|
expect(
|
|
await browser.eval(() => document.activeElement.getAttribute('href'))
|
|
).toEqual('#section-1')
|
|
}
|
|
})
|
|
// Fragment URI not targetted unlike native behavior
|
|
expect(await browser.locator(':target').isVisible()).toEqual(false)
|
|
})
|
|
})
|