@lwc/jest-preset
Version:
Jest preset configuration and stubs to help test LWC
172 lines (143 loc) • 6.09 kB
JavaScript
// TODO: remove after JSDOM merges this PR: https://github.com/jsdom/jsdom/pull/3586
require('./aria-reflection-polyfill');
const config = global['lwc-jest'] || {};
const { nativeShadow } = config;
if (!nativeShadow) {
if (
ShadowRoot.prototype.constructor.toString().includes('function SyntheticShadowRoot') ||
EventTarget.prototype.addEventListener
.toString()
.includes('function patchedAddEventListener')
) {
throw new Error(
'@lwc/synthetic-shadow is being loaded twice. Please examine your jest/jsdom configuration.'
);
}
require('@lwc/synthetic-shadow');
}
// Provides temporary backward compatibility for wire-protocol reform: lwc > 1.5.0
global.wireAdaptersRegistryHack = new Map();
let originalRegisterDecorators;
function isValidWireAdapter(adapter) {
let isValid = false;
if (typeof adapter === 'function') {
// lets check, if it is a valid adapter
try {
const adapterInstance = new adapter(() => {});
isValid =
typeof adapterInstance.connect === 'function' &&
typeof adapterInstance.update === 'function' &&
typeof adapterInstance.disconnect === 'function';
} catch (e) {
isValid = false;
}
}
return isValid;
}
/**
* Returns a wire adapter mock in the shape of:
*
* fn : Used when adapter is invoked imperatively. It proxies to the original adapter function, if is callable.
* fn.adapter : A valid wire adapter class, consumable by @lwc/engine-dom.
* If the @originalAdapter.adapter or @originalAdapter is a valid wire adapter class, fn.adapter will
* act as a proxy on it until a spy is attached.
*
* @param originalAdapter
* @returns {function(...[*]): *}
*/
function createWireAdapterMockClass(originalAdapter) {
const noopAdapter = {
connect() {},
update() {},
disconnect() {},
};
let baseAdapter;
let baseAdapterFn = () => {};
const spies = [];
if (Object.prototype.hasOwnProperty.call(originalAdapter, 'adapter')) {
// Is more likely that the originalAdapter was registered with the wire service
// .adapter is the one that the engine will use, lets make it our base adapter
baseAdapter = originalAdapter.adapter;
} else if (isValidWireAdapter(originalAdapter)) {
// it may be the case that original adapter is a valid one, if is the case, lets use it as our base adapter
baseAdapter = originalAdapter;
}
if (typeof originalAdapter === 'function') {
// Mostly used in apex methods
baseAdapterFn = originalAdapter;
}
// Support for adapters to be called imperatively, mainly for apex.
const newAdapterMock = function (...args) {
return baseAdapterFn.call(this, ...args);
};
newAdapterMock.adapter = class WireAdapterMock {
constructor(dataCallback) {
// if a test is spying these adapter, it means is overriding the implementation
this._originalAdapter =
spies.length === 0 && baseAdapter ? new baseAdapter(dataCallback) : noopAdapter;
this._dataCallback = dataCallback;
spies.forEach((spy) => spy.createInstance(this));
}
connect() {
spies.forEach((spy) => spy.connect(this));
this._originalAdapter.connect();
}
update(config) {
spies.forEach((spy) => spy.update(this, config));
this._originalAdapter.update(config);
}
disconnect() {
spies.forEach((spy) => spy.disconnect(this));
this._originalAdapter.disconnect();
}
emit(value) {
this._dataCallback(value);
}
static spyAdapter(spy) {
// this function is meant to be used by wire-service-jest-util library.
// When this is used, register* was called, thus replacing the wire behaviour.
// As consequence, it will stop calling the originalAdapter on new instances.
spies.push(spy);
}
};
return newAdapterMock;
}
function overriddenRegisterDecorators(Ctor, decoratorsMeta) {
const wire = decoratorsMeta.wire || {};
Object.keys(wire).forEach((adapterName) => {
const adapter = wire[adapterName].adapter;
let wireAdapterMock = global.wireAdaptersRegistryHack.get(adapter);
if (!wireAdapterMock) {
// Checking if the adapter is extensible, since with the wire reform,
// the adapterId must be:
// a) An extensible object (so backward compatibility is provided via register)
// b) A valid class.
if (!Object.isExtensible(adapter)) {
// if we are in case of a) lets throw now so the developer knows that they need to migrate their
// adapters.
throw new TypeError('Invalid adapterId, it must be extensible.');
}
// Lets create a whole replacement for this adapter.
wireAdapterMock = createWireAdapterMockClass(adapter);
global.wireAdaptersRegistryHack.set(adapter, wireAdapterMock);
}
// we are entirely replacing the wire adapter with one that can be spied on.
wire[adapterName].adapter = wireAdapterMock;
});
return originalRegisterDecorators(Ctor, decoratorsMeta);
}
function installRegisterDecoratorsTrap(lwc) {
const originalDescriptor = Object.getOwnPropertyDescriptor(lwc, 'registerDecorators');
if (originalDescriptor.value === overriddenRegisterDecorators) {
return;
}
originalRegisterDecorators = originalDescriptor.value;
const newDescriptor = {
...originalDescriptor,
value: overriddenRegisterDecorators,
};
Object.defineProperty(lwc, 'registerDecorators', newDescriptor);
}
const lwc = require('@lwc/engine-dom');
installRegisterDecoratorsTrap(lwc);
require('./matchers/expect-throw-in-connected-callback');