diff options
author | HiDeoo | 2024-04-30 17:21:19 +0200 |
---|---|---|
committer | GitHub | 2024-04-30 17:21:19 +0200 |
commit | 7dc503ea7993123a4aeff453d08de41cac887353 (patch) | |
tree | 10dadc3fa0d24e902810b56d84ddffadb1b6d834 | |
parent | 61493e55f1a80362af13f98d665018376e987439 (diff) | |
download | IT.starlight-7dc503ea7993123a4aeff453d08de41cac887353.tar.gz IT.starlight-7dc503ea7993123a4aeff453d08de41cac887353.tar.bz2 IT.starlight-7dc503ea7993123a4aeff453d08de41cac887353.zip |
Add support for synced tabs (#640)
Co-authored-by: Chris Swithinbank <357379+delucis@users.noreply.github.com>
Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>
Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
25 files changed, 627 insertions, 247 deletions
diff --git a/.changeset/config.json b/.changeset/config.json index 1a3e4d0d..f50e3128 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -9,7 +9,7 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": ["starlight-docs", "@example/*", "starlight-file-icons-generator"], + "ignore": ["starlight-docs", "@example/*", "starlight-file-icons-generator", "@e2e/*"], "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { "onlyUpdatePeerDependentsWhenOutOfRange": true } diff --git a/.changeset/eight-pens-end.md b/.changeset/eight-pens-end.md new file mode 100644 index 00000000..4aaee094 --- /dev/null +++ b/.changeset/eight-pens-end.md @@ -0,0 +1,5 @@ +--- +'@astrojs/starlight': minor +--- + +Adds support for syncing multiple sets of tabs on the same page. diff --git a/.changeset/slimy-swans-teach.md b/.changeset/slimy-swans-teach.md new file mode 100644 index 00000000..ae115067 --- /dev/null +++ b/.changeset/slimy-swans-teach.md @@ -0,0 +1,13 @@ +--- +'@astrojs/starlight': minor +--- + +Updates the default `line-height` from `1.8` to `1.75`. This change avoids having a line height with a fractional part which can cause scripts accessing dimensions involving the line height to get an inconsistent rounded value in various browsers. + +If you want to preserve the previous `line-height`, you can add the following custom CSS to your site: + +```css +:root { + --sl-line-height: 1.8; +} +``` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbde0027..beb12cf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,20 @@ jobs: - name: Test packages run: pnpm -r test:coverage + e2e-test: + name: Run E2E tests + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v3 + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + - run: pnpm i + - name: Test packages + run: pnpm -r test:e2e + pa11y: name: Check for accessibility issues runs-on: ubuntu-20.04 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 275ead40..973d01ef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -187,6 +187,36 @@ pnpm test:coverage This will print a table to your terminal and also generate an HTML report you can load in a web browser by opening [`packages/starlight/__coverage__/index.html`](./packages/starlight/__coverage__/index.html). +### End-to-end (E2E) tests + +Starlight also includes E2E tests in [`packages/starlight/__e2e__/`](./packages/starlight/__e2e__/), which are run using [Playwright][playwright]. + +To run these tests, move into the Starlight package and then run `pnpm test:e2e`: + +```sh +cd packages/starlight +pnpm test:e2e +``` + +#### Test fixtures + +Each subdirectory of `packages/starlight/__e2e__/fixtures` should contain the basic files needed to run Starlight (`package.json`, `astro.config.mjs`, a content collection configuration in `src/content/config.ts` and some content to render in `src/content/docs/`). + +The `testFactory()` helper can be used in a test file to define the fixture which will be built and loaded in a preview server during a set of tests. + +```ts +// packages/starlight/__e2e__/feature.test.ts +import { testFactory } from './test-utils'; + +const test = await testFactory('./fixtures/basics/'); +``` + +This allows you to run tests against different combinations of Astro and Starlight configuration options for various content. + +#### When to add E2E tests? + +E2E are most useful for testing what happens on a page after it has been loaded by a browser. They run slower than unit tests so they should be used sparingly when unit tests aren’t sufficient. + ## Translations Translations help make Starlight accessible to more people. @@ -254,6 +284,7 @@ To add a language, you will need its BCP-47 tag and a label. See [“Adding a ne [gfi]: https://github.com/withastro/starlight/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+ [api-docs]: https://docs.astro.build/en/reference/integrations-reference/ [vitest]: https://vitest.dev/ +[playwright]: https://playwright.dev/ ## Showcase diff --git a/docs/src/content/docs/getting-started.mdx b/docs/src/content/docs/getting-started.mdx index eb3a8f3e..810b17f8 100644 --- a/docs/src/content/docs/getting-started.mdx +++ b/docs/src/content/docs/getting-started.mdx @@ -15,7 +15,7 @@ See the [manual setup instructions](/manual-setup/) to add Starlight to an exist Create a new Astro + Starlight project by running the following command in your terminal: -<Tabs> +<Tabs syncKey="pkg"> <TabItem label="npm"> ```sh @@ -52,7 +52,7 @@ When working locally, [Astro’s development server](https://docs.astro.build/en Inside your project directory, run the following command to start the development server: -<Tabs> +<Tabs syncKey="pkg"> <TabItem label="npm"> ```sh @@ -103,7 +103,7 @@ Be sure to update Starlight regularly! Starlight is an Astro integration. You can update it and other Astro packages by running the following command in your terminal: -<Tabs> +<Tabs syncKey="pkg"> <TabItem label="npm"> ```sh diff --git a/docs/src/content/docs/guides/components.mdx b/docs/src/content/docs/guides/components.mdx index a193175b..b01050f0 100644 --- a/docs/src/content/docs/guides/components.mdx +++ b/docs/src/content/docs/guides/components.mdx @@ -87,6 +87,50 @@ The code above generates the following tabs on the page: </TabItem> </Tabs> +#### Synced tabs + +Keep multiple tab groups synchronized by adding the `syncKey` attribute. + +All `<Tabs>` on a page with the same `syncKey` value will display the same active label. This allows your reader to choose once (e.g. their operating system or package manager), and see their choice reflected throughout the page. + +To synchronize related tabs, add an identical `syncKey` property to each `<Tabs>` component and ensure that they all use the same `<TabItem>` labels: + +```mdx 'syncKey="constellations"' +# src/content/docs/example.mdx + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +_Some stars:_ + +<Tabs syncKey="constellations"> + <TabItem label="Orion">Bellatrix, Rigel, Betelgeuse</TabItem> + <TabItem label="Gemini">Pollux, Castor A, Castor B</TabItem> +</Tabs> + +_Some exoplanets:_ + +<Tabs syncKey="constellations"> + <TabItem label="Orion">HD 34445 b, Gliese 179 b, Wasp-82 b</TabItem> + <TabItem label="Gemini">Pollux b, HAT-P-24b, HD 50554 b</TabItem> +</Tabs> +``` + +The code above generates the following on the page: + +_Some stars:_ + +<Tabs syncKey="constellations"> + <TabItem label="Orion">Bellatrix, Rigel, Betelgeuse</TabItem> + <TabItem label="Gemini">Pollux, Castor A, Castor B</TabItem> +</Tabs> + +_Some exoplanets:_ + +<Tabs syncKey="constellations"> + <TabItem label="Orion">HD 34445 b, Gliese 179 b, Wasp-82 b</TabItem> + <TabItem label="Gemini">Pollux b, HAT-P-24b, HD 50554 b</TabItem> +</Tabs> + ### Cards import { Card, CardGrid } from '@astrojs/starlight/components'; diff --git a/docs/src/content/docs/guides/css-and-tailwind.mdx b/docs/src/content/docs/guides/css-and-tailwind.mdx index 2ef37b60..45c88e65 100644 --- a/docs/src/content/docs/guides/css-and-tailwind.mdx +++ b/docs/src/content/docs/guides/css-and-tailwind.mdx @@ -63,7 +63,7 @@ The Starlight Tailwind plugin applies the following configuration: Start a new Starlight project with Tailwind CSS pre-configured using `create astro`: -<Tabs> +<Tabs syncKey="pkg"> <TabItem label="npm"> ```sh @@ -95,7 +95,7 @@ If you already have a Starlight site and want to add Tailwind CSS, follow these 1. Add Astro’s Tailwind integration: - <Tabs> + <Tabs syncKey="pkg"> <TabItem label="npm"> @@ -125,7 +125,7 @@ If you already have a Starlight site and want to add Tailwind CSS, follow these 2. Install the Starlight Tailwind plugin: - <Tabs> + <Tabs syncKey="pkg"> <TabItem label="npm"> diff --git a/docs/src/content/docs/guides/customization.mdx b/docs/src/content/docs/guides/customization.mdx index c2eaa328..1bb35d21 100644 --- a/docs/src/content/docs/guides/customization.mdx +++ b/docs/src/content/docs/guides/customization.mdx @@ -130,7 +130,7 @@ You can customize — or even disable — the table of contents globally in the By default, `<h2>` and `<h3>` headings are included in the table of contents. Change which headings levels to include site-wide using the `minHeadingLevel` and `maxHeadingLevel` options in your [global `tableOfContents`](/reference/configuration/#tableofcontents). Override these defaults on an individual page by adding the corresponding [frontmatter `tableOfContents`](/reference/frontmatter/#tableofcontents) properties: -<Tabs> +<Tabs syncKey="config-type"> <TabItem label="Frontmatter"> ```md {4-6} @@ -164,7 +164,7 @@ defineConfig({ Disable the table of contents entirely by setting the `tableOfContents` option to `false`: -<Tabs> +<Tabs syncKey="config-type"> <TabItem label="Frontmatter"> ```md {4} diff --git a/package.json b/package.json index e0609e2a..8e0fd9e6 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ { "name": "/_astro/*.js", "path": "examples/basics/dist/_astro/*.js", - "limit": "22.5 kB" + "limit": "23 kB" }, { "name": "/_astro/*.css", diff --git a/packages/starlight/__e2e__/.gitignore b/packages/starlight/__e2e__/.gitignore new file mode 100644 index 00000000..bde2931f --- /dev/null +++ b/packages/starlight/__e2e__/.gitignore @@ -0,0 +1,3 @@ +# generated types +.astro/ +dist/ diff --git a/packages/starlight/__e2e__/fixtures/basics/astro.config.mjs b/packages/starlight/__e2e__/fixtures/basics/astro.config.mjs new file mode 100644 index 00000000..9e96cbad --- /dev/null +++ b/packages/starlight/__e2e__/fixtures/basics/astro.config.mjs @@ -0,0 +1,10 @@ +import starlight from '@astrojs/starlight'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + integrations: [ + starlight({ + title: 'Basics', + }), + ], +}); diff --git a/packages/starlight/__e2e__/fixtures/basics/package.json b/packages/starlight/__e2e__/fixtures/basics/package.json new file mode 100644 index 00000000..bbdb1613 --- /dev/null +++ b/packages/starlight/__e2e__/fixtures/basics/package.json @@ -0,0 +1,9 @@ +{ + "name": "@e2e/basics", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/starlight": "workspace:*", + "astro": "^4.3.5" + } +} diff --git a/packages/starlight/__e2e__/fixtures/basics/src/content/config.ts b/packages/starlight/__e2e__/fixtures/basics/src/content/config.ts new file mode 100644 index 00000000..45f60b01 --- /dev/null +++ b/packages/starlight/__e2e__/fixtures/basics/src/content/config.ts @@ -0,0 +1,6 @@ +import { defineCollection } from 'astro:content'; +import { docsSchema } from '@astrojs/starlight/schema'; + +export const collections = { + docs: defineCollection({ schema: docsSchema() }), +}; diff --git a/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs-variable-height.mdx b/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs-variable-height.mdx new file mode 100644 index 00000000..7c7a0485 --- /dev/null +++ b/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs-variable-height.mdx @@ -0,0 +1,151 @@ +--- +title: Tabs with varying content heights +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +**A set of tabs using a sync key with content of different heights between tabs.** + +<Tabs syncKey="things"> +<TabItem label="A"> + +A little bit of text. + +</TabItem> +<TabItem label="B"> + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +</TabItem> +</Tabs> + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +**Another set of tabs using the same sync key with content of different heights between tabs.** + +<Tabs syncKey="things"> +<TabItem label="A"> + +A little bit of text. + +</TabItem> +<TabItem label="B"> + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +A lot more text. + +</TabItem> +</Tabs> + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. diff --git a/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx b/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx new file mode 100644 index 00000000..17e643a6 --- /dev/null +++ b/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx @@ -0,0 +1,37 @@ +--- +title: Tabs +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +A set of tabs using the `pkg` sync key. + +<Tabs syncKey="pkg"> + <TabItem label="npm">npm command</TabItem> + <TabItem label="pnpm">pnpm command</TabItem> + <TabItem label="yarn">yarn command</TabItem> +</Tabs> + +A basic set of tabs. + +<Tabs> + <TabItem label="one">tab 1</TabItem> + <TabItem label="two">tab 2</TabItem> + <TabItem label="three">tab 3</TabItem> +</Tabs> + +Another set of tabs using the `pkg` sync key and an extra tab. + +<Tabs syncKey="pkg"> + <TabItem label="npm">another npm command</TabItem> + <TabItem label="pnpm">another pnpm command</TabItem> + <TabItem label="bun">another bun command</TabItem> + <TabItem label="yarn">another yarn command</TabItem> +</Tabs> + +A set of tabs using the `style` sync key. + +<Tabs syncKey="style"> + <TabItem label="css">css code</TabItem> + <TabItem label="tailwind">tailwind code</TabItem> +</Tabs> diff --git a/packages/starlight/__e2e__/fixtures/basics/src/env.d.ts b/packages/starlight/__e2e__/fixtures/basics/src/env.d.ts new file mode 100644 index 00000000..c13bd73c --- /dev/null +++ b/packages/starlight/__e2e__/fixtures/basics/src/env.d.ts @@ -0,0 +1,2 @@ +/// <reference path="../.astro/types.d.ts" /> +/// <reference types="astro/client" />
\ No newline at end of file diff --git a/packages/starlight/__e2e__/tabs.test.ts b/packages/starlight/__e2e__/tabs.test.ts new file mode 100644 index 00000000..e0a36ed5 --- /dev/null +++ b/packages/starlight/__e2e__/tabs.test.ts @@ -0,0 +1,122 @@ +import { expect, testFactory, type Locator } from './test-utils'; + +const test = await testFactory('./fixtures/basics/'); + +test('syncs tabs with a click event', async ({ page, starlight }) => { + await starlight.goto('/tabs'); + + const tabs = page.locator('starlight-tabs'); + const pkgTabsA = tabs.nth(0); + const pkgTabsB = tabs.nth(2); + + // Select the pnpm tab in the first set of synced tabs. + await pkgTabsA.getByRole('tab').filter({ hasText: 'pnpm' }).click(); + + await expectSelectedTab(pkgTabsA, 'pnpm', 'pnpm command'); + await expectSelectedTab(pkgTabsB, 'pnpm', 'another pnpm command'); + + // Select the yarn tab in the second set of synced tabs. + await pkgTabsB.getByRole('tab').filter({ hasText: 'yarn' }).click(); + + await expectSelectedTab(pkgTabsB, 'yarn', 'another yarn command'); + await expectSelectedTab(pkgTabsA, 'yarn', 'yarn command'); +}); + +test('syncs tabs with a keyboard event', async ({ page, starlight }) => { + await starlight.goto('/tabs'); + + const tabs = page.locator('starlight-tabs'); + const pkgTabsA = tabs.nth(0); + const pkgTabsB = tabs.nth(2); + + // Select the pnpm tab in the first set of synced tabs with the keyboard. + await pkgTabsA.getByRole('tab', { selected: true }).press('ArrowRight'); + + await expectSelectedTab(pkgTabsA, 'pnpm', 'pnpm command'); + await expectSelectedTab(pkgTabsB, 'pnpm', 'another pnpm command'); + + // Select back the npm tab in the second set of synced tabs with the keyboard. + const selectedTabB = pkgTabsB.getByRole('tab', { selected: true }); + await selectedTabB.press('ArrowRight'); + await selectedTabB.press('ArrowLeft'); + await selectedTabB.press('ArrowLeft'); + + await expectSelectedTab(pkgTabsA, 'npm', 'npm command'); + await expectSelectedTab(pkgTabsB, 'npm', 'another npm command'); +}); + +test('syncs only tabs using the same sync key', async ({ page, starlight }) => { + await starlight.goto('/tabs'); + + const tabs = page.locator('starlight-tabs'); + const pkgTabsA = tabs.nth(0); + const unsyncedTabs = tabs.nth(1); + const styleTabs = tabs.nth(3); + + // Select the pnpm tab in the set of tabs synced with the 'pkg' key. + await pkgTabsA.getByRole('tab').filter({ hasText: 'pnpm' }).click(); + + await expectSelectedTab(unsyncedTabs, 'one', 'tab 1'); + await expectSelectedTab(styleTabs, 'css', 'css code'); +}); + +test('supports synced tabs with different tab items', async ({ page, starlight }) => { + await starlight.goto('/tabs'); + + const tabs = page.locator('starlight-tabs'); + const pkgTabsA = tabs.nth(0); + const pkgTabsB = tabs.nth(2); // This set contains an extra tab item. + + // Select the bun tab in the second set of synced tabs. + await pkgTabsB.getByRole('tab').filter({ hasText: 'bun' }).click(); + + await expectSelectedTab(pkgTabsA, 'npm', 'npm command'); + await expectSelectedTab(pkgTabsB, 'bun', 'another bun command'); +}); + +test('persists the focus when syncing tabs', async ({ page, starlight }) => { + await starlight.goto('/tabs'); + + const pkgTabsA = page.locator('starlight-tabs').nth(0); + + // Focus the selected tab in the set of tabs synced with the 'pkg' key. + await pkgTabsA.getByRole('tab', { selected: true }).focus(); + // Select the pnpm tab in the set of tabs synced with the 'pkg' key using the keyboard. + await page.keyboard.press('ArrowRight'); + + expect( + await pkgTabsA + .getByRole('tab', { selected: true }) + .evaluate((node) => document.activeElement === node) + ).toBe(true); +}); + +test('preserves tabs position when alternating between tabs with different content heights', async ({ + page, + starlight, +}) => { + await starlight.goto('/tabs-variable-height'); + + const tabs = page.locator('starlight-tabs').nth(1); + const selectedTab = tabs.getByRole('tab', { selected: true }); + + // Scroll to the second set of synced tabs and focus the selected tab. + await tabs.scrollIntoViewIfNeeded(); + await selectedTab.focus(); + + // Get the bounding box of the tabs. + const initialBoundingBox = await tabs.boundingBox(); + + // Select the second tab which has a different height. + await selectedTab.press('ArrowRight'); + + // Ensure the tabs vertical position is exactly the same after selecting the second tab. + // Note that a small difference could be the result of the base line-height having a fractional part which can cause a + // sub-pixel difference in some browsers like Chrome or Firefox. + expect((await tabs.boundingBox())?.y).toBe(initialBoundingBox?.y); +}); + +async function expectSelectedTab(tabs: Locator, label: string, panel: string) { + expect((await tabs.getByRole('tab', { selected: true }).textContent())?.trim()).toBe(label); + expect((await tabs.getByRole('tabpanel').textContent())?.trim()).toBe(panel); +} diff --git a/packages/starlight/__e2e__/test-utils.ts b/packages/starlight/__e2e__/test-utils.ts new file mode 100644 index 00000000..e6047323 --- /dev/null +++ b/packages/starlight/__e2e__/test-utils.ts @@ -0,0 +1,53 @@ +import { fileURLToPath } from 'node:url'; +import { test as baseTest, type Page } from '@playwright/test'; +import { build, preview } from 'astro'; + +export { expect, type Locator } from '@playwright/test'; + +// Setup a test environment that will build and start a preview server for a given fixture path and +// provide a Starlight Playwright fixture accessible from within all tests. +export async function testFactory(fixturePath: string) { + let previewServer: PreviewServer | undefined; + + const test = baseTest.extend<{ starlight: StarlightPage }>({ + starlight: async ({ page }, use) => { + if (!previewServer) { + throw new Error('Could not find a preview server to run tests against.'); + } + + await use(new StarlightPage(previewServer, page)); + }, + }); + + test.beforeAll(async () => { + const root = fileURLToPath(new URL(fixturePath, import.meta.url)); + await build({ logLevel: 'error', root }); + previewServer = await preview({ logLevel: 'error', root }); + }); + + test.afterAll(async () => { + await previewServer?.stop(); + }); + + return test; +} + +// A Playwright test fixture accessible from within all tests. +class StarlightPage { + constructor( + private readonly previewServer: PreviewServer, + private readonly page: Page + ) {} + + // Navigate to a URL relative to the server used during a test run and return the resource response. + goto(url: string) { + return this.page.goto(this.resolveUrl(url)); + } + + // Resolve a URL relative to the server used during a test run. + resolveUrl(url: string) { + return `http://localhost:${this.previewServer.port}${url.replace(/^\/?/, '/')}`; + } +} + +type PreviewServer = Awaited<ReturnType<typeof preview>>; diff --git a/packages/starlight/package.json b/packages/starlight/package.json index 4991eec2..7f9c4f92 100644 --- a/packages/starlight/package.json +++ b/packages/starlight/package.json @@ -4,7 +4,8 @@ "description": "Build beautiful, high-performance documentation websites with Astro", "scripts": { "test": "vitest", - "test:coverage": "vitest run --coverage" + "test:coverage": "vitest run --coverage", + "test:e2e": "playwright install --with-deps chromium && playwright test" }, "keywords": [ "docs", @@ -183,6 +184,7 @@ }, "devDependencies": { "@astrojs/markdown-remark": "^4.2.1", + "@playwright/test": "^1.43.1", "@types/node": "^18.16.19", "@vitest/coverage-v8": "^1.3.1", "astro": "^4.3.5", diff --git a/packages/starlight/playwright.config.ts b/packages/starlight/playwright.config.ts new file mode 100644 index 00000000..01098ea8 --- /dev/null +++ b/packages/starlight/playwright.config.ts @@ -0,0 +1,15 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + forbidOnly: !!process.env['CI'], + projects: [ + { + name: 'Chrome Stable', + use: { + ...devices['Desktop Chrome'], + headless: true, + }, + }, + ], + testMatch: '__e2e__/*.test.ts', +}); diff --git a/packages/starlight/style/props.css b/packages/starlight/style/props.css index be9bbb9d..d8ef97ea 100644 --- a/packages/starlight/style/props.css +++ b/packages/starlight/style/props.css @@ -79,7 +79,7 @@ --sl-text-h4: var(--sl-text-xl); --sl-text-h5: var(--sl-text-lg); - --sl-line-height: 1.8; + --sl-line-height: 1.75; --sl-line-height-headings: 1.2; --sl-font-system: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, diff --git a/packages/starlight/user-components/Tabs.astro b/packages/starlight/user-components/Tabs.astro index 2f58103d..376fb19c 100644 --- a/packages/starlight/user-components/Tabs.astro +++ b/packages/starlight/user-components/Tabs.astro @@ -2,11 +2,16 @@ import Icon from './Icon.astro'; import { processPanels } from './rehype-tabs'; +interface Props { + syncKey?: string; +} + +const { syncKey } = Astro.props; const panelHtml = await Astro.slots.render('default'); const { html, panels } = processPanels(panelHtml); --- -<starlight-tabs> +<starlight-tabs data-sync-key={syncKey}> { panels && ( <div class="tablist-wrapper not-content"> @@ -74,14 +79,25 @@ const { html, panels } = processPanels(panelHtml); <script> class StarlightTabs extends HTMLElement { + // A map of sync keys to all tabs that are synced to that key. + static #syncedTabs = new Map<string, StarlightTabs[]>(); + tabs: HTMLAnchorElement[]; panels: HTMLElement[]; + #syncKey: string | undefined; constructor() { super(); const tablist = this.querySelector<HTMLUListElement>('[role="tablist"]')!; this.tabs = [...tablist.querySelectorAll<HTMLAnchorElement>('[role="tab"]')]; this.panels = [...this.querySelectorAll<HTMLElement>(':scope > [role="tabpanel"]')]; + this.#syncKey = this.dataset.syncKey; + + if (this.#syncKey) { + const syncedTabs = StarlightTabs.#syncedTabs.get(this.#syncKey) ?? []; + syncedTabs.push(this); + StarlightTabs.#syncedTabs.set(this.#syncKey, syncedTabs); + } this.tabs.forEach((tab, i) => { // Handle clicks for mouse users @@ -117,9 +133,14 @@ const { html, panels } = processPanels(panelHtml); }); } - switchTab(newTab: HTMLAnchorElement | null | undefined, index: number) { + switchTab(newTab: HTMLAnchorElement | null | undefined, index: number, shouldSync = true) { if (!newTab) return; + // If tabs should be synced, we store the current position so we can restore it after + // switching tabs to prevent the page from jumping when the new tab content is of a different + // height than the previous tab. + const previousTabsOffset = shouldSync ? this.getBoundingClientRect().top : 0; + // Mark all tabs as unselected and hide all tab panels. this.tabs.forEach((tab) => { tab.setAttribute('aria-selected', 'false'); @@ -135,7 +156,27 @@ const { html, panels } = processPanels(panelHtml); // Restore active tab to the default tab order. newTab.removeAttribute('tabindex'); newTab.setAttribute('aria-selected', 'true'); - newTab.focus(); + if (shouldSync) { + newTab.focus(); + StarlightTabs.#syncTabs(this, newTab.textContent); + window.scrollTo({ + top: window.scrollY + (this.getBoundingClientRect().top - previousTabsOffset), + }); + } + } + + static #syncTabs(emitter: StarlightTabs, label: string | null) { + const syncKey = emitter.#syncKey; + if (!syncKey || !label) return; + const syncedTabs = StarlightTabs.#syncedTabs.get(syncKey); + if (!syncedTabs) return; + + for (const receiver of syncedTabs) { + if (receiver === emitter) continue; + const labelIndex = receiver.tabs.findIndex((tab) => tab.textContent === label); + if (labelIndex === -1) continue; + receiver.switchTab(receiver.tabs[labelIndex], labelIndex, false); + } } } diff --git a/packages/starlight/vitest.config.ts b/packages/starlight/vitest.config.ts index 89e62f69..5440eaa3 100644 --- a/packages/starlight/vitest.config.ts +++ b/packages/starlight/vitest.config.ts @@ -11,7 +11,8 @@ const defaultCoverageExcludes = [ 'test?(-*).?(c|m)[jt]s?(x)', '**/*{.,-}{test,spec}.?(c|m)[jt]s?(x)', '**/__tests__/**', - '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*', + '**/__e2e__/**', + '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,playwright}.config.*', '**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}', ]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3536c01a..9c1dca04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,6 +185,9 @@ importers: '@astrojs/markdown-remark': specifier: ^4.2.1 version: 4.2.1 + '@playwright/test': + specifier: ^1.43.1 + version: 1.43.1 '@types/node': specifier: ^18.16.19 version: 18.16.19 @@ -198,6 +201,15 @@ importers: specifier: ^1.3.1 version: 1.3.1(@types/node@18.16.19) + packages/starlight/__e2e__/fixtures/basics: + dependencies: + '@astrojs/starlight': + specifier: workspace:* + version: link:../../.. + astro: + specifier: ^4.3.5 + version: 4.3.5(@types/node@18.16.19) + packages/tailwind: dependencies: '@astrojs/starlight': @@ -952,7 +964,6 @@ packages: cpu: [ppc64] os: [aix] requiresBuild: true - dev: false optional: true /@esbuild/android-arm64@0.19.12: @@ -961,15 +972,6 @@ packages: cpu: [arm64] os: [android] requiresBuild: true - dev: false - optional: true - - /@esbuild/android-arm64@0.19.8: - resolution: {integrity: sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true optional: true /@esbuild/android-arm@0.19.12: @@ -978,15 +980,6 @@ packages: cpu: [arm] os: [android] requiresBuild: true - dev: false - optional: true - - /@esbuild/android-arm@0.19.8: - resolution: {integrity: sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true optional: true /@esbuild/android-x64@0.19.12: @@ -995,15 +988,6 @@ packages: cpu: [x64] os: [android] requiresBuild: true - dev: false - optional: true - - /@esbuild/android-x64@0.19.8: - resolution: {integrity: sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true optional: true /@esbuild/darwin-arm64@0.19.12: @@ -1012,15 +996,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: false - optional: true - - /@esbuild/darwin-arm64@0.19.8: - resolution: {integrity: sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true optional: true /@esbuild/darwin-x64@0.19.12: @@ -1029,15 +1004,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: false - optional: true - - /@esbuild/darwin-x64@0.19.8: - resolution: {integrity: sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true optional: true /@esbuild/freebsd-arm64@0.19.12: @@ -1046,15 +1012,6 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true - dev: false - optional: true - - /@esbuild/freebsd-arm64@0.19.8: - resolution: {integrity: sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true optional: true /@esbuild/freebsd-x64@0.19.12: @@ -1063,15 +1020,6 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true - dev: false - optional: true - - /@esbuild/freebsd-x64@0.19.8: - resolution: {integrity: sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true optional: true /@esbuild/linux-arm64@0.19.12: @@ -1080,15 +1028,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-arm64@0.19.8: - resolution: {integrity: sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true optional: true /@esbuild/linux-arm@0.19.12: @@ -1097,15 +1036,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-arm@0.19.8: - resolution: {integrity: sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true optional: true /@esbuild/linux-ia32@0.19.12: @@ -1114,15 +1044,6 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-ia32@0.19.8: - resolution: {integrity: sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true optional: true /@esbuild/linux-loong64@0.19.12: @@ -1131,15 +1052,6 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-loong64@0.19.8: - resolution: {integrity: sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true optional: true /@esbuild/linux-mips64el@0.19.12: @@ -1148,15 +1060,6 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-mips64el@0.19.8: - resolution: {integrity: sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true optional: true /@esbuild/linux-ppc64@0.19.12: @@ -1165,15 +1068,6 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-ppc64@0.19.8: - resolution: {integrity: sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true optional: true /@esbuild/linux-riscv64@0.19.12: @@ -1182,15 +1076,6 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-riscv64@0.19.8: - resolution: {integrity: sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true optional: true /@esbuild/linux-s390x@0.19.12: @@ -1199,15 +1084,6 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-s390x@0.19.8: - resolution: {integrity: sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true optional: true /@esbuild/linux-x64@0.19.12: @@ -1216,15 +1092,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-x64@0.19.8: - resolution: {integrity: sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true optional: true /@esbuild/netbsd-x64@0.19.12: @@ -1233,15 +1100,6 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true - dev: false - optional: true - - /@esbuild/netbsd-x64@0.19.8: - resolution: {integrity: sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true optional: true /@esbuild/openbsd-x64@0.19.12: @@ -1250,15 +1108,6 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true - dev: false - optional: true - - /@esbuild/openbsd-x64@0.19.8: - resolution: {integrity: sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true optional: true /@esbuild/sunos-x64@0.19.12: @@ -1267,15 +1116,6 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true - dev: false - optional: true - - /@esbuild/sunos-x64@0.19.8: - resolution: {integrity: sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true optional: true /@esbuild/win32-arm64@0.19.12: @@ -1284,15 +1124,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-arm64@0.19.8: - resolution: {integrity: sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true optional: true /@esbuild/win32-ia32@0.19.12: @@ -1301,15 +1132,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-ia32@0.19.8: - resolution: {integrity: sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true optional: true /@esbuild/win32-x64@0.19.12: @@ -1318,15 +1140,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-x64@0.19.8: - resolution: {integrity: sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true optional: true /@expressive-code/core@0.35.2: @@ -1552,6 +1365,20 @@ packages: dev: false optional: true + /@parse5/tools@0.3.0: + resolution: {integrity: sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==} + dependencies: + parse5: 7.1.2 + dev: false + + /@playwright/test@1.43.1: + resolution: {integrity: sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==} + engines: {node: '>=16'} + hasBin: true + dependencies: + playwright: 1.43.1 + dev: true + /@rollup/rollup-android-arm-eabi@4.6.1: resolution: {integrity: sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==} cpu: [arm] @@ -2072,7 +1899,7 @@ packages: dlv: 1.1.3 dset: 3.1.3 es-module-lexer: 1.4.1 - esbuild: 0.19.8 + esbuild: 0.19.12 estree-walker: 3.0.3 execa: 8.0.1 fast-glob: 3.3.2 @@ -2963,36 +2790,6 @@ packages: '@esbuild/win32-arm64': 0.19.12 '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 - dev: false - - /esbuild@0.19.8: - resolution: {integrity: sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.19.8 - '@esbuild/android-arm64': 0.19.8 - '@esbuild/android-x64': 0.19.8 - '@esbuild/darwin-arm64': 0.19.8 - '@esbuild/darwin-x64': 0.19.8 - '@esbuild/freebsd-arm64': 0.19.8 - '@esbuild/freebsd-x64': 0.19.8 - '@esbuild/linux-arm': 0.19.8 - '@esbuild/linux-arm64': 0.19.8 - '@esbuild/linux-ia32': 0.19.8 - '@esbuild/linux-loong64': 0.19.8 - '@esbuild/linux-mips64el': 0.19.8 - '@esbuild/linux-ppc64': 0.19.8 - '@esbuild/linux-riscv64': 0.19.8 - '@esbuild/linux-s390x': 0.19.8 - '@esbuild/linux-x64': 0.19.8 - '@esbuild/netbsd-x64': 0.19.8 - '@esbuild/openbsd-x64': 0.19.8 - '@esbuild/sunos-x64': 0.19.8 - '@esbuild/win32-arm64': 0.19.8 - '@esbuild/win32-ia32': 0.19.8 - '@esbuild/win32-x64': 0.19.8 /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -3261,6 +3058,14 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -5381,6 +5186,22 @@ packages: pathe: 1.1.2 dev: true + /playwright-core@1.43.1: + resolution: {integrity: sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==} + engines: {node: '>=16'} + hasBin: true + dev: true + + /playwright@1.43.1: + resolution: {integrity: sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==} + engines: {node: '>=16'} + hasBin: true + dependencies: + playwright-core: 1.43.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /postcss-import@15.1.0(postcss@8.4.33): resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -6903,7 +6724,7 @@ packages: optional: true dependencies: '@types/node': 18.16.19 - esbuild: 0.19.8 + esbuild: 0.19.12 postcss: 8.4.33 rollup: 4.6.1 optionalDependencies: |