summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Swithinbank2023-07-24 18:19:07 +0200
committerGitHub2023-07-24 18:19:07 +0200
commit0ebc47e52dc420240c8cb724c01f98dc22bdfc60 (patch)
tree122fb398ce48e202fed03dd1eebc145e9fd11729
parent75148f9ef837bcef9405fe45dd2a81f7b74ed73c (diff)
downloadIT.starlight-0ebc47e52dc420240c8cb724c01f98dc22bdfc60.tar.gz
IT.starlight-0ebc47e52dc420240c8cb724c01f98dc22bdfc60.tar.bz2
IT.starlight-0ebc47e52dc420240c8cb724c01f98dc22bdfc60.zip
Add unit testing (#383)
-rw-r--r--.changeset/dry-insects-shave.md5
-rw-r--r--.github/workflows/ci.yml22
-rw-r--r--CONTRIBUTING.md65
-rw-r--r--packages/starlight/.gitignore5
-rw-r--r--packages/starlight/.npmignore6
-rw-r--r--packages/starlight/__tests__/basics/base.test.ts37
-rw-r--r--packages/starlight/__tests__/basics/config.test.ts22
-rw-r--r--packages/starlight/__tests__/basics/head.test.ts66
-rw-r--r--packages/starlight/__tests__/basics/i18n.test.ts15
-rw-r--r--packages/starlight/__tests__/basics/localizedUrl.test.ts7
-rw-r--r--packages/starlight/__tests__/basics/navigation.test.ts229
-rw-r--r--packages/starlight/__tests__/basics/routing.test.ts47
-rw-r--r--packages/starlight/__tests__/basics/slugs.test.ts78
-rw-r--r--packages/starlight/__tests__/basics/translations-with-user-config.test.ts17
-rw-r--r--packages/starlight/__tests__/basics/translations.test.ts32
-rw-r--r--packages/starlight/__tests__/basics/vitest.config.ts3
-rw-r--r--packages/starlight/__tests__/i18n-root-locale/config.test.ts17
-rw-r--r--packages/starlight/__tests__/i18n-root-locale/localizedUrl.test.ts22
-rw-r--r--packages/starlight/__tests__/i18n-root-locale/routing.test.ts63
-rw-r--r--packages/starlight/__tests__/i18n-root-locale/slugs.test.ts78
-rw-r--r--packages/starlight/__tests__/i18n-root-locale/vitest.config.ts10
-rw-r--r--packages/starlight/__tests__/i18n/config.test.ts17
-rw-r--r--packages/starlight/__tests__/i18n/localizedUrl.test.ts12
-rw-r--r--packages/starlight/__tests__/i18n/routing.test.ts66
-rw-r--r--packages/starlight/__tests__/i18n/vitest.config.ts11
-rw-r--r--packages/starlight/__tests__/sidebar/navigation.test.ts67
-rw-r--r--packages/starlight/__tests__/sidebar/vitest.config.ts22
-rw-r--r--packages/starlight/__tests__/test-config.ts16
-rw-r--r--packages/starlight/__tests__/test-utils.ts57
-rw-r--r--packages/starlight/index.ts59
-rw-r--r--packages/starlight/integrations/virtual-user-config.ts52
-rw-r--r--packages/starlight/package.json9
-rw-r--r--packages/starlight/types.ts2
-rw-r--r--packages/starlight/utils/slugs.ts2
-rw-r--r--packages/starlight/vitest.config.ts31
-rw-r--r--packages/starlight/vitest.workspace.ts1
-rw-r--r--pnpm-lock.yaml442
37 files changed, 1637 insertions, 75 deletions
diff --git a/.changeset/dry-insects-shave.md b/.changeset/dry-insects-shave.md
new file mode 100644
index 00000000..61e6b3bf
--- /dev/null
+++ b/.changeset/dry-insects-shave.md
@@ -0,0 +1,5 @@
+---
+"@astrojs/starlight": patch
+---
+
+Fix edge case where index files in an index directory would end up with the wrong slug
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5f3e206b..41900459 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,25 @@ concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request_target' && github.head_ref || github.ref }}
cancel-in-progress: true
+env:
+ NODE_VERSION: 16
+
jobs:
+ unit-test:
+ name: Run unit tests
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v3
+ - uses: pnpm/action-setup@v2
+ - uses: actions/setup-node@v3
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'pnpm'
+ - run: pnpm i
+ - name: Test packages/starlight
+ working-directory: ./packages/starlight
+ run: pnpm test:coverage
+
pa11y:
name: Check for accessibility issues
runs-on: ubuntu-20.04
@@ -26,7 +44,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v3
with:
- node-version: 16
+ node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install Dependencies
@@ -50,7 +68,7 @@ jobs:
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
- node-version: 16
+ node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- run: pnpm i
- name: Build docs site
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e3852b52..0cc41f03 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -76,7 +76,9 @@ Instead of working locally on your machine, you can also contribute using an onl
pnpm i
```
-### Testing changes while you work
+## Testing
+
+### Testing visual changes while you work
Run the Astro dev server on the docs site to see how changes you make impact a project using Starlight.
@@ -89,6 +91,66 @@ pnpm dev
You should then be able to open <http://localhost:3000> and see your changes.
+> **Note**
+> Changes to the Starlight integration will require you to quit and restart the dev server to take effect.
+
+### Unit tests
+
+The Starlight package includes unit tests in [`packages/starlight/__tests__/`](./packages/starlight/__tests__/), which are run using [Vitest][vitest].
+
+To run tests, move into the Starlight package and then run `pnpm test`:
+
+```sh
+cd packages/starlight
+pnpm test
+```
+
+This will run tests and then listen for changes, re-running tests when files change.
+
+#### Test environments
+
+A lot of Starlight code relies on Vite virtual modules provided either by Astro or by Starlight itself. Each subdirectory of `packages/starlight/__tests__/` should contain a `vitest.config.ts` file that uses the `defineVitestConfig()` helper to define a valid test environment for tests in that directory. This helper takes a single argument, which provides a Starlight user config object:
+
+```ts
+// packages/starlight/__tests/basics/vitest.config.ts
+import { defineVitestConfig } from '../test-config';
+
+export default defineVitestConfig({
+ title: 'Basics',
+});
+```
+
+This allows you to run tests of Starlight code against different combinations of Starlight configuration options.
+
+#### Mocking content collections
+
+Starlight relies on a user’s `docs` and (optional) `i18n` content collections, which aren’t available during testing. You can use a top-level `vi.mock()` call and the `mockedAstroContent` helper to set up fake collection entries for the current test file:
+
+```js
+import { describe, expect, test, vi } from 'vitest';
+
+vi.mock('astro:content', async () =>
+ (await import('../test-utils')).mockedAstroContent({
+ docs: [
+ ['index.mdx', { title: 'Home Page' }],
+ ['environmental-impact.md', { title: 'Eco-friendly docs' }],
+ ],
+ i18n: [['en', { 'page.editLink': 'Modify this doc!' }]],
+ })
+);
+```
+
+#### Test coverage
+
+To see how much of Starlight’s code is currently being tested, run `pnpm test:coverage` from the Starlight package:
+
+```sh
+cd packages/starlight
+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).
+
## Translations
Translations help make Starlight accessible to more people.
@@ -139,3 +201,4 @@ Visit **<https://i18n.starlight.astro.build>** to track translation progress for
[pr-docs]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects#making-a-pull-request
[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/
diff --git a/packages/starlight/.gitignore b/packages/starlight/.gitignore
new file mode 100644
index 00000000..01736279
--- /dev/null
+++ b/packages/starlight/.gitignore
@@ -0,0 +1,5 @@
+# Vitest
+__coverage__/
+
+# Astro generates this during tests, but we want to ignore it.
+src/env.d.ts
diff --git a/packages/starlight/.npmignore b/packages/starlight/.npmignore
new file mode 100644
index 00000000..26babf74
--- /dev/null
+++ b/packages/starlight/.npmignore
@@ -0,0 +1,6 @@
+# Vitest
+__coverage__/
+__tests__/
+vitest.*
+# Astro generates this during tests, but we want to ignore it.
+src/env.d.ts
diff --git a/packages/starlight/__tests__/basics/base.test.ts b/packages/starlight/__tests__/basics/base.test.ts
new file mode 100644
index 00000000..feed73e6
--- /dev/null
+++ b/packages/starlight/__tests__/basics/base.test.ts
@@ -0,0 +1,37 @@
+import { describe, expect, test, vi } from 'vitest';
+import { fileWithBase, pathWithBase } from '../../utils/base';
+
+describe('fileWithBase()', () => {
+ describe('with no base', () => {
+ test('does not prepend anything', () => {
+ expect(fileWithBase('/img.svg')).toBe('/img.svg');
+ });
+ test('adds leading slash if needed', () => {
+ expect(fileWithBase('img.svg')).toBe('/img.svg');
+ });
+ });
+
+ // TODO: Stubbing BASE_URL is not currently possible.
+ // Astro controls BASE_URL via its `vite-plugin-env`, which prevents Vitest’s stubbing from
+ // working and there’s also no way to pass in Astro config in Astro’s `getViteConfig` helper.
+ describe.todo('with base', () => {
+ test('prepends base', () => {
+ vi.stubEnv('BASE_URL', '/base/');
+ expect(fileWithBase('/img.svg')).toBe('/base/img.svg');
+ vi.unstubAllEnvs();
+ });
+ });
+});
+
+describe('pathWithBase()', () => {
+ describe('with no base', () => {
+ test('does not prepend anything', () => {
+ expect(pathWithBase('/path/')).toBe('/path/');
+ });
+ test('adds leading and trailing slashes if needed', () => {
+ expect(pathWithBase('path')).toBe('/path/');
+ });
+ });
+
+ describe.todo('with base');
+});
diff --git a/packages/starlight/__tests__/basics/config.test.ts b/packages/starlight/__tests__/basics/config.test.ts
new file mode 100644
index 00000000..b31d21ed
--- /dev/null
+++ b/packages/starlight/__tests__/basics/config.test.ts
@@ -0,0 +1,22 @@
+import config from 'virtual:starlight/user-config';
+import { expect, test } from 'vitest';
+
+test('test suite is using correct env', () => {
+ expect(config.title).toBe('Basics');
+});
+
+test('isMultilingual is false when no locales configured ', () => {
+ expect(config.locales).toBeUndefined();
+ expect(config.isMultilingual).toBe(false);
+});
+
+test('default locale is set when no locales configured', () => {
+ expect(config.defaultLocale).not.toBeUndefined();
+ expect(config.defaultLocale.lang).toBe('en');
+ expect(config.defaultLocale.label).toBe('English');
+ expect(config.defaultLocale.dir).toBe('ltr');
+});
+
+test('lastUpdated defaults to false', () => {
+ expect(config.lastUpdated).toBe(false);
+});
diff --git a/packages/starlight/__tests__/basics/head.test.ts b/packages/starlight/__tests__/basics/head.test.ts
new file mode 100644
index 00000000..2a189566
--- /dev/null
+++ b/packages/starlight/__tests__/basics/head.test.ts
@@ -0,0 +1,66 @@
+import { describe, expect, test } from 'vitest';
+import { createHead } from '../../utils/head';
+
+describe('createHead', () => {
+ test('merges two <title> tags', () => {
+ expect(
+ createHead(
+ [{ tag: 'title', content: 'Default' }],
+ [{ tag: 'title', content: 'Override', attrs: {} }]
+ )
+ ).toEqual([{ tag: 'title', content: 'Override', attrs: {} }]);
+ });
+
+ for (const prop of ['name', 'property', 'http-equiv']) {
+ test(`merges two <meta> tags with same ${prop} value`, () => {
+ expect(
+ createHead(
+ [{ tag: 'meta', attrs: { [prop]: 'x', content: 'Default' } }],
+ [{ tag: 'meta', attrs: { [prop]: 'x', content: 'Test' }, content: '' }]
+ )
+ ).toEqual([{ tag: 'meta', content: '', attrs: { [prop]: 'x', content: 'Test' } }]);
+ });
+ }
+
+ for (const prop of ['name', 'property', 'http-equiv']) {
+ test(`does not merge <meta> tags with different ${prop} values`, () => {
+ expect(
+ createHead(
+ [{ tag: 'meta', attrs: { [prop]: 'x', content: 'X' } }],
+ [{ tag: 'meta', attrs: { [prop]: 'y', content: 'Y' }, content: '' }]
+ )
+ ).toEqual([
+ { tag: 'meta', content: '', attrs: { [prop]: 'x', content: 'X' } },
+ { tag: 'meta', content: '', attrs: { [prop]: 'y', content: 'Y' } },
+ ]);
+ });
+ }
+
+ test('sorts head by tag importance', () => {
+ expect(
+ createHead([
+ // SEO meta tags
+ { tag: 'meta', attrs: { name: 'x' } },
+ // Others
+ { tag: 'link', attrs: { rel: 'stylesheet' } },
+ // Important meta tags
+ { tag: 'meta', attrs: { charset: 'utf-8' } },
+ { tag: 'meta', attrs: { name: 'viewport' } },
+ { tag: 'meta', attrs: { 'http-equiv': 'x' } },
+ // <title>
+ { tag: 'title', content: 'Title' },
+ ])
+ ).toEqual([
+ // Important meta tags
+ { tag: 'meta', attrs: { charset: 'utf-8' }, content: '' },
+ { tag: 'meta', attrs: { name: 'viewport' }, content: '' },
+ { tag: 'meta', attrs: { 'http-equiv': 'x' }, content: '' },
+ // <title>
+ { tag: 'title', attrs: {}, content: 'Title' },
+ // Others
+ { tag: 'link', attrs: { rel: 'stylesheet' }, content: '' },
+ // SEO meta tags
+ { tag: 'meta', attrs: { name: 'x' }, content: '' },
+ ]);
+ });
+});
diff --git a/packages/starlight/__tests__/basics/i18n.test.ts b/packages/starlight/__tests__/basics/i18n.test.ts
new file mode 100644
index 00000000..1bed4644
--- /dev/null
+++ b/packages/starlight/__tests__/basics/i18n.test.ts
@@ -0,0 +1,15 @@
+import { describe, expect, test } from 'vitest';
+import { pickLang } from '../../utils/i18n';
+
+describe('pickLang', () => {
+ const dictionary = { en: 'Hello', fr: 'Bonjour' };
+
+ test('returns the requested language string', () => {
+ expect(pickLang(dictionary, 'en')).toBe('Hello');
+ expect(pickLang(dictionary, 'fr')).toBe('Bonjour');
+ });
+
+ test('returns undefined for unknown languages', () => {
+ expect(pickLang(dictionary, 'ar' as any)).toBeUndefined();
+ });
+});
diff --git a/packages/starlight/__tests__/basics/localizedUrl.test.ts b/packages/starlight/__tests__/basics/localizedUrl.test.ts
new file mode 100644
index 00000000..fa8586d5
--- /dev/null
+++ b/packages/starlight/__tests__/basics/localizedUrl.test.ts
@@ -0,0 +1,7 @@
+import { expect, test } from 'vitest';
+import { localizedUrl } from '../../utils/localizedUrl';
+
+test('it has no effect in a monolingual project', () => {
+ const url = new URL('https://example.com/en/guide/');
+ expect(localizedUrl(url, undefined).href).toBe(url.href);
+});
diff --git a/packages/starlight/__tests__/basics/navigation.test.ts b/packages/starlight/__tests__/basics/navigation.test.ts
new file mode 100644
index 00000000..56ca5a02
--- /dev/null
+++ b/packages/starlight/__tests__/basics/navigation.test.ts
@@ -0,0 +1,229 @@
+import { describe, expect, test, vi } from 'vitest';
+import { flattenSidebar, getPrevNextLinks, getSidebar } from '../../utils/navigation';
+
+vi.mock('astro:content', async () =>
+ (await import('../test-utils')).mockedAstroContent({
+ docs: [
+ ['index.mdx', { title: 'Home Page' }],
+ ['environmental-impact.md', { title: 'Eco-friendly docs' }],
+ ['guides/authoring-content.md', { title: 'Authoring Markdown' }],
+ ['guides/components.mdx', { title: 'Components' }],
+ ],
+ })
+);
+
+describe('getSidebar', () => {
+ test('returns an array of sidebar entries', () => {
+ expect(getSidebar('/', undefined)).toMatchInlineSnapshot(`
+ [
+ {
+ "href": "/",
+ "isCurrent": true,
+ "label": "Home Page",
+ "type": "link",
+ },
+ {
+ "href": "/environmental-impact/",
+ "isCurrent": false,
+ "label": "Eco-friendly docs",
+ "type": "link",
+ },
+ {
+ "collapsed": false,
+ "entries": [
+ {
+ "href": "/guides/authoring-content/",
+ "isCurrent": false,
+ "label": "Authoring Markdown",
+ "type": "link",
+ },
+ {
+ "href": "/guides/components/",
+ "isCurrent": false,
+ "label": "Components",
+ "type": "link",
+ },
+ ],
+ "label": "guides",
+ "type": "group",
+ },
+ ]
+ `);
+ });
+
+ test('marks current path with isCurrent', () => {
+ const paths = ['/', '/environmental-impact/', '/guides/authoring-content/'];
+ for (const currentPath of paths) {
+ const items = flattenSidebar(getSidebar(currentPath, undefined));
+ const currentItems = items.filter((item) => item.type === 'link' && item.isCurrent);
+ expect(currentItems).toHaveLength(1);
+ const currentItem = currentItems[0];
+ if (currentItem?.type !== 'link') throw new Error('Expected current item to be link');
+ expect(currentItem.href).toBe(currentPath);
+ }
+ });
+
+ test('nests files in subdirectory in group when autogenerating', () => {
+ const sidebar = getSidebar('/', undefined);
+ expect(sidebar.every((item) => item.type === 'group' || !item.href.startsWith('/guides/')));
+ const guides = sidebar.find((item) => item.type === 'group' && item.label === 'guides');
+ expect(guides?.type).toBe('group');
+ // @ts-expect-error — TypeScript doesn’t know we know we’re in a group.
+ expect(guides.entries).toHaveLength(2);
+ });
+
+ test('uses page title as label when autogenerating', () => {
+ const sidebar = getSidebar('/', undefined);
+ const homeLink = sidebar.find((item) => item.type === 'link' && item.href === '/');
+ expect(homeLink?.label).toBe('Home Page');
+ });
+});
+
+describe('flattenSidebar', () => {
+ test('flattens nested sidebar array', () => {
+ const sidebar = getSidebar('/', undefined);
+ const flattened = flattenSidebar(sidebar);
+ // Sidebar should include some nested group items.
+ expect(sidebar.some((item) => item.type === 'group')).toBe(true);
+ // Flattened sidebar should only include link items.
+ expect(flattened.every((item) => item.type === 'link')).toBe(true);
+
+ expect(flattened).toMatchInlineSnapshot(`
+ [
+ {
+ "href": "/",
+ "isCurrent": true,
+ "label": "Home Page",
+ "type": "link",
+ },
+ {
+ "href": "/environmental-impact/",
+ "isCurrent": false,
+ "label": "Eco-friendly docs",
+ "type": "link",
+ },
+ {
+ "href": "/guides/authoring-content/",
+ "isCurrent": false,
+ "label": "Authoring Markdown",
+ "type": "link",
+ },
+ {
+ "href": "/guides/components/",
+ "isCurrent": false,
+ "label": "Components",
+ "type": "link",
+ },
+ ]
+ `);
+ });
+});
+
+describe('getPrevNextLinks', () => {
+ test('returns stable previous/next values', () => {
+ const sidebar = getSidebar('/environmental-impact/', undefined);
+ const links = getPrevNextLinks(sidebar, true, {});
+ expect(links).toMatchInlineSnapshot(`
+ {
+ "next": {
+ "href": "/guides/authoring-content/",
+ "isCurrent": false,
+ "label": "Authoring Markdown",
+ "type": "link",
+ },
+ "prev": {
+ "href": "/",
+ "isCurrent": false,
+ "label": "Home Page",
+ "type": "link",
+ },
+ }
+ `);
+ });
+
+ test('returns no links when pagination is disabled', () => {
+ const sidebar = getSidebar('/environmental-impact/', undefined);
+ const links = getPrevNextLinks(sidebar, false, {});
+ expect(links).toEqual({ prev: undefined, next: undefined });
+ });
+
+ test('returns no previous link for first item', () => {
+ const sidebar = getSidebar('/', undefined);
+ const links = getPrevNextLinks(sidebar, true, {});
+ expect(links.prev).toBeUndefined();
+ });
+
+ test('returns no next link for last item', () => {
+ const sidebar = getSidebar('/guides/components/', undefined);
+ const links = getPrevNextLinks(sidebar, true, {});
+ expect(links.next).toBeUndefined();
+ });
+
+ test('final parameter can disable prev/next', () => {
+ const sidebar = getSidebar('/environmental-impact/', undefined);
+ expect(getPrevNextLinks(sidebar, true, { prev: true }).prev).toBeDefined();
+ expect(getPrevNextLinks(sidebar, true, { prev: false }).prev).toBeUndefined();
+ expect(getPrevNextLinks(sidebar, true, { next: true }).next).toBeDefined();
+ expect(getPrevNextLinks(sidebar, true, { next: false }).next).toBeUndefined();
+ });
+
+ test('final parameter can set custom link label with string', () => {
+ const sidebar = getSidebar('/environmental-impact/', undefined);
+ const withDefaultLabels = getPrevNextLinks(sidebar, true, {});
+ const withCustomLabels = getPrevNextLinks(sidebar, true, { prev: 'x', next: 'y' });
+ expect(withCustomLabels.prev?.label).toBe('x');
+ expect(withCustomLabels.prev?.label).not.toBe(withDefaultLabels.prev?.label);
+ expect(withCustomLabels.next?.label).toBe('y');
+ expect(withCustomLabels.next?.label).not.toBe(withDefaultLabels.next?.label);
+ });
+
+ test('final parameter can set custom link label with object', () => {
+ const sidebar = getSidebar('/environmental-impact/', undefined);
+ const withDefaultLabels = getPrevNextLinks(sidebar, true, {});
+ const withCustomLabels = getPrevNextLinks(sidebar, true, {
+ prev: { label: 'x' },
+ next: { label: 'y' },
+ });
+ expect(withCustomLabels.prev?.label).toBe('x');
+ expect(withCustomLabels.prev?.label).not.toBe(withDefaultLabels.prev?.label);
+ expect(withCustomLabels.next?.label).toBe('y');
+ expect(withCustomLabels.next?.label).not.toBe(withDefaultLabels.next?.label);
+ });
+
+ test('final parameter can set custom link destination', () => {
+ const sidebar = getSidebar('/environmental-impact/', undefined);
+ const withDefaults = getPrevNextLinks(sidebar, true, {});
+ const withCustomLinks = getPrevNextLinks(sidebar, true, {
+ prev: { link: '/x' },
+ next: { link: '/y' },
+ });
+ expect(withCustomLinks.prev?.href).toBe('/x');
+ expect(withCustomLinks.prev?.href).not.toBe(withDefaults.prev?.href);
+ expect(withCustomLinks.prev?.label).toBe(withDefaults.prev?.label);
+ expect(withCustomLinks.next?.href).toBe('/y');
+ expect(withCustomLinks.next?.href).not.toBe(withDefaults.next?.href);
+ expect(withCustomLinks.next?.label).toBe(withDefaults.next?.label);
+ });
+
+ test('final parameter can set custom link even if no default link existed', () => {
+ const sidebar = getSidebar('/', undefined);
+ const withDefaults = getPrevNextLinks(sidebar, true, {});
+ const withCustomLinks = getPrevNextLinks(sidebar, true, {
+ prev: { link: 'x', label: 'X' },
+ });
+ expect(withDefaults.prev).toBeUndefined();
+ expect(withCustomLinks.prev).toEqual({
+ type: 'link',
+ href: '/x/',
+ label: 'X',
+ isCurrent: false,
+ });
+ });
+
+ test('final parameter can override global pagination toggle', () => {
+ const sidebar = getSidebar('/environmental-impact/', undefined);
+ const withDefaults = getPrevNextLinks(sidebar, true, {});
+ const withOverrides = getPrevNextLinks(sidebar, false, { prev: true, next: true });
+ expect(withOverrides).toEqual(withDefaults);
+ });
+});
diff --git a/packages/starlight/__tests__/basics/routing.test.ts b/packages/starlight/__tests__/basics/routing.test.ts
new file mode 100644
index 00000000..18fc0605
--- /dev/null
+++ b/packages/starlight/__tests__/basics/routing.test.ts
@@ -0,0 +1,47 @@
+import { getCollection } from 'astro:content';
+import config from 'virtual:starlight/user-config';
+import { expect, test, vi } from 'vitest';
+import { routes } from '../../utils/routing';
+
+vi.mock('astro:content', async () =>
+ (await import('../test-utils')).mockedAstroContent({
+ docs: [
+ ['404.md', { title: 'Not found' }],
+ ['index.mdx', { title: 'Home page' }],
+ ['guides/authoring-content.md', { title: 'Authoring content' }],
+ ],
+ })
+);
+
+test('test suite is using correct env', () => {
+ expect(config.title).toBe('Basics');
+});
+
+test('route slugs are normalized', () => {
+ const indexRoute = routes.find((route) => route.id.startsWith('index.md'));
+ expect(indexRoute?.slug).toBe('');
+});
+
+test('routes are sorted by slug', () => {
+ expect(routes[0]?.slug).toBe('');
+});
+
+test('routes contain copy of original doc as entry', async () => {
+ const docs = await getCollection('docs');
+ for (const route of routes) {
+ const doc = docs.find((doc) => doc.id === route.id);
+ if (!doc) throw new Error('Expected to find doc for route ' + route.id);
+ // Compare without slug as slugs can be normalized.
+ const { slug: _, ...entry } = route.entry;
+ const { slug: __, ...input } = doc;
+ expect(entry).toEqual(input);
+ }
+});
+
+test('routes have locale data added', () => {
+ for (const route of routes) {
+ expect(route.lang).toBe('en');
+ expect(route.dir).toBe('ltr');
+ expect(route.locale).toBeUndefined();
+ }
+});
diff --git a/packages/starlight/__tests__/basics/slugs.test.ts b/packages/starlight/__tests__/basics/slugs.test.ts
new file mode 100644
index 00000000..86ee432b
--- /dev/null
+++ b/packages/starlight/__tests__/basics/slugs.test.ts
@@ -0,0 +1,78 @@
+import { describe, expect, test } from 'vitest';
+import {
+ localeToLang,
+ localizedId,
+ localizedSlug,
+ slugToLocaleData,
+ slugToParam,
+ slugToPathname,
+} from '../../utils/slugs';
+
+describe('slugToLocaleData', () => {
+ test('returns an undefined locale for root locale slugs', () => {
+ expect(slugToLocaleData('test').locale).toBeUndefined();
+ expect(slugToLocaleData('dir/test').locale).toBeUndefined();
+ });
+ test('returns default "en" lang when no locale config is set', () => {
+ expect(slugToLocaleData('test').lang).toBe('en');
+ expect(slugToLocaleData('dir/test').lang).toBe('en');
+ });
+ test('returns default "ltr" dir when no locale config is set', () => {
+ expect(slugToLocaleData('test').dir).toBe('ltr');
+ expect(slugToLocaleData('dir/test').dir).toBe('ltr');
+ });
+});
+
+describe('slugToParam', () => {
+ test('returns undefined for empty slug (index)', () => {
+ expect(slugToParam('')).toBeUndefined();
+ });
+ test('returns undefined for root index', () => {
+ expect(slugToParam('index')).toBeUndefined();
+ });
+ test('strips index from end of nested slug', () => {
+ expect(slugToParam('dir/index')).toBe('dir');
+ expect(slugToParam('dir/index/sub-dir/index')).toBe('dir/index/sub-dir');
+ });
+ test('returns other slugs unchanged', () => {
+ expect(slugToParam('slug')).toBe('slug');
+ expect(slugToParam('dir/page')).toBe('dir/page');
+ expect(slugToParam('dir/sub-dir/page')).toBe('dir/sub-dir/page');
+ });
+});
+
+describe('slugToPathname', () => {
+ test('returns "/" for empty slug', () => {
+ expect(slugToPathname('')).toBe('/');
+ });
+ test('returns "/" for root index', () => {
+ expect(slugToPathname('index')).toBe('/');
+ });
+ test('strips index from end of nested slug', () => {
+ expect(slugToPathname('dir/index')).toBe('/dir/');
+ expect(slugToPathname('dir/index/sub-dir/index')).toBe('/dir/index/sub-dir/');
+ });
+ test('returns slugs with leading and trailing slashes added', () => {
+ expect(slugToPathname('slug')).toBe('/slug/');
+ expect(slugToPathname('dir/page')).toBe('/dir/page/');
+ expect(slugToPathname('dir/sub-dir/page')).toBe('/dir/sub-dir/page/');
+ });
+});
+
+describe('localeToLang', () => {
+ test('returns lang for root locale', () => {
+ expect(localeToLang(undefined)).toBe('en');
+ });
+});
+
+describe('localizedId', () => {
+ test('returns unchanged when no locales are set', () => {
+ expect(localizedId('test.md', undefined)).toBe('test.md');
+ });
+});
+
+describe('localizedSlug', () => {
+ test('returns unchanged when no locales are set', () => {
+ expect(localizedSlug('test', undefined)).toBe('test');
+ });
+});
diff --git a/packages/starlight/__tests__/basics/translations-with-user-config.test.ts b/packages/starlight/__tests__/basics/translations-with-user-config.test.ts
new file mode 100644
index 00000000..4f76a67d
--- /dev/null
+++ b/packages/starlight/__tests__/basics/translations-with-user-config.test.ts
@@ -0,0 +1,17 @@
+import { describe, expect, test, vi } from 'vitest';
+import translations from '../../translations';
+import { useTranslations } from '../../utils/translations';
+
+vi.mock('astro:content', async () =>
+ (await import('../test-utils')).mockedAstroContent({
+ i18n: [['en', { 'page.editLink': 'Modify this doc!' }]],
+ })
+);
+
+describe('useTranslations()', () => {
+ test('uses user-defined translations', () => {
+ const t = useTranslations(undefined);
+ expect(t('page.editLink')).toBe('Modify this doc!');
+ expect(t('page.editLink')).not.toBe(translations.en?.['page.editLink']);
+ });
+});
diff --git a/packages/starlight/__tests__/basics/translations.test.ts b/packages/starlight/__tests__/basics/translations.test.ts
new file mode 100644
index 00000000..7336cbf3
--- /dev/null
+++ b/packages/starlight/__tests__/basics/translations.test.ts
@@ -0,0 +1,32 @@
+import { describe, expect, test, vi } from 'vitest';
+import translations from '../../translations';
+import { useTranslations } from '../../utils/translations';
+
+describe('built-in translations', () => {
+ test('includes English', () => {
+ expect(translations).toHaveProperty('en');
+ });
+});
+
+describe('useTranslations()', () => {
+ test('works when no i18n collection is available', () => {
+ const t = useTranslations(undefined);
+ expect(t).toBeTypeOf('function');
+ expect(t('page.editLink')).toBe(translations.en?.['page.editLink']);
+ });
+
+ test('returns default locale for unknown language', () => {
+ const locale = 'xx';
+ expect(translations).not.toHaveProperty(locale);
+ const t = useTranslations(locale);
+ expect(t('page.editLink')).toBe(translations.en?.['page.editLink']);
+ });
+
+ test('returns a pick method for filtering by key', () => {
+ const t = useTranslations('en');
+ expect(t.pick('tableOfContents.')).toEqual({
+ 'tableOfContents.onThisPage': 'On this page',
+ 'tableOfContents.overview': 'Overview',
+ });
+ });
+});
diff --git a/packages/starlight/__tests__/basics/vitest.config.ts b/packages/starlight/__tests__/basics/vitest.config.ts
new file mode 100644
index 00000000..ec3c32e5
--- /dev/null
+++ b/packages/starlight/__tests__/basics/vitest.config.ts
@@ -0,0 +1,3 @@
+import { defineVitestConfig } from '../test-config';
+
+export default defineVitestConfig({ title: 'Basics' });
diff --git a/packages/starlight/__tests__/i18n-root-locale/config.test.ts b/packages/starlight/__tests__/i18n-root-locale/config.test.ts
new file mode 100644
index 00000000..012c0a4e
--- /dev/null
+++ b/packages/starlight/__tests__/i18n-root-locale/config.test.ts
@@ -0,0 +1,17 @@
+import config from 'virtual:starlight/user-config';
+import { expect, test } from 'vitest';
+
+test('test suite is using correct env', () => {
+ expect(config.title).toBe('i18n with root locale');
+});
+
+test('config.isMultilingual is true with multiple locales', () => {
+ expect(config.isMultilingual).toBe(true);
+ expect(config.locales).keys('root', 'en', 'ar');
+});
+
+test('config.defaultLocale is populated from root locale', () => {
+ expect(config.defaultLocale.lang).toBe('fr');
+ expect(config.defaultLocale.dir).toBe('ltr');
+ expect(config.defaultLocale.locale).toBeUndefined();
+});
diff --git a/packages/starlight/__tests__/i18n-root-locale/localizedUrl.test.ts b/packages/starlight/__tests__/i18n-root-locale/localizedUrl.test.ts
new file mode 100644
index 00000000..0f062222
--- /dev/null
+++ b/packages/starlight/__tests__/i18n-root-locale/localizedUrl.test.ts
@@ -0,0 +1,22 @@
+import { expect, test } from 'vitest';
+import { localizedUrl } from '../../utils/localizedUrl';
+
+test('it has no effect if locale matches', () => {
+ const url = new URL('https://example.com/en/guide/');
+ expect(localizedUrl(url, 'en').href).toBe(url.href);
+});
+
+test('it changes locale to requested locale', () => {
+ const url = new URL('https://example.com/en/guide/');
+ expect(localizedUrl(url, 'ar').href).toBe('https://example.com/ar/guide/');
+});
+
+test('it can change to root locale', () => {
+ const url = new URL('https://example.com/en/guide/');
+ expect(localizedUrl(url, undefined).href).toBe('https://example.com/guide/');
+});
+
+test('it can change from root locale', () => {
+ const url = new URL('https://example.com/guide/');
+ expect(localizedUrl(url, 'en').href).toBe('https://example.com/en/guide/');
+});
diff --git a/packages/starlight/__tests__/i18n-root-locale/routing.test.ts b/packages/starlight/__tests__/i18n-root-locale/routing.test.ts
new file mode 100644
index 00000000..4d5a3c1c
--- /dev/null
+++ b/packages/starlight/__tests__/i18n-root-locale/routing.test.ts
@@ -0,0 +1,63 @@
+import config from 'virtual:starlight/user-config';
+import { expect, test, vi } from 'vitest';
+import { routes } from '../../utils/routing';
+
+vi.mock('astro:content', async () =>
+ (await import('../test-utils')).mockedAstroContent({
+ docs: [
+ ['404.md', { title: 'Page introuvable' }],
+ ['index.mdx', { title: 'Accueil' }],
+ // @ts-expect-error — Using a slug not present in Starlight docs site
+ ['en/index.mdx', { title: 'Home page' }],
+ // @ts-expect-error — Using a slug not present in Starlight docs site
+ ['ar/index.mdx', { title: 'الصفحة الرئيسية' }],
+ ['guides/authoring-content.md', { title: 'Création de contenu en Markdown' }],
+ ],
+ })
+);
+
+test('test suite is using correct env', () => {
+ expect(config.title).toBe('i18n with root locale');
+});
+
+test('routes includes fallback entries for untranslated pages', () => {
+ const numLocales = config.isMultilingual ? Object.keys(config.locales).length : 1;
+ const guides = routes.filter((route) => route.id.includes('guides/'));
+ expect(guides).toHaveLength(numLocales);
+});
+
+test('routes have locale data added', () => {
+ for (const { id, lang, dir, locale } of routes) {
+ if (id.startsWith('en')) {
+ expect(lang).toBe('en-US');
+ expect(dir).toBe('ltr');
+ expect(locale).toBe('en');
+ } else if (id.startsWith('ar')) {
+ expect(lang).toBe('ar');
+ expect(dir).toBe('rtl');
+ expect(locale).toBe('ar');
+ } else {
+ expect(lang).toBe('fr');
+ expect(dir).toBe('ltr');
+ expect(locale).toBeUndefined();
+ }
+ }
+});
+
+test('fallback routes have fallback locale data in entryMeta', () => {
+ const fallbacks = routes.filter((route) => route.isFallback);
+ expect(fallbacks.length).toBeGreaterThan(0);
+ for (const route of fallbacks) {
+ expect(route.entryMeta.locale).toBeUndefined();
+ expect(route.entryMeta.locale).not.toBe(route.locale);
+ expect(route.entryMeta.lang).toBe('fr');
+ expect(route.entryMeta.lang).not.toBe(route.lang);
+ }
+});
+
+test('fallback routes use their own locale data', () => {
+ const enGuide = routes.find((route) => route.id === 'en/guides/authoring-content.md');
+ if (!enGuide) throw new Error('Expected to find English fallback route for authoring-content.md');
+ expect(enGuide.locale).toBe('en');
+ expect(enGuide.lang).toBe('en-US');
+});
diff --git a/packages/starlight/__tests__/i18n-root-locale/slugs.test.ts b/packages/starlight/__tests__/i18n-root-locale/slugs.test.ts
new file mode 100644
index 00000000..2614712a
--- /dev/null
+++ b/packages/starlight/__tests__/i18n-root-locale/slugs.test.ts
@@ -0,0 +1,78 @@
+import { describe, expect, test } from 'vitest';
+import { localeToLang, localizedId, localizedSlug, slugToLocaleData } from '../../utils/slugs';
+
+describe('slugToLocaleData', () => {
+ test('returns an undefined locale for root locale slugs', () => {
+ expect(slugToLocaleData('test').locale).toBeUndefined();
+ expect(slugToLocaleData('dir/test').locale).toBeUndefined();
+ });
+ test('returns a locale for localized slugs', () => {
+ expect(slugToLocaleData('en/test').locale).toBe('en');
+ expect(slugToLocaleData('ar/test').locale).toBe('ar');
+ });
+ test('returns default locale lang for root locale slugs', () => {
+ expect(slugToLocaleData('test').lang).toBe('fr');
+ expect(slugToLocaleData('dir/test').lang).toBe('fr');
+ });
+ test('returns langs for localized slugs', () => {
+ expect(slugToLocaleData('ar/test').lang).toBe('ar');
+ expect(slugToLocaleData('en/dir/test').lang).toBe('en-US');
+ });
+ test('returns default locale dir for root locale slugs', () => {
+ expect(slugToLocaleData('test').dir).toBe('ltr');
+ expect(slugToLocaleData('dir/test').dir).toBe('ltr');
+ });
+ test('returns configured dir for localized slugs', () => {
+ expect(slugToLocaleData('ar/test').dir).toBe('rtl');
+ expect(slugToLocaleData('en/dir/test').dir).toBe('ltr');
+ });
+});
+
+describe('localeToLang', () => {
+ test('returns lang for root locale', () => {
+ expect(localeToLang(undefined)).toBe('fr');
+ });
+ test('returns lang for non-root locales', () => {
+ expect(localeToLang('en')).toBe('en-US');
+ expect(localeToLang('ar')).toBe('ar');
+ });
+});
+
+describe('localizedId', () => {
+ test('returns unchanged when already in requested locale', () => {
+ expect(localizedId('test.md', undefined)).toBe('test.md');
+ expect(localizedId('dir/test.md', undefined)).toBe('dir/test.md');
+ expect(localizedId('en/test.md', 'en')).toBe('en/test.md');
+ expect(localizedId('en/dir/test.md', 'en')).toBe('en/dir/test.md');
+ expect(localizedId('ar/test.md', 'ar')).toBe('ar/test.md');
+ expect(localizedId('ar/dir/test.md', 'ar')).toBe('ar/dir/test.md');
+ });
+ test('returns localized id for requested locale', () => {
+ expect(localizedId('test.md', 'en')).toBe('en/test.md');
+ expect(localizedId('dir/test.md', 'en')).toBe('en/dir/test.md');
+ expect(localizedId('en/test.md', 'ar')).toBe('ar/test.md');
+ expect(localizedId('en/test.md', undefined)).toBe('test.md');
+ });
+});
+
+describe('localizedSlug', () => {
+ test('returns unchanged when already in requested locale', () => {
+ expect(localizedSlug('', undefined)).toBe('');
+ expect(localizedSlug('test', undefined)).toBe('test');
+ expect(localizedSlug('dir/test', undefined)).toBe('dir/test');
+ expect(localizedSlug('en', 'en')).toBe('en');
+ expect(localizedSlug('en/test', 'en')).toBe('en/test');
+ expect(localizedSlug('en/dir/test', 'en')).toBe('en/dir/test');
+ });
+ test('returns localized slug for requested locale', () => {
+ expect(localizedSlug('', 'en')).toBe('en');
+ expect(localizedSlug('test', 'en')).toBe('en/test');
+ expect(localizedSlug('dir/test', 'en')).toBe('en/dir/test');
+ expect(localizedSlug('en', undefined)).toBe('');
+ expect(localizedSlug('en/test', undefined)).toBe('test');
+ expect(localizedSlug('en/dir/test', undefined)).toBe('dir/test');
+ expect(localizedSlug('en', 'ar')).toBe('ar');
+ expect(localizedSlug('en/test', 'ar')).toBe('ar/test');
+ expect(localizedSlug('en/dir/test', 'ar')).toBe('ar/dir/test');
+ });
+});
diff --git a/packages/starlight/__tests__/i18n-root-locale/vitest.config.ts b/packages/starlight/__tests__/i18n-root-locale/vitest.config.ts
new file mode 100644
index 00000000..c9f51421
--- /dev/null
+++ b/packages/starlight/__tests__/i18n-root-locale/vitest.config.ts
@@ -0,0 +1,10 @@
+import { defineVitestConfig } from '../test-config';
+
+export default defineVitestConfig({
+ title: 'i18n with root locale',
+ locales: {
+ root: { label: 'French', lang: 'fr' },
+ en: { label: 'English', lang: 'en-US' },
+ ar: { label: 'Arabic', dir: 'rtl' },
+ },
+});
diff --git a/packages/starlight/__tests__/i18n/config.test.ts b/packages/starlight/__tests__/i18n/config.test.ts
new file mode 100644
index 00000000..642ce9fd
--- /dev/null
+++ b/packages/starlight/__tests__/i18n/config.test.ts
@@ -0,0 +1,17 @@
+import config from 'virtual:starlight/user-config';
+import { expect, test } from 'vitest';
+
+test('test suite is using correct env', () => {
+ expect(config.title).toBe('i18n with no root locale');
+});
+
+test('config.isMultilingual is true with multiple locales', () => {
+ expect(config.isMultilingual).toBe(true);
+ expect(config.locales).keys('fr', 'en', 'ar');
+});
+
+test('config.defaultLocale is populated from the user’s chosen default', () => {
+ expect(config.defaultLocale.locale).toBe('en');
+ expect(config.defaultLocale.lang).toBe('en-US');
+ expect(config.defaultLocale.dir).toBe('ltr');
+});
diff --git a/packages/starlight/__tests__/i18n/localizedUrl.test.ts b/packages/starlight/__tests__/i18n/localizedUrl.test.ts
new file mode 100644
index 00000000..4f09f031
--- /dev/null
+++ b/packages/starlight/__tests__/i18n/localizedUrl.test.ts
@@ -0,0 +1,12 @@
+import { expect, test } from 'vitest';
+import { localizedUrl } from '../../utils/localizedUrl';
+
+test('it has no effect if locale matches', () => {
+ const url = new URL('https://example.com/en/guide/');
+ expect(localizedUrl(url, 'en').href).toBe(url.href);
+});
+
+test('it changes locale to requested locale', () => {
+ const url = new URL('https://example.com/en/guide/');
+ expect(localizedUrl(url, 'fr').href).toBe('https://example.com/fr/guide/');
+});
diff --git a/packages/starlight/__tests__/i18n/routing.test.ts b/packages/starlight/__tests__/i18n/routing.test.ts
new file mode 100644
index 00000000..438d5494
--- /dev/null
+++ b/packages/starlight/__tests__/i18n/routing.test.ts
@@ -0,0 +1,66 @@
+import config from 'virtual:starlight/user-config';
+import { expect, test, vi } from 'vitest';
+import { routes } from '../../utils/routing';
+
+vi.mock('astro:content', async () =>
+ (await import('../test-utils')).mockedAstroContent({
+ docs: [
+ ['fr/index.mdx', { title: 'Accueil' }],
+ // @ts-expect-error — Using a slug not present in Starlight docs site
+ ['en/index.mdx', { title: 'Home page' }],
+ // @ts-expect-error — Using a slug not present in Starlight docs site
+ ['ar/index.mdx', { title: 'الصفحة الرئيسية' }],
+ // @ts-expect-error — Using a slug not present in Starlight docs site
+ ['en/guides/authoring-content.md', { title: 'Création de contenu en Markdown' }],
+ // @ts-expect-error — Using a slug not present in Starlight docs site
+ ['en/404.md', { title: 'Page introuvable' }],
+ ],
+ })
+);
+
+test('test suite is using correct env', () => {
+ expect(config.title).toBe('i18n with no root locale');
+});
+
+test('routes includes fallback entries for untranslated pages', () => {
+ const numLocales = config.isMultilingual ? Object.keys(config.locales).length : 1;
+ const guides = routes.filter((route) => route.id.includes('/guides/'));
+ expect(guides).toHaveLength(numLocales);
+});
+
+test('routes have locale data added', () => {
+ for (const { id, lang, dir, locale } of routes) {
+ if (id.startsWith('en')) {
+ expect(lang).toBe('en-US');
+ expect(dir).toBe('ltr');
+ expect(locale).toBe('en');
+ } else if (id.startsWith('ar')) {
+ expect(lang).toBe('ar');
+ expect(dir).toBe('rtl');
+ expect(locale).toBe('ar');
+ } else {
+ expect(lang).toBe('fr');
+ expect(dir).toBe('ltr');
+ expect(locale).toBe('fr');
+ }
+ }
+});
+
+test('fallback routes have fallback locale data in entryMeta', () => {
+ const fallbacks = routes.filter((route) => route.isFallback);
+ expect(fallbacks.length).toBeGreaterThan(0);
+ for (const route of fallbacks) {
+ expect(route.entryMeta.locale).toBe('en');
+ expect(route.entryMeta.locale).not.toBe(route.locale);
+ expect(route.entryMeta.lang).toBe('en-US');
+ expect(route.entryMeta.lang).not.toBe(route.lang);
+ }
+});
+
+test('fallback routes use their own locale data', () => {
+ const arGuide = routes.find((route) => route.id === 'ar/guides/authoring-content.md');
+ if (!arGuide) throw new Error('Expected to find Arabic fallback route for authoring-content.md');
+ expect(arGuide.locale).toBe('ar');
+ expect(arGuide.lang).toBe('ar');
+ expect(arGuide.dir).toBe('rtl');
+});
diff --git a/packages/starlight/__tests__/i18n/vitest.config.ts b/packages/starlight/__tests__/i18n/vitest.config.ts
new file mode 100644
index 00000000..84c58a05
--- /dev/null
+++ b/packages/starlight/__tests__/i18n/vitest.config.ts
@@ -0,0 +1,11 @@
+import { defineVitestConfig } from '../test-config';
+
+export default defineVitestConfig({
+ title: 'i18n with no root locale',
+ defaultLocale: 'en',
+ locales: {
+ fr: { label: 'French' },
+ en: { label: 'English', lang: 'en-US' },
+ ar: { label: 'Arabic', dir: 'rtl' },
+ },
+});
diff --git a/packages/starlight/__tests__/sidebar/navigation.test.ts b/packages/starlight/__tests__/sidebar/navigation.test.ts
new file mode 100644
index 00000000..5f9e362c
--- /dev/null
+++ b/packages/starlight/__tests__/sidebar/navigation.test.ts
@@ -0,0 +1,67 @@
+import { describe, expect, test, vi } from 'vitest';
+import { getSidebar } from '../../utils/navigation';
+
+vi.mock('astro:content', async () =>
+ (await import('../test-utils')).mockedAstroContent({
+ docs: [
+ ['index.mdx', { title: 'Home Page' }],
+ ['environmental-impact.md', { title: 'Eco-friendly docs' }],
+ ['reference/configuration.md', { title: 'Config Reference' }],
+ ['reference/frontmatter.md', { title: 'Frontmatter Reference' }],
+ ['guides/components.mdx', { title: 'Components' }],
+ ],
+ })
+);
+
+describe('getSidebar', () => {
+ test('returns an array of sidebar entries', () => {
+ expect(getSidebar('/', undefined)).toMatchInlineSnapshot(`
+ [
+ {
+ "href": "/",
+ "isCurrent": true,
+ "label": "Home",
+ "type": "link",
+ },
+ {
+ "collapsed": false,
+ "entries": [
+ {
+ "href": "/intro/",
+ "isCurrent": false,
+ "label": "Introduction",
+ "type": "link",
+ },
+ {
+ "href": "/next-steps/",
+ "isCurrent": false,
+ "label": "Next Steps",
+ "type": "link",
+ },
+ ],
+ "label": "Start Here",
+ "type": "group",
+ },
+ {
+ "collapsed": false,
+ "entries": [
+ {
+ "href": "/reference/configuration/",
+ "isCurrent": false,
+ "label": "Config Reference",
+ "type": "link",
+ },
+ {
+ "href": "/reference/frontmatter/",
+ "isCurrent": false,
+ "label": "Frontmatter Reference",
+ "type": "link",
+ },
+ ],
+ "label": "Reference",
+ "type": "group",
+ },
+ ]
+ `);
+ });
+});
diff --git a/packages/starlight/__tests__/sidebar/vitest.config.ts b/packages/starlight/__tests__/sidebar/vitest.config.ts
new file mode 100644
index 00000000..0a17416b
--- /dev/null
+++ b/packages/starlight/__tests__/sidebar/vitest.config.ts
@@ -0,0 +1,22 @@
+import { defineVitestConfig } from '../test-config';
+
+export default defineVitestConfig({
+ title: 'Sidebar Test',
+ sidebar: [
+ // A single link item labelled “Home”.
+ { label: 'Home', link: '/' },
+ // A group labelled “Start Here” containing two links.
+ {
+ label: 'Start Here',
+ items: [
+ { label: 'Introduction', link: '/intro' },
+ { label: 'Next Steps', link: '/next-steps' },
+ ],
+ },
+ // A group linking to all pages in the reference directory.
+ {
+ label: 'Reference',
+ autogenerate: { directory: 'reference' },
+ },
+ ],
+});
diff --git a/packages/starlight/__tests__/test-config.ts b/packages/starlight/__tests__/test-config.ts
new file mode 100644
index 00000000..304093a5
--- /dev/null
+++ b/packages/starlight/__tests__/test-config.ts
@@ -0,0 +1,16 @@
+/// <reference types="vitest" />
+
+import { getViteConfig } from 'astro/config';
+import type { z } from 'astro/zod';
+import { vitePluginStarlightUserConfig } from '../integrations/virtual-user-config';
+import { StarlightConfigSchema } from '../utils/user-config';
+
+export function defineVitestConfig(config: z.input<typeof StarlightConfigSchema>) {
+ return getViteConfig({
+ plugins: [
+ vitePluginStarlightUserConfig(StarlightConfigSchema.parse(config), {
+ root: new URL(import.meta.url),
+ }),
+ ],
+ });
+}
diff --git a/packages/starlight/__tests__/test-utils.ts b/packages/starlight/__tests__/test-utils.ts
new file mode 100644
index 00000000..e8d87ce3
--- /dev/null
+++ b/packages/starlight/__tests__/test-utils.ts
@@ -0,0 +1,57 @@
+import { z } from 'astro/zod';
+import { docsSchema, i18nSchema } from '../schema';
+import type { StarlightDocsEntry } from '../utils/routing';
+import { vi } from 'vitest';
+
+const frontmatterSchema = docsSchema()({
+ image: () =>
+ z.object({
+ src: z.string(),
+ width: z.number(),
+ height: z.number(),
+ format: z.union([
+ z.literal('png'),
+ z.literal('jpg'),
+ z.literal('jpeg'),
+ z.literal('tiff'),
+ z.literal('webp'),
+ z.literal('gif'),
+ z.literal('svg'),
+ ]),
+ }),
+});
+
+function mockDoc(
+ id: StarlightDocsEntry['id'],
+ data: z.input<typeof frontmatterSchema>,
+ body = ''
+): StarlightDocsEntry {
+ return {
+ id,
+ slug: id.replace(/\.[^\.]+$/, '').replace(/\/index$/, ''),
+ body,
+ collection: 'docs',
+ data: frontmatterSchema.parse(data),
+ render: (() => {}) as StarlightDocsEntry['render'],
+ };
+}
+
+function mockDict(id: string, data: z.input<ReturnType<typeof i18nSchema>>) {
+ return { id, data: i18nSchema().parse(data) };
+}
+
+export async function mockedAstroContent({
+ docs = [],
+ i18n = [],
+}: {
+ docs?: Parameters<typeof mockDoc>[];
+ i18n?: Parameters<typeof mockDict>[];
+}) {
+ const mod = await vi.importActual<typeof import('astro:content')>('astro:content');
+ const mockDocs = docs.map((doc) => mockDoc(...doc));
+ const mockDicts = i18n.map((dict) => mockDict(...dict));
+ return {
+ ...mod,
+ getCollection: (collection: 'docs' | 'i18n') => (collection === 'i18n' ? mockDicts : mockDocs),
+ };
+}
diff --git a/packages/starlight/index.ts b/packages/starlight/index.ts
index 80d3b630..63167eed 100644
--- a/packages/starlight/index.ts
+++ b/packages/starlight/index.ts
@@ -1,21 +1,16 @@
import mdx from '@astrojs/mdx';
-import type {
- AstroConfig,
- AstroIntegration,
- AstroUserConfig,
- ViteUserConfig,
-} from 'astro';
+import type { AstroIntegration, AstroUserConfig } from 'astro';
import { spawn } from 'node:child_process';
-import { dirname, relative, resolve } from 'node:path';
+import { dirname, relative } from 'node:path';
import { fileURLToPath } from 'node:url';
import { starlightAsides } from './integrations/asides';
import { starlightSitemap } from './integrations/sitemap';
+import { vitePluginStarlightUserConfig } from './integrations/virtual-user-config';
+import { errorMap } from './utils/error-map';
import {
- StarlightUserConfig,
- StarlightConfig,
StarlightConfigSchema,
+ StarlightUserConfig,
} from './utils/user-config';
-import { errorMap } from './utils/error-map';
export default function StarlightIntegration(
opts: StarlightUserConfig
@@ -78,47 +73,3 @@ export default function StarlightIntegration(
return [starlightSitemap(userConfig), Starlight, mdx()];
}
-
-function resolveVirtualModuleId(id: string) {
- return '\0' + id;
-}
-
-/** Expose the Starlight user config object via a virtual module. */
-function vitePluginStarlightUserConfig(
- opts: StarlightConfig,
- { root }: AstroConfig
-): NonNullable<ViteUserConfig['plugins']>[number] {
- const resolveRelativeId = (id: string) =>
- JSON.stringify(id.startsWith('.') ? resolve(fileURLToPath(root), id) : id);
- const modules = {
- 'virtual:starlight/user-config': `export default ${JSON.stringify(opts)}`,
- 'virtual:starlight/project-context': `export default ${JSON.stringify({
- root,
- })}`,
- 'virtual:starlight/user-css': opts.customCss
- .map((id) => `import ${resolveRelativeId(id)};`)
- .join(''),
- 'virtual:starlight/user-images': opts.logo
- ? 'src' in opts.logo
- ? `import src from ${resolveRelativeId(opts.logo.src)}; export const logos = { dark: src, light: src };`
- : `import dark from ${resolveRelativeId(opts.logo.dark)}; import light from ${resolveRelativeId(opts.logo.light)}; export const logos = { dark, light };`
- : 'export const logos = {};',
- };
- const resolutionMap = Object.fromEntries(
- (Object.keys(modules) as (keyof typeof modules)[]).map((key) => [
- resolveVirtualModuleId(key),
- key,
- ])
- );
-
- return {
- name: 'vite-plugin-starlight-user-config',
- resolveId(id): string | void {
- if (id in modules) return resolveVirtualModuleId(id);
- },
- load(id): string | void {
- const resolution = resolutionMap[id];
- if (resolution) return modules[resolution];
- },
- };
-}
diff --git a/packages/starlight/integrations/virtual-user-config.ts b/packages/starlight/integrations/virtual-user-config.ts
new file mode 100644
index 00000000..1ddf4995
--- /dev/null
+++ b/packages/starlight/integrations/virtual-user-config.ts
@@ -0,0 +1,52 @@
+import type { AstroConfig, ViteUserConfig } from 'astro';
+import { resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import type { StarlightConfig } from '../utils/user-config';
+
+function resolveVirtualModuleId<T extends string>(id: T): `\0${T}` {
+ return `\0${id}`;
+}
+
+/** Vite plugin that exposes Starlight user config and project context via virtual modules. */
+export function vitePluginStarlightUserConfig(
+ opts: StarlightConfig,
+ { root }: Pick<AstroConfig, 'root'>
+): NonNullable<ViteUserConfig['plugins']>[number] {
+ const resolveId = (id: string) =>
+ JSON.stringify(id.startsWith('.') ? resolve(fileURLToPath(root), id) : id);
+
+ /** Map of virtual module names to their code contents as strings. */
+ const modules = {
+ 'virtual:starlight/user-config': `export default ${JSON.stringify(opts)}`,
+ 'virtual:starlight/project-context': `export default ${JSON.stringify({ root })}`,
+ 'virtual:starlight/user-css': opts.customCss.map((id) => `import ${resolveId(id)};`).join(''),
+ 'virtual:starlight/user-images': opts.logo
+ ? 'src' in opts.logo
+ ? `import src from ${resolveId(
+ opts.logo.src
+ )}; export const logos = { dark: src, light: src };`
+ : `import dark from ${resolveId(opts.logo.dark)}; import light from ${resolveId(
+ opts.logo.light
+ )}; export const logos = { dark, light };`
+ : 'export const logos = {};',
+ } satisfies Record<string, string>;
+
+ /** Mapping names prefixed with `\0` to their original form. */
+ const resolutionMap = Object.fromEntries(
+ (Object.keys(modules) as (keyof typeof modules)[]).map((key) => [
+ resolveVirtualModuleId(key),
+ key,
+ ])
+ );
+
+ return {
+ name: 'vite-plugin-starlight-user-config',
+ resolveId(id): string | void {
+ if (id in modules) return resolveVirtualModuleId(id);
+ },
+ load(id): string | void {
+ const resolution = resolutionMap[id];
+ if (resolution) return modules[resolution];
+ },
+ };
+}
diff --git a/packages/starlight/package.json b/packages/starlight/package.json
index 8f765289..f30834ba 100644
--- a/packages/starlight/package.json
+++ b/packages/starlight/package.json
@@ -2,7 +2,10 @@
"name": "@astrojs/starlight",
"version": "0.5.5",
"description": "Build beautiful, high-performance documentation websites with Astro",
- "scripts": {},
+ "scripts": {
+ "test": "vitest",
+ "test:coverage": "vitest run --coverage"
+ },
"keywords": [
"docs",
"documentation",
@@ -33,7 +36,9 @@
},
"devDependencies": {
"@types/node": "^18.16.19",
- "astro": "^2.8.5"
+ "@vitest/coverage-v8": "^0.33.0",
+ "astro": "^2.8.5",
+ "vitest": "^0.33.0"
},
"dependencies": {
"@astrojs/mdx": "^0.19.7",
diff --git a/packages/starlight/types.ts b/packages/starlight/types.ts
index 70513dc2..8d99451e 100644
--- a/packages/starlight/types.ts
+++ b/packages/starlight/types.ts
@@ -1 +1 @@
-export { StarlightConfig } from './utils/user-config';
+export type { StarlightConfig } from './utils/user-config';
diff --git a/packages/starlight/utils/slugs.ts b/packages/starlight/utils/slugs.ts
index 9706b653..c6956509 100644
--- a/packages/starlight/utils/slugs.ts
+++ b/packages/starlight/utils/slugs.ts
@@ -56,7 +56,7 @@ export function slugToParam(slug: string): string | undefined {
return slug === 'index' || slug === ''
? undefined
: slug.endsWith('/index')
- ? slug.replace('/index', '')
+ ? slug.replace(/\/index$/, '')
: slug;
}
diff --git a/packages/starlight/vitest.config.ts b/packages/starlight/vitest.config.ts
new file mode 100644
index 00000000..8312dbdf
--- /dev/null
+++ b/packages/starlight/vitest.config.ts
@@ -0,0 +1,31 @@
+import { defineConfig } from 'vitest/config';
+
+// Copy of https://github.com/vitest-dev/vitest/blob/8693449b412743f20a63fd9bfa1a9054aa74613f/packages/vitest/src/defaults.ts#L13C1-L26C1
+const defaultCoverageExcludes = [
+ 'coverage/**',
+ 'dist/**',
+ 'packages/*/test?(s)/**',
+ '**/*.d.ts',
+ 'cypress/**',
+ 'test?(s)/**',
+ '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.*',
+ '**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}',
+];
+
+export default defineConfig({
+ test: {
+ coverage: {
+ all: true,
+ reportsDirectory: './__coverage__',
+ exclude: [...defaultCoverageExcludes, '**/vitest.*', 'components.ts', 'types.ts'],
+ thresholdAutoUpdate: true,
+ lines: 52.11,
+ functions: 82.35,
+ branches: 89.17,
+ statements: 52.11,
+ },
+ },
+});
diff --git a/packages/starlight/vitest.workspace.ts b/packages/starlight/vitest.workspace.ts
new file mode 100644
index 00000000..4cfafe82
--- /dev/null
+++ b/packages/starlight/vitest.workspace.ts
@@ -0,0 +1 @@
+export default ['__tests__/*'];
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 32db1365..24434380 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,5 +1,9 @@
lockfileVersion: '6.0'
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
importers:
.:
@@ -147,9 +151,15 @@ importers:
'@types/node':
specifier: ^18.16.19
version: 18.16.19
+ '@vitest/coverage-v8':
+ specifier: ^0.33.0
+ version: 0.33.0(vitest@0.33.0)
astro:
specifier: ^2.8.5
version: 2.8.5(@types/node@18.16.19)
+ vitest:
+ specifier: ^0.33.0
+ version: 0.33.0
packages:
@@ -160,6 +170,14 @@ packages:
'@jridgewell/gen-mapping': 0.1.1
'@jridgewell/trace-mapping': 0.3.18
+ /@ampproject/remapping@2.2.1:
+ resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.2
+ '@jridgewell/trace-mapping': 0.3.18
+ dev: true
+
/@astrojs/compiler@1.5.3:
resolution: {integrity: sha512-/HSFkJ+Yv+WUWSq0QVsIlhBKam5VUpGV+s8MvPguC/krHmw4Ww9TIgmfJSvV8/BN0sHJB7pCgf7yInae1zb+TQ==}
@@ -190,7 +208,7 @@ packages:
astro: ^2.5.0
dependencies:
'@astrojs/prism': 2.1.2
- astro: 2.8.5(sharp@0.32.3)
+ astro: 2.8.5(@types/node@18.16.19)
github-slugger: 1.5.0
import-meta-resolve: 2.2.2
rehype-raw: 6.1.1
@@ -500,6 +518,10 @@ packages:
'@babel/helper-validator-identifier': 7.22.5
to-fast-properties: 2.0.0
+ /@bcoe/v8-coverage@0.2.3:
+ resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+ dev: true
+
/@changesets/apply-release-plan@6.1.3:
resolution: {integrity: sha512-ECDNeoc3nfeAe1jqJb5aFQX7CqzQhD2klXRez2JDb/aVpGUbX673HgKrnrgJRuQR/9f2TtLoYIzrGB9qwD77mg==}
dependencies:
@@ -920,19 +942,31 @@ packages:
'@hapi/hoek': 9.3.0
dev: true
+ /@istanbuljs/schema@0.1.3:
+ resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /@jest/schemas@29.6.0:
+ resolution: {integrity: sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dependencies:
+ '@sinclair/typebox': 0.27.8
+ dev: true
+
/@jridgewell/gen-mapping@0.1.1:
resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/set-array': 1.1.2
- '@jridgewell/sourcemap-codec': 1.4.14
+ '@jridgewell/sourcemap-codec': 1.4.15
/@jridgewell/gen-mapping@0.3.2:
resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/set-array': 1.1.2
- '@jridgewell/sourcemap-codec': 1.4.14
+ '@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.18
/@jridgewell/resolve-uri@3.1.0:
@@ -946,6 +980,9 @@ packages:
/@jridgewell/sourcemap-codec@1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
+ /@jridgewell/sourcemap-codec@1.4.15:
+ resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+
/@jridgewell/trace-mapping@0.3.18:
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
dependencies:
@@ -1095,6 +1132,10 @@ packages:
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
dev: true
+ /@sinclair/typebox@0.27.8:
+ resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
+ dev: true
+
/@size-limit/file@8.2.4(size-limit@8.2.4):
resolution: {integrity: sha512-xLuF97W7m7lxrRJvqXRlxO/4t7cpXtfxOnjml/t4aRVUCMXLdyvebRr9OM4jjoK8Fmiz8jomCbETUCI3jVhLzA==}
engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
@@ -1136,6 +1177,16 @@ packages:
dependencies:
'@babel/types': 7.22.5
+ /@types/chai-subset@1.3.3:
+ resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
+ dependencies:
+ '@types/chai': 4.3.5
+ dev: true
+
+ /@types/chai@4.3.5:
+ resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==}
+ dev: true
+
/@types/debug@4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
dependencies:
@@ -1166,6 +1217,10 @@ packages:
ci-info: 3.8.0
dev: true
+ /@types/istanbul-lib-coverage@2.0.4:
+ resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
+ dev: true
+
/@types/json5@0.0.30:
resolution: {integrity: sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==}
@@ -1201,11 +1256,6 @@ packages:
/@types/node@18.16.19:
resolution: {integrity: sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==}
- /@types/node@20.4.2:
- resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==}
- dev: true
- optional: true
-
/@types/normalize-package-data@2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
dev: true
@@ -1240,10 +1290,69 @@ packages:
resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
requiresBuild: true
dependencies:
- '@types/node': 20.4.2
+ '@types/node': 18.16.19
dev: true
optional: true
+ /@vitest/coverage-v8@0.33.0(vitest@0.33.0):
+ resolution: {integrity: sha512-Rj5IzoLF7FLj6yR7TmqsfRDSeaFki6NAJ/cQexqhbWkHEV2htlVGrmuOde3xzvFsCbLCagf4omhcIaVmfU8Okg==}
+ peerDependencies:
+ vitest: '>=0.32.0 <1'
+ dependencies:
+ '@ampproject/remapping': 2.2.1
+ '@bcoe/v8-coverage': 0.2.3
+ istanbul-lib-coverage: 3.2.0
+ istanbul-lib-report: 3.0.0
+ istanbul-lib-source-maps: 4.0.1
+ istanbul-reports: 3.1.5
+ magic-string: 0.30.1
+ picocolors: 1.0.0
+ std-env: 3.3.3
+ test-exclude: 6.0.0
+ v8-to-istanbul: 9.1.0
+ vitest: 0.33.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@vitest/expect@0.33.0:
+ resolution: {integrity: sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==}
+ dependencies:
+ '@vitest/spy': 0.33.0
+ '@vitest/utils': 0.33.0
+ chai: 4.3.7
+ dev: true
+
+ /@vitest/runner@0.33.0:
+ resolution: {integrity: sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==}
+ dependencies:
+ '@vitest/utils': 0.33.0
+ p-limit: 4.0.0
+ pathe: 1.1.1
+ dev: true
+
+ /@vitest/snapshot@0.33.0:
+ resolution: {integrity: sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==}
+ dependencies:
+ magic-string: 0.30.1
+ pathe: 1.1.1
+ pretty-format: 29.6.1
+ dev: true
+
+ /@vitest/spy@0.33.0:
+ resolution: {integrity: sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==}
+ dependencies:
+ tinyspy: 2.1.1
+ dev: true
+
+ /@vitest/utils@0.33.0:
+ resolution: {integrity: sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==}
+ dependencies:
+ diff-sequences: 29.4.3
+ loupe: 2.3.6
+ pretty-format: 29.6.1
+ dev: true
+
/@vscode/emmet-helper@2.8.8:
resolution: {integrity: sha512-QuD4CmNeXSFxuP8VZwI6qL+8vmmd7JcSdwsEIdsrzb4YumWs/+4rXRX9MM+NsFfUO69g6ezngCD7XRd6jY9TQw==}
dependencies:
@@ -1268,6 +1377,11 @@ packages:
acorn: 8.9.0
dev: false
+ /acorn-walk@8.2.0:
+ resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
+ engines: {node: '>=0.4.0'}
+ dev: true
+
/acorn@8.9.0:
resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==}
engines: {node: '>=0.4.0'}
@@ -1315,6 +1429,11 @@ packages:
dependencies:
color-convert: 2.0.1
+ /ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+ dev: true
+
/ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
@@ -1379,6 +1498,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /assertion-error@1.1.0:
+ resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
+ dev: true
+
/astring@1.8.4:
resolution: {integrity: sha512-97a+l2LBU3Op3bBQEff79i/E4jMD2ZLFD8rHx9B6mXyB2uQwhJQYfiDqUwtfjF4QA1F2qs//N6Cw8LetMbQjcw==}
hasBin: true
@@ -1458,7 +1581,6 @@ packages:
- sugarss
- supports-color
- terser
- dev: true
/astro@2.8.5(sharp@0.32.3):
resolution: {integrity: sha512-qfPUKLpZ9lVi5Hc5MrzyekUUx54AyrEphW5eetNQj/+d0iodHEneZXFDzZxTEsk3rL8Y2Y9pYFXJPmQB3eahUA==}
@@ -1701,6 +1823,11 @@ packages:
engines: {node: '>= 0.8'}
dev: true
+ /cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+ dev: true
+
/call-bind@1.0.2:
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
dependencies:
@@ -1732,6 +1859,19 @@ packages:
/ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+ /chai@4.3.7:
+ resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==}
+ engines: {node: '>=4'}
+ dependencies:
+ assertion-error: 1.1.0
+ check-error: 1.0.2
+ deep-eql: 4.1.3
+ get-func-name: 2.0.0
+ loupe: 2.3.6
+ pathval: 1.1.1
+ type-detect: 4.0.8
+ dev: true
+
/chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@@ -1768,6 +1908,10 @@ packages:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
dev: true
+ /check-error@1.0.2:
+ resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
+ dev: true
+
/check-more-types@2.24.0:
resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==}
engines: {node: '>= 0.8.0'}
@@ -2026,6 +2170,13 @@ packages:
resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==}
dev: true
+ /deep-eql@4.1.3:
+ resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
+ engines: {node: '>=6'}
+ dependencies:
+ type-detect: 4.0.8
+ dev: true
+
/deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
@@ -2076,6 +2227,11 @@ packages:
resolution: {integrity: sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==}
dev: true
+ /diff-sequences@29.4.3:
+ resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dev: true
+
/diff@5.1.0:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
engines: {node: '>=0.3.1'}
@@ -2817,6 +2973,10 @@ packages:
engines: {node: 6.* || 8.* || >= 10.*}
dev: true
+ /get-func-name@2.0.0:
+ resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
+ dev: true
+
/get-intrinsic@1.2.0:
resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==}
dependencies:
@@ -3124,6 +3284,10 @@ packages:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
dev: true
+ /html-escaper@2.0.2:
+ resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+ dev: true
+
/html-escaper@3.0.3:
resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
@@ -3449,6 +3613,39 @@ packages:
/isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ /istanbul-lib-coverage@3.2.0:
+ resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /istanbul-lib-report@3.0.0:
+ resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==}
+ engines: {node: '>=8'}
+ dependencies:
+ istanbul-lib-coverage: 3.2.0
+ make-dir: 3.1.0
+ supports-color: 7.2.0
+ dev: true
+
+ /istanbul-lib-source-maps@4.0.1:
+ resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
+ engines: {node: '>=10'}
+ dependencies:
+ debug: 4.3.4
+ istanbul-lib-coverage: 3.2.0
+ source-map: 0.6.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /istanbul-reports@3.1.5:
+ resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==}
+ engines: {node: '>=8'}
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.0
+ dev: true
+
/joi@17.9.2:
resolution: {integrity: sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==}
dependencies:
@@ -3536,6 +3733,11 @@ packages:
pify: 4.0.1
strip-bom: 3.0.0
+ /local-pkg@0.4.3:
+ resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
+ engines: {node: '>=14'}
+ dev: true
+
/locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@@ -3566,6 +3768,12 @@ packages:
/longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+ /loupe@2.3.6:
+ resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
+ dependencies:
+ get-func-name: 2.0.0
+ dev: true
+
/lru-cache@4.1.5:
resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
dependencies:
@@ -3590,6 +3798,20 @@ packages:
dependencies:
'@jridgewell/sourcemap-codec': 1.4.14
+ /magic-string@0.30.1:
+ resolution: {integrity: sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==}
+ engines: {node: '>=12'}
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.4.15
+ dev: true
+
+ /make-dir@3.1.0:
+ resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
+ engines: {node: '>=8'}
+ dependencies:
+ semver: 6.3.0
+ dev: true
+
/map-obj@1.0.1:
resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==}
engines: {node: '>=0.10.0'}
@@ -4228,6 +4450,15 @@ packages:
deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
dev: true
+ /mlly@1.4.0:
+ resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==}
+ dependencies:
+ acorn: 8.9.0
+ pathe: 1.1.1
+ pkg-types: 1.0.3
+ ufo: 1.1.2
+ dev: true
+
/mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@@ -4604,6 +4835,14 @@ packages:
engines: {node: '>=8'}
dev: true
+ /pathe@1.1.1:
+ resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
+ dev: true
+
+ /pathval@1.1.1:
+ resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
+ dev: true
+
/pause-stream@0.0.11:
resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==}
dependencies:
@@ -4656,6 +4895,14 @@ packages:
dependencies:
find-up: 4.1.0
+ /pkg-types@1.0.3:
+ resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
+ dependencies:
+ jsonc-parser: 3.2.0
+ mlly: 1.4.0
+ pathe: 1.1.1
+ dev: true
+
/postcss@8.4.23:
resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==}
engines: {node: ^10 || ^12 || >=14}
@@ -4716,6 +4963,15 @@ packages:
engines: {node: '>=10.13.0'}
hasBin: true
+ /pretty-format@29.6.1:
+ resolution: {integrity: sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dependencies:
+ '@jest/schemas': 29.6.0
+ ansi-styles: 5.2.0
+ react-is: 18.2.0
+ dev: true
+
/prismjs@1.29.0:
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
engines: {node: '>=6'}
@@ -4810,6 +5066,10 @@ packages:
minimist: 1.2.8
strip-json-comments: 2.0.1
+ /react-is@18.2.0:
+ resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
+ dev: true
+
/read-pkg-up@7.0.1:
resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
engines: {node: '>=8'}
@@ -5192,6 +5452,10 @@ packages:
object-inspect: 1.12.3
dev: true
+ /siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+ dev: true
+
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@@ -5269,6 +5533,11 @@ packages:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
+ /source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
/source-map@0.7.4:
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
engines: {node: '>= 8'}
@@ -5315,6 +5584,10 @@ packages:
/sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+ /stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+ dev: true
+
/start-server-and-test@2.0.0:
resolution: {integrity: sha512-UqKLw0mJbfrsG1jcRLTUlvuRi9sjNuUiDOLI42r7R5fA9dsFoywAy9DoLXNYys9B886E4RCKb+qM1Gzu96h7DQ==}
engines: {node: '>=6'}
@@ -5332,6 +5605,10 @@ packages:
- supports-color
dev: true
+ /std-env@3.3.3:
+ resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==}
+ dev: true
+
/stdin-discarder@0.1.0:
resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -5456,6 +5733,12 @@ packages:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
+ /strip-literal@1.0.1:
+ resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==}
+ dependencies:
+ acorn: 8.9.0
+ dev: true
+
/style-to-object@0.4.1:
resolution: {integrity: sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==}
dependencies:
@@ -5527,6 +5810,15 @@ packages:
engines: {node: '>=8'}
dev: true
+ /test-exclude@6.0.0:
+ resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@istanbuljs/schema': 0.1.3
+ glob: 7.2.3
+ minimatch: 3.1.2
+ dev: true
+
/through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: true
@@ -5537,6 +5829,20 @@ packages:
globalyzer: 0.1.0
globrex: 0.1.2
+ /tinybench@2.5.0:
+ resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==}
+ dev: true
+
+ /tinypool@0.6.0:
+ resolution: {integrity: sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==}
+ engines: {node: '>=14.0.0'}
+ dev: true
+
+ /tinyspy@2.1.1:
+ resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==}
+ engines: {node: '>=14.0.0'}
+ dev: true
+
/tmp@0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
engines: {node: '>=0.6.0'}
@@ -5613,6 +5919,11 @@ packages:
dependencies:
safe-buffer: 5.2.1
+ /type-detect@4.0.8:
+ resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
+ engines: {node: '>=4'}
+ dev: true
+
/type-fest@0.13.1:
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
engines: {node: '>=10'}
@@ -5644,6 +5955,10 @@ packages:
engines: {node: '>=4.2.0'}
hasBin: true
+ /ufo@1.1.2:
+ resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==}
+ dev: true
+
/unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies:
@@ -5771,6 +6086,15 @@ packages:
kleur: 4.1.5
sade: 1.8.1
+ /v8-to-istanbul@9.1.0:
+ resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==}
+ engines: {node: '>=10.12.0'}
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.18
+ '@types/istanbul-lib-coverage': 2.0.4
+ convert-source-map: 1.9.0
+ dev: true
+
/validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
dependencies:
@@ -5798,6 +6122,27 @@ packages:
unist-util-stringify-position: 3.0.3
vfile-message: 3.1.4
+ /vite-node@0.33.0(@types/node@18.16.19):
+ resolution: {integrity: sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==}
+ engines: {node: '>=v14.18.0'}
+ hasBin: true
+ dependencies:
+ cac: 6.7.14
+ debug: 4.3.4
+ mlly: 1.4.0
+ pathe: 1.1.1
+ picocolors: 1.0.0
+ vite: 4.3.9(@types/node@18.16.19)
+ transitivePeerDependencies:
+ - '@types/node'
+ - less
+ - sass
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ dev: true
+
/vite@4.3.9(@types/node@18.16.19):
resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -5840,6 +6185,70 @@ packages:
dependencies:
vite: 4.3.9(@types/node@18.16.19)
+ /vitest@0.33.0:
+ resolution: {integrity: sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==}
+ engines: {node: '>=v14.18.0'}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@vitest/browser': '*'
+ '@vitest/ui': '*'
+ happy-dom: '*'
+ jsdom: '*'
+ playwright: '*'
+ safaridriver: '*'
+ webdriverio: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+ playwright:
+ optional: true
+ safaridriver:
+ optional: true
+ webdriverio:
+ optional: true
+ dependencies:
+ '@types/chai': 4.3.5
+ '@types/chai-subset': 1.3.3
+ '@types/node': 18.16.19
+ '@vitest/expect': 0.33.0
+ '@vitest/runner': 0.33.0
+ '@vitest/snapshot': 0.33.0
+ '@vitest/spy': 0.33.0
+ '@vitest/utils': 0.33.0
+ acorn: 8.9.0
+ acorn-walk: 8.2.0
+ cac: 6.7.14
+ chai: 4.3.7
+ debug: 4.3.4
+ local-pkg: 0.4.3
+ magic-string: 0.30.1
+ pathe: 1.1.1
+ picocolors: 1.0.0
+ std-env: 3.3.3
+ strip-literal: 1.0.1
+ tinybench: 2.5.0
+ tinypool: 0.6.0
+ vite: 4.3.9(@types/node@18.16.19)
+ vite-node: 0.33.0(@types/node@18.16.19)
+ why-is-node-running: 2.2.2
+ transitivePeerDependencies:
+ - less
+ - sass
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ dev: true
+
/vscode-css-languageservice@6.2.5:
resolution: {integrity: sha512-/1oyBZK3jfx6A0cA46FCUpy6OlqEsMT47LUIldCIP1YMKRYezJ9No+aNj9IM0AqhRZ92DxZ1DmU5lJ+biuiacA==}
dependencies:
@@ -5979,6 +6388,15 @@ packages:
dependencies:
isexe: 2.0.0
+ /why-is-node-running@2.2.2:
+ resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}
+ engines: {node: '>=8'}
+ hasBin: true
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+ dev: true
+
/widest-line@4.0.1:
resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
engines: {node: '>=12'}
@@ -6112,7 +6530,3 @@ packages:
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
-
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false