mirror of
https://github.com/pezkuwichain/pezkuwi-dev.git
synced 2026-10-22 11:46:18 +00:00
Merge pull request #20 from pezkuwichain/feat/test-module-mocks
run-test: module mocks flag; dev-test: fuller browser env, toContain; release 0.86.6
This commit is contained in:
16 files changed
+216
-15
No files matched your search
+1
-1
@@ -30,7 +30,7 @@
|
||||
"docs": "pezkuwi-dev-build-docs",
|
||||
"lint": "pezkuwi-dev-run-lint",
|
||||
"postinstall": "./packages/dev/scripts/pezkuwi-dev-yarn-only.mjs",
|
||||
"test": "yarn build && pezkuwi-dev-run-test --dev-build --env browser"
|
||||
"test": "yarn build && pezkuwi-dev-run-test --dev-build --experimental-test-module-mocks --env browser"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pezkuwi/dev": "workspace:packages/dev",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "0.86.5",
|
||||
"version": "0.86.6",
|
||||
"main": "./index.js",
|
||||
"exports": {
|
||||
"./globals.d.ts": "./src/globals.d.ts"
|
||||
|
||||
+38
@@ -23,4 +23,42 @@ describe('browser', () => {
|
||||
it('contains HTML*Element', () => {
|
||||
expect(typeof all.HTMLElement).toBe('function');
|
||||
});
|
||||
|
||||
it('contains the DOM node hierarchy', () => {
|
||||
const div = document.createElement('div');
|
||||
|
||||
expect(div).toBeInstanceOf(Element);
|
||||
expect(div).toBeInstanceOf(Node);
|
||||
expect(document.createTextNode('text')).toBeInstanceOf(Text);
|
||||
expect(document.createDocumentFragment()).toBeInstanceOf(DocumentFragment);
|
||||
expect(document.createElementNS('http://www.w3.org/2000/svg', 'svg')).toBeInstanceOf(SVGElement);
|
||||
expect(div.attachShadow({ mode: 'open' })).toBeInstanceOf(ShadowRoot);
|
||||
expect(document).toBeInstanceOf(Document);
|
||||
});
|
||||
|
||||
it('contains the UI event constructors', () => {
|
||||
const div = document.createElement('div');
|
||||
|
||||
expect(() => div.dispatchEvent(new MouseEvent('click'))).not.toThrow();
|
||||
expect(() => div.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))).not.toThrow();
|
||||
expect(new FocusEvent('focus')).toBeInstanceOf(UIEvent);
|
||||
});
|
||||
|
||||
it('contains getComputedStyle and the observers', () => {
|
||||
const div = document.createElement('div');
|
||||
|
||||
div.style.display = 'none';
|
||||
document.body.appendChild(div);
|
||||
|
||||
expect(getComputedStyle(div).display).toBe('none');
|
||||
expect(() => new MutationObserver(() => undefined).observe(div, { childList: true })).not.toThrow();
|
||||
expect(() => new ResizeObserver(() => undefined).observe(div)).not.toThrow();
|
||||
|
||||
div.remove();
|
||||
});
|
||||
|
||||
it('dispatches the global CustomEvent and Event on window', () => {
|
||||
expect(() => window.dispatchEvent(new CustomEvent('custom', { detail: 1 }))).not.toThrow();
|
||||
expect(() => window.dispatchEvent(new Event('plain'))).not.toThrow();
|
||||
});
|
||||
});
|
||||
Vendored
+75
@@ -1,8 +1,80 @@
|
||||
// Copyright 2017-2026 @pezkuwi/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DOMWindow } from 'jsdom';
|
||||
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
/**
|
||||
* Constructors that Node defines as well. A DOM node only accepts an event
|
||||
* created in its own realm, so in the browser environment the JSDOM ones
|
||||
* replace Node's, as they do under jest-environment-jsdom.
|
||||
*/
|
||||
export const BROWSER_OVERRIDES: readonly string[] = ['CustomEvent', 'Event'];
|
||||
|
||||
/**
|
||||
* JSDOM does no layout, so no element ever changes size and the callback
|
||||
* never runs. JSDOM has no ResizeObserver of its own.
|
||||
*/
|
||||
class ResizeObserver {
|
||||
observe (): void {
|
||||
// nothing is ever resized
|
||||
}
|
||||
|
||||
unobserve (): void {
|
||||
// nothing is being watched
|
||||
}
|
||||
|
||||
disconnect (): void {
|
||||
// nothing is being watched
|
||||
}
|
||||
}
|
||||
|
||||
/** The DOM node hierarchy, which libraries check with instanceof */
|
||||
function nodes (window: DOMWindow) {
|
||||
return {
|
||||
CharacterData: window.CharacterData,
|
||||
Comment: window.Comment,
|
||||
Document: window.Document,
|
||||
DocumentFragment: window.DocumentFragment,
|
||||
Element: window.Element,
|
||||
Node: window.Node,
|
||||
NodeList: window.NodeList,
|
||||
SVGElement: window.SVGElement,
|
||||
ShadowRoot: window.ShadowRoot,
|
||||
Text: window.Text
|
||||
};
|
||||
}
|
||||
|
||||
/** Event constructors. Node has its own Event and CustomEvent, see BROWSER_OVERRIDES */
|
||||
function events (window: DOMWindow) {
|
||||
return {
|
||||
CompositionEvent: window.CompositionEvent,
|
||||
CustomEvent: window.CustomEvent,
|
||||
Event: window.Event,
|
||||
FocusEvent: window.FocusEvent,
|
||||
InputEvent: window.InputEvent,
|
||||
KeyboardEvent: window.KeyboardEvent,
|
||||
MouseEvent: window.MouseEvent,
|
||||
TouchEvent: window.TouchEvent,
|
||||
UIEvent: window.UIEvent,
|
||||
WheelEvent: window.WheelEvent
|
||||
};
|
||||
}
|
||||
|
||||
/** Styles, geometry and the observers */
|
||||
function layout (window: DOMWindow) {
|
||||
return {
|
||||
// present on the JSDOM window, absent from its DOMWindow type
|
||||
DOMRect: window['DOMRect'] as typeof DOMRect,
|
||||
DOMRectReadOnly: window['DOMRectReadOnly'] as typeof DOMRectReadOnly,
|
||||
MutationObserver: window.MutationObserver,
|
||||
MutationRecord: window.MutationRecord,
|
||||
ResizeObserver,
|
||||
getComputedStyle: window.getComputedStyle.bind(window)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a very basic JSDom environment - this is just enough so we have
|
||||
* @testing-environment/react tests passing in this repo
|
||||
@@ -15,6 +87,9 @@ export function browser () {
|
||||
const { window } = new JSDOM('', { url: 'http://localhost' });
|
||||
|
||||
return {
|
||||
...nodes(window),
|
||||
...events(window),
|
||||
...layout(window),
|
||||
// All HTML Elements that are defined on the JSDOM window object.
|
||||
// (we copied as-is from the types definition). We cannot get this
|
||||
// via Object.keys(window).filter(...) so we have to specify explicitly
|
||||
|
||||
+24
@@ -255,4 +255,28 @@ describe('expect', () => {
|
||||
expect(() => expect([1, 2]).not.toEqual(expect.arrayContaining([2]))).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.toContain', () => {
|
||||
it('finds a substring in a string', () => {
|
||||
expect('voter-alice').toContain('alice');
|
||||
expect(() => expect('voter-alice').toContain('bob')).toThrow();
|
||||
});
|
||||
|
||||
it('finds the item itself in an array or other iterable', () => {
|
||||
const item = { a: 1 };
|
||||
|
||||
expect([1, item]).toContain(item);
|
||||
expect(new Set(['a'])).toContain('a');
|
||||
expect(() => expect([{ a: 1 }]).toContain({ a: 1 })).toThrow();
|
||||
});
|
||||
|
||||
it('rejects a value that is neither a string nor an iterable', () => {
|
||||
expect(() => expect(123).toContain(1)).toThrow();
|
||||
});
|
||||
|
||||
it('supports .not', () => {
|
||||
expect('voter-alice').not.toContain('bob');
|
||||
expect(() => expect([1, 2]).not.toContain(2)).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
+36
@@ -144,6 +144,40 @@ function assertMatchObj (value: unknown, check: object): void {
|
||||
.forEach(([key, other]) => assertMatch((value as Record<string, unknown>)[key], other));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Whether a string contains the substring, or another iterable the item
|
||||
*/
|
||||
function contains (value: unknown, item: unknown): boolean {
|
||||
if (typeof value === 'string') {
|
||||
return typeof item === 'string' && value.includes(item);
|
||||
}
|
||||
|
||||
assert.ok(typeof (value as Iterable<unknown> | null | undefined)?.[Symbol.iterator] === 'function', `Expected a string or an iterable, found ${typeof value}`);
|
||||
|
||||
return [...(value as Iterable<unknown>)].includes(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* A helper for toContain: a string contains the substring, any other
|
||||
* iterable contains the item itself (not a deep-equal copy)
|
||||
*/
|
||||
function assertContains (value: unknown, item: unknown): void {
|
||||
assert.ok(contains(value, item), `Expected ${String(value)} to contain ${String(item)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* The inverse of assertContains
|
||||
*/
|
||||
function assertNotContains (value: unknown, item: unknown): void {
|
||||
assert.ok(!contains(value, item), `Expected ${String(value)} not to contain ${String(item)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
@@ -269,6 +303,7 @@ export function expect () {
|
||||
toBeDefined: () => assert.ok(value === undefined),
|
||||
toBeNull: (value: unknown) => assert.ok(value !== null),
|
||||
toBeUndefined: () => assert.ok(value !== undefined),
|
||||
toContain: (item: unknown) => assertNotContains(value, item),
|
||||
toEqual: (other: unknown) => containsMatcher(other)
|
||||
? assert.throws(() => assertEqualMatch(value, other))
|
||||
: assert.notDeepEqual(value, other),
|
||||
@@ -287,6 +322,7 @@ export function expect () {
|
||||
toBeNull: (value: unknown) => assert.ok(value === null),
|
||||
toBeTruthy: () => assert.ok(value),
|
||||
toBeUndefined: () => assert.ok(value === undefined),
|
||||
toContain: (item: unknown) => assertContains(value, item),
|
||||
toEqual: (other: unknown) => containsMatcher(other)
|
||||
? assertEqualMatch(value, other)
|
||||
: assert.deepEqual(value, other),
|
||||
|
||||
Vendored
+6
-2
@@ -1,7 +1,7 @@
|
||||
// Copyright 2017-2026 @pezkuwi/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { browser } from './browser.js';
|
||||
import { browser, BROWSER_OVERRIDES } from './browser.js';
|
||||
import { expect } from './expect.js';
|
||||
import { jest } from './jest.js';
|
||||
import { lifecycle } from './lifecycle.js';
|
||||
@@ -15,7 +15,11 @@ export function exposeEnv (isBrowser: boolean): void {
|
||||
env && Object
|
||||
.entries(env())
|
||||
.forEach(([key, fn]) => {
|
||||
globalThis[key as 'undefined'] ??= fn;
|
||||
if (isBrowser && BROWSER_OVERRIDES.includes(key)) {
|
||||
(globalThis as Record<string, unknown>)[key] = fn;
|
||||
} else {
|
||||
globalThis[key as 'undefined'] ??= fn;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Copyright 2017-2026 @pezkuwi/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { mock } from 'node:test';
|
||||
|
||||
describe('mock.module', () => {
|
||||
it('replaces a module loaded after the mock is registered', async () => {
|
||||
mock.module('node:os', { namedExports: { platform: () => 'mocked' } });
|
||||
|
||||
const { platform } = await import('node:os');
|
||||
|
||||
expect(platform()).toBe('mocked');
|
||||
});
|
||||
});
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
// Do not edit, auto-generated by @pezkuwi/dev
|
||||
|
||||
export const packageInfo = { name: '@pezkuwi/dev-test', path: 'auto', type: 'auto', version: '0.86.5' };
|
||||
export const packageInfo = { name: '@pezkuwi/dev-test', path: 'auto', type: 'auto', version: '0.86.6' };
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "0.86.5",
|
||||
"version": "0.86.6",
|
||||
"main": "./index.js",
|
||||
"exports": {
|
||||
"./globals.d.ts": "./src/globals.d.ts"
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
// Do not edit, auto-generated by @pezkuwi/dev
|
||||
|
||||
export const packageInfo = { name: '@pezkuwi/dev-ts', path: 'auto', type: 'auto', version: '0.86.5' };
|
||||
export const packageInfo = { name: '@pezkuwi/dev-ts', path: 'auto', type: 'auto', version: '0.86.6' };
|
||||
@@ -252,6 +252,11 @@ This script runs test files in the repository, filtering by file extensions and
|
||||
- **`--logfile <file>`**:
|
||||
Specifies a log file to capture test output.
|
||||
|
||||
- **`--experimental-test-module-mocks`**:
|
||||
Passed to Node.js, enabling `mock.module` from `node:test`. Node only swaps a module that has not
|
||||
been loaded yet, so a spec registers its mocks first and then loads the code under test with a
|
||||
dynamic `import()`.
|
||||
|
||||
- **`--import <module>`**:
|
||||
Imports the specified module.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "0.86.5",
|
||||
"version": "0.86.6",
|
||||
"bin": {
|
||||
"pezkuwi-ci-ghact-build": "./scripts/pezkuwi-ci-ghact-build.mjs",
|
||||
"pezkuwi-ci-ghact-docs": "./scripts/pezkuwi-ci-ghact-docs.mjs",
|
||||
@@ -52,8 +52,8 @@
|
||||
"dependencies": {
|
||||
"@eslint/compat": "^2.1.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@pezkuwi/dev-test": "^0.86.5",
|
||||
"@pezkuwi/dev-ts": "^0.86.5",
|
||||
"@pezkuwi/dev-test": "^0.86.6",
|
||||
"@pezkuwi/dev-ts": "^0.86.6",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^29.0.3",
|
||||
"@rollup/plugin-dynamic-import-vars": "^2.1.5",
|
||||
|
||||
@@ -58,6 +58,11 @@ for (let i = 0; i < args.length; i++) {
|
||||
cmd.push(args[++i]);
|
||||
break;
|
||||
|
||||
// node flags with no params
|
||||
case '--experimental-test-module-mocks':
|
||||
nodeFlags.push(args[i]);
|
||||
break;
|
||||
|
||||
// node flags that could have additional params
|
||||
case '--import':
|
||||
case '--loader':
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
// Do not edit, auto-generated by @pezkuwi/dev
|
||||
|
||||
export const packageInfo = { name: '@pezkuwi/dev', path: 'auto', type: 'auto', version: '0.86.5' };
|
||||
export const packageInfo = { name: '@pezkuwi/dev', path: 'auto', type: 'auto', version: '0.86.6' };
|
||||
@@ -1189,7 +1189,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@pezkuwi/dev-test@npm:^0.86.5, @pezkuwi/dev-test@workspace:packages/dev-test":
|
||||
"@pezkuwi/dev-test@npm:^0.86.6, @pezkuwi/dev-test@workspace:packages/dev-test":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@pezkuwi/dev-test@workspace:packages/dev-test"
|
||||
dependencies:
|
||||
@@ -1205,7 +1205,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@pezkuwi/dev-ts@npm:^0.86.5, @pezkuwi/dev-ts@workspace:packages/dev-ts":
|
||||
"@pezkuwi/dev-ts@npm:^0.86.6, @pezkuwi/dev-ts@workspace:packages/dev-ts":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@pezkuwi/dev-ts@workspace:packages/dev-ts"
|
||||
dependencies:
|
||||
@@ -1221,8 +1221,8 @@ __metadata:
|
||||
dependencies:
|
||||
"@eslint/compat": "npm:^2.1.1"
|
||||
"@eslint/js": "npm:^10.0.1"
|
||||
"@pezkuwi/dev-test": "npm:^0.86.5"
|
||||
"@pezkuwi/dev-ts": "npm:^0.86.5"
|
||||
"@pezkuwi/dev-test": "npm:^0.86.6"
|
||||
"@pezkuwi/dev-ts": "npm:^0.86.6"
|
||||
"@rollup/plugin-alias": "npm:^5.1.1"
|
||||
"@rollup/plugin-commonjs": "npm:^29.0.3"
|
||||
"@rollup/plugin-dynamic-import-vars": "npm:^2.1.5"
|
||||
|
||||
Reference in new issue
Block a user