@ng-native/testing 0.0.1 → 0.1.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -1
  3. package/dist/events.d.ts +45 -0
  4. package/dist/events.js +144 -0
  5. package/dist/events.js.map +1 -0
  6. package/dist/gesture-handler.d.ts +39 -0
  7. package/dist/gesture-handler.js +42 -0
  8. package/dist/gesture-handler.js.map +1 -0
  9. package/dist/gesture-of.d.ts +8 -0
  10. package/dist/gesture-of.js +29 -0
  11. package/dist/gesture-of.js.map +1 -0
  12. package/dist/gestures.d.ts +16 -0
  13. package/dist/gestures.js +46 -0
  14. package/dist/gestures.js.map +1 -0
  15. package/dist/index.d.ts +15 -0
  16. package/dist/index.js +14 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/queries.d.ts +73 -0
  19. package/dist/queries.js +148 -0
  20. package/dist/queries.js.map +1 -0
  21. package/dist/reanimated-library.d.ts +62 -0
  22. package/dist/reanimated-library.js +95 -0
  23. package/dist/reanimated-library.js.map +1 -0
  24. package/dist/reanimated.d.ts +30 -0
  25. package/dist/reanimated.js +80 -0
  26. package/dist/reanimated.js.map +1 -0
  27. package/dist/render.d.ts +92 -0
  28. package/dist/render.js +182 -0
  29. package/dist/render.js.map +1 -0
  30. package/dist/test-utils.d.ts +66 -0
  31. package/dist/test-utils.js +162 -0
  32. package/dist/test-utils.js.map +1 -0
  33. package/dist/wait-for.d.ts +25 -0
  34. package/dist/wait-for.js +21 -0
  35. package/dist/wait-for.js.map +1 -0
  36. package/dist/worklets-library.d.ts +10 -0
  37. package/dist/worklets-library.js +23 -0
  38. package/dist/worklets-library.js.map +1 -0
  39. package/package.json +86 -3
  40. package/runner/compile.mjs +82 -0
  41. package/runner/loader.mjs +73 -0
  42. package/runner/register.mjs +17 -0
  43. package/runner/setup.mjs +10 -0
  44. package/runner/vitest.d.mts +12 -0
  45. package/runner/vitest.mjs +142 -0
