@johnf/react-native-owl
Version:
Visual regression testing for React Native
200 lines (190 loc) • 6.83 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.waitForWebSocket = exports.patchReact = exports.initClient = exports.handleMessage = exports.applyJsxChildrenElementTracking = exports.applyElementTracking = void 0;
var _react = _interopRequireDefault(require("react"));
var _reactNative = require("react-native");
var _logger = require("../logger.js");
var _constants = require("./constants.js");
var _websocket = require("./websocket.js");
var _trackedElements = require("./trackedElements.js");
var _handleAction = require("./handleAction.js");
require("../NativeOwl.js");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
// NOTE: On new arch the module isn't autoloaded
const logger = new _logger.Logger(true);
let isReactUpdating = true;
let owlClient;
const initClient = () => {
logger.info('[OWL - Client] Initialising OWL client');
patchReact();
waitForWebSocket();
};
/**
* Based on an elements props, store element tracking data and return updated props
*/
exports.initClient = initClient;
const applyElementTracking = (props, isJsx = false) => {
if (isJsx) {
applyJsxChildrenElementTracking(props);
}
const testID = props?.testID;
const returnProps = {
...props,
showsHorizontalScrollIndicator: false,
showsVerticalScrollIndicator: false
};
if (!testID) {
return returnProps;
}
const existingTrackedElement = (0, _trackedElements.get)(testID);
const ref = props?.ref || existingTrackedElement?.ref || /*#__PURE__*/_react.default.createRef();
const trackData = {
ref: existingTrackedElement?.ref || ref,
onPress: existingTrackedElement?.onPress || props?.onPress,
onLongPress: existingTrackedElement?.onLongPress || props?.onLongPress,
onChangeText: existingTrackedElement?.onChangeText || props?.onChangeText
};
(0, _trackedElements.add)(logger, testID, trackData);
return {
...returnProps,
ref
};
};
/**
* To get access to the prop callbacks when the element is created, we need to check the children
*/
exports.applyElementTracking = applyElementTracking;
const applyJsxChildrenElementTracking = props => {
if (props.children && Array.isArray(props.children)) {
props.children.forEach(child => {
const testID = child?.props?.testID;
if (!testID) {
return;
}
const existingTrackedElement = (0, _trackedElements.get)(testID);
const ref = child?.props?.ref || existingTrackedElement?.ref || /*#__PURE__*/_react.default.createRef();
const trackData = {
ref: existingTrackedElement?.ref || ref,
onPress: existingTrackedElement?.onPress || child?.props?.onPress,
onLongPress: existingTrackedElement?.onLongPress || child?.props?.onLongPress,
onChangeText: existingTrackedElement?.onChangeText || child?.props?.onChangeText
};
(0, _trackedElements.add)(logger, testID, trackData);
});
}
};
/**
* We patch react so that we can maintain a list of elements that have testID's
* We can then use this list to find the element when we receive an action
*/
exports.applyJsxChildrenElementTracking = applyJsxChildrenElementTracking;
const patchReact = () => {
const originalReactCreateElement = _react.default.createElement;
let automateTimeout;
if (parseInt(_react.default.version.split('.')?.[0] || '0', 10) >= 18) {
const jsxRuntime = require('react/jsx-runtime');
const origJsx = jsxRuntime.jsx;
// @ts-ignore
jsxRuntime.jsx = (type, config, maybeKey) => {
const newProps = applyElementTracking(config, true);
clearTimeout(automateTimeout);
automateTimeout = setTimeout(() => {
isReactUpdating = false;
}, _constants.CHECK_INTERVAL);
isReactUpdating = true;
return origJsx(type, newProps, maybeKey);
};
}
// @ts-ignore
_react.default.createElement = (type, props, ...children) => {
const newProps = applyElementTracking(props);
clearTimeout(automateTimeout);
automateTimeout = setTimeout(() => {
isReactUpdating = false;
}, _constants.CHECK_INTERVAL);
isReactUpdating = true;
return originalReactCreateElement(type, newProps, ...children);
};
};
/**
* The app might launch before the OWL server starts, so we need to keep trying...
*/
exports.patchReact = patchReact;
const waitForWebSocket = async () => {
try {
owlClient = await (0, _websocket.initWebSocket)(logger, _reactNative.Platform.OS === 'android' ? 'android' : 'ios', handleMessage);
logger.info('[OWL - Websocket] Connection established');
} catch {
setTimeout(waitForWebSocket, _constants.SOCKET_WAIT_TIMEOUT);
}
};
/**
* When we receive a message, we need to find the element that corresponds to the testID,
* then attempt to handle the requested action on it.
*/
exports.waitForWebSocket = waitForWebSocket;
const handleMessage = async message => {
const socketEvent = JSON.parse(message);
const testID = socketEvent.testID;
let element;
try {
element = await getElementByTestId(testID);
} catch (error) {
sendNotFound(testID);
}
if (element) {
try {
if (socketEvent.type === 'ACTION') {
(0, _handleAction.handleAction)(logger, testID, element, socketEvent.action, socketEvent.value);
setTimeout(sendDone, 1000);
} else {
sendDone();
}
} catch (error) {
let message = 'Unknown error';
if (error instanceof Error) {
message = error.message;
}
sendError(testID, message);
}
}
};
exports.handleMessage = handleMessage;
const sendEvent = event => owlClient.send(JSON.stringify(event));
const sendNotFound = testID => sendEvent({
type: 'NOT_FOUND',
testID
});
const sendDone = () => sendEvent({
type: 'DONE'
});
const sendError = (testID, message) => sendEvent({
type: 'ERROR',
testID,
message
});
/**
* This function resolves the tracked element by its testID, so that we can handle events on it.
* If the element is not immedietly available, we wait for it to be available for some time.
*/
const getElementByTestId = async testID => new Promise((resolve, reject) => {
logger.info(`[OWL - Client] Looking for Element with testID ${testID}`);
const rejectTimeout = setTimeout(() => {
logger.error(`[OWL - Client] ❌ not found`);
clearInterval(checkInterval);
reject(new Error(`Element with testID ${testID} not found`));
}, _constants.MAX_CHECK_TIMEOUT);
const checkInterval = setInterval(() => {
const element = (0, _trackedElements.get)(testID);
if (isReactUpdating || !element) {
return;
}
logger.info(`[OWL - Client] ✓ found`);
clearInterval(checkInterval);
clearTimeout(rejectTimeout);
resolve(element);
}, _constants.CHECK_INTERVAL);
});
//# sourceMappingURL=client.js.map