mirror of
https://github.com/pezkuwichain/pezkuwi-dev.git
synced 2026-04-22 06:47:54 +00:00
Initial rebrand: @polkadot -> @pezkuwi (3 packages)
- Package namespace: @polkadot/dev -> @pezkuwi/dev - Repository: polkadot-js/dev -> pezkuwichain/pezkuwi-dev - Author: Pezkuwi Team <team@pezkuwichain.io> Packages: - @pezkuwi/dev (build tools, linting, CI scripts) - @pezkuwi/dev-test (test runner) - @pezkuwi/dev-ts (TypeScript build) Upstream: polkadot-js/dev v0.83.3
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# @pezkuwi/dev-test
|
||||
|
||||
This is a very basic Jest-compatible environment that could be used alongside tests. The need for this came from replacing Jest with `node --test` without rewriting all assertions.
|
||||
|
||||
It provides the following -
|
||||
|
||||
1. Browser `window`, `document`, `navigator` (see usage for browser-specific path)
|
||||
2. `jest` functions, specifically `spyOn` (not comprehensive, some will error, some witll noop)
|
||||
3. `expect` functions (not comprehensive, caters for specific polkadot-js usage)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
On thing to note is that `node:test` is still rapidly evolving - this includes the APIs and features. As such this requires at least Node 18.14, however 18.15+ is recommended.
|
||||
|
||||
The entry points are different based on the environment you would like to operate in. For a browser-like environment,
|
||||
|
||||
```
|
||||
node --require @pezkuwi/dev-test/browser ...
|
||||
```
|
||||
|
||||
or for a basic describe/expect/jest-only global environment
|
||||
|
||||
```
|
||||
node --require @pezkuwi/dev-test/node ...
|
||||
```
|
||||
|
||||
The `...` above indicates any additional Node options, for instance a full command could be -
|
||||
|
||||
```
|
||||
node --require @pezkuwi/dev-test/node --test something.test.js
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"bugs": "https://github.com/pezkuwi/dev/issues",
|
||||
"description": "A basic test-functions-as-global library on top of node:test",
|
||||
"engines": {
|
||||
"node": ">=18.14"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwi/dev/tree/master/packages/dev-test#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/dev-test",
|
||||
"repository": {
|
||||
"directory": "packages/dev-test",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwi/dev.git"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "0.84.2",
|
||||
"main": "./index.js",
|
||||
"exports": {
|
||||
"./globals.d.ts": "./src/globals.d.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsdom": "^24.0.0",
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsdom": "^21.1.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { exposeEnv } from './env/index.js';
|
||||
|
||||
exposeEnv(true);
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { browser } from './browser.js';
|
||||
|
||||
const all = browser();
|
||||
|
||||
describe('browser', () => {
|
||||
it('contains window', () => {
|
||||
expect(all.window).toBeDefined();
|
||||
});
|
||||
|
||||
it('contains a crypto implementation', () => {
|
||||
expect(all.crypto).toBeTruthy();
|
||||
expect(typeof all.crypto.getRandomValues).toBe('function');
|
||||
});
|
||||
|
||||
it('contains the top-level objects', () => {
|
||||
expect(all.document).toBeDefined();
|
||||
expect(all.navigator).toBeDefined();
|
||||
});
|
||||
|
||||
it('contains HTML*Element', () => {
|
||||
expect(typeof all.HTMLElement).toBe('function');
|
||||
});
|
||||
});
|
||||
Vendored
+100
@@ -0,0 +1,100 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
/**
|
||||
* Export a very basic JSDom environment - this is just enough so we have
|
||||
* @testing-environment/react tests passing in this repo
|
||||
*
|
||||
* FIXME: This approach is actually _explicitly_ discouraged by JSDOM - when
|
||||
* using window you should run the tests inside that context, instead of just
|
||||
* blindly relying on the globals as we do here
|
||||
*/
|
||||
export function browser () {
|
||||
const { window } = new JSDOM('', { url: 'http://localhost' });
|
||||
|
||||
return {
|
||||
// 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
|
||||
HTMLAnchorElement: window.HTMLAnchorElement,
|
||||
HTMLAreaElement: window.HTMLAreaElement,
|
||||
HTMLAudioElement: window.HTMLAudioElement,
|
||||
HTMLBRElement: window.HTMLBRElement,
|
||||
HTMLBaseElement: window.HTMLBaseElement,
|
||||
HTMLBodyElement: window.HTMLBodyElement,
|
||||
HTMLButtonElement: window.HTMLButtonElement,
|
||||
HTMLCanvasElement: window.HTMLCanvasElement,
|
||||
HTMLDListElement: window.HTMLDListElement,
|
||||
HTMLDataElement: window.HTMLDataElement,
|
||||
HTMLDataListElement: window.HTMLDataListElement,
|
||||
HTMLDetailsElement: window.HTMLDetailsElement,
|
||||
HTMLDialogElement: window.HTMLDialogElement,
|
||||
HTMLDirectoryElement: window.HTMLDirectoryElement,
|
||||
HTMLDivElement: window.HTMLDivElement,
|
||||
HTMLElement: window.HTMLElement,
|
||||
HTMLEmbedElement: window.HTMLEmbedElement,
|
||||
HTMLFieldSetElement: window.HTMLFieldSetElement,
|
||||
HTMLFontElement: window.HTMLFontElement,
|
||||
HTMLFormElement: window.HTMLFormElement,
|
||||
HTMLFrameElement: window.HTMLFrameElement,
|
||||
HTMLFrameSetElement: window.HTMLFrameSetElement,
|
||||
HTMLHRElement: window.HTMLHRElement,
|
||||
HTMLHeadElement: window.HTMLHeadElement,
|
||||
HTMLHeadingElement: window.HTMLHeadingElement,
|
||||
HTMLHtmlElement: window.HTMLHtmlElement,
|
||||
HTMLIFrameElement: window.HTMLIFrameElement,
|
||||
HTMLImageElement: window.HTMLImageElement,
|
||||
HTMLInputElement: window.HTMLInputElement,
|
||||
HTMLLIElement: window.HTMLLIElement,
|
||||
HTMLLabelElement: window.HTMLLabelElement,
|
||||
HTMLLegendElement: window.HTMLLegendElement,
|
||||
HTMLLinkElement: window.HTMLLinkElement,
|
||||
HTMLMapElement: window.HTMLMapElement,
|
||||
HTMLMarqueeElement: window.HTMLMarqueeElement,
|
||||
HTMLMediaElement: window.HTMLMediaElement,
|
||||
HTMLMenuElement: window.HTMLMenuElement,
|
||||
HTMLMetaElement: window.HTMLMetaElement,
|
||||
HTMLMeterElement: window.HTMLMeterElement,
|
||||
HTMLModElement: window.HTMLModElement,
|
||||
HTMLOListElement: window.HTMLOListElement,
|
||||
HTMLObjectElement: window.HTMLObjectElement,
|
||||
HTMLOptGroupElement: window.HTMLOptGroupElement,
|
||||
HTMLOptionElement: window.HTMLOptionElement,
|
||||
HTMLOutputElement: window.HTMLOutputElement,
|
||||
HTMLParagraphElement: window.HTMLParagraphElement,
|
||||
HTMLParamElement: window.HTMLParamElement,
|
||||
HTMLPictureElement: window.HTMLPictureElement,
|
||||
HTMLPreElement: window.HTMLPreElement,
|
||||
HTMLProgressElement: window.HTMLProgressElement,
|
||||
HTMLQuoteElement: window.HTMLQuoteElement,
|
||||
HTMLScriptElement: window.HTMLScriptElement,
|
||||
HTMLSelectElement: window.HTMLSelectElement,
|
||||
HTMLSlotElement: window.HTMLSlotElement,
|
||||
HTMLSourceElement: window.HTMLSourceElement,
|
||||
HTMLSpanElement: window.HTMLSpanElement,
|
||||
HTMLStyleElement: window.HTMLStyleElement,
|
||||
HTMLTableCaptionElement: window.HTMLTableCaptionElement,
|
||||
HTMLTableCellElement: window.HTMLTableCellElement,
|
||||
HTMLTableColElement: window.HTMLTableColElement,
|
||||
HTMLTableElement: window.HTMLTableElement,
|
||||
HTMLTableRowElement: window.HTMLTableRowElement,
|
||||
HTMLTableSectionElement: window.HTMLTableSectionElement,
|
||||
HTMLTemplateElement: window.HTMLTemplateElement,
|
||||
HTMLTextAreaElement: window.HTMLTextAreaElement,
|
||||
HTMLTimeElement: window.HTMLTimeElement,
|
||||
HTMLTitleElement: window.HTMLTitleElement,
|
||||
HTMLTrackElement: window.HTMLTrackElement,
|
||||
HTMLUListElement: window.HTMLUListElement,
|
||||
HTMLUnknownElement: window.HTMLUnknownElement,
|
||||
HTMLVideoElement: window.HTMLVideoElement,
|
||||
// normal service resumes, the base top-level names
|
||||
crypto: window.crypto,
|
||||
document: window.document,
|
||||
localStorage: window.localStorage,
|
||||
navigator: window.navigator,
|
||||
// window...
|
||||
window
|
||||
};
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
describe('expect', () => {
|
||||
it('has been decorated', () => {
|
||||
expect(expect(true).not).toBeDefined();
|
||||
});
|
||||
|
||||
it('throws on unimplemented', () => {
|
||||
expect(
|
||||
() => expect(true).not.toHaveReturnedWith()
|
||||
).toThrow('expect(...).not.toHaveReturnedWith has not been implemented');
|
||||
});
|
||||
|
||||
it('throws on unimplemented (with alternative)', () => {
|
||||
expect(
|
||||
() => expect(true).not.toBeFalsy()
|
||||
).toThrow('expect(...).not.toBeFalsy has not been implemented (Use expect(...).toBeTruthy instead)');
|
||||
});
|
||||
|
||||
describe('rejects', () => {
|
||||
it('matches a rejection via .toThrow', async () => {
|
||||
await expect(
|
||||
Promise.reject(new Error('this is a rejection message'))
|
||||
).rejects.toThrow(/rejection/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.toBeDefined', () => {
|
||||
it('does not throw on null', () => {
|
||||
expect(null).toBeDefined();
|
||||
});
|
||||
|
||||
it('throws on undefined', () => {
|
||||
expect(
|
||||
() => expect(undefined).toBeDefined()
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('.not does not throw on undefined', () => {
|
||||
expect(undefined).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.toThrow', () => {
|
||||
const thrower = () => {
|
||||
throw new Error('some error');
|
||||
};
|
||||
|
||||
it('matches error with empty throw', () => {
|
||||
expect(thrower).toThrow();
|
||||
});
|
||||
|
||||
it('matches error with exact message', () => {
|
||||
expect(thrower).toThrow('some error');
|
||||
});
|
||||
|
||||
it('matches error with regex message', () => {
|
||||
expect(thrower).toThrow(/me er/);
|
||||
});
|
||||
|
||||
it('handles .not correctly (no throw, empty message)', () => {
|
||||
expect(() => undefined).not.toThrow();
|
||||
});
|
||||
|
||||
it('handles .not correctly (no throw, regex match)', () => {
|
||||
expect(() => undefined).not.toThrow(/me er/);
|
||||
});
|
||||
|
||||
it('handles .not correctly (throw, string match)', () => {
|
||||
expect(() => undefined).not.toThrow('no match');
|
||||
});
|
||||
|
||||
it('handles .not correctly (throw, regex match)', () => {
|
||||
expect(() => undefined).not.toThrow(/no match/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.toMatch', () => {
|
||||
it('fails matching when non-object passed in', () => {
|
||||
expect(
|
||||
() => expect(undefined).toMatch(/match/)
|
||||
).toThrow(/Expected string/);
|
||||
});
|
||||
|
||||
it('fails matching when non-matching string passed in', () => {
|
||||
expect(
|
||||
() => expect('some').toMatch(/match/)
|
||||
).toThrow(/did not match/);
|
||||
});
|
||||
|
||||
it('matches string passed', () => {
|
||||
expect(
|
||||
() => expect('matching').toMatch(/match/)
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.toMatchObject', () => {
|
||||
it('fails matching when non-object passed in', () => {
|
||||
expect(
|
||||
() => expect(undefined).toMatchObject({ foo: 'bar' })
|
||||
).toThrow(/Expected object/);
|
||||
});
|
||||
|
||||
it('matches empty object', () => {
|
||||
expect({
|
||||
a: 'foo',
|
||||
b: 'bar'
|
||||
}).toMatchObject({});
|
||||
});
|
||||
|
||||
it('matches object with some fields', () => {
|
||||
expect({
|
||||
a: 'foo',
|
||||
b: 'bar',
|
||||
c: 123,
|
||||
d: [456, 789]
|
||||
}).toMatchObject({
|
||||
a: 'foo',
|
||||
c: 123,
|
||||
d: [456, 789]
|
||||
});
|
||||
});
|
||||
|
||||
it('matches an object with some expect.stringMatching supplied', () => {
|
||||
expect({
|
||||
a: 'foo bar',
|
||||
b: 'baz',
|
||||
c: 'zaz'
|
||||
}).toMatchObject({
|
||||
a: expect.stringMatching(/o b/),
|
||||
b: expect.stringMatching('baz'),
|
||||
c: 'zaz'
|
||||
});
|
||||
});
|
||||
|
||||
it('matches an object with expect.any supplied', () => {
|
||||
expect({
|
||||
a: 123,
|
||||
b: Boolean(true),
|
||||
c: 'foo'
|
||||
}).toMatchObject({
|
||||
a: expect.any(Number),
|
||||
b: expect.any(Boolean),
|
||||
c: 'foo'
|
||||
});
|
||||
});
|
||||
|
||||
it('does not match an object with non instance value for expect.any', () => {
|
||||
expect(
|
||||
() => expect({
|
||||
a: true,
|
||||
b: 'foo'
|
||||
}).toMatchObject({
|
||||
a: expect.any(Number),
|
||||
b: 'foo'
|
||||
})
|
||||
).toThrow(/not an instance of Number/);
|
||||
});
|
||||
|
||||
it('matches an object with expect.anything supplied', () => {
|
||||
expect({
|
||||
a: 123,
|
||||
b: 'foo'
|
||||
}).toMatchObject({
|
||||
a: expect.anything(),
|
||||
b: 'foo'
|
||||
});
|
||||
});
|
||||
|
||||
it('does not match an object with undefined value for expect.anything', () => {
|
||||
expect(
|
||||
() => expect({
|
||||
b: 'foo'
|
||||
}).toMatchObject({
|
||||
a: expect.anything(),
|
||||
b: 'foo'
|
||||
})
|
||||
).toThrow(/non-nullish/);
|
||||
});
|
||||
|
||||
it('does not match an object with non-array value', () => {
|
||||
expect(
|
||||
() => expect({
|
||||
a: 'foo',
|
||||
b: 'bar'
|
||||
}).toMatchObject({
|
||||
a: 'foo',
|
||||
b: [123, 456]
|
||||
})
|
||||
).toThrow(/Expected array value/);
|
||||
});
|
||||
|
||||
it('allows for deep matching', () => {
|
||||
expect({
|
||||
a: 123,
|
||||
b: {
|
||||
c: 456,
|
||||
d: {
|
||||
e: 'foo',
|
||||
f: 'bar',
|
||||
g: {
|
||||
h: [789, { z: 'baz' }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}).toMatchObject({
|
||||
a: 123,
|
||||
b: {
|
||||
c: expect.any(Number),
|
||||
d: {
|
||||
f: 'bar',
|
||||
g: {
|
||||
h: [expect.any(Number), { z: 'baz' }]
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
+248
@@ -0,0 +1,248 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyFn, WithMock } from '../types.js';
|
||||
|
||||
import { strict as assert } from 'node:assert';
|
||||
|
||||
import { enhanceObj, stubObj } from '../util.js';
|
||||
|
||||
type AssertMatchFn = (value: unknown) => void;
|
||||
|
||||
type Mocked = Partial<WithMock<AnyFn>>;
|
||||
|
||||
// logged via Object.keys(expect).sort()
|
||||
const EXPECT_KEYS = ['addEqualityTesters', 'addSnapshotSerializer', 'any', 'anything', 'arrayContaining', 'assertions', 'closeTo', 'extend', 'extractExpectedAssertionsErrors', 'getState', 'hasAssertions', 'not', 'objectContaining', 'setState', 'stringContaining', 'stringMatching', 'toMatchInlineSnapshot', 'toMatchSnapshot', 'toThrowErrorMatchingInlineSnapshot', 'toThrowErrorMatchingSnapshot'] as const;
|
||||
|
||||
// logged via Object.keys(expect(0)).sort()
|
||||
const EXPECT_KEYS_FN = ['lastCalledWith', 'lastReturnedWith', 'not', 'nthCalledWith', 'nthReturnedWith', 'rejects', 'resolves', 'toBe', 'toBeCalled', 'toBeCalledTimes', 'toBeCalledWith', 'toBeCloseTo', 'toBeDefined', 'toBeFalsy', 'toBeGreaterThan', 'toBeGreaterThanOrEqual', 'toBeInstanceOf', 'toBeLessThan', 'toBeLessThanOrEqual', 'toBeNaN', 'toBeNull', 'toBeTruthy', 'toBeUndefined', 'toContain', 'toContainEqual', 'toEqual', 'toHaveBeenCalled', 'toHaveBeenCalledTimes', 'toHaveBeenCalledWith', 'toHaveBeenLastCalledWith', 'toHaveBeenNthCalledWith', 'toHaveLastReturnedWith', 'toHaveLength', 'toHaveNthReturnedWith', 'toHaveProperty', 'toHaveReturned', 'toHaveReturnedTimes', 'toHaveReturnedWith', 'toMatch', 'toMatchInlineSnapshot', 'toMatchObject', 'toMatchSnapshot', 'toReturn', 'toReturnTimes', 'toReturnWith', 'toStrictEqual', 'toThrow', 'toThrowError', 'toThrowErrorMatchingInlineSnapshot', 'toThrowErrorMatchingSnapshot'] as const;
|
||||
|
||||
const stubExpect = stubObj('expect', EXPECT_KEYS);
|
||||
const stubExpectFn = stubObj('expect(...)', EXPECT_KEYS_FN, {
|
||||
toThrowError: 'expect(...).toThrow'
|
||||
});
|
||||
const stubExpectFnRejects = stubObj('expect(...).rejects', EXPECT_KEYS_FN, {
|
||||
toThrowError: 'expect(...).rejects.toThrow'
|
||||
});
|
||||
const stubExpectFnResolves = stubObj('expect(...).resolves', EXPECT_KEYS_FN);
|
||||
const stubExpectFnNot = stubObj('expect(...).not', EXPECT_KEYS_FN, {
|
||||
toBeFalsy: 'expect(...).toBeTruthy',
|
||||
toBeTruthy: 'expect(...).toBeFalsy',
|
||||
toThrowError: 'expect(...).not.toThrow'
|
||||
});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* A helper that wraps a matching function in an ExpectMatcher. This is (currently)
|
||||
* only used/checked for in the calledWith* helpers
|
||||
*
|
||||
* TODO We don't use it in polkadot-js, but a useful enhancement could be for
|
||||
* any of the to* expectations to detect and use those. An example of useful code
|
||||
* in that case:
|
||||
*
|
||||
* ```js
|
||||
* expect({
|
||||
* a: 'blah',
|
||||
* b: 3
|
||||
* }).toEqual(
|
||||
* expect.objectContaining({ b: 3 })
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* An example of matcher use can be seen in the isCalledWith loops
|
||||
*/
|
||||
class Matcher {
|
||||
assertMatch: AssertMatchFn;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor (assertFn: (value: any, check: any) => void, check?: unknown) {
|
||||
this.assertMatch = (value) => assertFn(value, check);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Asserts that the input value is non-nullish
|
||||
*/
|
||||
function assertNonNullish (value: unknown): void {
|
||||
assert.ok(value !== null && value !== undefined, `Expected non-nullish value, found ${value as string}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* A helper that checks a single call arguments, which may include the
|
||||
* use of matchers. This is used in finding any call or checking a specific
|
||||
* call
|
||||
*/
|
||||
function assertCallHasArgs (call: { arguments: unknown[] } | undefined, args: unknown[]): void {
|
||||
assert.ok(call && args.length === call.arguments?.length, 'Number of arguments does not match');
|
||||
|
||||
args.forEach((arg, i) => assertMatch(call.arguments[i], arg));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* A helper that checks for the first instance of a match on the actual call
|
||||
* arguments (this extracts the toHaveBeenCalledWith logic)
|
||||
*/
|
||||
function assertSomeCallHasArgs (value: Mocked | undefined, args: unknown[]) {
|
||||
assert.ok(value?.mock?.calls.some((call) => {
|
||||
try {
|
||||
assertCallHasArgs(call, args);
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}), 'No call found matching arguments');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Asserts that the value is either (equal deep) or matches the matcher (if supplied)
|
||||
*/
|
||||
function assertMatch (value: unknown, check: unknown): void {
|
||||
check instanceof Matcher
|
||||
? check.assertMatch(value)
|
||||
: Array.isArray(check)
|
||||
? assertMatchArr(value, check)
|
||||
: check && typeof check === 'object'
|
||||
? assertMatchObj(value, check)
|
||||
: assert.deepStrictEqual(value, check);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* A helper to match the supplied array check against the resulting array
|
||||
*
|
||||
* @param {unknown} value
|
||||
* @param {unknown[]} check
|
||||
*/
|
||||
function assertMatchArr (value: unknown, check: unknown[]): void {
|
||||
assert.ok(value && Array.isArray(value), `Expected array value, found ${typeof value}`);
|
||||
assert.ok(value.length === check.length, `Expected array with ${check.length} entries, found ${value.length}`);
|
||||
|
||||
check.forEach((other, i) => assertMatch(value[i], other));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* A helper to match the supplied fields against the resulting object
|
||||
*/
|
||||
function assertMatchObj (value: unknown, check: object): void {
|
||||
assert.ok(value && typeof value === 'object', `Expected object value, found ${typeof value}`);
|
||||
|
||||
Object
|
||||
.entries(check)
|
||||
.forEach(([key, other]) => assertMatch((value as Record<string, unknown>)[key], other));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* A helper to match a string value against another string or regex
|
||||
*/
|
||||
function assertMatchStr (value: unknown, check: string | RegExp): void {
|
||||
assert.ok(typeof value === 'string', `Expected string value, found ${typeof value}`);
|
||||
|
||||
typeof check === 'string'
|
||||
? assert.strictEqual(value, check)
|
||||
: assert.match(value, check);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* A helper to check the type of a specific value as used in the expect.any(Clazz) matcher
|
||||
*
|
||||
* @see https://github.com/facebook/jest/blob/a49c88610e49a3242576160740a32a2fe11161e1/packages/expect/src/asymmetricMatchers.ts#L103-L133
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
function assertInstanceOf (value: unknown, Clazz: Function): void {
|
||||
assert.ok(
|
||||
(Clazz === Array && Array.isArray(value)) ||
|
||||
(Clazz === BigInt && typeof value === 'bigint') ||
|
||||
(Clazz === Boolean && typeof value === 'boolean') ||
|
||||
(Clazz === Function && typeof value === 'function') ||
|
||||
(Clazz === Number && typeof value === 'number') ||
|
||||
(Clazz === Object && typeof value === 'object') ||
|
||||
(Clazz === String && typeof value === 'string') ||
|
||||
(Clazz === Symbol && typeof value === 'symbol') ||
|
||||
(value instanceof Clazz),
|
||||
`${value as string} is not an instance of ${Clazz.name}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* A helper to ensure that the supplied string/array does include the checker string.
|
||||
*
|
||||
* @param {string | unknown[]} value
|
||||
* @param {string} check
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
function assertIncludes (value: string | unknown[], [check, Clazz]: [string, Function]): void {
|
||||
assertInstanceOf(value, Clazz);
|
||||
assert.ok(value?.includes(check), `${value as string} does not include ${check}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the shimmed expect(...) function, including all .to* and .not.to*
|
||||
* functions. This is not comprehensive, rather is contains what we need to
|
||||
* make all polkadot-js usages pass
|
||||
**/
|
||||
export function expect () {
|
||||
const rootMatchers = {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
any: (Clazz: Function) => new Matcher(assertInstanceOf, Clazz),
|
||||
anything: () => new Matcher(assertNonNullish),
|
||||
arrayContaining: (check: string) => new Matcher(assertIncludes, [check, Array]),
|
||||
objectContaining: (check: object) => new Matcher(assertMatchObj, check),
|
||||
stringContaining: (check: string) => new Matcher(assertIncludes, [check, String]),
|
||||
stringMatching: (check: string | RegExp) => new Matcher(assertMatchStr, check)
|
||||
};
|
||||
|
||||
return {
|
||||
expect: enhanceObj(enhanceObj((value: unknown) =>
|
||||
enhanceObj({
|
||||
not: enhanceObj({
|
||||
toBe: (other: unknown) => assert.notStrictEqual(value, other),
|
||||
toBeDefined: () => assert.ok(value === undefined),
|
||||
toBeNull: (value: unknown) => assert.ok(value !== null),
|
||||
toBeUndefined: () => assert.ok(value !== undefined),
|
||||
toEqual: (other: unknown) => assert.notDeepEqual(value, other),
|
||||
toHaveBeenCalled: () => assert.ok(!(value as Mocked | undefined)?.mock?.calls.length),
|
||||
toThrow: (message?: RegExp | Error | string) => assert.doesNotThrow(value as () => unknown, message && { message } as Error)
|
||||
}, stubExpectFnNot),
|
||||
rejects: enhanceObj({
|
||||
toThrow: (message?: RegExp | Error | string) => assert.rejects(value as Promise<unknown>, message && { message } as Error)
|
||||
}, stubExpectFnRejects),
|
||||
resolves: enhanceObj({}, stubExpectFnResolves),
|
||||
toBe: (other: unknown) => assert.strictEqual(value, other),
|
||||
toBeDefined: () => assert.ok(value !== undefined),
|
||||
toBeFalsy: () => assert.ok(!value),
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
toBeInstanceOf: (Clazz: Function) => assertInstanceOf(value, Clazz),
|
||||
toBeNull: (value: unknown) => assert.ok(value === null),
|
||||
toBeTruthy: () => assert.ok(value),
|
||||
toBeUndefined: () => assert.ok(value === undefined),
|
||||
toEqual: (other: unknown) => assert.deepEqual(value, other),
|
||||
toHaveBeenCalled: () => assert.ok((value as Mocked | undefined)?.mock?.calls.length),
|
||||
toHaveBeenCalledTimes: (count: number) => assert.equal((value as Mocked | undefined)?.mock?.calls.length, count),
|
||||
toHaveBeenCalledWith: (...args: unknown[]) => assertSomeCallHasArgs((value as Mocked | undefined), args),
|
||||
toHaveBeenLastCalledWith: (...args: unknown[]) => assertCallHasArgs((value as Mocked | undefined)?.mock?.calls.at(-1), args),
|
||||
toHaveLength: (length: number) => assert.equal((value as unknown[] | undefined)?.length, length),
|
||||
toMatch: (check: string | RegExp) => assertMatchStr(value, check),
|
||||
toMatchObject: (check: object) => assertMatchObj(value, check),
|
||||
toThrow: (message?: RegExp | Error | string) => assert.throws(value as () => unknown, message && { message } as Error)
|
||||
}, stubExpectFn), rootMatchers), stubExpect)
|
||||
};
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { browser } from './browser.js';
|
||||
import { expect } from './expect.js';
|
||||
import { jest } from './jest.js';
|
||||
import { lifecycle } from './lifecycle.js';
|
||||
import { suite } from './suite.js';
|
||||
|
||||
/**
|
||||
* Exposes the jest-y environment via globals.
|
||||
*/
|
||||
export function exposeEnv (isBrowser: boolean): void {
|
||||
[expect, jest, lifecycle, suite, isBrowser && browser].forEach((env) => {
|
||||
env && Object
|
||||
.entries(env())
|
||||
.forEach(([key, fn]) => {
|
||||
globalThis[key as 'undefined'] ??= fn;
|
||||
});
|
||||
});
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
describe('jest', () => {
|
||||
it('has been enhanced', () => {
|
||||
expect(jest.setTimeout).toBeDefined();
|
||||
});
|
||||
|
||||
describe('.fn', () => {
|
||||
it('works on .toHaveBeenCalled', () => {
|
||||
const mock = jest.fn(() => 3);
|
||||
|
||||
expect(mock).not.toHaveBeenCalled();
|
||||
expect(mock()).toBe(3);
|
||||
expect(mock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('works on .toHaveBeenCalledTimes', () => {
|
||||
const mock = jest.fn(() => 3);
|
||||
|
||||
expect(mock()).toBe(3);
|
||||
expect(mock()).toBe(3);
|
||||
|
||||
expect(mock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('works with .toHaveBeenCalledWith', () => {
|
||||
const sum = jest.fn((a: number, b: number) => a + b);
|
||||
|
||||
expect(sum(1, 2)).toBe(3);
|
||||
|
||||
expect(sum).toHaveBeenCalledWith(1, 2);
|
||||
|
||||
expect(sum(2, 3)).toBe(5);
|
||||
expect(sum(4, 5)).toBe(9);
|
||||
|
||||
expect(sum).toHaveBeenCalledWith(1, 2);
|
||||
expect(sum).toHaveBeenCalledWith(2, 3);
|
||||
expect(sum).toHaveBeenCalledWith(4, 5);
|
||||
|
||||
expect(sum).toHaveBeenLastCalledWith(4, 5);
|
||||
});
|
||||
|
||||
it('works with .toHaveBeenCalledWith & expect.objectContaining', () => {
|
||||
const test = jest.fn((a: unknown, b: unknown) => !!a && !!b);
|
||||
|
||||
test({ a: 123, b: 'test' }, null);
|
||||
|
||||
expect(test).toHaveBeenLastCalledWith({ a: 123, b: 'test' }, null);
|
||||
expect(test).toHaveBeenLastCalledWith(expect.objectContaining({}), null);
|
||||
expect(test).toHaveBeenLastCalledWith(expect.objectContaining({ a: 123 }), null);
|
||||
expect(test).toHaveBeenLastCalledWith(expect.objectContaining({ b: 'test' }), null);
|
||||
});
|
||||
|
||||
it('allows .mockImplementation', () => {
|
||||
const mock = jest.fn(() => 3);
|
||||
|
||||
expect(mock()).toBe(3);
|
||||
|
||||
mock.mockImplementation(() => 4);
|
||||
|
||||
expect(mock()).toBe(4);
|
||||
expect(mock()).toBe(4);
|
||||
});
|
||||
|
||||
it('allows .mockImplementationOnce', () => {
|
||||
const mock = jest.fn(() => 3);
|
||||
|
||||
expect(mock()).toBe(3);
|
||||
|
||||
mock.mockImplementationOnce(() => 4);
|
||||
|
||||
expect(mock()).toBe(4);
|
||||
expect(mock()).toBe(3);
|
||||
});
|
||||
|
||||
it('allows resets', () => {
|
||||
const mock = jest.fn(() => 3);
|
||||
|
||||
expect(mock).not.toHaveBeenCalled();
|
||||
expect(mock()).toBe(3);
|
||||
expect(mock).toHaveBeenCalled();
|
||||
|
||||
mock.mockReset();
|
||||
|
||||
expect(mock).not.toHaveBeenCalled();
|
||||
expect(mock()).toBe(3);
|
||||
expect(mock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.spyOn', () => {
|
||||
it('works on .toHaveBeenCalled', () => {
|
||||
const obj = {
|
||||
add: (a: number, b: number) => a + b
|
||||
};
|
||||
const spy = jest.spyOn(obj, 'add');
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
expect(obj.add(1, 2)).toBe(3);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('allows .mockImplementation', () => {
|
||||
const obj = {
|
||||
add: (a: number, b: number) => a + b
|
||||
};
|
||||
const spy = jest.spyOn(obj, 'add');
|
||||
|
||||
expect(obj.add(1, 2)).toBe(3);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
spy.mockImplementation(() => 4);
|
||||
|
||||
expect(obj.add(1, 2)).toBe(4);
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(obj.add(1, 2)).toBe(4);
|
||||
expect(spy).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('allows .mockImplementationOnce', () => {
|
||||
const obj = {
|
||||
add: (a: number, b: number) => a + b
|
||||
};
|
||||
const spy = jest.spyOn(obj, 'add');
|
||||
|
||||
expect(obj.add(1, 2)).toBe(3);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
spy.mockImplementationOnce(() => 4);
|
||||
|
||||
expect(obj.add(1, 2)).toBe(4);
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(obj.add(1, 2)).toBe(3);
|
||||
expect(spy).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('allows resets', () => {
|
||||
const obj = {
|
||||
add: (a: number, b: number) => a + b
|
||||
};
|
||||
const spy = jest.spyOn(obj, 'add');
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
expect(obj.add(1, 2)).toBe(3);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
spy.mockReset();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
expect(obj.add(1, 2)).toBe(3);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('allows restores', () => {
|
||||
const obj = {
|
||||
add: (a: number, b: number) => a + b
|
||||
};
|
||||
const spy = jest.spyOn(obj, 'add');
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
expect(obj.add(1, 2)).toBe(3);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyFn, WithMock } from '../types.js';
|
||||
|
||||
import { mock } from 'node:test';
|
||||
|
||||
import { enhanceObj, stubObj, warnObj } from '../util.js';
|
||||
|
||||
// logged via Object.keys(jest).sort()
|
||||
const JEST_KEYS_STUB = ['advanceTimersByTime', 'advanceTimersToNextTimer', 'autoMockOff', 'autoMockOn', 'clearAllMocks', 'clearAllTimers', 'createMockFromModule', 'deepUnmock', 'disableAutomock', 'doMock', 'dontMock', 'enableAutomock', 'fn', 'genMockFromModule', 'getRealSystemTime', 'getSeed', 'getTimerCount', 'isEnvironmentTornDown', 'isMockFunction', 'isolateModules', 'isolateModulesAsync', 'mock', 'mocked', 'now', 'replaceProperty', 'requireActual', 'requireMock', 'resetAllMocks', 'resetModules', 'restoreAllMocks', 'retryTimes', 'runAllImmediates', 'runAllTicks', 'runAllTimers', 'runOnlyPendingTimers', 'setMock', 'setSystemTime', 'setTimeout', 'spyOn', 'unmock', 'unstable_mockModule', 'useFakeTimers', 'useRealTimers'] as const;
|
||||
|
||||
const JEST_KEYS_WARN = ['setTimeout'] as const;
|
||||
|
||||
// logged via Object.keys(jest.fn()).sort()
|
||||
const MOCK_KEYS_STUB = ['_isMockFunction', 'getMockImplementation', 'getMockName', 'mock', 'mockClear', 'mockImplementation', 'mockImplementationOnce', 'mockName', 'mockRejectedValue', 'mockRejectedValueOnce', 'mockReset', 'mockResolvedValue', 'mockResolvedValueOnce', 'mockRestore', 'mockReturnThis', 'mockReturnValue', 'mockReturnValueOnce', 'withImplementation'] as const;
|
||||
|
||||
const jestStub = stubObj('jest', JEST_KEYS_STUB);
|
||||
const jestWarn = warnObj('jest', JEST_KEYS_WARN);
|
||||
const mockStub = stubObj('jest.fn()', MOCK_KEYS_STUB);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* This adds the mockReset and mockRestore functionality to any
|
||||
* spy or mock function
|
||||
**/
|
||||
function extendMock <F extends AnyFn> (mocked: WithMock<F>) {
|
||||
// We use the node:test mock here for casting below - however we
|
||||
// don't want this in any method signature since this is a private
|
||||
// types export, which could get us in "some" trouble
|
||||
//
|
||||
// Effectively the casts below ensure that our WithMock<*> aligns
|
||||
// on a high-level to what we use via private type...
|
||||
const spy = (mocked as unknown as ReturnType<typeof mock['fn']>);
|
||||
|
||||
return enhanceObj(enhanceObj(mocked, {
|
||||
mockImplementation: <F extends AnyFn> (fn: F): void => {
|
||||
spy.mock.mockImplementation(fn);
|
||||
},
|
||||
mockImplementationOnce: <F extends AnyFn> (fn: F): void => {
|
||||
spy.mock.mockImplementationOnce(fn);
|
||||
},
|
||||
mockReset: (): void => {
|
||||
spy.mock.resetCalls();
|
||||
},
|
||||
mockRestore: (): void => {
|
||||
spy.mock.restore();
|
||||
}
|
||||
}), mockStub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the jest object. This is certainly not extensive, and probably
|
||||
* not quite meant to be (never say never). Rather this adds the functionality
|
||||
* that we use in the polkadot-js projects.
|
||||
**/
|
||||
export function jest () {
|
||||
return {
|
||||
jest: enhanceObj(enhanceObj({
|
||||
fn: <F extends AnyFn> (fn?: F) => extendMock<F>(mock.fn(fn)),
|
||||
restoreAllMocks: () => {
|
||||
mock.reset();
|
||||
},
|
||||
spyOn: <F extends AnyFn> (obj: object, key: string) => extendMock<F>(mock.method(obj, key as keyof typeof obj))
|
||||
}, jestWarn), jestStub)
|
||||
};
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { after, afterEach, before, beforeEach } from 'node:test';
|
||||
|
||||
/**
|
||||
* This ensures that the before/after functions are exposed
|
||||
**/
|
||||
export function lifecycle () {
|
||||
return {
|
||||
after,
|
||||
afterAll: after,
|
||||
afterEach,
|
||||
before,
|
||||
beforeAll: before,
|
||||
beforeEach
|
||||
};
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
describe('describe()', () => {
|
||||
// eslint-disable-next-line jest/no-focused-tests
|
||||
describe.only('.only', () => {
|
||||
it('runs this one', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.skip', () => {
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
describe.skip('.only (.skip)', () => {
|
||||
it('skips inside .only', () => {
|
||||
expect(true).toBe(true);
|
||||
|
||||
throw new Error('FATAL: This should not run');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('it()', () => {
|
||||
it('has been enhanced', () => {
|
||||
expect(it.todo).toBeDefined();
|
||||
});
|
||||
|
||||
it('allows promises', async () => {
|
||||
expect(await Promise.resolve(true)).toBe(true);
|
||||
});
|
||||
|
||||
describe('.only', () => {
|
||||
// eslint-disable-next-line jest/no-focused-tests
|
||||
it.only('runs this test when .only is used', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('skips when .skip is used', () => {
|
||||
expect(true).toBe(true);
|
||||
|
||||
throw new Error('FATAL: This should not run');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.skip', () => {
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('skips when .skip is used', () => {
|
||||
expect(true).toBe(true);
|
||||
|
||||
throw new Error('FATAL: This should not run');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.todo', () => {
|
||||
it.todo('marks as a todo when .todo is used', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
import { enhanceObj } from '../util.js';
|
||||
|
||||
interface WrapOpts {
|
||||
only?: boolean;
|
||||
skip?: boolean;
|
||||
todo?: boolean;
|
||||
}
|
||||
|
||||
type WrapFn = (name: string, options: { only?: boolean; skip?: boolean; timeout?: number; todo?: boolean; }, fn: () => void | Promise<void>) => void | Promise<void>;
|
||||
|
||||
const MINUTE = 60 * 1000;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Wraps either describe or it with relevant .only, .skip, .todo & .each helpers,
|
||||
* shimming it into a Jest-compatible environment.
|
||||
*
|
||||
* @param {} fn
|
||||
*/
|
||||
function createWrapper <T extends WrapFn> (fn: T, defaultTimeout: number) {
|
||||
const wrap = (opts: WrapOpts) => (name: string, exec: () => void | Promise<void>, timeout?: number) => fn(name, { ...opts, timeout: (timeout || defaultTimeout) }, exec) as unknown as void;
|
||||
|
||||
// Ensure that we have consistent helpers on the function. These are not consistently
|
||||
// applied accross all node:test versions, latest has all, so always apply ours.
|
||||
// Instead of node:test options for e.g. timeout, we provide a Jest-compatible signature
|
||||
return enhanceObj(wrap({}), {
|
||||
only: wrap({ only: true }),
|
||||
skip: wrap({ skip: true }),
|
||||
todo: wrap({ todo: true })
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that the describe and it functions match our actual usages.
|
||||
* This includes .only, .skip and .todo helpers (.each is not applied)
|
||||
**/
|
||||
export function suite () {
|
||||
return {
|
||||
describe: createWrapper(describe, 60 * MINUTE),
|
||||
it: createWrapper(it, 2 * MINUTE)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
module.exports = {};
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* eslint-disable no-var */
|
||||
|
||||
import type { expect } from './env/expect.js';
|
||||
import type { jest } from './env/jest.js';
|
||||
import type { lifecycle } from './env/lifecycle.js';
|
||||
import type { suite } from './env/suite.js';
|
||||
|
||||
type Expect = ReturnType<typeof expect>;
|
||||
|
||||
type Jest = ReturnType<typeof jest>;
|
||||
|
||||
type Lifecycle = ReturnType<typeof lifecycle>;
|
||||
|
||||
type Suite = ReturnType<typeof suite>;
|
||||
|
||||
declare global {
|
||||
var after: Lifecycle['after'];
|
||||
var afterAll: Lifecycle['afterAll'];
|
||||
var afterEach: Lifecycle['afterEach'];
|
||||
var before: Lifecycle['before'];
|
||||
var beforeAll: Lifecycle['beforeAll'];
|
||||
var beforeEach: Lifecycle['beforeEach'];
|
||||
var describe: Suite['describe'];
|
||||
var expect: Expect['expect'];
|
||||
var it: Suite['it'];
|
||||
var jest: Jest['jest'];
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
throw new Error('Use node --require @polkadot/dev-test/{node, browser} depending on the required environment');
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { exposeEnv } from './env/index.js';
|
||||
|
||||
exposeEnv(false);
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
|
||||
export const packageInfo = { name: '@polkadot/dev-test', path: 'auto', type: 'auto', version: '0.84.2' };
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type AnyFn = (...args: any[]) => any;
|
||||
|
||||
export type BaseObj = Record<string, unknown>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export type BaseFn = Function;
|
||||
|
||||
export type StubFn = (...args: unknown[]) => unknown;
|
||||
|
||||
// These basically needs to align with the ReturnType<typeof node:test:mock['fn']>
|
||||
// functions at least for the functionality that we are using: accessing calls &
|
||||
// managing the mock interface with resets and restores
|
||||
export type WithMock<F extends AnyFn> = F & {
|
||||
mock: {
|
||||
calls: {
|
||||
arguments: unknown[];
|
||||
}[];
|
||||
|
||||
mockImplementation: (fn: AnyFn) => void;
|
||||
mockImplementationOnce: (fn: AnyFn) => void;
|
||||
resetCalls: () => void;
|
||||
restore: () => void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { enhanceObj, stubObj, warnObj } from './util.js';
|
||||
|
||||
describe('enhanceObj', () => {
|
||||
it('extends objects with non-existing values', () => {
|
||||
const test = enhanceObj(
|
||||
enhanceObj(
|
||||
{ a: () => 1 },
|
||||
{ b: () => 2 }
|
||||
),
|
||||
{ c: () => 3 }
|
||||
);
|
||||
|
||||
expect(test.a()).toBe(1);
|
||||
expect(test.b()).toBe(2);
|
||||
expect(test.c()).toBe(3);
|
||||
});
|
||||
|
||||
it('does not override existing values', () => {
|
||||
const test = enhanceObj(
|
||||
enhanceObj(
|
||||
{ a: 0, b: () => 1 },
|
||||
{ a: () => 0, b: () => 2 }
|
||||
),
|
||||
{ c: () => 2 }
|
||||
);
|
||||
|
||||
expect(test.a).toBe(0);
|
||||
expect(test.b()).toBe(1);
|
||||
expect(test.c()).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stubObj', () => {
|
||||
it('has entries throwing for unimplemented values', () => {
|
||||
const test = stubObj('obj', ['a', 'b'] as const);
|
||||
|
||||
expect(
|
||||
() => test.b()
|
||||
).toThrow('obj.b has not been implemented');
|
||||
});
|
||||
|
||||
it('has entries throwing for unimplemented values (w/ alternatives)', () => {
|
||||
const test = stubObj('obj', ['a', 'b'] as const, { b: 'obj.a' });
|
||||
|
||||
expect(
|
||||
() => test.b()
|
||||
).toThrow('obj.b has not been implemented (Use obj.a instead)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('warnObj', () => {
|
||||
let spy: ReturnType<typeof jest.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
spy = jest.spyOn(console, 'warn');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('has entries warning on unimplemented', () => {
|
||||
const test = warnObj('obj', ['a', 'b'] as const);
|
||||
|
||||
test.b();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('obj.b has been implemented as a noop');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2017-2025 @polkadot/dev-test authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BaseFn, BaseObj, StubFn } from './types.js';
|
||||
|
||||
/**
|
||||
* Extends an existing object with the additional function if they
|
||||
* are not already existing.
|
||||
*/
|
||||
export function enhanceObj <T extends BaseObj | BaseFn, X> (obj: T, extra: X) {
|
||||
Object
|
||||
.entries(extra as Record<string, unknown>)
|
||||
.forEach(([key, value]) => {
|
||||
(obj as Record<string, unknown>)[key] ??= value;
|
||||
});
|
||||
|
||||
return obj as T & Omit<X, keyof T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* A helper to create a stub object based wite the stub creator supplied
|
||||
*/
|
||||
function createStub <N extends readonly string[]> (keys: N, creator: (key: string) => StubFn) {
|
||||
return keys.reduce<Record<string, StubFn>>((obj, key) => {
|
||||
obj[key] ??= creator(key);
|
||||
|
||||
return obj;
|
||||
}, {}) as unknown as { [K in N[number]]: StubFn };
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends a given object with the named functions if they do not
|
||||
* already exist on the object.
|
||||
*
|
||||
* @type {StubObjFn}
|
||||
*/
|
||||
export function stubObj <N extends readonly string[]> (objName: string, keys: N, alts?: Record<string, string>) {
|
||||
return createStub(keys, (key) => () => {
|
||||
const alt = alts?.[key];
|
||||
|
||||
throw new Error(`${objName}.${key} has not been implemented${alt ? ` (Use ${alt} instead)` : ''}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends a given object with the named functions if they do not
|
||||
* already exist on the object.
|
||||
*
|
||||
* @type {StubObjFn}
|
||||
*/
|
||||
export function warnObj <N extends readonly string[]> (objName: string, keys: N) {
|
||||
return createStub(keys, (key) => () => {
|
||||
console.warn(`${objName}.${key} has been implemented as a noop`);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"exclude": [
|
||||
"**/mod.ts",
|
||||
"src/**/*.spec.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"references": []
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src",
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.spec.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../dev-test/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user