@rm-industries/create-forge 0.4.0-beta.4 → 0.4.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/template/.lighthouserc.json +3 -6
- package/dist/template/README.md +8 -5
- package/dist/template/docs/performance-budget.md +4 -1
- package/dist/template/package-lock.json +3 -3
- package/dist/template/package.json +2 -1
- package/dist/template/playwright.config.ts +4 -2
- package/dist/template/scripts/run-lighthouse.test.ts +21 -0
- package/dist/template/scripts/run-lighthouse.ts +30 -0
- package/dist/template/tests/accessibility.spec.ts +8 -6
- package/dist/template/tests/navigation.spec.ts +9 -7
- package/dist/template/tests/preview.ts +11 -0
- package/dist/template/tests/routes.spec.ts +22 -17
- package/dist/template/tests/theme.spec.ts +6 -4
- package/package.json +1 -1
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"ci": {
|
|
3
3
|
"collect": {
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"http://localhost/articles/",
|
|
8
|
-
"http://localhost/articles/designing-a-calm-starting-point/"
|
|
9
|
-
],
|
|
4
|
+
"startServerCommand": "npm run preview -- --host 127.0.0.1",
|
|
5
|
+
"startServerReadyPattern": "Local",
|
|
6
|
+
"startServerReadyTimeout": 30000,
|
|
10
7
|
"numberOfRuns": 3
|
|
11
8
|
},
|
|
12
9
|
"assert": {
|
package/dist/template/README.md
CHANGED
|
@@ -69,7 +69,8 @@ the complete Playwright and axe suite, and Lighthouse budgets in that order.
|
|
|
69
69
|
Each stage is joined with `&&`, so the command stops immediately and returns a
|
|
70
70
|
non-zero status when any stage fails. Browser tests and Lighthouse start from
|
|
71
71
|
their own production build so they remain independently runnable and cannot
|
|
72
|
-
accidentally inspect stale output.
|
|
72
|
+
accidentally inspect stale output. Both use Astro's preview server and derive
|
|
73
|
+
any deployment base path from the configured canonical site URL.
|
|
73
74
|
|
|
74
75
|
The complete command is intentionally high cost. Use `npm run quality:static`
|
|
75
76
|
for the quickest source-only feedback or `npm run quality:core` for every check
|
|
@@ -147,10 +148,12 @@ alternative-text quality.
|
|
|
147
148
|
|
|
148
149
|
Lighthouse CI runs three times each against the production home, article
|
|
149
150
|
listing, and article detail pages, then enforces median category scores and
|
|
150
|
-
transfer budgets.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
transfer budgets. Its typed runner prefixes those routes with a configured
|
|
152
|
+
project path, so local checks exercise GitHub Pages project sites with the same
|
|
153
|
+
asset URLs used in production. The measured baseline, headroom, exclusions, and
|
|
154
|
+
update rules are documented in `docs/performance-budget.md`. Reports are written
|
|
155
|
+
to the generated `.lighthouseci/` directory; CI retains them for seven days even
|
|
156
|
+
when an assertion fails.
|
|
154
157
|
|
|
155
158
|
The configured exclusions cover only generated output, dependency directories,
|
|
156
159
|
and tool artifacts. Stylelint's exceptions recognize Tailwind and DaisyUI
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
Lighthouse CI protects the generated site's public-page baseline. Run
|
|
4
4
|
`npm run lighthouse:ci` after changes that can affect rendering, assets,
|
|
5
5
|
metadata, or client behavior. The command builds the production site, collects
|
|
6
|
-
three reports per route, and evaluates the median
|
|
6
|
+
three reports per route from Astro's preview server, and evaluates the median
|
|
7
|
+
result. The typed runner derives Astro's optional deployment base from
|
|
8
|
+
`src/config/site.ts`, ensuring project-path assets and routes are measured at
|
|
9
|
+
their production-relative locations.
|
|
7
10
|
|
|
8
11
|
## Enforced routes
|
|
9
12
|
|
|
@@ -9235,9 +9235,9 @@
|
|
|
9235
9235
|
}
|
|
9236
9236
|
},
|
|
9237
9237
|
"node_modules/fast-uri": {
|
|
9238
|
-
"version": "3.1.
|
|
9239
|
-
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.
|
|
9240
|
-
"integrity": "sha512-
|
|
9238
|
+
"version": "3.1.7",
|
|
9239
|
+
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
|
|
9240
|
+
"integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
|
|
9241
9241
|
"dev": true,
|
|
9242
9242
|
"funding": [
|
|
9243
9243
|
{
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"lint:css:fix": "stylelint --fix \"**/*.css\" --ignore-path .gitignore",
|
|
22
22
|
"lint:markdown": "markdownlint \"**/*.md\" --ignore-path .gitignore",
|
|
23
23
|
"lint:markdown:fix": "markdownlint --fix \"**/*.md\" --ignore-path .gitignore",
|
|
24
|
-
"lighthouse:
|
|
24
|
+
"lighthouse:autorun": "lhci autorun",
|
|
25
|
+
"lighthouse:ci": "npm run build && node scripts/run-lighthouse.ts",
|
|
25
26
|
"spellcheck": "cspell \"**\"",
|
|
26
27
|
"typecheck": "astro sync && tsc --noEmit",
|
|
27
28
|
"test": "npm run test:unit",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { defineConfig, devices } from '@playwright/test';
|
|
2
2
|
|
|
3
|
+
import { previewOrigin, resolvePreviewUrl } from './tests/preview';
|
|
4
|
+
|
|
3
5
|
export default defineConfig({
|
|
4
6
|
testDir: './tests',
|
|
5
7
|
outputDir: 'test-results',
|
|
@@ -8,7 +10,7 @@ export default defineConfig({
|
|
|
8
10
|
retries: process.env.CI ? 2 : 0,
|
|
9
11
|
reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list',
|
|
10
12
|
use: {
|
|
11
|
-
baseURL:
|
|
13
|
+
baseURL: previewOrigin,
|
|
12
14
|
screenshot: 'only-on-failure',
|
|
13
15
|
trace: 'retain-on-failure',
|
|
14
16
|
},
|
|
@@ -24,7 +26,7 @@ export default defineConfig({
|
|
|
24
26
|
...process.env,
|
|
25
27
|
ASTRO_PREVIEW_BACKGROUND: '0',
|
|
26
28
|
},
|
|
27
|
-
url: '
|
|
29
|
+
url: resolvePreviewUrl('/'),
|
|
28
30
|
reuseExistingServer: !process.env.CI,
|
|
29
31
|
},
|
|
30
32
|
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { getLighthouseUrls } from './run-lighthouse';
|
|
4
|
+
|
|
5
|
+
describe('getLighthouseUrls', () => {
|
|
6
|
+
it('audits root deployments from the preview origin', () => {
|
|
7
|
+
expect(getLighthouseUrls('https://example.com')).toEqual([
|
|
8
|
+
'http://127.0.0.1:4321/',
|
|
9
|
+
'http://127.0.0.1:4321/articles/',
|
|
10
|
+
'http://127.0.0.1:4321/articles/designing-a-calm-starting-point/',
|
|
11
|
+
]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('audits project deployments below their configured base path', () => {
|
|
15
|
+
expect(getLighthouseUrls('https://example.github.io/example-project')).toEqual([
|
|
16
|
+
'http://127.0.0.1:4321/example-project/',
|
|
17
|
+
'http://127.0.0.1:4321/example-project/articles/',
|
|
18
|
+
'http://127.0.0.1:4321/example-project/articles/designing-a-calm-starting-point/',
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { getDeploymentConfig } from '../src/config/deployment.ts';
|
|
6
|
+
import { site } from '../src/config/site.ts';
|
|
7
|
+
import { resolveSiteHref } from '../src/lib/paths.ts';
|
|
8
|
+
|
|
9
|
+
const previewOrigin = 'http://127.0.0.1:4321';
|
|
10
|
+
const auditedPaths = ['/', '/articles/', '/articles/designing-a-calm-starting-point/'] as const;
|
|
11
|
+
|
|
12
|
+
export const getLighthouseUrls = (siteUrl: string, origin = previewOrigin): string[] => {
|
|
13
|
+
const deployment = getDeploymentConfig(siteUrl);
|
|
14
|
+
|
|
15
|
+
return auditedPaths.map((path) => new URL(resolveSiteHref(path, deployment.base ?? '/'), origin).href);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const runLighthouse = (): void => {
|
|
19
|
+
const lighthouseCli = createRequire(import.meta.url).resolve('@lhci/cli/src/cli.js');
|
|
20
|
+
const urlArguments = getLighthouseUrls(site.url).map((url) => `--collect.url=${url}`);
|
|
21
|
+
const result = spawnSync(process.execPath, [lighthouseCli, 'autorun', ...urlArguments], {
|
|
22
|
+
env: { ...process.env, ASTRO_PREVIEW_BACKGROUND: '0' },
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (result.error) throw result.error;
|
|
27
|
+
if (result.status !== 0) process.exitCode = result.status ?? 1;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) runLighthouse();
|
|
@@ -2,6 +2,8 @@ import AxeBuilder from '@axe-core/playwright';
|
|
|
2
2
|
import { expect, test } from '@playwright/test';
|
|
3
3
|
import type { Page } from '@playwright/test';
|
|
4
4
|
|
|
5
|
+
import { resolvePreviewPath } from './preview';
|
|
6
|
+
|
|
5
7
|
const publicRoutes = [
|
|
6
8
|
{ name: 'home', path: '/' },
|
|
7
9
|
{ name: 'about', path: '/about/' },
|
|
@@ -22,7 +24,7 @@ for (const colorScheme of ['light', 'dark'] as const) {
|
|
|
22
24
|
for (const route of publicRoutes) {
|
|
23
25
|
test(`${route.name} has no serious or critical violations in ${colorScheme} mode`, async ({ page }) => {
|
|
24
26
|
await page.emulateMedia({ colorScheme });
|
|
25
|
-
await page.goto(route.path);
|
|
27
|
+
await page.goto(resolvePreviewPath(route.path));
|
|
26
28
|
|
|
27
29
|
expect(await analyzePage(page)).toEqual([]);
|
|
28
30
|
});
|
|
@@ -31,7 +33,7 @@ for (const colorScheme of ['light', 'dark'] as const) {
|
|
|
31
33
|
|
|
32
34
|
for (const route of publicRoutes) {
|
|
33
35
|
test(`${route.name} exposes one main landmark and one level-one heading`, async ({ page }) => {
|
|
34
|
-
await page.goto(route.path);
|
|
36
|
+
await page.goto(resolvePreviewPath(route.path));
|
|
35
37
|
|
|
36
38
|
await expect(page.getByRole('banner')).toHaveCount(1);
|
|
37
39
|
await expect(page.getByRole('main')).toHaveCount(1);
|
|
@@ -41,14 +43,14 @@ for (const route of publicRoutes) {
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
test('identifies the current primary navigation item', async ({ page }) => {
|
|
44
|
-
await page.goto('/');
|
|
46
|
+
await page.goto(resolvePreviewPath('/'));
|
|
45
47
|
|
|
46
48
|
await expect(page.getByRole('navigation', { name: 'Primary navigation' })).toBeVisible();
|
|
47
49
|
await expect(page.getByRole('link', { name: 'Home' })).toHaveAttribute('aria-current', 'page');
|
|
48
50
|
});
|
|
49
51
|
|
|
50
52
|
test('moves keyboard users directly to the main content', async ({ page }) => {
|
|
51
|
-
await page.goto('/');
|
|
53
|
+
await page.goto(resolvePreviewPath('/'));
|
|
52
54
|
await page.keyboard.press('Tab');
|
|
53
55
|
|
|
54
56
|
const skipLink = page.getByRole('link', { name: 'Skip to content' });
|
|
@@ -62,7 +64,7 @@ test('moves keyboard users directly to the main content', async ({ page }) => {
|
|
|
62
64
|
|
|
63
65
|
test('keeps navigation available without horizontal overflow on small screens', async ({ page }) => {
|
|
64
66
|
await page.setViewportSize({ width: 320, height: 640 });
|
|
65
|
-
await page.goto('/');
|
|
67
|
+
await page.goto(resolvePreviewPath('/'));
|
|
66
68
|
|
|
67
69
|
await page.locator('summary').click();
|
|
68
70
|
await expect(page.getByRole('navigation', { name: 'Mobile navigation' })).toBeVisible();
|
|
@@ -75,7 +77,7 @@ test('keeps navigation available without horizontal overflow on small screens',
|
|
|
75
77
|
|
|
76
78
|
test('honors reduced-motion preferences', async ({ page }) => {
|
|
77
79
|
await page.emulateMedia({ reducedMotion: 'reduce' });
|
|
78
|
-
await page.goto('/');
|
|
80
|
+
await page.goto(resolvePreviewPath('/'));
|
|
79
81
|
|
|
80
82
|
const motionStyles = await page.getByRole('link', { name: 'Browse the articles' }).evaluate((element) => {
|
|
81
83
|
const styles = getComputedStyle(element);
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { expect, test } from '@playwright/test';
|
|
2
2
|
|
|
3
|
+
import { resolvePreviewPath, resolvePreviewUrl } from './preview';
|
|
4
|
+
|
|
3
5
|
test('uses primary navigation to move between pages and identify the current page', async ({ page }) => {
|
|
4
|
-
await page.goto('/');
|
|
6
|
+
await page.goto(resolvePreviewPath('/'));
|
|
5
7
|
|
|
6
8
|
const navigation = page.getByRole('navigation', { name: 'Primary navigation' });
|
|
7
9
|
await expect(navigation.getByRole('link', { name: 'Home' })).toHaveAttribute('aria-current', 'page');
|
|
8
10
|
|
|
9
11
|
await navigation.getByRole('link', { name: 'Articles' }).click();
|
|
10
|
-
await expect(page).toHaveURL('/articles/');
|
|
12
|
+
await expect(page).toHaveURL(resolvePreviewUrl('/articles/'));
|
|
11
13
|
await expect(page.getByRole('heading', { level: 1, name: 'Articles' })).toBeVisible();
|
|
12
14
|
await expect(
|
|
13
15
|
page.getByRole('navigation', { name: 'Primary navigation' }).getByRole('link', { name: 'Articles' }),
|
|
@@ -16,28 +18,28 @@ test('uses primary navigation to move between pages and identify the current pag
|
|
|
16
18
|
|
|
17
19
|
test('opens mobile navigation and follows a configured link', async ({ page }) => {
|
|
18
20
|
await page.setViewportSize({ width: 320, height: 640 });
|
|
19
|
-
await page.goto('/');
|
|
21
|
+
await page.goto(resolvePreviewPath('/'));
|
|
20
22
|
|
|
21
23
|
await page.locator('summary').filter({ hasText: 'Menu' }).click();
|
|
22
24
|
const navigation = page.getByRole('navigation', { name: 'Mobile navigation' });
|
|
23
25
|
await expect(navigation).toBeVisible();
|
|
24
26
|
await navigation.getByRole('link', { name: 'About' }).click();
|
|
25
27
|
|
|
26
|
-
await expect(page).toHaveURL('/about/');
|
|
28
|
+
await expect(page).toHaveURL(resolvePreviewUrl('/about/'));
|
|
27
29
|
await expect(
|
|
28
30
|
page.getByRole('heading', { level: 1, name: 'A useful foundation, not a finished identity.' }),
|
|
29
31
|
).toBeVisible();
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
test('moves from the article listing into an article and through article pagination', async ({ page }) => {
|
|
33
|
-
await page.goto('/articles/');
|
|
35
|
+
await page.goto(resolvePreviewPath('/articles/'));
|
|
34
36
|
|
|
35
37
|
await page.getByRole('link', { name: /Designing a calm starting point/u }).click();
|
|
36
|
-
await expect(page).toHaveURL('/articles/designing-a-calm-starting-point/');
|
|
38
|
+
await expect(page).toHaveURL(resolvePreviewUrl('/articles/designing-a-calm-starting-point/'));
|
|
37
39
|
await expect(page.getByRole('heading', { level: 1, name: 'Designing a calm starting point' })).toBeVisible();
|
|
38
40
|
|
|
39
41
|
const articleNavigation = page.getByRole('navigation', { name: 'Article navigation' });
|
|
40
42
|
await articleNavigation.getByRole('link', { name: /Content that travels well/u }).click();
|
|
41
|
-
await expect(page).toHaveURL('/articles/content-that-travels-well/');
|
|
43
|
+
await expect(page).toHaveURL(resolvePreviewUrl('/articles/content-that-travels-well/'));
|
|
42
44
|
await expect(page.getByRole('heading', { level: 1, name: 'Content that travels well' })).toBeVisible();
|
|
43
45
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { getDeploymentConfig } from '../src/config/deployment';
|
|
2
|
+
import { site } from '../src/config/site';
|
|
3
|
+
import { resolveSiteHref } from '../src/lib/paths';
|
|
4
|
+
|
|
5
|
+
export const previewOrigin = 'http://127.0.0.1:4321';
|
|
6
|
+
|
|
7
|
+
const deployment = getDeploymentConfig(site.url);
|
|
8
|
+
|
|
9
|
+
export const resolvePreviewPath = (path: string): string => resolveSiteHref(path, deployment.base ?? '/');
|
|
10
|
+
|
|
11
|
+
export const resolvePreviewUrl = (path: string): string => new URL(resolvePreviewPath(path), previewOrigin).href;
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import { expect, test } from '@playwright/test';
|
|
2
2
|
|
|
3
3
|
import { site } from '../src/config/site';
|
|
4
|
+
import { resolvePreviewPath, resolvePreviewUrl } from './preview';
|
|
4
5
|
|
|
5
6
|
const routes = ['/', '/about/', '/articles/', '/articles/designing-a-calm-starting-point/', '/admin/', '/404/'];
|
|
6
7
|
|
|
7
8
|
test('serves every baseline page with configured canonical and social metadata', async ({ page }) => {
|
|
8
9
|
for (const route of routes) {
|
|
9
|
-
await page.goto(route);
|
|
10
|
+
await page.goto(resolvePreviewPath(route));
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
const canonicalUrl = new URL(resolvePreviewPath(route), site.url).href;
|
|
13
|
+
|
|
14
|
+
await expect(page.locator('link[rel="canonical"]')).toHaveAttribute('href', canonicalUrl);
|
|
12
15
|
await expect(page.locator('meta[property="og:site_name"]')).toHaveAttribute('content', site.name);
|
|
13
|
-
await expect(page.locator('meta[property="og:url"]')).toHaveAttribute('content',
|
|
16
|
+
await expect(page.locator('meta[property="og:url"]')).toHaveAttribute('content', canonicalUrl);
|
|
14
17
|
await expect(page.locator('meta[name="twitter:card"]')).toHaveAttribute('content', 'summary_large_image');
|
|
15
18
|
}
|
|
16
19
|
});
|
|
17
20
|
|
|
18
21
|
test('serves the content manager without allowing search indexing', async ({ page }) => {
|
|
19
|
-
await page.goto('/admin/');
|
|
22
|
+
await page.goto(resolvePreviewPath('/admin/'));
|
|
20
23
|
|
|
21
24
|
await expect(page.locator('meta[name="robots"][content="noindex, nofollow"]')).toHaveCount(1);
|
|
22
25
|
await expect(page).toHaveTitle(/Content Manager/u);
|
|
@@ -24,19 +27,21 @@ test('serves the content manager without allowing search indexing', async ({ pag
|
|
|
24
27
|
});
|
|
25
28
|
|
|
26
29
|
test('renders generic article tags on listings and detail pages', async ({ page }) => {
|
|
27
|
-
await page.goto('/articles/');
|
|
30
|
+
await page.goto(resolvePreviewPath('/articles/'));
|
|
28
31
|
await expect(
|
|
29
|
-
page
|
|
32
|
+
page
|
|
33
|
+
.locator(`a[href="${resolvePreviewPath('/articles/designing-a-calm-starting-point/')}"]`)
|
|
34
|
+
.getByLabel('Article tags'),
|
|
30
35
|
).toContainText('Design');
|
|
31
36
|
|
|
32
|
-
await page.goto('/articles/designing-a-calm-starting-point/');
|
|
37
|
+
await page.goto(resolvePreviewPath('/articles/designing-a-calm-starting-point/'));
|
|
33
38
|
await expect(page.getByLabel('Article tags')).toContainText('Defaults');
|
|
34
39
|
});
|
|
35
40
|
|
|
36
41
|
test('resolves every internal page link', async ({ page, request }) => {
|
|
37
|
-
await page.goto('/');
|
|
42
|
+
await page.goto(resolvePreviewPath('/'));
|
|
38
43
|
|
|
39
|
-
const pending = ['/'];
|
|
44
|
+
const pending = [resolvePreviewPath('/')];
|
|
40
45
|
const visited = new Set<string>();
|
|
41
46
|
|
|
42
47
|
while (pending.length > 0) {
|
|
@@ -52,7 +57,7 @@ test('resolves every internal page link', async ({ page, request }) => {
|
|
|
52
57
|
|
|
53
58
|
for (const href of links) {
|
|
54
59
|
const url = new URL(href);
|
|
55
|
-
if (url.origin !== '
|
|
60
|
+
if (url.origin !== new URL(resolvePreviewUrl('/')).origin) continue;
|
|
56
61
|
|
|
57
62
|
const linkedPath = url.pathname;
|
|
58
63
|
const response = await request.get(linkedPath);
|
|
@@ -64,31 +69,31 @@ test('resolves every internal page link', async ({ page, request }) => {
|
|
|
64
69
|
});
|
|
65
70
|
|
|
66
71
|
test('serves feed, crawler, manifest, and not-found metadata', async ({ request }) => {
|
|
67
|
-
const feed = await request.get('/rss.xml');
|
|
72
|
+
const feed = await request.get(resolvePreviewPath('/rss.xml'));
|
|
68
73
|
expect(feed.ok()).toBe(true);
|
|
69
74
|
expect(await feed.text()).toContain(new URL('articles/designing-a-calm-starting-point/', site.url).href);
|
|
70
75
|
expect(await feed.text()).not.toContain('future-draft');
|
|
71
76
|
|
|
72
|
-
const robots = await request.get('/robots.txt');
|
|
77
|
+
const robots = await request.get(resolvePreviewPath('/robots.txt'));
|
|
73
78
|
expect(robots.ok()).toBe(true);
|
|
74
79
|
expect(await robots.text()).toContain(`Sitemap: ${new URL('sitemap-index.xml', site.url).href}`);
|
|
75
80
|
|
|
76
|
-
const manifest = await request.get('/site.webmanifest');
|
|
81
|
+
const manifest = await request.get(resolvePreviewPath('/site.webmanifest'));
|
|
77
82
|
expect(manifest.ok()).toBe(true);
|
|
78
83
|
expect(manifest.headers()['content-type']).toMatch(/application\/manifest\+json/u);
|
|
79
|
-
expect(await manifest.json()).toMatchObject({ name: site.name, start_url: '/' });
|
|
84
|
+
expect(await manifest.json()).toMatchObject({ name: site.name, start_url: resolvePreviewPath('/') });
|
|
80
85
|
|
|
81
|
-
const missing = await request.get('/does-not-exist/');
|
|
86
|
+
const missing = await request.get(resolvePreviewPath('/does-not-exist/'));
|
|
82
87
|
expect(missing.status()).toBe(404);
|
|
83
88
|
expect(await missing.text()).toContain('That page is not here.');
|
|
84
89
|
});
|
|
85
90
|
|
|
86
91
|
test('helps visitors recover from a missing page', async ({ page }) => {
|
|
87
|
-
const response = await page.goto('/does-not-exist/');
|
|
92
|
+
const response = await page.goto(resolvePreviewPath('/does-not-exist/'));
|
|
88
93
|
|
|
89
94
|
expect(response?.status()).toBe(404);
|
|
90
95
|
await expect(page.getByRole('heading', { level: 1, name: 'That page is not here.' })).toBeVisible();
|
|
91
96
|
await page.getByRole('link', { name: 'Return home' }).click();
|
|
92
|
-
await expect(page).toHaveURL('/');
|
|
97
|
+
await expect(page).toHaveURL(resolvePreviewUrl('/'));
|
|
93
98
|
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
|
|
94
99
|
});
|
|
@@ -2,6 +2,8 @@ import { flavors } from '@catppuccin/palette';
|
|
|
2
2
|
import { expect, test } from '@playwright/test';
|
|
3
3
|
import type { Page } from '@playwright/test';
|
|
4
4
|
|
|
5
|
+
import { previewOrigin, resolvePreviewPath } from './preview';
|
|
6
|
+
|
|
5
7
|
const toRgb = (hex: string) => {
|
|
6
8
|
const value = Number.parseInt(hex.slice(1), 16);
|
|
7
9
|
|
|
@@ -13,7 +15,7 @@ const pageBackground = async (page: Page) =>
|
|
|
13
15
|
|
|
14
16
|
test('uses Latte for light mode and Mocha for dark mode', async ({ page }) => {
|
|
15
17
|
await page.emulateMedia({ colorScheme: 'light' });
|
|
16
|
-
await page.goto('/');
|
|
18
|
+
await page.goto(resolvePreviewPath('/'));
|
|
17
19
|
await expect(page.locator('html')).toHaveCSS('background-color', toRgb(flavors.latte.colors.base.hex));
|
|
18
20
|
await expect(page.locator('main h1')).toHaveCSS('color', toRgb(flavors.latte.colors.text.hex));
|
|
19
21
|
|
|
@@ -23,7 +25,7 @@ test('uses Latte for light mode and Mocha for dark mode', async ({ page }) => {
|
|
|
23
25
|
});
|
|
24
26
|
|
|
25
27
|
test('makes every Catppuccin flavor available explicitly', async ({ page }) => {
|
|
26
|
-
await page.goto('/');
|
|
28
|
+
await page.goto(resolvePreviewPath('/'));
|
|
27
29
|
|
|
28
30
|
for (const flavor of ['latte', 'frappe', 'macchiato', 'mocha'] as const) {
|
|
29
31
|
await page.locator('html').evaluate((element, theme) => element.setAttribute('data-theme', theme), flavor);
|
|
@@ -37,11 +39,11 @@ test('serves fonts locally and presents a visible keyboard focus indicator', asy
|
|
|
37
39
|
if (request.resourceType() === 'font') fontRequests.push(request.url());
|
|
38
40
|
});
|
|
39
41
|
|
|
40
|
-
await page.goto('/');
|
|
42
|
+
await page.goto(resolvePreviewPath('/'));
|
|
41
43
|
await page.keyboard.press('Tab');
|
|
42
44
|
|
|
43
45
|
await expect(page.locator('a').first()).toBeFocused();
|
|
44
46
|
await expect(page.locator('a').first()).not.toHaveCSS('outline-style', 'none');
|
|
45
47
|
expect(fontRequests.length).toBeGreaterThan(0);
|
|
46
|
-
expect(fontRequests.every((url) => new URL(url).origin ===
|
|
48
|
+
expect(fontRequests.every((url) => new URL(url).origin === previewOrigin)).toBe(true);
|
|
47
49
|
});
|