@aamini/config 0.0.20 → 0.2.1
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/README.md +111 -20
- package/dist/anti-slop/index.d.mts +6 -0
- package/dist/anti-slop/index.mjs +2414 -0
- package/dist/anti-slop/index.mjs.map +1 -0
- package/dist/merge-BEomHgbw.d.mts +21 -0
- package/dist/merge-DThKwm_6.mjs +193 -0
- package/dist/merge-DThKwm_6.mjs.map +1 -0
- package/dist/playwright.d.mts +8 -3
- package/dist/playwright.mjs +20 -11
- package/dist/playwright.mjs.map +1 -1
- package/dist/root-U5B4x0aj.mjs +15 -0
- package/dist/root-U5B4x0aj.mjs.map +1 -0
- package/dist/tanstack/index.d.mts +45 -0
- package/dist/tanstack/index.mjs +191 -0
- package/dist/tanstack/index.mjs.map +1 -0
- package/dist/vite-plus/index.d.mts +101 -0
- package/dist/vite-plus/index.mjs +2 -0
- package/dist/vitest/browser.d.mts +22 -0
- package/dist/vitest/browser.mjs +84 -0
- package/dist/vitest/browser.mjs.map +1 -0
- package/dist/vitest/db.d.mts +28 -0
- package/dist/vitest/db.mjs +42 -0
- package/dist/vitest/db.mjs.map +1 -0
- package/dist/vitest/index.d.mts +29 -0
- package/dist/vitest/index.mjs +2 -0
- package/dist/vitest/react.d.mts +34 -0
- package/dist/vitest/react.mjs +43 -0
- package/dist/vitest/react.mjs.map +1 -0
- package/dist/vitest/server.d.mts +13 -0
- package/dist/vitest/server.mjs +28 -0
- package/dist/vitest/server.mjs.map +1 -0
- package/dist/vitest-CXdvqyRH.mjs +169 -0
- package/dist/vitest-CXdvqyRH.mjs.map +1 -0
- package/package.json +169 -95
- package/tsconfig/astro.json +9 -0
- package/tsconfig/{tsconfig.strict.json → base.json} +8 -0
- package/tsconfig/react.json +9 -0
- package/dist/setup/styles.d.mts +0 -1
- package/dist/setup/styles.mjs +0 -2
- package/dist/test/browser.d.mts +0 -17
- package/dist/test/browser.mjs +0 -35
- package/dist/test/browser.mjs.map +0 -1
- package/dist/test/db.d.mts +0 -41
- package/dist/test/db.mjs +0 -93
- package/dist/test/db.mjs.map +0 -1
- package/dist/test/server.d.mts +0 -16
- package/dist/test/server.mjs +0 -30
- package/dist/test/server.mjs.map +0 -1
- package/dist/test/visual-page.d.mts +0 -30
- package/dist/test/visual-page.mjs +0 -40
- package/dist/test/visual-page.mjs.map +0 -1
- package/dist/vite.d.mts +0 -73
- package/dist/vite.mjs +0 -211
- package/dist/vite.mjs.map +0 -1
- package/tsconfig/tsconfig.lib.json +0 -19
- package/tsconfig/tsconfig.tanstack.json +0 -17
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { n as ToolkitConfig, r as mergeToolkitConfig, t as ConfigOverride } from "../merge-BEomHgbw.mjs";
|
|
2
|
+
//#region src/vite-plus/fmt.d.ts
|
|
3
|
+
export declare const fmt: {
|
|
4
|
+
singleQuote: true;
|
|
5
|
+
semi: false;
|
|
6
|
+
useTabs: true;
|
|
7
|
+
experimentalTailwindcss: {};
|
|
8
|
+
experimentalSortImports: {};
|
|
9
|
+
printWidth: number;
|
|
10
|
+
experimentalSortPackageJson: boolean;
|
|
11
|
+
proseWrap: "always";
|
|
12
|
+
ignorePatterns: string[];
|
|
13
|
+
overrides: {
|
|
14
|
+
files: string[];
|
|
15
|
+
options: {
|
|
16
|
+
useTabs: false;
|
|
17
|
+
};
|
|
18
|
+
}[];
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/vite-plus/lint.d.ts
|
|
22
|
+
type Framework = 'react' | 'astro' | 'node';
|
|
23
|
+
/** Oxlint preset for one framework. Every app shares the base and anti-slop rules. */
|
|
24
|
+
export declare function lint(framework: Framework): {
|
|
25
|
+
plugins: ("eslint" | "react" | "unicorn" | "typescript" | "oxc" | "import" | "jsdoc" | "jsx-a11y" | "react-perf" | "promise" | "node")[];
|
|
26
|
+
jsPlugins: {
|
|
27
|
+
name: string;
|
|
28
|
+
specifier: string;
|
|
29
|
+
}[];
|
|
30
|
+
categories: {};
|
|
31
|
+
options: {
|
|
32
|
+
maxWarnings: number;
|
|
33
|
+
typeAware: true;
|
|
34
|
+
typeCheck: true;
|
|
35
|
+
};
|
|
36
|
+
rules: {
|
|
37
|
+
'eslint-js/no-restricted-syntax'?: ["error", {
|
|
38
|
+
selector: "JSXAttribute[name.name=\"className\"] TemplateLiteral";
|
|
39
|
+
message: "Do not build className with template literals. Use cn() from \"cn\" instead.";
|
|
40
|
+
}];
|
|
41
|
+
'anti-slop/no-array-filter-map': "error";
|
|
42
|
+
'anti-slop/no-reduce-accumulator-copy': "error";
|
|
43
|
+
'anti-slop/no-chained-type-assertions': "error";
|
|
44
|
+
'anti-slop/no-conditional-empty-object-spread': "error";
|
|
45
|
+
'anti-slop/no-known-value-widening': "error";
|
|
46
|
+
'anti-slop/no-object-parameters': "error";
|
|
47
|
+
'anti-slop/no-reflect-apply': "error";
|
|
48
|
+
'anti-slop/no-reflect-get': "error";
|
|
49
|
+
'anti-slop/no-runtime-typeof': "error";
|
|
50
|
+
'anti-slop/no-shape-in-symbol-names': "error";
|
|
51
|
+
'anti-slop/no-unknown-parameters': "error";
|
|
52
|
+
'anti-slop/no-unknown-returns': "error";
|
|
53
|
+
'anti-slop/no-unknown-type-aliases': "error";
|
|
54
|
+
'anti-slop/no-unsafe-dictionary-type': "error";
|
|
55
|
+
'anti-slop/no-widen-then-assert': "error";
|
|
56
|
+
'anti-slop/require-readable-spacing': "error";
|
|
57
|
+
'anti-slop/require-safety-comment-for-type-assertion': "error";
|
|
58
|
+
'no-empty-pattern': "off";
|
|
59
|
+
'no-console': ["error", {
|
|
60
|
+
allow: string[];
|
|
61
|
+
}];
|
|
62
|
+
'typescript/consistent-type-assertions': ["error", {
|
|
63
|
+
assertionStyle: "never";
|
|
64
|
+
}];
|
|
65
|
+
'oxc/no-accumulating-spread': "error";
|
|
66
|
+
};
|
|
67
|
+
overrides: {
|
|
68
|
+
files: string[];
|
|
69
|
+
rules: {
|
|
70
|
+
'no-console': "off";
|
|
71
|
+
};
|
|
72
|
+
}[];
|
|
73
|
+
settings: {
|
|
74
|
+
jsdoc: {
|
|
75
|
+
ignorePrivate: false;
|
|
76
|
+
ignoreInternal: false;
|
|
77
|
+
ignoreReplacesDocs: true;
|
|
78
|
+
overrideReplacesDocs: true;
|
|
79
|
+
augmentsExtendsReplacesDocs: false;
|
|
80
|
+
implementsReplacesDocs: false;
|
|
81
|
+
exemptDestructuredRootsFromChecks: false;
|
|
82
|
+
tagNamePreference: {};
|
|
83
|
+
};
|
|
84
|
+
'jsx-a11y'?: {
|
|
85
|
+
components: {};
|
|
86
|
+
attributes: {};
|
|
87
|
+
};
|
|
88
|
+
react?: {
|
|
89
|
+
formComponents: never[];
|
|
90
|
+
linkComponents: never[];
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
env: {
|
|
94
|
+
builtin: true;
|
|
95
|
+
};
|
|
96
|
+
globals: {};
|
|
97
|
+
ignorePatterns: string[];
|
|
98
|
+
};
|
|
99
|
+
//#endregion
|
|
100
|
+
export { type ConfigOverride, type Framework, type ToolkitConfig, mergeToolkitConfig };
|
|
101
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Locator } from "vite-plus/test/browser";
|
|
2
|
+
//#region src/vitest/browser.d.ts
|
|
3
|
+
declare module 'vitest/browser' {
|
|
4
|
+
interface BrowserCommands {
|
|
5
|
+
resetScreenshotPointer: () => Promise<void>;
|
|
6
|
+
resizeBrowserViewport: (width: number, height: number) => Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export interface ScreenshotOptions {
|
|
10
|
+
/** Grows the viewport to the full target height before the capture. */
|
|
11
|
+
fullHeight?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Waits for fonts and two animation frames, so layout and paint settle. */
|
|
14
|
+
export declare function waitForPageIdle(): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Compares the target with its baseline at zero tolerance. The capture stops
|
|
17
|
+
* at the viewport unless `fullHeight` is set. Each browser instance pins its
|
|
18
|
+
* own viewport. Mobile baselines get a `-mobile` suffix.
|
|
19
|
+
*/
|
|
20
|
+
export declare function expectScreenshot(target: Locator | HTMLElement, name: string, options?: ScreenshotOptions): Promise<void>;
|
|
21
|
+
//#endregion
|
|
22
|
+
//# sourceMappingURL=browser.d.mts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { expect } from "vite-plus/test";
|
|
2
|
+
import { page, server } from "vite-plus/test/browser";
|
|
3
|
+
//#region src/vitest/browser.ts
|
|
4
|
+
const mobileWidth = 768;
|
|
5
|
+
/** Waits for fonts and two animation frames, so layout and paint settle. */
|
|
6
|
+
async function waitForPageIdle() {
|
|
7
|
+
await document.fonts.ready;
|
|
8
|
+
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
9
|
+
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
10
|
+
}
|
|
11
|
+
async function resizeViewport(width, height) {
|
|
12
|
+
await server.commands.resizeBrowserViewport(width, height);
|
|
13
|
+
await page.viewport(width, height);
|
|
14
|
+
}
|
|
15
|
+
function targetElement(target) {
|
|
16
|
+
return target instanceof HTMLElement ? target : target.element();
|
|
17
|
+
}
|
|
18
|
+
async function growToFullHeight(element) {
|
|
19
|
+
const width = window.innerWidth;
|
|
20
|
+
const minHeight = window.innerHeight;
|
|
21
|
+
let height = 0;
|
|
22
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
23
|
+
await resizeViewport(width, Math.max(minHeight, Math.ceil(element.scrollHeight)));
|
|
24
|
+
await waitForPageIdle();
|
|
25
|
+
const next = Math.ceil(element.scrollHeight);
|
|
26
|
+
if (next === height) break;
|
|
27
|
+
height = next;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function viewportClip(element) {
|
|
31
|
+
const rect = element.getBoundingClientRect();
|
|
32
|
+
if (rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth) return;
|
|
33
|
+
const top = Math.max(rect.top, 0);
|
|
34
|
+
const left = Math.max(rect.left, 0);
|
|
35
|
+
const frame = document.createElement("div");
|
|
36
|
+
Object.assign(frame.style, {
|
|
37
|
+
position: "fixed",
|
|
38
|
+
top: `${top}px`,
|
|
39
|
+
left: `${left}px`,
|
|
40
|
+
width: `${Math.min(rect.right, window.innerWidth) - left}px`,
|
|
41
|
+
height: `${Math.min(rect.bottom, window.innerHeight) - top}px`,
|
|
42
|
+
pointerEvents: "none"
|
|
43
|
+
});
|
|
44
|
+
return document.body.appendChild(frame);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Compares the target with its baseline at zero tolerance. The capture stops
|
|
48
|
+
* at the viewport unless `fullHeight` is set. Each browser instance pins its
|
|
49
|
+
* own viewport. Mobile baselines get a `-mobile` suffix.
|
|
50
|
+
*/
|
|
51
|
+
async function expectScreenshot(target, name, options = {}) {
|
|
52
|
+
const element = targetElement(target);
|
|
53
|
+
const suffix = window.innerWidth < mobileWidth ? "-mobile" : "";
|
|
54
|
+
const viewport = {
|
|
55
|
+
width: window.innerWidth,
|
|
56
|
+
height: window.innerHeight
|
|
57
|
+
};
|
|
58
|
+
let clip;
|
|
59
|
+
try {
|
|
60
|
+
if (options.fullHeight) await growToFullHeight(element);
|
|
61
|
+
await expect.poll(() => Array.from(element.querySelectorAll("img")).every((image) => image.complete)).toBe(true);
|
|
62
|
+
await server.commands.resetScreenshotPointer();
|
|
63
|
+
await waitForPageIdle();
|
|
64
|
+
if (!options.fullHeight) clip = viewportClip(element);
|
|
65
|
+
await expect.element(clip ?? element).toMatchScreenshot(`${name}${suffix}.png`, {
|
|
66
|
+
screenshotOptions: {
|
|
67
|
+
animations: "disabled",
|
|
68
|
+
caret: "hide",
|
|
69
|
+
scale: "css"
|
|
70
|
+
},
|
|
71
|
+
comparatorOptions: {
|
|
72
|
+
allowedMismatchedPixelRatio: 0,
|
|
73
|
+
allowedMismatchedPixels: 0
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
} finally {
|
|
77
|
+
clip?.remove();
|
|
78
|
+
if (options.fullHeight) await resizeViewport(viewport.width, viewport.height);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { expectScreenshot, waitForPageIdle };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=browser.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.mjs","names":[],"sources":["../../src/vitest/browser.ts"],"sourcesContent":["import { expect } from 'vite-plus/test'\nimport { page, server, type Locator } from 'vite-plus/test/browser'\n\ndeclare module 'vitest/browser' {\n\tinterface BrowserCommands {\n\t\tresetScreenshotPointer: () => Promise<void>\n\t\tresizeBrowserViewport: (width: number, height: number) => Promise<void>\n\t}\n}\n\nexport interface ScreenshotOptions {\n\t/** Grows the viewport to the full target height before the capture. */\n\tfullHeight?: boolean\n}\n\n// Matches Tailwind's `md` breakpoint, which the mobile instance falls below.\nconst mobileWidth = 768\n\n/** Waits for fonts and two animation frames, so layout and paint settle. */\nexport async function waitForPageIdle() {\n\tawait document.fonts.ready\n\tawait new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))\n\tawait new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))\n}\n\n// Vitest scales its iframe to fit the browser window. Resize both so tall\n// captures stay at one CSS pixel per image pixel.\nasync function resizeViewport(width: number, height: number) {\n\tawait server.commands.resizeBrowserViewport(width, height)\n\tawait page.viewport(width, height)\n}\n\nfunction targetElement(target: Locator | HTMLElement) {\n\treturn target instanceof HTMLElement ? target : target.element()\n}\n\nasync function growToFullHeight(element: Element) {\n\tconst width = window.innerWidth\n\tconst minHeight = window.innerHeight\n\tlet height = 0\n\n\t// Viewport-relative content can keep growing after each resize.\n\tfor (let attempt = 0; attempt < 5; attempt++) {\n\t\tawait resizeViewport(\n\t\t\twidth,\n\t\t\tMath.max(minHeight, Math.ceil(element.scrollHeight)),\n\t\t)\n\t\tawait waitForPageIdle()\n\n\t\tconst next = Math.ceil(element.scrollHeight)\n\n\t\tif (next === height) break\n\n\t\theight = next\n\t}\n}\n\n// Vitest renders tests in an iframe that paints only the viewport, so a\n// capture past it comes out white. Playwright captures the page pixels under\n// an element, so a transparent fixed frame clips the capture to the viewport.\nfunction viewportClip(element: Element) {\n\tconst rect = element.getBoundingClientRect()\n\n\tif (\n\t\trect.top >= 0 &&\n\t\trect.left >= 0 &&\n\t\trect.bottom <= window.innerHeight &&\n\t\trect.right <= window.innerWidth\n\t) {\n\t\treturn undefined\n\t}\n\n\tconst top = Math.max(rect.top, 0)\n\tconst left = Math.max(rect.left, 0)\n\tconst frame = document.createElement('div')\n\n\tObject.assign(frame.style, {\n\t\tposition: 'fixed',\n\t\ttop: `${top}px`,\n\t\tleft: `${left}px`,\n\t\twidth: `${Math.min(rect.right, window.innerWidth) - left}px`,\n\t\theight: `${Math.min(rect.bottom, window.innerHeight) - top}px`,\n\t\tpointerEvents: 'none',\n\t})\n\n\treturn document.body.appendChild(frame)\n}\n\n/**\n * Compares the target with its baseline at zero tolerance. The capture stops\n * at the viewport unless `fullHeight` is set. Each browser instance pins its\n * own viewport. Mobile baselines get a `-mobile` suffix.\n */\nexport async function expectScreenshot(\n\ttarget: Locator | HTMLElement,\n\tname: string,\n\toptions: ScreenshotOptions = {},\n) {\n\tconst element = targetElement(target)\n\tconst suffix = window.innerWidth < mobileWidth ? '-mobile' : ''\n\tconst viewport = { width: window.innerWidth, height: window.innerHeight }\n\tlet clip: HTMLElement | undefined\n\n\ttry {\n\t\tif (options.fullHeight) await growToFullHeight(element)\n\n\t\tawait expect\n\t\t\t.poll(() =>\n\t\t\t\tArray.from(element.querySelectorAll('img')).every(\n\t\t\t\t\t(image) => image.complete,\n\t\t\t\t),\n\t\t\t)\n\t\t\t.toBe(true)\n\t\tawait server.commands.resetScreenshotPointer()\n\t\tawait waitForPageIdle()\n\n\t\tif (!options.fullHeight) clip = viewportClip(element)\n\n\t\tawait expect\n\t\t\t.element(clip ?? element)\n\t\t\t.toMatchScreenshot(`${name}${suffix}.png`, {\n\t\t\t\tscreenshotOptions: {\n\t\t\t\t\tanimations: 'disabled',\n\t\t\t\t\tcaret: 'hide',\n\t\t\t\t\tscale: 'css',\n\t\t\t\t},\n\t\t\t\tcomparatorOptions: {\n\t\t\t\t\tallowedMismatchedPixelRatio: 0,\n\t\t\t\t\tallowedMismatchedPixels: 0,\n\t\t\t\t},\n\t\t\t})\n\t} finally {\n\t\tclip?.remove()\n\n\t\tif (options.fullHeight)\n\t\t\tawait resizeViewport(viewport.width, viewport.height)\n\t}\n}\n"],"mappings":";;;AAgBA,MAAM,cAAc;;AAGpB,eAAsB,kBAAkB;CACvC,MAAM,SAAS,MAAM;CACrB,MAAM,IAAI,SAAe,YAAY,4BAA4B,QAAQ,CAAC,CAAC;CAC3E,MAAM,IAAI,SAAe,YAAY,4BAA4B,QAAQ,CAAC,CAAC;AAC5E;AAIA,eAAe,eAAe,OAAe,QAAgB;CAC5D,MAAM,OAAO,SAAS,sBAAsB,OAAO,MAAM;CACzD,MAAM,KAAK,SAAS,OAAO,MAAM;AAClC;AAEA,SAAS,cAAc,QAA+B;CACrD,OAAO,kBAAkB,cAAc,SAAS,OAAO,QAAQ;AAChE;AAEA,eAAe,iBAAiB,SAAkB;CACjD,MAAM,QAAQ,OAAO;CACrB,MAAM,YAAY,OAAO;CACzB,IAAI,SAAS;CAGb,KAAK,IAAI,UAAU,GAAG,UAAU,GAAG,WAAW;EAC7C,MAAM,eACL,OACA,KAAK,IAAI,WAAW,KAAK,KAAK,QAAQ,YAAY,CAAC,CACpD;EACA,MAAM,gBAAgB;EAEtB,MAAM,OAAO,KAAK,KAAK,QAAQ,YAAY;EAE3C,IAAI,SAAS,QAAQ;EAErB,SAAS;CACV;AACD;AAKA,SAAS,aAAa,SAAkB;CACvC,MAAM,OAAO,QAAQ,sBAAsB;CAE3C,IACC,KAAK,OAAO,KACZ,KAAK,QAAQ,KACb,KAAK,UAAU,OAAO,eACtB,KAAK,SAAS,OAAO,YAErB;CAGD,MAAM,MAAM,KAAK,IAAI,KAAK,KAAK,CAAC;CAChC,MAAM,OAAO,KAAK,IAAI,KAAK,MAAM,CAAC;CAClC,MAAM,QAAQ,SAAS,cAAc,KAAK;CAE1C,OAAO,OAAO,MAAM,OAAO;EAC1B,UAAU;EACV,KAAK,GAAG,IAAI;EACZ,MAAM,GAAG,KAAK;EACd,OAAO,GAAG,KAAK,IAAI,KAAK,OAAO,OAAO,UAAU,IAAI,KAAK;EACzD,QAAQ,GAAG,KAAK,IAAI,KAAK,QAAQ,OAAO,WAAW,IAAI,IAAI;EAC3D,eAAe;CAChB,CAAC;CAED,OAAO,SAAS,KAAK,YAAY,KAAK;AACvC;;;;;;AAOA,eAAsB,iBACrB,QACA,MACA,UAA6B,CAAC,GAC7B;CACD,MAAM,UAAU,cAAc,MAAM;CACpC,MAAM,SAAS,OAAO,aAAa,cAAc,YAAY;CAC7D,MAAM,WAAW;EAAE,OAAO,OAAO;EAAY,QAAQ,OAAO;CAAY;CACxE,IAAI;CAEJ,IAAI;EACH,IAAI,QAAQ,YAAY,MAAM,iBAAiB,OAAO;EAEtD,MAAM,OACJ,WACA,MAAM,KAAK,QAAQ,iBAAiB,KAAK,CAAC,CAAC,CAAC,OAC1C,UAAU,MAAM,QAClB,CACD,CAAC,CACA,KAAK,IAAI;EACX,MAAM,OAAO,SAAS,uBAAuB;EAC7C,MAAM,gBAAgB;EAEtB,IAAI,CAAC,QAAQ,YAAY,OAAO,aAAa,OAAO;EAEpD,MAAM,OACJ,QAAQ,QAAQ,OAAO,CAAC,CACxB,kBAAkB,GAAG,OAAO,OAAO,OAAO;GAC1C,mBAAmB;IAClB,YAAY;IACZ,OAAO;IACP,OAAO;GACR;GACA,mBAAmB;IAClB,6BAA6B;IAC7B,yBAAyB;GAC1B;EACD,CAAC;CACH,UAAU;EACT,MAAM,OAAO;EAEb,IAAI,QAAQ,YACX,MAAM,eAAe,SAAS,OAAO,SAAS,MAAM;CACtD;AACD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
2
|
+
import { Pool } from "pg";
|
|
3
|
+
//#region src/vitest/db.d.ts
|
|
4
|
+
export type TestDatabase = NodePgDatabase & {
|
|
5
|
+
$client: Pool;
|
|
6
|
+
};
|
|
7
|
+
export type Seed = (db: TestDatabase) => Promise<void> | void;
|
|
8
|
+
export interface DbTestOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Drizzle migrations. A relative path resolves against the app root that
|
|
11
|
+
* the TanStack `defineToolkit()` provides, else against the current
|
|
12
|
+
* working directory. Default: `src/db/migrations`.
|
|
13
|
+
*/
|
|
14
|
+
migrationsFolder?: string;
|
|
15
|
+
/** Postgres extensions to create before the migrations run. */
|
|
16
|
+
extensions?: readonly string[];
|
|
17
|
+
/** Default: `postgres:17`. */
|
|
18
|
+
image?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns `initDb(seed?)`. Each call gives a test with a `db` fixture: one
|
|
22
|
+
* migrated Postgres container per test file, shared by its tests.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createDbTest(options?: DbTestOptions): (seed?: Seed) => import("vitest").TestAPI<{
|
|
25
|
+
db: TestDatabase;
|
|
26
|
+
}>;
|
|
27
|
+
//#endregion
|
|
28
|
+
//# sourceMappingURL=db.d.mts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { n as rootKey, t as resolveFromRoot } from "../root-U5B4x0aj.mjs";
|
|
2
|
+
import { inject, test } from "vite-plus/test";
|
|
3
|
+
import { PostgreSqlContainer } from "@testcontainers/postgresql";
|
|
4
|
+
import { sql } from "drizzle-orm";
|
|
5
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
6
|
+
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
7
|
+
import { Pool } from "pg";
|
|
8
|
+
//#region src/vitest/db.ts
|
|
9
|
+
/**
|
|
10
|
+
* Returns `initDb(seed?)`. Each call gives a test with a `db` fixture: one
|
|
11
|
+
* migrated Postgres container per test file, shared by its tests.
|
|
12
|
+
*/
|
|
13
|
+
function createDbTest(options = {}) {
|
|
14
|
+
const migrationsPath = options.migrationsFolder ?? "src/db/migrations";
|
|
15
|
+
const image = options.image ?? "postgres:17";
|
|
16
|
+
return function initDb(seed) {
|
|
17
|
+
return test.extend({ db: [async ({}, use) => {
|
|
18
|
+
const container = await new PostgreSqlContainer(image).start();
|
|
19
|
+
try {
|
|
20
|
+
const client = new Pool({ connectionString: container.getConnectionUri() });
|
|
21
|
+
client.on("error", (error) => {
|
|
22
|
+
console.error("test pg pool error:", error.message);
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
const db = drizzle({ client });
|
|
26
|
+
for (const extension of options.extensions ?? []) await db.execute(sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)}`);
|
|
27
|
+
await migrate(db, { migrationsFolder: resolveFromRoot(inject(rootKey), migrationsPath) });
|
|
28
|
+
await seed?.(db);
|
|
29
|
+
await use(db);
|
|
30
|
+
} finally {
|
|
31
|
+
await client.end();
|
|
32
|
+
}
|
|
33
|
+
} finally {
|
|
34
|
+
await container.stop();
|
|
35
|
+
}
|
|
36
|
+
}, { scope: "file" }] });
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { createDbTest };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=db.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.mjs","names":["baseTest"],"sources":["../../src/vitest/db.ts"],"sourcesContent":["import { PostgreSqlContainer } from '@testcontainers/postgresql'\nimport { sql } from 'drizzle-orm'\nimport { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres'\nimport { migrate } from 'drizzle-orm/node-postgres/migrator'\nimport { Pool } from 'pg'\nimport { test as baseTest, inject } from 'vite-plus/test'\n\nimport { resolveFromRoot, rootKey } from './root.ts'\n\nexport type TestDatabase = NodePgDatabase & { $client: Pool }\n\nexport type Seed = (db: TestDatabase) => Promise<void> | void\n\nexport interface DbTestOptions {\n\t/**\n\t * Drizzle migrations. A relative path resolves against the app root that\n\t * the TanStack `defineToolkit()` provides, else against the current\n\t * working directory. Default: `src/db/migrations`.\n\t */\n\tmigrationsFolder?: string\n\t/** Postgres extensions to create before the migrations run. */\n\textensions?: readonly string[]\n\t/** Default: `postgres:17`. */\n\timage?: string\n}\n\n/**\n * Returns `initDb(seed?)`. Each call gives a test with a `db` fixture: one\n * migrated Postgres container per test file, shared by its tests.\n */\nexport function createDbTest(options: DbTestOptions = {}) {\n\tconst migrationsPath = options.migrationsFolder ?? 'src/db/migrations'\n\tconst image = options.image ?? 'postgres:17'\n\n\treturn function initDb(seed?: Seed) {\n\t\treturn baseTest.extend<{ db: TestDatabase }>({\n\t\t\tdb: [\n\t\t\t\tasync ({}, use) => {\n\t\t\t\t\tconst container = await new PostgreSqlContainer(image).start()\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst client = new Pool({\n\t\t\t\t\t\t\tconnectionString: container.getConnectionUri(),\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\t// Postgres ends pooled connections when the container stops.\n\t\t\t\t\t\t// Without a listener those errors become uncaught exceptions\n\t\t\t\t\t\t// that fail the whole run despite passing tests.\n\t\t\t\t\t\tclient.on('error', (error) => {\n\t\t\t\t\t\t\tconsole.error('test pg pool error:', error.message)\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst db = drizzle({ client })\n\n\t\t\t\t\t\t\tfor (const extension of options.extensions ?? []) {\n\t\t\t\t\t\t\t\tawait db.execute(\n\t\t\t\t\t\t\t\t\tsql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)}`,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tawait migrate(db, {\n\t\t\t\t\t\t\t\tmigrationsFolder: resolveFromRoot(\n\t\t\t\t\t\t\t\t\tinject(rootKey),\n\t\t\t\t\t\t\t\t\tmigrationsPath,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tawait seed?.(db)\n\t\t\t\t\t\t\tawait use(db)\n\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\tawait client.end()\n\t\t\t\t\t\t}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tawait container.stop()\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ scope: 'file' },\n\t\t\t],\n\t\t})\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AA8BA,SAAgB,aAAa,UAAyB,CAAC,GAAG;CACzD,MAAM,iBAAiB,QAAQ,oBAAoB;CACnD,MAAM,QAAQ,QAAQ,SAAS;CAE/B,OAAO,SAAS,OAAO,MAAa;EACnC,OAAOA,KAAS,OAA6B,EAC5C,IAAI,CACH,OAAO,IAAI,QAAQ;GAClB,MAAM,YAAY,MAAM,IAAI,oBAAoB,KAAK,CAAC,CAAC,MAAM;GAE7D,IAAI;IACH,MAAM,SAAS,IAAI,KAAK,EACvB,kBAAkB,UAAU,iBAAiB,EAC9C,CAAC;IAKD,OAAO,GAAG,UAAU,UAAU;KAC7B,QAAQ,MAAM,uBAAuB,MAAM,OAAO;IACnD,CAAC;IAED,IAAI;KACH,MAAM,KAAK,QAAQ,EAAE,OAAO,CAAC;KAE7B,KAAK,MAAM,aAAa,QAAQ,cAAc,CAAC,GAC9C,MAAM,GAAG,QACR,GAAG,kCAAkC,IAAI,IAAI,SAAS,GACvD;KAGD,MAAM,QAAQ,IAAI,EACjB,kBAAkB,gBACjB,OAAO,OAAO,GACd,cACD,EACD,CAAC;KACD,MAAM,OAAO,EAAE;KACf,MAAM,IAAI,EAAE;IACb,UAAU;KACT,MAAM,OAAO,IAAI;IAClB;GACD,UAAU;IACT,MAAM,UAAU,KAAK;GACtB;EACD,GACA,EAAE,OAAO,OAAO,CACjB,EACD,CAAC;CACF;AACD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Plugin, TestProjectInlineConfiguration } from "vite-plus";
|
|
2
|
+
//#region src/vitest/index.d.ts
|
|
3
|
+
type ProjectTest = NonNullable<TestProjectInlineConfiguration['test']>;
|
|
4
|
+
/** Vitest `test` options for one project. Arrays replace the toolkit values. */
|
|
5
|
+
export type ProjectOptions = ProjectTest;
|
|
6
|
+
export interface TestProject {
|
|
7
|
+
extends: true;
|
|
8
|
+
plugins?: Plugin[];
|
|
9
|
+
test: ProjectOptions;
|
|
10
|
+
}
|
|
11
|
+
export declare const viewports: {
|
|
12
|
+
desktop: {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
mobile: {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export declare function unitProject(options?: ProjectOptions): TestProject;
|
|
22
|
+
export declare function serverProject(options?: ProjectOptions): TestProject;
|
|
23
|
+
/**
|
|
24
|
+
* The browser project: Chromium through Playwright, one instance per
|
|
25
|
+
* viewport. The instance name is the baseline suffix for mobile captures.
|
|
26
|
+
*/
|
|
27
|
+
export declare function browserProject(options?: ProjectOptions): Promise<TestProject>;
|
|
28
|
+
//#endregion
|
|
29
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ScreenshotOptions } from "./browser.mjs";
|
|
2
|
+
import { Locator } from "vite-plus/test/browser";
|
|
3
|
+
import { ComponentType, ReactNode } from "react";
|
|
4
|
+
import { RenderResult } from "vitest-browser-react";
|
|
5
|
+
//#region src/vitest/react.d.ts
|
|
6
|
+
export interface PageWrapperProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
/** The URL path that the page renders at. */
|
|
9
|
+
path: string;
|
|
10
|
+
/** The page component under test. */
|
|
11
|
+
component: ComponentType;
|
|
12
|
+
}
|
|
13
|
+
export interface RenderPageOptions {
|
|
14
|
+
/** The URL path that the page renders at. Default: `/`. */
|
|
15
|
+
path?: string;
|
|
16
|
+
/** App providers around the page, for example a router and a query client. */
|
|
17
|
+
wrapper?: ComponentType<PageWrapperProps>;
|
|
18
|
+
/** Runs after the reset and before the render, for example to set a theme. */
|
|
19
|
+
setup?: () => Promise<void> | void;
|
|
20
|
+
/** An element that is visible once the page is ready to capture. */
|
|
21
|
+
waitFor?: (screen: RenderResult) => Locator;
|
|
22
|
+
}
|
|
23
|
+
export interface RenderedPage {
|
|
24
|
+
screen: RenderResult;
|
|
25
|
+
/** Compares the page with its baseline. */
|
|
26
|
+
expectScreenshot: (name: string, options?: ScreenshotOptions) => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Renders a page component full width in a clean document at the browser
|
|
30
|
+
* instance viewport.
|
|
31
|
+
*/
|
|
32
|
+
export declare function renderPage(component: ComponentType, options?: RenderPageOptions): Promise<RenderedPage>;
|
|
33
|
+
//#endregion
|
|
34
|
+
//# sourceMappingURL=react.d.mts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { expectScreenshot, waitForPageIdle } from "./browser.mjs";
|
|
2
|
+
import { expect } from "vite-plus/test";
|
|
3
|
+
import { page, server } from "vite-plus/test/browser";
|
|
4
|
+
import { createElement } from "react";
|
|
5
|
+
import { render } from "vitest-browser-react";
|
|
6
|
+
//#region src/vitest/react.tsx
|
|
7
|
+
function PassThrough({ children }) {
|
|
8
|
+
return children;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Renders a page component full width in a clean document at the browser
|
|
12
|
+
* instance viewport.
|
|
13
|
+
*/
|
|
14
|
+
async function renderPage(component, options = {}) {
|
|
15
|
+
const path = options.path ?? "/";
|
|
16
|
+
const Wrapper = options.wrapper ?? PassThrough;
|
|
17
|
+
localStorage.clear();
|
|
18
|
+
document.body.replaceChildren();
|
|
19
|
+
await options.setup?.();
|
|
20
|
+
await server.commands.resizeBrowserViewport(window.innerWidth, window.innerHeight);
|
|
21
|
+
await page.viewport(window.innerWidth, window.innerHeight);
|
|
22
|
+
const container = document.body.appendChild(document.createElement("div"));
|
|
23
|
+
container.style.width = "100vw";
|
|
24
|
+
container.style.minHeight = "100vh";
|
|
25
|
+
const screen = await render(createElement(component), {
|
|
26
|
+
container,
|
|
27
|
+
wrapper: ({ children }) => createElement(Wrapper, {
|
|
28
|
+
children,
|
|
29
|
+
path,
|
|
30
|
+
component
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
if (options.waitFor) await expect.element(options.waitFor(screen), { timeout: 3e3 }).toBeVisible();
|
|
34
|
+
await waitForPageIdle();
|
|
35
|
+
return {
|
|
36
|
+
screen,
|
|
37
|
+
expectScreenshot: (name, screenshotOptions) => expectScreenshot(screen.locator, name, screenshotOptions)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { renderPage };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=react.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.mjs","names":[],"sources":["../../src/vitest/react.tsx"],"sourcesContent":["import { createElement, type ComponentType, type ReactNode } from 'react'\nimport { expect } from 'vite-plus/test'\nimport { page, server, type Locator } from 'vite-plus/test/browser'\nimport { render, type RenderResult } from 'vitest-browser-react'\n\nimport {\n\texpectScreenshot,\n\twaitForPageIdle,\n\ttype ScreenshotOptions,\n} from './browser.ts'\n\nexport interface PageWrapperProps {\n\tchildren: ReactNode\n\t/** The URL path that the page renders at. */\n\tpath: string\n\t/** The page component under test. */\n\tcomponent: ComponentType\n}\n\nexport interface RenderPageOptions {\n\t/** The URL path that the page renders at. Default: `/`. */\n\tpath?: string\n\t/** App providers around the page, for example a router and a query client. */\n\twrapper?: ComponentType<PageWrapperProps>\n\t/** Runs after the reset and before the render, for example to set a theme. */\n\tsetup?: () => Promise<void> | void\n\t/** An element that is visible once the page is ready to capture. */\n\twaitFor?: (screen: RenderResult) => Locator\n}\n\nexport interface RenderedPage {\n\tscreen: RenderResult\n\t/** Compares the page with its baseline. */\n\texpectScreenshot: (name: string, options?: ScreenshotOptions) => Promise<void>\n}\n\nfunction PassThrough({ children }: PageWrapperProps) {\n\treturn children\n}\n\n/**\n * Renders a page component full width in a clean document at the browser\n * instance viewport.\n */\nexport async function renderPage(\n\tcomponent: ComponentType,\n\toptions: RenderPageOptions = {},\n): Promise<RenderedPage> {\n\tconst path = options.path ?? '/'\n\tconst Wrapper = options.wrapper ?? PassThrough\n\n\tlocalStorage.clear()\n\tdocument.body.replaceChildren()\n\tawait options.setup?.()\n\t// Syncs the Vitest iframe with the instance viewport before the render.\n\tawait server.commands.resizeBrowserViewport(\n\t\twindow.innerWidth,\n\t\twindow.innerHeight,\n\t)\n\tawait page.viewport(window.innerWidth, window.innerHeight)\n\n\tconst container = document.body.appendChild(document.createElement('div'))\n\tcontainer.style.width = '100vw'\n\tcontainer.style.minHeight = '100vh'\n\n\tconst screen = await render(createElement(component), {\n\t\tcontainer,\n\t\twrapper: ({ children }: { children: ReactNode }) =>\n\t\t\tcreateElement(Wrapper, { children, path, component }),\n\t})\n\n\tif (options.waitFor) {\n\t\t// A cold route's first commit can land past the 1s poll default.\n\t\tawait expect\n\t\t\t.element(options.waitFor(screen), { timeout: 3_000 })\n\t\t\t.toBeVisible()\n\t}\n\n\tawait waitForPageIdle()\n\n\treturn {\n\t\tscreen,\n\t\texpectScreenshot: (name, screenshotOptions) =>\n\t\t\texpectScreenshot(screen.locator, name, screenshotOptions),\n\t}\n}\n"],"mappings":";;;;;;AAoCA,SAAS,YAAY,EAAE,YAA8B;CACpD,OAAO;AACR;;;;;AAMA,eAAsB,WACrB,WACA,UAA6B,CAAC,GACN;CACxB,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,UAAU,QAAQ,WAAW;CAEnC,aAAa,MAAM;CACnB,SAAS,KAAK,gBAAgB;CAC9B,MAAM,QAAQ,QAAQ;CAEtB,MAAM,OAAO,SAAS,sBACrB,OAAO,YACP,OAAO,WACR;CACA,MAAM,KAAK,SAAS,OAAO,YAAY,OAAO,WAAW;CAEzD,MAAM,YAAY,SAAS,KAAK,YAAY,SAAS,cAAc,KAAK,CAAC;CACzE,UAAU,MAAM,QAAQ;CACxB,UAAU,MAAM,YAAY;CAE5B,MAAM,SAAS,MAAM,OAAO,cAAc,SAAS,GAAG;EACrD;EACA,UAAU,EAAE,eACX,cAAc,SAAS;GAAE;GAAU;GAAM;EAAU,CAAC;CACtD,CAAC;CAED,IAAI,QAAQ,SAEX,MAAM,OACJ,QAAQ,QAAQ,QAAQ,MAAM,GAAG,EAAE,SAAS,IAAM,CAAC,CAAC,CACpD,YAAY;CAGf,MAAM,gBAAgB;CAEtB,OAAO;EACN;EACA,mBAAmB,MAAM,sBACxB,iBAAiB,OAAO,SAAS,MAAM,iBAAiB;CAC1D;AACD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SetupServer } from "msw/node";
|
|
2
|
+
import { RequestHandler } from "msw";
|
|
3
|
+
//#region src/vitest/server.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A test with an MSW server fixture for one worker. Unhandled requests pass
|
|
6
|
+
* through. Handlers that a test adds with `server.use()` reset after it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createServerTest(handlers?: readonly RequestHandler[]): import("vitest").TestAPI<{
|
|
9
|
+
server: SetupServer;
|
|
10
|
+
_resetHandlers: void;
|
|
11
|
+
}>;
|
|
12
|
+
//#endregion
|
|
13
|
+
//# sourceMappingURL=server.d.mts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { test } from "vite-plus/test";
|
|
2
|
+
import { setupServer } from "msw/node";
|
|
3
|
+
//#region src/vitest/server.ts
|
|
4
|
+
/**
|
|
5
|
+
* A test with an MSW server fixture for one worker. Unhandled requests pass
|
|
6
|
+
* through. Handlers that a test adds with `server.use()` reset after it.
|
|
7
|
+
*/
|
|
8
|
+
function createServerTest(handlers = []) {
|
|
9
|
+
const server = setupServer(...handlers);
|
|
10
|
+
return test.extend({
|
|
11
|
+
server: [async ({}, use) => {
|
|
12
|
+
server.listen({ onUnhandledRequest: "bypass" });
|
|
13
|
+
await use(server);
|
|
14
|
+
server.close();
|
|
15
|
+
}, {
|
|
16
|
+
auto: true,
|
|
17
|
+
scope: "worker"
|
|
18
|
+
}],
|
|
19
|
+
_resetHandlers: [async ({ server }, use) => {
|
|
20
|
+
await use();
|
|
21
|
+
server.resetHandlers();
|
|
22
|
+
}, { auto: true }]
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { createServerTest };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.mjs","names":["baseTest"],"sources":["../../src/vitest/server.ts"],"sourcesContent":["import type { RequestHandler } from 'msw'\nimport { setupServer, type SetupServer } from 'msw/node'\nimport { test as baseTest } from 'vite-plus/test'\n\n/**\n * A test with an MSW server fixture for one worker. Unhandled requests pass\n * through. Handlers that a test adds with `server.use()` reset after it.\n */\nexport function createServerTest(handlers: readonly RequestHandler[] = []) {\n\tconst server = setupServer(...handlers)\n\n\treturn baseTest.extend<{ server: SetupServer; _resetHandlers: void }>({\n\t\tserver: [\n\t\t\tasync ({}, use) => {\n\t\t\t\tserver.listen({ onUnhandledRequest: 'bypass' })\n\t\t\t\tawait use(server)\n\t\t\t\tserver.close()\n\t\t\t},\n\t\t\t{ auto: true, scope: 'worker' },\n\t\t],\n\t\t_resetHandlers: [\n\t\t\tasync ({ server }, use) => {\n\t\t\t\tawait use()\n\t\t\t\tserver.resetHandlers()\n\t\t\t},\n\t\t\t{ auto: true },\n\t\t],\n\t})\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,iBAAiB,WAAsC,CAAC,GAAG;CAC1E,MAAM,SAAS,YAAY,GAAG,QAAQ;CAEtC,OAAOA,KAAS,OAAsD;EACrE,QAAQ,CACP,OAAO,IAAI,QAAQ;GAClB,OAAO,OAAO,EAAE,oBAAoB,SAAS,CAAC;GAC9C,MAAM,IAAI,MAAM;GAChB,OAAO,MAAM;EACd,GACA;GAAE,MAAM;GAAM,OAAO;EAAS,CAC/B;EACA,gBAAgB,CACf,OAAO,EAAE,UAAU,QAAQ;GAC1B,MAAM,IAAI;GACV,OAAO,cAAc;EACtB,GACA,EAAE,MAAM,KAAK,CACd;CACD,CAAC;AACF"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
//#region src/options.ts
|
|
4
|
+
function isPlainObject(value) {
|
|
5
|
+
return value !== null && value instanceof Object && Object.getPrototypeOf(value) === Object.prototype;
|
|
6
|
+
}
|
|
7
|
+
/** Deep merges toolkit options. Arrays and other values replace. */
|
|
8
|
+
function mergeOptions(defaults, override) {
|
|
9
|
+
if (override === void 0) return defaults;
|
|
10
|
+
const merged = {
|
|
11
|
+
...defaults,
|
|
12
|
+
...override
|
|
13
|
+
};
|
|
14
|
+
for (const key in override) {
|
|
15
|
+
const preset = defaults[key];
|
|
16
|
+
const value = override[key];
|
|
17
|
+
if (isPlainObject(preset) && isPlainObject(value)) merged[key] = mergeOptions(preset, value);
|
|
18
|
+
}
|
|
19
|
+
return merged;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/peers.ts
|
|
23
|
+
/** Resolves from this package, so pnpm sees each declared peer. */
|
|
24
|
+
function canResolve(specifier) {
|
|
25
|
+
try {
|
|
26
|
+
import.meta.resolve(specifier);
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function missingPeer(entry, peer, cause) {
|
|
33
|
+
return new Error(`${entry} needs ${peer.name}. Install it with: pnpm add -D ${peer.name}`, { cause });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Imports a peer module. If the app did not install the peer, the error names
|
|
37
|
+
* the package and the command that installs it.
|
|
38
|
+
*/
|
|
39
|
+
async function importPeer(entry, peer, load) {
|
|
40
|
+
try {
|
|
41
|
+
return await load();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error instanceof Error && !canResolve(peer.specifier)) throw missingPeer(entry, peer, error);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Checks a peer that another tool loads, for example a Babel or Oxlint plugin. */
|
|
48
|
+
function requirePeer(entry, peer) {
|
|
49
|
+
if (!canResolve(peer.specifier)) throw missingPeer(entry, peer);
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/vitest/msw-worker.ts
|
|
53
|
+
const workerModule = "msw/mockServiceWorker.js";
|
|
54
|
+
/**
|
|
55
|
+
* Serves the MSW service worker script to browser tests from the `msw`
|
|
56
|
+
* package. Vitest 4 served this path itself; Vitest 5 does not. Apps keep the
|
|
57
|
+
* script out of `public/`, so production never ships it.
|
|
58
|
+
*/
|
|
59
|
+
function mswWorker() {
|
|
60
|
+
const workerPath = fileURLToPath(import.meta.resolve(workerModule));
|
|
61
|
+
return {
|
|
62
|
+
name: "aamini:msw-worker",
|
|
63
|
+
configureServer(server) {
|
|
64
|
+
server.middlewares.use("/mockServiceWorker.js", (_request, response) => {
|
|
65
|
+
readFile(workerPath).then((script) => {
|
|
66
|
+
response.setHeader("Content-Type", "text/javascript");
|
|
67
|
+
response.end(script);
|
|
68
|
+
}).catch((error) => {
|
|
69
|
+
server.config.logger.error(`Failed to serve mockServiceWorker.js: ${error instanceof Error ? error.message : String(error)}`);
|
|
70
|
+
response.statusCode = 500;
|
|
71
|
+
response.end();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/vitest/index.ts
|
|
79
|
+
const viewports = {
|
|
80
|
+
desktop: {
|
|
81
|
+
width: 1280,
|
|
82
|
+
height: 720
|
|
83
|
+
},
|
|
84
|
+
mobile: {
|
|
85
|
+
width: 375,
|
|
86
|
+
height: 812
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function project(defaults, options) {
|
|
90
|
+
return {
|
|
91
|
+
extends: true,
|
|
92
|
+
test: mergeOptions(defaults, options)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function unitProject(options) {
|
|
96
|
+
return project({
|
|
97
|
+
name: "unit",
|
|
98
|
+
include: [
|
|
99
|
+
"src/**/*.unit.test.ts",
|
|
100
|
+
"src/**/*.test.unit.ts",
|
|
101
|
+
"tests/**/*.unit.test.ts"
|
|
102
|
+
]
|
|
103
|
+
}, options);
|
|
104
|
+
}
|
|
105
|
+
function serverProject(options) {
|
|
106
|
+
return project({
|
|
107
|
+
name: "server",
|
|
108
|
+
include: ["src/**/*.server.test.ts", "src/**/server.test.ts"],
|
|
109
|
+
testTimeout: 3e4,
|
|
110
|
+
fileParallelism: false
|
|
111
|
+
}, options);
|
|
112
|
+
}
|
|
113
|
+
const resetScreenshotPointer = async ({ page }) => {
|
|
114
|
+
await page.mouse.move(0, 0);
|
|
115
|
+
};
|
|
116
|
+
const resizeBrowserViewport = async ({ page }, width, height) => {
|
|
117
|
+
await page.setViewportSize({
|
|
118
|
+
width,
|
|
119
|
+
height
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The browser project: Chromium through Playwright, one instance per
|
|
124
|
+
* viewport. The instance name is the baseline suffix for mobile captures.
|
|
125
|
+
*/
|
|
126
|
+
async function browserProject(options) {
|
|
127
|
+
const { playwright } = await importPeer("@aamini/config/vitest", {
|
|
128
|
+
name: "@vitest/browser-playwright",
|
|
129
|
+
specifier: "@vitest/browser-playwright"
|
|
130
|
+
}, () => import("vite-plus/test/browser-playwright"));
|
|
131
|
+
const browser = project({
|
|
132
|
+
name: "browser",
|
|
133
|
+
sequence: { groupOrder: 1 },
|
|
134
|
+
include: ["src/**/*.test.tsx", "tests/**/*.test.tsx"],
|
|
135
|
+
fileParallelism: false,
|
|
136
|
+
retry: 0,
|
|
137
|
+
testTimeout: 15e3,
|
|
138
|
+
browser: {
|
|
139
|
+
enabled: true,
|
|
140
|
+
headless: true,
|
|
141
|
+
commands: {
|
|
142
|
+
resetScreenshotPointer,
|
|
143
|
+
resizeBrowserViewport
|
|
144
|
+
},
|
|
145
|
+
instances: [{
|
|
146
|
+
browser: "chromium",
|
|
147
|
+
name: "desktop",
|
|
148
|
+
viewport: viewports.desktop
|
|
149
|
+
}, {
|
|
150
|
+
browser: "chromium",
|
|
151
|
+
name: "mobile",
|
|
152
|
+
viewport: viewports.mobile
|
|
153
|
+
}],
|
|
154
|
+
provider: playwright({
|
|
155
|
+
launchOptions: {
|
|
156
|
+
channel: "chromium",
|
|
157
|
+
args: ["--disable-lcd-text", "--headless=new"]
|
|
158
|
+
},
|
|
159
|
+
actionTimeout: 3e3
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
}, options);
|
|
163
|
+
if (canResolve("msw/mockServiceWorker.js")) browser.plugins = [mswWorker()];
|
|
164
|
+
return browser;
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
export { canResolve as a, mergeOptions as c, viewports as i, serverProject as n, importPeer as o, unitProject as r, requirePeer as s, browserProject as t };
|
|
168
|
+
|
|
169
|
+
//# sourceMappingURL=vitest-CXdvqyRH.mjs.map
|