@shivaedev/quality 0.26.0 → 0.27.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.27.0 - 2026-10-18
4
+
5
+ ### Added
6
+
7
+ - `testProjects()` selects test-story's DOM environment for DOM tests and specs, preserving Node host constructors alongside the happy-dom window without a project remap.
8
+ - `testProjects({ coverage: true })` enables V8 coverage for selected projects, including unvisited source files and excluding tests and fixtures. The exported `TestProjectsOptions` type also accepts native Vitest coverage options through `coverage` to customize the report.
9
+
10
+ ### Breaking
11
+
12
+ - DOM projects use test-story's environment instead of plain happy-dom. Install `@shivaedev/test-story` alongside `happy-dom`, and target the DOM window when stubbing browser APIs. Storage fixtures should bind global storage reads to `window.localStorage`; the Node host and DOM window keep separate constructors.
13
+
3
14
  ## 0.26.0 - 2026-10-18
4
15
 
5
16
  ### Added
package/README.md CHANGED
@@ -258,7 +258,7 @@ The TypeScript base leaves `types`, `jsx`, `paths`, `include` and output directo
258
258
  }
259
259
  ```
260
260
 
261
- For Vitest, install `vitest` and `happy-dom` when the repository has DOM tests, then use:
261
+ For Vitest, install `vitest`, and add `@shivaedev/test-story` and `happy-dom` when the repository has DOM tests, then use:
262
262
 
263
263
  ```ts
264
264
  import { testProjects } from "@shivaedev/quality/testProjects.ts";
@@ -267,7 +267,9 @@ import { defineConfig } from "vitest/config";
267
267
  export default defineConfig({ test: testProjects() });
