Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions e2e/website/docs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, test } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
import { readFileSync } from 'node:fs'
import process from 'node:process'
import { getReleasePresentation } from '../../website/content/release'

const generatedPublicApi = JSON.parse(
Expand Down Expand Up @@ -320,19 +321,23 @@ test('API reference renders every generated entry exactly once', async ({

test('API signatures stay out of keyboard order while links remain reachable', async ({
page,
browserName,
}) => {
await page.goto('/docs/api/')

const source = page.getByRole('link', { name: 'View Sheet.Root source' })
const controlledRecipe = page.getByRole('link', {
name: 'Run the controlled-state recipe',
})
// macOS Safari uses Option+Tab to include links in keyboard navigation.
const modifier =
browserName === 'webkit' && process.platform === 'darwin' ? 'Alt+' : ''
await source.focus()
await page.keyboard.press('Shift+Tab')
await page.keyboard.press(`${modifier}Shift+Tab`)
await expect(page.locator(':focus')).toHaveAttribute('href')
await page.keyboard.press('Tab')
await page.keyboard.press(`${modifier}Tab`)
await expect(source).toBeFocused()
await page.keyboard.press('Tab')
await page.keyboard.press(`${modifier}Tab`)
await expect(controlledRecipe).toBeFocused()
})

Expand Down Expand Up @@ -459,9 +464,16 @@ test('home page does not overflow a mobile viewport', async ({ page }) => {
expect(dimensions.demo?.right).toBeLessThanOrEqual(dimensions.viewport)
})

test('skip link moves keyboard focus to the main content', async ({ page }) => {
test('skip link moves keyboard focus to the main content', async ({
page,
browserName,
}) => {
await page.goto('/')
await page.keyboard.press('Tab')
const tab =
browserName === 'webkit' && process.platform === 'darwin'
? 'Alt+Tab'
: 'Tab'
await page.keyboard.press(tab)

const skipLink = page.getByRole('link', { name: 'Skip to content' })
await expect(skipLink).toBeFocused()
Expand Down
72 changes: 71 additions & 1 deletion e2e/website/home.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,63 @@ import AxeBuilder from '@axe-core/playwright'
import { readFileSync } from 'node:fs'
import { buildEvidence } from '../../website/content/evidence'

test('homepage support links follow the demo and remain usable at mobile sizes', async ({
page,
}, testInfo) => {
await page.goto('/')
const support = page.getByRole('region', { name: 'Useful in your project?' })
await expect(support).toBeVisible()
await expect(
support.getByRole('heading', { level: 2, name: 'Useful in your project?' }),
).toBeVisible()
expect(
await support.evaluate((element) =>
element.previousElementSibling?.matches('.docs-hero'),
),
).toBe(true)
const links = [
[
'Star on GitHub',
'https://github.com/NIPE-Solutions/react-spring-bottom-sheet',
],
['Explore NIPE Open Source', 'https://opensource.nipesolutions.com'],
] as const
for (const width of [1440, 320]) {
await page.setViewportSize({ width, height: 900 })
await support.screenshot({
path: testInfo.outputPath(`support-${width.toString()}.png`),
})
for (const [name, href] of links) {
const link = support.getByRole('link', { name, exact: true })
await expect(link).toHaveAttribute('href', href)
await link.focus()
await expect(link).toBeFocused()
await expect(link).toHaveCSS('outline-style', 'solid')
const bounds = await link.boundingBox()
expect(bounds?.height).toBeGreaterThanOrEqual(44)
expect(bounds?.width).toBeGreaterThanOrEqual(44)
expect(bounds?.x).toBeGreaterThanOrEqual(0)
expect((bounds?.x ?? 0) + (bounds?.width ?? 0)).toBeLessThanOrEqual(width)
}
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
).toBe(width)
}
})

test('support section stays off task and legal routes', async ({ page }) => {
for (const route of ['/impressum/', '/privacy/', '/examples/basic/embed/']) {
await page.goto(route)
await expect(page.getByRole('main')).toBeVisible()
await expect(
page.getByRole('region', { name: 'Useful in your project?' }),
).toHaveCount(0)
await expect(
page.getByRole('link', { name: 'Star on GitHub', exact: true }),
).toHaveCount(0)
}
})

const packageVersion = JSON.parse(
readFileSync(new URL('../../package.json', import.meta.url), 'utf8'),
).version as string
Expand Down Expand Up @@ -138,11 +195,24 @@ test('live sheet opens, changes destination, and restores focus', async ({
await page.goto('/')
const trigger = page.getByRole('button', { name: 'Open the live sheet' })

await trigger.click()
await trigger.focus()
await trigger.press('Enter')
const phone = page.getByLabel('Interactive sheet preview')
const dialog = page.getByRole('dialog', { name: 'Try the real package' })
await expect(dialog).toBeVisible()
await expect(phone.locator('[role="dialog"]')).toHaveCount(1)
await expect
.poll(() =>
dialog.evaluate((element) => {
const phone = document
.querySelector('.docs-phone-screen')
?.getBoundingClientRect()
return phone
? element.getBoundingClientRect().top < phone.bottom
: false
}),
)
.toBe(true)
const containment = await page.evaluate(() => {
const screen = document.querySelector('.docs-phone-screen')
const phone = screen?.getBoundingClientRect()
Expand Down
9 changes: 8 additions & 1 deletion e2e/website/recipes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1031,10 +1031,14 @@ test('basic recipe opens, closes, and restores focus', async ({ page }) => {
const frame = recipeFrame(page)
const trigger = frame.getByRole('button', { name: 'Open basic sheet' })

await trigger.click()
await trigger.focus()
await trigger.press('Enter')
await expect(
frame.getByRole('dialog', { name: 'Basic bottom sheet' }),
).toBeVisible()
await expect(
frame.getByRole('dialog', { name: 'Basic bottom sheet' }),
).toHaveAttribute('data-rsbs-state', 'open')
await frame.getByRole('button', { name: 'Close sheet' }).click()
await expect(frame.getByRole('dialog')).toHaveCount(0)
await expect(trigger).toBeFocused()
Expand Down Expand Up @@ -1263,6 +1267,9 @@ test('confirmation recipe cannot dismiss without an explicit choice', async ({
await page.goto('/examples/confirmation/')
const frame = recipeFrame(page)
await frame.getByRole('button', { name: 'Delete workspace' }).click()
await expect(
frame.getByRole('dialog', { name: 'Delete this workspace?' }),
).toHaveAttribute('data-rsbs-state', 'open')
await page.keyboard.press('Escape')
await expect(
frame.getByRole('dialog', { name: 'Delete this workspace?' }),
Expand Down
21 changes: 21 additions & 0 deletions website/app/(site)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,27 @@ export default function Home() {
</div>
<LiveSheet />
</section>
<section className="docs-support" aria-labelledby="docs-support-title">
<h2 id="docs-support-title">Useful in your project?</h2>
<p>
If this sheet helps your project, a GitHub star is a simple way to
support the work and help others find it.
</p>
<div className="docs-actions">
<a
className="docs-button docs-button-primary"
href="https://github.com/NIPE-Solutions/react-spring-bottom-sheet"
>
Star on GitHub
</a>
<a
className="docs-button"
href="https://opensource.nipesolutions.com"
>
Explore NIPE Open Source
</a>
</div>
</section>
<Evidence evidence={buildEvidence} />
<Capabilities />
<StylingContract />
Expand Down
16 changes: 16 additions & 0 deletions website/app/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,22 @@
color: var(--docs-muted);
font-size: 0.86rem;
}
.docs-support {
padding: clamp(2rem, 5vw, 4rem) clamp(1.2rem, 7vw, 7rem);
border-block: 1px solid var(--docs-rule);
}
.docs-support p {
max-width: 65ch;
color: var(--docs-muted);
}
.docs-support .docs-actions {
margin-bottom: 0;
}
.docs-support .docs-button {
display: inline-flex;
align-items: center;
min-height: 44px;
}
.docs-lede {
max-width: 44rem;
color: var(--docs-muted);
Expand Down
Loading