@@ -0,0 +1,23 @@
1
+ /**
2
+ * What a test gets for `react-native-worklets`, whose source Node cannot load. There is one
3
+ * runtime in a test, so scheduling a function on either runtime calls it at once.
4
+ */
5
+ export function scheduleOnRN(fn, ...args) {
6
+ fn(...args);
7
+ }
8
+ export function scheduleOnUI(fn, ...args) {
9
+ fn(...args);
10
+ }
11
+ export function runOnUISync(fn, ...args) {
12
+ return fn(...args);
13
+ }
14
+ export function runOnJS(fn) {
15
+ return fn;
16
+ }
17
+ export function runOnUI(fn) {
18
+ return fn;
19
+ }
20
+ export function isWorkletFunction(_value) {
21
+ return false;
22
+ }
23
+ //# sourceMappingURL=worklets-library.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worklets-library.js","sourceRoot":"","sources":["../src/worklets-library.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAsB,EAA2B,EAAE,GAAG,IAAO;IACvF,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,YAAY,CAAsB,EAA2B,EAAE,GAAG,IAAO;IACvF,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,WAAW,CAAyB,EAAqB,EAAE,GAAG,IAAO;IACnF,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,OAAO,CAAyB,EAAqB;IACnE,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,OAAO,CAAyB,EAAqB;IACnE,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAe;IAC/C,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["/**\n * What a test gets for `react-native-worklets`, whose source Node cannot load. There is one\n * runtime in a test, so scheduling a function on either runtime calls it at once.\n */\nexport function scheduleOnRN<A extends unknown[]>(fn: (...args: A) => unknown, ...args: A): void {\n fn(...args);\n}\n\nexport function scheduleOnUI<A extends unknown[]>(fn: (...args: A) => unknown, ...args: A): void {\n fn(...args);\n}\n\nexport function runOnUISync<A extends unknown[], R>(fn: (...args: A) => R, ...args: A): R {\n return fn(...args);\n}\n\nexport function runOnJS<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R {\n return fn;\n}\n\nexport function runOnUI<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R {\n return fn;\n}\n\nexport function isWorkletFunction(_value: unknown): boolean {\n return false;\n}\n"]}
package/package.json CHANGED
@@ -1,12 +1,95 @@
1
1
  {
2
2
  "name": "@ng-native/testing",
3
- "version": "0.0.1",
4
- "description": "Reserved for ng-native. The first release follows.",
3
+ "version": "0.1.1",
4
+ "description": "Test Angular Native components in Node with no simulator: a fake Fabric, Testing Library queries and events, and Vitest and node:test integration.",
5
+ "keywords": [
6
+ "angular",
7
+ "react-native",
8
+ "angular-native",
9
+ "ios",
10
+ "android",
11
+ "fabric",
12
+ "native",
13
+ "testing",
14
+ "testing-library",
15
+ "vitest",
16
+ "node-test"
17
+ ],
18
+ "type": "module",
19
+ "main": "dist/index.js",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./vitest": {
26
+ "types": "./runner/vitest.d.mts",
27
+ "default": "./runner/vitest.mjs"
28
+ },
29
+ "./register": "./runner/register.mjs",
30
+ "./loader": "./runner/loader.mjs",
31
+ "./package.json": "./package.json"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "@ng-native/metro": "0.1.1",
38
+ "@ng-native/platform": "0.1.1",
39
+ "@ng-native/fabric": "0.1.1"
40
+ },
41
+ "peerDependencies": {
42
+ "@angular/compiler": "^22.0.0",
43
+ "@angular/core": "^22.0.0",
44
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
45
+ "vitest": "^4.0.8 || ^5.0.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "@angular/compiler": {
49
+ "optional": true
50
+ },
51
+ "vite": {
52
+ "optional": true
53
+ },
54
+ "vitest": {
55
+ "optional": true
56
+ }
57
+ },
58
+ "devDependencies": {
59
+ "@angular/common": "22.2.0",
60
+ "@angular/compiler": "22.2.0",
61
+ "@angular/core": "22.2.0",
62
+ "@angular/forms": "22.2.0",
63
+ "@angular/router": "22.2.0",
64
+ "vite": "^8.3.1",
65
+ "vitest": "^5.0.2",
66
+ "@ng-native/components": "0.1.1",
67
+ "@ng-native/router": "0.1.1"
68
+ },
69
+ "nx": {
70
+ "tags": [
71
+ "scope:testing",
72
+ "layer:testing"
73
+ ]
74
+ },
5
75
  "license": "MIT",
76
+ "homepage": "https://ng-native.com/packages/testing",
77
+ "bugs": {
78
+ "url": "https://github.com/ng-native/ng-native/issues"
79
+ },
6
80
  "repository": {
7
81
  "type": "git",
8
82
  "url": "git+https://github.com/ng-native/ng-native.git",
9
83
  "directory": "packages/testing"
10
84
  },
11
- "homepage": "https://ng-native.com/packages/testing"
85
+ "files": [
86
+ "dist",
87
+ "runner"
88
+ ],
89
+ "scripts": {
90
+ "build": "ngc -p tsconfig.build.json",
91
+ "typecheck": "tsc -p tsconfig.json --noEmit",
92
+ "test": "node --import ./runner/register.mjs --test \"src/**/*.node.test.ts\" && vitest run"
93
+ },
94
+ "types": "dist/index.d.ts"
12
95
  }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The one decision both test runners make about a file: does Angular have to touch it before it
