node-esm-mock 0.1.1 → 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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const mockedModules: Map<any, any>;
2
- export declare function mock(mocks?: Record<string, Record<string, unknown>>): {
3
- for<T = any>(specifier: string): Promise<T>;
1
+ export declare const mocksFor: Map<string, Map<string, any>>;
2
+ export declare function mock(modules?: Record<string, any>): {
3
+ for<T = any>(specifier: string, keep?: boolean): Promise<T>;
4
4
  };
package/dist/index.js CHANGED
@@ -1,94 +1,83 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  import { registerHooks } from 'node:module';
2
- const mockedModuleExports = new Map();
3
- let mainImportURL = import.meta.url;
4
11
  registerHooks({
5
- resolve(specifier, context, nextResolve) {
6
- var _a;
7
- const def = nextResolve(specifier, context);
8
- if (!((_a = context.parentURL) === null || _a === void 0 ? void 0 : _a.startsWith('mock-facade:'))) {
9
- if (mockedModuleExports.has(def.url)) {
10
- return {
11
- shortCircuit: true, url: `mock-facade:${encodeURIComponent(def.url)}`,
12
- };
12
+ resolve(specifier, context, nextResolver) {
13
+ var _a, _b;
14
+ // If the parentURL is not a file and contains a query, it means it's a mocked module, so we should ignore the parentURL to avoid issues with resolving the original module.
15
+ if (!((_a = context.parentURL) === null || _a === void 0 ? void 0 : _a.startsWith('file:')) && ((_b = context.parentURL) === null || _b === void 0 ? void 0 : _b.includes('?'))) {
16
+ context.parentURL = undefined;
17
+ }
18
+ const resolved = nextResolver(specifier, context);
19
+ const mocksForSpecifier = mocksFor.get(specifier);
20
+ if (mocksForSpecifier) {
21
+ mocksFor.set(resolved.url, mocksForSpecifier);
22
+ }
23
+ if (context.parentURL) {
24
+ const mocksForParentURL = mocksFor.get(context.parentURL);
25
+ if (mocksForParentURL) {
26
+ const scope = context.parentURL.split('?')[1];
27
+ const resolvedUrl = `${resolved.url}?${scope}`;
28
+ if (mocksForParentURL.has(resolvedUrl)) {
29
+ return {
30
+ url: resolvedUrl, format: 'mocked', importAttributes: {
31
+ parentURL: context.parentURL,
32
+ }, shortCircuit: true
33
+ };
34
+ }
13
35
  }
14
36
  }
15
- return def;
16
- }, load(url, context, nextLoad) {
17
- if (url.startsWith('mock-facade:')) {
18
- const encodedTargetURL = url.slice(url.lastIndexOf(':') + 1);
19
- return {
20
- shortCircuit: true, source: generateModule(encodedTargetURL), format: 'module',
21
- };
37
+ return resolved;
38
+ }, load(url, context, nextLoader) {
39
+ if (context.format === 'mocked') {
40
+ const source = generateModule(context.importAttributes.parentURL, url);
41
+ return { source, format: 'module', shortCircuit: true };
22
42
  }
23
- return nextLoad(url, context);
43
+ return nextLoader(url, context);
24
44
  }
25
45
  });
26
- function generateModule(encodedTargetURL) {
27
- const exports = mockedModuleExports.get(decodeURIComponent(encodedTargetURL));
46
+ export const mocksFor = new Map();
47
+ function generateModule(parent, url) {
28
48
  const body = [
29
- `import { mockedModules } from ${JSON.stringify(mainImportURL)};`,
30
- 'export {};',
31
- 'let mapping = {__proto__: null};',
32
- `const mock = mockedModules.get(${JSON.stringify(encodedTargetURL)});`,
49
+ `import {mocksFor} from ${JSON.stringify(import.meta.url)};`,
50
+ `const exports = mocksFor.get(${JSON.stringify(parent)});`,
33
51
  ];
34
- for (const [i, name] of Object.entries(exports)) {
35
- const key = JSON.stringify(name);
36
- body.push(`var _${i} = mock.namespace[${key}];`);
37
- body.push(`Object.defineProperty(mapping, ${key}, { enumerable: true, set(v) {_${i} = v;}, get() {return _${i};} });`);
38
- body.push(`export {_${i} as ${name}};`);
39
- }
40
- body.push(`mock.listeners.push(() => {
41
- for (var k in mapping) {
42
- mapping[k] = mock.namespace[k];
43
- }
44
- });`);
45
- return body.join('\n');
46
- }
47
- export const mockedModules = new Map();
48
- function add(resolved, replacementProperties) {
49
- const exportNames = Object.keys(replacementProperties);
50
- const namespace = { __proto__: null };
51
- const listeners = [];
52
- for (const name of exportNames) {
53
- let currentValueForPropertyName = replacementProperties[name];
54
- Object.defineProperty(namespace, name, {
55
- // @ts-ignore
56
- __proto__: null,
57
- enumerable: true,
58
- get() {
59
- return currentValueForPropertyName;
60
- }, set(v) {
61
- currentValueForPropertyName = v;
62
- for (const fn of listeners) {
63
- try {
64
- fn(name);
65
- }
66
- catch (_a) {
67
- /* noop */
68
- }
52
+ if (parent) {
53
+ const mocksForParent = mocksFor.get(parent);
54
+ if (mocksForParent) {
55
+ const exports = mocksForParent.get(url);
56
+ if (exports) {
57
+ for (const key in exports) {
58
+ body.push(`export const ${key} = exports.get(${JSON.stringify(url)})[${JSON.stringify(key)}];`);
69
59
  }
70
- },
71
- });
60
+ }
61
+ }
72
62
  }
73
- mockedModules.set(encodeURIComponent(resolved), {
74
- namespace, listeners,
75
- });
76
- mockedModuleExports.set(resolved, exportNames);
77
- return namespace;
63
+ return body.join('\n');
78
64
  }
79
- export function mock(mocks = {}) {
80
- const mockedModules = new Map();
65
+ let version = 0;
66
+ export function mock(modules = {}) {
81
67
  return {
82
- for(specifier) {
83
- try {
84
- for (const spec in mocks) {
85
- mockedModules.set(spec, add(spec, mocks[spec]));
68
+ for(specifier_1) {
69
+ return __awaiter(this, arguments, void 0, function* (specifier, keep = false) {
70
+ try {
71
+ const scope = (version++).toString() + +new Date();
72
+ specifier = `${specifier}?${scope}`;
73
+ mocksFor.set(specifier, new Map(Object.entries(modules).map(([k, v]) => [`${k}?${scope}`, v])));
74
+ return yield import(specifier);
86
75
  }
87
- return import(`${specifier}?${+new Date()}`);
88
- }
89
- finally {
90
- mockedModules.forEach((_, mockedModule) => mockedModuleExports.delete(mockedModule));
91
- }
76
+ finally {
77
+ if (!keep)
78
+ mocksFor.delete(specifier);
79
+ }
80
+ });
92
81
  }
93
82
  };
94
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-esm-mock",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Mock ES modules in Node.js using registerHooks",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -35,13 +35,15 @@
35
35
  "typescript": "^5.5.3"
36
36
  },
37
37
  "devDependencies": {
38
+ "@types/chai": "^5.2.3",
38
39
  "@types/node": "^25.9.3",
39
40
  "@types/sinon": "^21.0.1",
41
+ "chai": "^6.2.2",
40
42
  "sinon": "^22.0.0",
41
43
  "ts-node": "^10.9.2"
42
44
  },
43
45
  "scripts": {
44
46
  "build": "tsc",
45
- "test": "node --test test/index.ts"
47
+ "test": "node --import ./utils/register.ts --no-warnings --test"
46
48
  }
47
49
  }