@ng-native/testing 0.1.3 → 0.2.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/package.json +6 -6
- package/runner/vitest.mjs +73 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-native/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
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
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@ng-native/metro": "0.
|
|
38
|
-
"@ng-native/
|
|
39
|
-
"@ng-native/
|
|
37
|
+
"@ng-native/metro": "0.2.0",
|
|
38
|
+
"@ng-native/fabric": "0.2.0",
|
|
39
|
+
"@ng-native/platform": "0.2.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@angular/compiler": "^22.0.0",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"@angular/router": "22.2.0",
|
|
64
64
|
"vite": "^8.3.1",
|
|
65
65
|
"vitest": "^5.0.2",
|
|
66
|
-
"@ng-native/
|
|
67
|
-
"@ng-native/
|
|
66
|
+
"@ng-native/router": "0.2.0",
|
|
67
|
+
"@ng-native/components": "0.2.0"
|
|
68
68
|
},
|
|
69
69
|
"nx": {
|
|
70
70
|
"tags": [
|
package/runner/vitest.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `ngNative()`: the Vite plugin that lets Vitest run Angular Native code in Node, with no DOM.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Five things, each of which Vitest would otherwise get wrong on its own:
|
|
5
5
|
*
|
|
6
6
|
* - It compiles. Every decorated `.ts` goes through `@ng-native/metro`'s AOT transform and every
|
|
7
7
|
* partial-compiled package through the linker (see `compile.mjs`), before Vite strips types.
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
* - It stubs assets. With no `require`, an app's `require('./logo.png')` would throw; it becomes
|
|
14
14
|
* `{ testUri }` instead. And it stands in for the gesture and animation entry points, whose
|
|
15
15
|
* React Native source Node cannot load (`STAND_INS`).
|
|
16
|
+
* - It resolves as Metro does. A workspace library's copy of a package at the app's version
|
|
17
|
+
* resolves to the app's copy (`appCopy`), so a test has one `@ng-native/components`, as the
|
|
18
|
+
* bundle does.
|
|
16
19
|
*
|
|
17
20
|
* The environment is Vitest's default, `node`. There is no DOM to emulate: the renderer talks to
|
|
18
21
|
* a fake Fabric, and jsdom would only give Angular a `document` to misread.
|
|
@@ -100,11 +103,74 @@ function hideRequire(code, file) {
|
|
|
100
103
|
return `var require = undefined; ${code}`;
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
/** @param {string} dir */
|
|
107
|
+
function manifest(dir) {
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
110
|
+
} catch {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The directory of the `name` package a resolved file is in, or nothing: installed, or a linked
|
|
117
|
+
* workspace package, whose real path has no `node_modules` in it.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} id
|
|
120
|
+
* @param {string} name
|
|
121
|
+
*/
|
|
122
|
+
function packageRoot(id, name) {
|
|
123
|
+
const file = id.split('?')[0];
|
|
124
|
+
const marker = `/node_modules/${name}/`;
|
|
125
|
+
const at = file.lastIndexOf(marker);
|
|
126
|
+
if (at !== -1) return file.slice(0, at + marker.length - 1);
|
|
127
|
+
if (!path.isAbsolute(file)) return undefined;
|
|
128
|
+
for (let dir = path.dirname(file); dir !== path.dirname(dir); dir = path.dirname(dir)) {
|
|
129
|
+
// A package's dist can hold a package.json of its own with only a `type` in it.
|
|
130
|
+
const found = manifest(dir)?.name;
|
|
131
|
+
if (found) return found === name ? dir : undefined;
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The app's copy of a package, when a bare import resolved to another copy at the same version:
|
|
138
|
+
* the rule the Metro preset applies to a bundle, so a test loads what the device does.
|
|
139
|
+
*
|
|
140
|
+
* pnpm installs a package once per set of peers it resolves, so a workspace library that lists
|
|
141
|
+
* `@ng-native/components` gets a copy of its own at the app's version when it was installed after
|
|
142
|
+
* the app. Loaded from the library, that is a second component registry. A copy at another
|
|
143
|
+
* version is the library's own choice, and stays.
|
|
144
|
+
*
|
|
145
|
+
* @param {import('vite').Rollup.PluginContext} context
|
|
146
|
+
* @param {string} source
|
|
147
|
+
* @param {string} importer
|
|
148
|
+
* @param {object} options what Vite passed to `resolveId`
|
|
149
|
+
* @param {string} root the app's directory
|
|
150
|
+
*/
|
|
151
|
+
async function appCopy(context, source, importer, options, root) {
|
|
152
|
+
const resolution = await context.resolve(source, importer, { ...options, skipSelf: true });
|
|
153
|
+
const pkg = /^(?![./\0])(@[^/]+\/[^/]+|[^/]+)/.exec(source)?.[1];
|
|
154
|
+
const own = pkg && resolution && !resolution.external && packageRoot(resolution.id, pkg);
|
|
155
|
+
if (!own) return resolution;
|
|
156
|
+
// The app's copy can fail where the library's resolved: another version that does not export
|
|
157
|
+
// the subpath. The library's copy is then its own choice.
|
|
158
|
+
const app = await context
|
|
159
|
+
.resolve(source, path.join(root, 'package.json'), { ...options, skipSelf: true })
|
|
160
|
+
.catch(() => null);
|
|
161
|
+
const appRoot = app && !app.external && packageRoot(app.id, pkg);
|
|
162
|
+
const version = (/** @type {string} */ dir) => manifest(dir)?.version;
|
|
163
|
+
return appRoot && appRoot !== own && version(appRoot) && version(appRoot) === version(own)
|
|
164
|
+
? app
|
|
165
|
+
: resolution;
|
|
166
|
+
}
|
|
167
|
+
|
|
103
168
|
/**
|
|
104
169
|
* @param {{ inline?: (string | RegExp)[] }} [options]
|
|
105
170
|
* @returns {import('vitest/config').Plugin}
|
|
106
171
|
*/
|
|
107
172
|
export function ngNative(options = {}) {
|
|
173
|
+
let root = process.cwd();
|
|
108
174
|
return {
|
|
109
175
|
name: 'ng-native',
|
|
110
176
|
enforce: 'pre',
|
|
@@ -123,8 +189,12 @@ export function ngNative(options = {}) {
|
|
|
123
189
|
server: { deps: { inline: [...INLINE, ...(options.inline ?? [])] } },
|
|
124
190
|
},
|
|
125
191
|
}),
|
|
126
|
-
|
|
127
|
-
|
|
192
|
+
configResolved(config) {
|
|
193
|
+
root = config.root;
|
|
194
|
+
},
|
|
195
|
+
resolveId(source, importer, resolveOptions) {
|
|
196
|
+
if (Object.hasOwn(STAND_INS, source)) return STAND_INS[source];
|
|
197
|
+
return importer ? appCopy(this, source, importer, resolveOptions, root) : null;
|
|
128
198
|
},
|
|
129
199
|
transform(source, id) {
|
|
130
200
|
const file = id.split('?')[0];
|