3
+ * can run, and if so, what does it become.
4
+ *
5
+ * Shared by the Node hook (`loader.mjs`) and the Vitest plugin (`vitest.mjs`) so the two cannot
6
+ * drift, and both delegate the compiling itself to `@ng-native/metro`'s `transformAngular` so a
7
+ * test runs what Metro would have put in the app bundle rather than a lookalike.
8
+ *
9
+ * Two kinds of file need it, for the same reason - nothing downstream understands Angular:
10
+ *
11
+ * - A TypeScript source with an Angular decorator. Node's type stripping and Vite's both erase
12
+ * types and nothing else, and a decorator is syntax neither accepts. That covers an app's own
13
+ * components, a test file that declares one inline, and the `@ng-native/*` packages' source in
14
+ * this workspace.
15
+ * - A package published partial-compiled. `@angular/common`, `@angular/router`, a published
16
+ * `@ng-native/*` package and most Angular libraries on npm are full of `ɵɵngDeclare*` calls, which compile eagerly at module evaluation
17
+ * and throw "needs to be compiled using the JIT compiler" without the linker.
18
+ */
19
+ import { createRequire } from 'node:module';
20
+ import path from 'node:path';
21
+
22
+ const require = createRequire(import.meta.url);
23
+
24
+ const IS_SOURCE = /\.m?tsx?$/;
25
+ const HAS_ANGULAR_DECORATOR = /@(Component|Directive|Pipe|Injectable|NgModule|Service)\s*\(/;
26
+ /** Metro's own test, so a test links exactly the files an app bundle would. */
27
+ const IS_PARTIAL_COMPILED =
28
+ /ɵɵngDeclare(Component|Directive|Pipe|Injectable|NgModule|Service|Factory)/;
29
+
30
+ /** @type {((source: string, file: string, options?: { platform?: string }) => { code: string, dependencies: string[], map?: string, mapLineOffset?: number }) | undefined} */
31
+ let transformAngular;
32
+
33
+ /**
34
+ * @param {string} source
35
+ * @param {string} file
36
+ */
37
+ export function needsAngular(source, file) {
38
+ return IS_SOURCE.test(file)
39
+ ? HAS_ANGULAR_DECORATOR.test(source)
40
+ : IS_PARTIAL_COMPILED.test(source);
41
+ }
42
+
43
+ /**
44
+ * Compile or link one file. The output of a decorated source is still TypeScript: the runner's
45
+ * own type stripping takes it from there, as Metro's babel preset does in an app.
46
+ *
47
+ * `dependencies` are the `templateUrl` and `styleUrls` files, as absolute paths, for a watcher.
48
+ *
49
+ * @param {string} source
50
+ * @param {string} file
51
+ * @param {{ platform?: string }} [options] `platform: 'web'` compiles as Metro does for a web view.
52
+ * @returns {{ code: string, dependencies: string[], map?: string }}
53
+ */
54
+ export function compileAngular(source, file, options = {}) {
55
+ transformAngular ??= require('@ng-native/metro/angular-transform.cjs').transformAngular;
56
+ const result = transformAngular(source, file, options);
57
+ // Metro gets a module-graph edge to each template and stylesheet, as a bare `import "./x.html"`
58
+ // it erases itself. Neither runner here knows what to do with one, and the content has already
59
+ // been compiled in, so the edges are dropped. They are exactly the first `mapLineOffset` lines,
60
+ // which also leaves the source map lined up with the code again.
61
+ const code = result.mapLineOffset
62
+ ? result.code.split('\n').slice(result.mapLineOffset).join('\n')
63
+ : result.code;
64
+ const directory = path.dirname(file);
65
+ return {
66
+ code,
67
+ dependencies: result.dependencies.map((dependency) => path.resolve(directory, dependency)),
68
+ map: result.map,
69
+ };
70
+ }
71
+
72
+ /**
73
+ * `require('./logo.png')`, which Metro turns into an asset id. An ES module has no `require` under
74
+ * Vitest or under Node, so the module would throw as it is evaluated; it gets `{ testUri }`
75
+ * instead, as under React Native's Jest preset, which is enough for a component to render and for
76
+ * a test to see which image. Only an app's own files: a package's are left as they are.
77
+ */
78
+ const ASSET =
79
+ /\brequire\(\s*(['"])([^'"]+\.(?:png|jpe?g|gif|webp|bmp|svg|ttf|otf|mp3|mp4|wav|m4a|mov|json5?))\1\s*\)/g;
80
+ /** @param {string} code */
81
+ export const stubAssets = (code) =>
82
+ code.replace(ASSET, (_, _quote, path) => `({ testUri: ${JSON.stringify(path)} })`);
@@ -0,0 +1,73 @@
1
+ /**
2
+ * A Node module hook that compiles Angular for `node --test`, the way the Metro transformer does
3
+ * for the app. `register.mjs` installs it; see `compile.mjs` for which files need it and why.
4
+ *
5
+ * One more job than the Vitest plugin has: Node refuses to strip types from a `.ts` file under
6
+ * `node_modules` at all, and that is exactly where an installed `@ng-native/*` package's source
7
+ * lives. So every `.ts` there is loaded here, compiled if it is decorated and stripped either way.
8
+ *
9
+ * `initialize` takes `{ skip: string[] }` - URL fragments whose TypeScript is left to Node, for a
10
+ * suite that compiles some files itself. This workspace's integration suite uses it for its
11
+ * fixtures and generated copies; an app has no reason to.
12
+ *
13
+ * `web: string[]` does the same for files compiled as Metro compiles for the browser - a
14
+ * `<dom-component>`'s own component, whose CSS a web view applies rather than native.
15
+ */
16
+ import { readFileSync } from 'node:fs';
17
+ import { stripTypeScriptTypes } from 'node:module';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { compileAngular, needsAngular, stubAssets } from './compile.mjs';
20
+
21
+ /** @type {string[]} */
22
+ let skip = [];
23
+ /** @type {string[]} */
24
+ let web = [];
25
+
26
+ /** @param {{ skip?: string[], web?: string[] } | undefined} data */
27
+ export function initialize(data) {
28
+ skip = data?.skip ?? [];
29
+ web = data?.web ?? [];
30
+ }
31
+
32
+ /**
33
+ * @param {string} url
34
+ * @param {{ format?: string }} context
35
+ * @param {(url: string, context: object) => Promise<{ format?: string, source?: string | ArrayBuffer | Uint8Array }>} nextLoad
36
+ */
37
+ export async function load(url, context, nextLoad) {
38
+ const installed = url.includes('/node_modules/');
39
+
40
+ if (url.startsWith('file:') && url.endsWith('.ts') && !skip.some((part) => url.includes(part))) {
41
+ const loaded = loadTypeScript(fileURLToPath(url), installed);
42
+ if (loaded) return loaded;
43
+ }
44
+
45
+ const result = await nextLoad(url, context);
46
+ if (!installed || result.format !== 'module') return result;
47
+
48
+ // Any package, not only Angular's own: `@ng-icons/core` ships partial-compiled too.
49
+ const source = result.source?.toString();
50
+ if (!source || !needsAngular(source, url)) return result;
51
+ return { ...result, source: compileAngular(source, fileURLToPath(url)).code };
52
+ }
53
+
54
+ /**
55
+ * A `.ts` file this hook has to handle itself: decorated, installed, or requiring an asset. Any
56
+ * other is left to Node, which strips types faster than this does.
57
+ *
58
+ * @param {string} file
59
+ * @param {boolean} installed
60
+ */
61
+ function loadTypeScript(file, installed) {
62
+ const read = readFileSync(file, 'utf8');
63
+ const source = installed ? read : stubAssets(read);
64
+ const decorated = needsAngular(source, file);
65
+ if (!decorated && !installed && source === read) return null;
66
+ const platform = web.some((part) => file.includes(part)) ? 'web' : undefined;
67
+ const code = decorated ? compileAngular(source, file, { platform }).code : source;
68
+ return {
69
+ format: 'module',
70
+ source: stripTypeScriptTypes(code, { mode: 'strip' }),
71
+ shortCircuit: true,
72
+ };
73
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `node --import @ng-native/testing/register --test` - compiles Angular for Node's own test
3
+ * runner. See `loader.mjs`.
4
+ *
5
+ * The animation globals go in first, and have to: `@angular/core` decides whether `animate.enter`
6
+ * and `animate.leave` do anything at all when it is first evaluated, so this is the last moment
7
+ * that answer can be influenced. An app does the same thing with a Metro polyfill. `ngDevMode`
8
+ * is left undefined, which Angular reads as dev mode - what a Metro dev build runs - and
9
+ * `__DEV__` is true for the same reason.
10
+ */
11
+ import { createRequire, register } from 'node:module';
12
+
13
+ createRequire(import.meta.url)('@ng-native/metro/polyfills/animation-globals.js');
14
+ // Metro's prelude defines this in every bundle, and app code reads it bare.
15
+ globalThis.__DEV__ ??= true;
16
+
17
+ register('./loader.mjs', import.meta.url);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The setup file `ngNative()` adds to Vitest: the globals the Metro preset installs as polyfills,
3
+ * in place before a test file evaluates `@angular/core`. See `register.mjs` for why that order
4
+ * matters and why `ngDevMode` is left alone.
5
+ */
6
+ import { createRequire } from 'node:module';
7
+
8
+ createRequire(import.meta.url)('@ng-native/metro/polyfills/animation-globals.js');
9
+ // Metro's prelude defines this in every bundle, and app code reads it bare.
10
+ globalThis.__DEV__ ??= true;
@@ -0,0 +1,12 @@
1
+ import type { Plugin } from 'vitest/config';
2
+
3
+ export interface NgNativeOptions {
4
+ /**
5
+ * More packages for Vitest to process rather than hand to Node, for an Angular library that
6
+ * ships partial-compiled. `@angular/*`, `@ng-native/*` and `@ng-icons/*` are always included.
7
+ */
8
+ inline?: (string | RegExp)[];
9
+ }
10
+
11
+ /** Compile Angular for Vitest: AOT for decorated sources, the linker for partial-compiled packages. */
12
+ export declare function ngNative(options?: NgNativeOptions): Plugin;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * `ngNative()`: the Vite plugin that lets Vitest run Angular Native code in Node, with no DOM.
3
+ *
4
+ * Four things, each of which Vitest would otherwise get wrong on its own:
5
+ *
6
+ * - It compiles. Every decorated `.ts` goes through `@ng-native/metro`'s AOT transform and every
7
+ * partial-compiled package through the linker (see `compile.mjs`), before Vite strips types.
8
+ * - It inlines. Vitest hands `node_modules` straight to Node by default, which would mean an
9
+ * `@ng-native/*` package's `.ts` source reaching a runtime that refuses to strip types under
10
+ * `node_modules`, and `@angular/*` reaching it unlinked. Inlined, both come through the plugin.
11
+ * - It adds a setup file with the globals Angular reads as it is first evaluated, and runs ES
12
+ * modules without the CommonJS variables Vitest otherwise injects into them.
13
+ * - It stubs assets. With no `require`, an app's `require('./logo.png')` would throw; it becomes
14
+ * `{ testUri }` instead. And it stands in for the gesture and animation entry points, whose
15
+ * React Native source Node cannot load (`STAND_INS`).
16
+ *
17
+ * The environment is Vitest's default, `node`. There is no DOM to emulate: the renderer talks to
18
+ * a fake Fabric, and jsdom would only give Angular a `document` to misread.
19
+ */
20
+ import { readFileSync } from 'node:fs';
21
+ import path from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { defaultClientConditions, defaultServerConditions } from 'vite';
24
+ import { compileAngular, needsAngular, stubAssets } from './compile.mjs';
25
+
26
+ const SETUP = fileURLToPath(new URL('./setup.mjs', import.meta.url));
27
+
28
+ /**
29
+ * Entry points a test gets a stand-in for, because what they import is React Native source that
30
+ * Node cannot load: the gestures and reanimated entry points of `@ng-native/components`, the
31
+ * gesture library itself, which an app imports for `Gesture`, and Reanimated and worklets, which
32
+ * an app imports for `withTiming` and `scheduleOnRN`. See each stand-in for what it does and does
33
+ * not do.
34
+ */
35
+ const STAND_INS = {
36
+ '@ng-native/components/gestures': fileURLToPath(new URL('../src/gestures.ts', import.meta.url)),
37
+ 'react-native-gesture-handler': fileURLToPath(
38
+ new URL('../src/gesture-handler.ts', import.meta.url),
39
+ ),
40
+ '@ng-native/components/reanimated': fileURLToPath(
41
+ new URL('../src/reanimated.ts', import.meta.url),
42
+ ),
43
+ 'react-native-reanimated': fileURLToPath(
44
+ new URL('../src/reanimated-library.ts', import.meta.url),
45
+ ),
46
+ 'react-native-worklets': fileURLToPath(new URL('../src/worklets-library.ts', import.meta.url)),
47
+ };
48
+
49
+ /**
50
+ * ponytail: inlining is by package name, so a third-party Angular library other than these has
51
+ * to be named in `inline` by the app. Linking whatever turns out to be partial-compiled would
52
+ * mean inlining every dependency, which is slower and breaks CommonJS packages.
53
+ */
54
+ const INLINE = [
55
+ /\/node_modules\/@angular\//,
56
+ /\/node_modules\/@ng-native\//,
57
+ /\/node_modules\/@ng-icons\//,
58
+ ];
59
+
60
+ /**
61
+ * `compilerOptions.customConditions` from the project's `tsconfig.json`, less `react-native`,
62
+ * which would send a test to React Native's Flow source.
63
+ *
64
+ * @param {string} root
65
+ * @returns {string[]}
66
+ */
67
+ function tsconfigConditions(root) {
68
+ try {
69
+ const conditions = JSON.parse(readFileSync(path.join(root, 'tsconfig.json'), 'utf8'))
70
+ .compilerOptions?.customConditions;
71
+ return Array.isArray(conditions) ? conditions.filter((c) => c !== 'react-native') : [];
72
+ } catch {
73
+ return [];
74
+ }
75
+ }
76
+
77
+ /** @param {string[]} conditions */
78
+ function withConditions(conditions) {
79
+ if (conditions.length === 0) return {};
80
+ return {
81
+ resolve: { conditions: [...conditions, ...defaultClientConditions] },
82
+ ssr: { resolve: { conditions: [...conditions, ...defaultServerConditions] } },
83
+ };
84
+ }
85
+
86
+ /**
87
+ * `injectCjsGlobals: false` again, for Vitest 4, which has no such option and hands every module
88
+ * a `require` regardless. A module that probes for one then requires React Native's Flow source.
89
+ * A `var` of the same name shadows the one Vitest passes in, and is harmless where there is none.
90
+ * On the first line, so every other line keeps its number. ES modules only: a CommonJS file needs
91
+ * the real thing.
92
+ *
93
+ * @param {string} code
94
+ * @param {string} file
95
+ */
96
+ function hideRequire(code, file) {
97
+ if (!/\.(ts|mts|mjs)$/.test(file) || !/\brequire\b/.test(code)) return code;
98
+ // A module that makes its own, with `createRequire`, already sees the one it means.
99
+ if (/\b(?:const|let|var|function|class)\s+require\b/.test(code)) return code;
100
+ return `var require = undefined; ${code}`;
101
+ }
102
+
103
+ /**
104
+ * @param {{ inline?: (string | RegExp)[] }} [options]
105
+ * @returns {import('vitest/config').Plugin}
106
+ */
107
+ export function ngNative(options = {}) {
108
+ return {
109
+ name: 'ng-native',
110
+ enforce: 'pre',
111
+ config: (config) => ({
112
+ // The conditions the project's tsconfig resolves packages with, ahead of Vite's own, so a
113
+ // test takes the same entry tsc does: Nx's TypeScript preset exports a library's source
114
+ // only under one.
115
+ ...withConditions(tsconfigConditions(config?.root ?? process.cwd())),
116
+ test: {
117
+ setupFiles: [SETUP],
118
+ // Vitest hands every ES module a CommonJS `require` by default, for compatibility with an
119
+ // older runner. Code here checks for one to tell a device from Node: given one, it
120
+ // requires `react-native`, whose Flow source nothing in a test can parse. Off, a module
121
+ // sees what it sees under Node itself.
122
+ injectCjsGlobals: false,
123
+ server: { deps: { inline: [...INLINE, ...(options.inline ?? [])] } },
124
+ },
125
+ }),
126
+ resolveId(source) {
127
+ return Object.hasOwn(STAND_INS, source) ? STAND_INS[source] : null;
128
+ },
129
+ transform(source, id) {
130
+ const file = id.split('?')[0];
131
+ if (file.startsWith('\0') || !/\.m?[jt]s$/.test(file)) return null;
132
+ const code = file.includes('/node_modules/') ? source : stubAssets(source);
133
+ if (!needsAngular(code, file)) {
134
+ const hidden = hideRequire(code, file);
135
+ return hidden === source ? null : { code: hidden, map: null };
136
+ }
137
+ const result = compileAngular(code, file);
138
+ for (const dependency of result.dependencies) this.addWatchFile(dependency);
139
+ return { code: hideRequire(result.code, file), map: result.map ?? null };
140
+ },
141
+ };
142
+ }