268
268
  ```
269
269
 
270
- `testProjects` runs unit tests and specs in Node and `.dom` tests and specs in happy-dom by default. Each test gets 30 seconds instead of Vitest's 5, because tests that start servers, browsers or CLIs take seconds on a busy CI runner. `.slow` tests run only when `--project slow` asks for them. Type tests are excluded from Vitest and belong in the compiler's input. The helper takes no options.
270
+ `testProjects` runs unit tests and specs in Node and `.dom` tests and specs in test-story's DOM environment by default. The DOM environment keeps Node's host constructors separate from the happy-dom window, so DOM specs need no project remap. Each test gets 30 seconds instead of Vitest's 5, because tests that start servers, browsers or CLIs take seconds on a busy CI runner. `.slow` tests run only when `--project slow` asks for them. Type tests are excluded from Vitest and belong in the compiler's input.
271
+
272
+ Coverage is opt-in: `testProjects({ coverage: true })` enables Vitest's V8 provider across the selected projects. Install `@vitest/coverage-v8` at the same version as Vitest. The report includes unvisited `src/**/*.ts` and `src/**/*.tsx` files and excludes declarations, tests, specs and `test-support`. To customize it, pass native Vitest coverage options, for example `testProjects({ coverage: { reporter: ["lcov", "text-summary"] } })`. No separate coverage config is needed.
271
273
 
272
274
  Outside CI, `testProjects` sets `silent: "passed-only"` so local runs show logs only from failing tests. When `CI` is set to a nonempty value, it sets `silent: false` so CI keeps all test logs. It leaves `reporters` unset so Vitest can auto-detect its agent reporter. If a repository adds a reporter, spread `configDefaults.reporters` instead of replacing the defaults:
273
275
 
@@ -0,0 +1,4 @@
1
+ import type { Environment } from "vitest/runtime";
2
+ declare const environment: Environment;
3
+ export default environment;
4
+ //# sourceMappingURL=domEnvironment.config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domEnvironment.config.d.ts","sourceRoot":"","sources":["../src/domEnvironment.config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAIlD,QAAA,MAAiB,WAAW,aAE3B,CAAC;eAEa,WAAW"}
@@ -0,0 +1,17 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { createRequire } from "node:module";
10
+ import { join } from "node:path";
11
+ import process from "node:process";
12
+ import { pathToFileURL } from "node:url";
13
+ // Resolve the DOM plugin from the consumer; depending on test-story would cycle through its Quality tooling.
14
+ const consumer = createRequire(join(process.cwd(), "package.json"));
15
+ const { default: environment } = await import(__rewriteRelativeImportExtension(pathToFileURL(consumer.resolve("@shivaedev/test-story/domEnvironment.config.ts")).href));
16
+ export default environment;
17
+ //# sourceMappingURL=domEnvironment.config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domEnvironment.config.js","sourceRoot":"","sources":["../src/domEnvironment.config.ts"],"names":[],"mappings":";;;;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,6GAA6G;AAC7G,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;AACpE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAsC,MAAM,MAAM,kCAC/E,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,gDAAgD,CAAC,CAAC,CAAC,IAAI,EACtF,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -1,3 +1,6 @@
1
1
  import { type TestUserConfig } from "vitest/config";
2
- export declare function testProjects(): TestUserConfig;
2
+ export interface TestProjectsOptions {
3
+ readonly coverage?: boolean | NonNullable<TestUserConfig["coverage"]>;
4
+ }
5
+ export declare function testProjects({ coverage }?: TestProjectsOptions): TestUserConfig;
3
6
  //# sourceMappingURL=testProjects.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"testProjects.d.ts","sourceRoot":"","sources":["../src/testProjects.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAepE,wBAAgB,YAAY,IAAI,cAAc,CAa7C"}
1
+ {"version":3,"file":"testProjects.d.ts","sourceRoot":"","sources":["../src/testProjects.ts"],"names":[],"mappings":"AAEA,OAAO,EAA0C,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAe5F,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;CACtE;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAgB,EAAE,GAAE,mBAAwB,GAAG,cAAc,CAgC3F"}
@@ -1,5 +1,6 @@
1
+ import { fileURLToPath } from "node:url";
1
2
  import { Config, ConfigProvider, Effect } from "effect";
2
- import { defaultExclude } from "vitest/config";
3
+ import { coverageConfigDefaults, defaultExclude } from "vitest/config";
3
4
  const EXTENSION = "?(c|m)[jt]s?(x)";
4
5
  const TYPECHECK_ALONE = `**/typecheck.test.${EXTENSION}`;
5
6
  function named(environment) {
@@ -8,13 +9,32 @@ function named(environment) {
8
9
  function excluding(...patterns) {
9
10
  return [...defaultExclude, named("typecheck"), TYPECHECK_ALONE, ...patterns];
10
11
  }
11
- export function testProjects() {
12
+ export function testProjects({ coverage = false } = {}) {
12
13
  const ci = Effect.runSync(Config.String("CI").pipe(Config.withDefault("")).parse(ConfigProvider.fromEnv()));
13
14
  return {
15
+ ...(coverage === false
16
+ ? {}
17
+ : {
18
+ coverage: {
19
+ enabled: true,
20
+ exclude: [...coverageConfigDefaults.exclude, "**/*.d.ts", "**/*.test.*", "**/*.spec.*", "**/test-support/**"],
21
+ include: ["src/**/*.{ts,tsx}"],
22
+ provider: "v8",
23
+ ...(coverage === true ? {} : coverage),
24
+ },
25
+ }),
14
26
  project: ["unit", "dom"],
15
27
  projects: [
16
28
  { extends: true, test: { environment: "node", exclude: excluding(named("dom"), named("slow")), include: [named()], name: "unit" } },
17
- { extends: true, test: { environment: "happy-dom", exclude: excluding(named("slow")), include: [named("dom")], name: "dom" } },
29
+ {
30
+ extends: true,
31
+ test: {
32
+ environment: fileURLToPath(import.meta.resolve("#domEnvironment.config.ts")),
33
+ exclude: excluding(named("slow")),
34
+ include: [named("dom")],
35
+ name: "dom",
36
+ },
37
+ },
18
38
  { extends: true, test: { environment: "node", exclude: excluding(), include: [named("slow")], name: "slow" } },
19
39
  ],
20
40
  silent: ci ? false : "passed-only",
@@ -1 +1 @@
1
- {"version":3,"file":"testProjects.js","sourceRoot":"","sources":["../src/testProjects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAE,cAAc,EAAuB,MAAM,eAAe,CAAC;AAGpE,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAEpC,MAAM,eAAe,GAAG,qBAAqB,SAAS,EAAE,CAAC;AAEzD,SAAS,KAAK,CAAC,WAAyB;IACvC,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,qBAAqB,SAAS,EAAE,CAAC,CAAC,CAAC,QAAQ,WAAW,iBAAiB,SAAS,EAAE,CAAC;AACvH,CAAC;AAED,SAAS,SAAS,CAAC,GAAG,QAA2B;IAChD,OAAO,CAAC,GAAG,cAAc,EAAE,KAAK,CAAC,WAAW,CAAC,EAAE,eAAe,EAAE,GAAG,QAAQ,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,YAAY;IAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC5G,OAAO;QACN,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;QACxB,QAAQ,EAAE;YACT,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;YACnI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;YAC9H,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;SAC9G;QACD,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa;QAClC,+IAA+I;QAC/I,WAAW,EAAE,MAAM;KACnB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"testProjects.js","sourceRoot":"","sources":["../src/testProjects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAuB,MAAM,eAAe,CAAC;AAG5F,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAEpC,MAAM,eAAe,GAAG,qBAAqB,SAAS,EAAE,CAAC;AAEzD,SAAS,KAAK,CAAC,WAAyB;IACvC,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,qBAAqB,SAAS,EAAE,CAAC,CAAC,CAAC,QAAQ,WAAW,iBAAiB,SAAS,EAAE,CAAC;AACvH,CAAC;AAED,SAAS,SAAS,CAAC,GAAG,QAA2B;IAChD,OAAO,CAAC,GAAG,cAAc,EAAE,KAAK,CAAC,WAAW,CAAC,EAAE,eAAe,EAAE,GAAG,QAAQ,CAAC,CAAC;AAC9E,CAAC;AAMD,MAAM,UAAU,YAAY,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,GAAwB,EAAE;IAC1E,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC5G,OAAO;QACN,GAAG,CAAC,QAAQ,KAAK,KAAK;YACrB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACA,QAAQ,EAAE;oBACT,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE,CAAC,GAAG,sBAAsB,CAAC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,CAAC;oBAC7G,OAAO,EAAE,CAAC,mBAAmB,CAAC;oBAC9B,QAAQ,EAAE,IAAI;oBACd,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;iBACtC;aACD,CAAC;QACJ,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;QACxB,QAAQ,EAAE;YACT,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;YACnI;gBACC,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE;oBACL,WAAW,EAAE,aAAa,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;oBAC5E,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBACjC,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;oBACvB,IAAI,EAAE,KAAK;iBACX;aACD;YACD,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;SAC9G;QACD,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa;QAClC,+IAA+I;QAC/I,WAAW,EAAE,MAAM;KACnB,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/quality",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Repository quality gate with typed rules, a baseline of existing debt and a registry of reasoned exceptions",
5
5
  "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/quality#readme",
6
6
  "bugs": {
@@ -32,6 +32,7 @@
32
32
  "./tsconfig/base.json": "./tsconfig/base.json",
33
33
  "./tsconfig/package.json": "./tsconfig/package.json",
34
34
  "./package.json": "./package.json",
35
+ "./domEnvironment.config.ts": null,
35
36
  "./imports/isTooling.ts": null,
36
37
  "./rules/imports/bundleInputs.ts": null
37
38
  },
@@ -0,0 +1,13 @@
1
+ import { createRequire } from "node:module";
2
+ import { join } from "node:path";
3
+ import process from "node:process";
4
+ import { pathToFileURL } from "node:url";
5
+ import type { Environment } from "vitest/runtime";
6
+
7
+ // Resolve the DOM plugin from the consumer; depending on test-story would cycle through its Quality tooling.
8
+ const consumer = createRequire(join(process.cwd(), "package.json"));
9
+ const { default: environment }: { readonly default: Environment } = await import(
10
+ pathToFileURL(consumer.resolve("@shivaedev/test-story/domEnvironment.config.ts")).href
11
+ );
12
+
13
+ export default environment;
@@ -1,5 +1,6 @@
1
+ import { fileURLToPath } from "node:url";
1
2
  import { Config, ConfigProvider, Effect } from "effect";
2
- import { defaultExclude, type TestUserConfig } from "vitest/config";
3
+ import { coverageConfigDefaults, defaultExclude, type TestUserConfig } from "vitest/config";
3
4
  import type { Environment } from "#naming/testName.ts";
4
5
 
5
6
  const EXTENSION = "?(c|m)[jt]s?(x)";
@@ -14,13 +15,36 @@ function excluding(...patterns: readonly string[]): string[] {
14
15
  return [...defaultExclude, named("typecheck"), TYPECHECK_ALONE, ...patterns];
15
16
  }
16
17
 
17
- export function testProjects(): TestUserConfig {
18
+ export interface TestProjectsOptions {
19
+ readonly coverage?: boolean | NonNullable<TestUserConfig["coverage"]>;
20
+ }
21
+
22
+ export function testProjects({ coverage = false }: TestProjectsOptions = {}): TestUserConfig {
18
23
  const ci = Effect.runSync(Config.String("CI").pipe(Config.withDefault("")).parse(ConfigProvider.fromEnv()));
19
24
  return {
25
+ ...(coverage === false
26
+ ? {}
27
+ : {
28
+ coverage: {
29
+ enabled: true,
30
+ exclude: [...coverageConfigDefaults.exclude, "**/*.d.ts", "**/*.test.*", "**/*.spec.*", "**/test-support/**"],
31
+ include: ["src/**/*.{ts,tsx}"],
32
+ provider: "v8",
33
+ ...(coverage === true ? {} : coverage),
34
+ },
35
+ }),
20
36
  project: ["unit", "dom"],
21
37
  projects: [
22
38
  { extends: true, test: { environment: "node", exclude: excluding(named("dom"), named("slow")), include: [named()], name: "unit" } },
23
- { extends: true, test: { environment: "happy-dom", exclude: excluding(named("slow")), include: [named("dom")], name: "dom" } },
39
+ {
40
+ extends: true,
41
+ test: {
42
+ environment: fileURLToPath(import.meta.resolve("#domEnvironment.config.ts")),
43
+ exclude: excluding(named("slow")),
44
+ include: [named("dom")],
45
+ name: "dom",
46
+ },
47
+ },
24
48
  { extends: true, test: { environment: "node", exclude: excluding(), include: [named("slow")], name: "slow" } },
25
49
  ],
26
50
  silent: ci ? false : "passed-only",