UNPKG

creevey

Version:

Cross-browser screenshot testing tool for Storybook with fancy UI Runner

482 lines (402 loc) 19.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.calcStatus = calcStatus; exports.getTestPath = getTestPath; exports.getSuiteByPath = getSuiteByPath; exports.getTestByPath = getTestByPath; exports.getTestsByStoryId = getTestsByStoryId; exports.checkSuite = checkSuite; exports.treeifyTests = treeifyTests; exports.getCheckedTests = getCheckedTests; exports.updateTestStatus = updateTestStatus; exports.removeTests = removeTests; exports.filterTests = filterTests; exports.openSuite = openSuite; exports.flattenSuite = flattenSuite; exports.countTestsStatus = countTestsStatus; exports.getConnectionUrl = getConnectionUrl; exports.getImageUrl = getImageUrl; exports.getBorderSize = getBorderSize; exports.useLoadImages = useLoadImages; exports.useResizeObserver = useResizeObserver; exports.useApplyScale = useApplyScale; exports.useCalcScale = useCalcScale; exports.useTheme = useTheme; exports.setSearchParams = setSearchParams; exports.getTestPathFromSearch = getTestPathFromSearch; exports.useForceUpdate = useForceUpdate; var _theming = require("@storybook/theming"); var _qs = require("qs"); var _react = require("react"); var _types = require("../../types"); function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } var statusUpdatesMap = new Map([[undefined, /(unknown|success|failed|pending|running)/], ['unknown', /(success|failed|pending|running)/], ['success', /(failed|pending|running)/], ['failed', /(pending|running)/], ['pending', /running/]]); function makeEmptySuiteNode() { var path = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; return { path: path, skip: true, opened: false, checked: true, indeterminate: false, children: {} }; } function calcStatus(oldStatus, newStatus) { var _statusUpdatesMap$get; return newStatus && (_statusUpdatesMap$get = statusUpdatesMap.get(oldStatus)) !== null && _statusUpdatesMap$get !== void 0 && _statusUpdatesMap$get.test(newStatus) ? newStatus : oldStatus; } function getTestPath(test) { var browser = test.browser, testName = test.testName, storyPath = test.storyPath; return [].concat(_toConsumableArray(storyPath), [testName, browser]).filter(_types.isDefined); } function getSuiteByPath(suite, path) { return path.reduce(function (suiteOrTest, pathToken) { return (0, _types.isTest)(suiteOrTest) ? suiteOrTest : suiteOrTest === null || suiteOrTest === void 0 ? void 0 : suiteOrTest.children[pathToken]; }, suite); } function getTestByPath(suite, path) { var _getSuiteByPath; var test = (_getSuiteByPath = getSuiteByPath(suite, path)) !== null && _getSuiteByPath !== void 0 ? _getSuiteByPath : suite; return (0, _types.isTest)(test) ? test : null; } function getTestsByStoryId(suite, storyId) { return Object.values(suite.children).filter(_types.isDefined).flatMap(function (suiteOrTest) { if ((0, _types.isTest)(suiteOrTest)) return suiteOrTest.storyId === storyId ? suiteOrTest : []; return getTestsByStoryId(suiteOrTest, storyId); }).filter(_types.isDefined); } function checkTests(suiteOrTest, checked) { suiteOrTest.checked = checked; if (!(0, _types.isTest)(suiteOrTest)) { suiteOrTest.indeterminate = false; Object.values(suiteOrTest.children).filter(_types.isDefined).forEach(function (child) { return checkTests(child, checked); }); } } function updateChecked(suite) { var children = Object.values(suite.children).filter(_types.isDefined).filter(function (child) { return !child.skip; }); var checkedEvery = children.every(function (test) { return test.checked; }); var checkedSome = children.some(function (test) { return test.checked; }); var indeterminate = children.some(function (test) { return (0, _types.isTest)(test) ? false : test.indeterminate; }) || !checkedEvery && checkedSome; var checked = indeterminate || suite.checked == checkedEvery ? suite.checked : checkedEvery; suite.checked = checked; suite.indeterminate = indeterminate; } function checkSuite(suite, path, checked) { var subSuite = getSuiteByPath(suite, path); if (subSuite) checkTests(subSuite, checked); path.slice(0, -1).map(function (_, index, tokens) { return tokens.slice(0, tokens.length - index); }).forEach(function (parentPath) { var parentSuite = getSuiteByPath(suite, parentPath); if ((0, _types.isTest)(parentSuite)) return; if (parentSuite) updateChecked(parentSuite); }); updateChecked(suite); } function treeifyTests(testsById) { var rootSuite = makeEmptySuiteNode(); rootSuite.opened = true; Object.values(testsById).forEach(function (test) { if (!test) return; var _getTestPath$reverse = getTestPath(test).reverse(), _getTestPath$reverse2 = _toArray(_getTestPath$reverse), browser = _getTestPath$reverse2[0], testPath = _getTestPath$reverse2.slice(1); var lastSuite = testPath.reverse().reduce(function (suite, token) { var subSuite = suite.children[token] || makeEmptySuiteNode([].concat(_toConsumableArray(suite.path), [token])); subSuite.status = calcStatus(subSuite.status, test.status); if (!test.skip) subSuite.skip = false; if (!subSuite.skip) suite.skip = false; suite.children[token] = subSuite; suite.status = calcStatus(suite.status, subSuite.status); if ((0, _types.isTest)(subSuite)) { throw new Error("Suite and Test should not have same path '".concat(JSON.stringify(getTestPath(subSuite)), "'")); } return subSuite; }, rootSuite); lastSuite.children[browser] = _objectSpread(_objectSpread({}, test), {}, { checked: true }); }); return rootSuite; } function getCheckedTests(suite) { return Object.values(suite.children).filter(_types.isDefined).flatMap(function (suiteOrTest) { if ((0, _types.isTest)(suiteOrTest)) return suiteOrTest.checked ? suiteOrTest : []; if (!suiteOrTest.checked && !suiteOrTest.indeterminate) return []; return getCheckedTests(suiteOrTest); }); } function updateTestStatus(suite, path, update) { var _suite$children$title; var title = path.shift(); if (!title) return; var suiteOrTest = (_suite$children$title = suite.children[title]) !== null && _suite$children$title !== void 0 ? _suite$children$title : suite.children[title] = _objectSpread(_objectSpread({}, path.length == 0 ? update : makeEmptySuiteNode([].concat(_toConsumableArray(suite.path), [title]))), {}, { checked: suite.checked }); if ((0, _types.isTest)(suiteOrTest)) { var _test$results; var test = suiteOrTest; var skip = update.skip, status = update.status, results = update.results, approved = update.approved; if ((0, _types.isDefined)(skip)) test.skip = skip; if ((0, _types.isDefined)(status)) test.status = status; if ((0, _types.isDefined)(results)) test.results ? (_test$results = test.results).push.apply(_test$results, _toConsumableArray(results)) : test.results = results; if ((0, _types.isDefined)(approved)) Object.entries(approved).forEach(function (_ref) { var _ref2 = _slicedToArray(_ref, 2), image = _ref2[0], retry = _ref2[1]; return retry !== undefined && ((test.approved = test.approved || {})[image] = retry); }); } else { var subSuite = suiteOrTest; updateTestStatus(subSuite, path, update); } suite.skip = Object.values(suite.children).filter(_types.isDefined).map(function (_ref3) { var skip = _ref3.skip; return skip; }).every(Boolean); suite.status = Object.values(suite.children).filter(_types.isDefined).map(function (_ref4) { var status = _ref4.status; return status; }).reduce(calcStatus); } function removeTests(suite, path) { var _suiteOrTest$children; var title = path.shift(); if (!title) return; var suiteOrTest = suite.children[title]; if (suiteOrTest && !(0, _types.isTest)(suiteOrTest)) removeTests(suiteOrTest, path); if ((0, _types.isTest)(suiteOrTest) || Object.keys((_suiteOrTest$children = suiteOrTest === null || suiteOrTest === void 0 ? void 0 : suiteOrTest.children) !== null && _suiteOrTest$children !== void 0 ? _suiteOrTest$children : {}).length == 0) delete suite.children[title]; if (Object.keys(suite.children).length == 0) return; updateChecked(suite); suite.skip = Object.values(suite.children).filter(_types.isDefined).map(function (_ref5) { var skip = _ref5.skip; return skip; }).every(Boolean); suite.status = Object.values(suite.children).filter(_types.isDefined).map(function (_ref6) { var status = _ref6.status; return status; }).reduce(calcStatus); } function filterTests(suite, filter) { var status = filter.status, subStrings = filter.subStrings; if (!status && !subStrings.length) return suite; var filteredSuite = _objectSpread(_objectSpread({}, suite), {}, { children: {} }); Object.entries(suite.children).forEach(function (_ref7) { var _ref8 = _slicedToArray(_ref7, 2), title = _ref8[0], suiteOrTest = _ref8[1]; if (!suiteOrTest || suiteOrTest.skip) return; if (!status && subStrings.some(function (subString) { return title.toLowerCase().includes(subString); })) { filteredSuite.children[title] = suiteOrTest; } else if ((0, _types.isTest)(suiteOrTest)) { if (status && suiteOrTest.status && ['pending', 'running', status].includes(suiteOrTest.status)) filteredSuite.children[title] = suiteOrTest; } else { var filteredSubSuite = filterTests(suiteOrTest, filter); if (Object.keys(filteredSubSuite.children).length == 0) return; filteredSuite.children[title] = filteredSubSuite; } }); return filteredSuite; } function openSuite(suite, path, opened) { var subSuite = path.reduce(function (suiteOrTest, pathToken) { if (suiteOrTest && !(0, _types.isTest)(suiteOrTest)) { if (opened) suiteOrTest.opened = opened; return suiteOrTest.children[pathToken]; } }, suite); if (subSuite && !(0, _types.isTest)(subSuite)) subSuite.opened = opened; } function flattenSuite(suite) { if (!suite.opened) return []; return Object.entries(suite.children).flatMap(function (_ref9) { var _ref10 = _slicedToArray(_ref9, 2), title = _ref10[0], subSuite = _ref10[1]; return subSuite ? [{ title: title, suite: subSuite }].concat(_toConsumableArray((0, _types.isTest)(subSuite) ? [] : flattenSuite(subSuite))) : []; }); } function countTestsStatus(suite) { var successCount = 0; var failedCount = 0; var skippedCount = 0; var pendingCount = 0; var cases = Object.values(suite.children).filter(_types.isDefined); var suiteOrTest; while (suiteOrTest = cases.pop()) { if ((0, _types.isTest)(suiteOrTest)) { if (suiteOrTest.skip) skippedCount++; if (suiteOrTest.status === 'success') successCount++; if (suiteOrTest.status === 'failed') failedCount++; if (suiteOrTest.status === 'pending') pendingCount++; } else { cases.push.apply(cases, _toConsumableArray(Object.values(suiteOrTest.children).filter(_types.isDefined))); } } return { successCount: successCount, failedCount: failedCount, skippedCount: skippedCount, pendingCount: pendingCount }; } function getConnectionUrl() { return [window.location.hostname, typeof __CREEVEY_SERVER_PORT__ == 'undefined' ? window.location.port : __CREEVEY_SERVER_PORT__].filter(Boolean).join(':'); } function getImageUrl(path, imageName) { // path => [kind, story, test, browser] var browser = path.slice(-1)[0]; var imagesUrl = window.location.host ? "".concat(window.location.protocol, "//").concat(getConnectionUrl()).concat(window.location.pathname == '/' ? '/report' : window.location.pathname.split('/').slice(0, -1).join('/'), "/").concat(encodeURI(path.slice(0, -1).join('/'))) : encodeURI(path.slice(0, -1).join('/')); return imageName == browser ? imagesUrl : "".concat(imagesUrl, "/").concat(encodeURI(browser)); } function getBorderSize(element) { // NOTE Firefox returns empty string for `borderWidth` prop var borderSize = parseFloat(getComputedStyle(element).borderTopWidth); return Number.isNaN(borderSize) ? 0 : borderSize; } function useLoadImages(s1, s2, s3) { var _useState = (0, _react.useState)(false), _useState2 = _slicedToArray(_useState, 2), loaded = _useState2[0], setLoaded = _useState2[1]; (0, _react.useEffect)(function () { setLoaded(false); void Promise.all([s1, s2, s3].map(function (url) { return new Promise(function (resolve) { var image = document.createElement('img'); image.src = url; image.onload = resolve; image.onerror = resolve; }); })).then(function () { return setLoaded(true); }); }, [s1, s2, s3]); return loaded; } /** * Uses the ResizeObserver API to observe changes within the given HTML Element DOM Rect. * * @returns dimensions of element's content box (which means without paddings and border width) */ function useResizeObserver(elementRef, onResize) { var debounceTimeout = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 16; var observerRef = (0, _react.useRef)(null); (0, _react.useEffect)(function () { if (!elementRef.current) return; observerRef.current = new ResizeObserver(onResize); observerRef.current.observe(elementRef.current); return function () { var _observerRef$current; return (_observerRef$current = observerRef.current) === null || _observerRef$current === void 0 ? void 0 : _observerRef$current.disconnect(); }; }, [debounceTimeout, elementRef, onResize]); } function useApplyScale(imageRef, scale, dependency) { (0, _react.useLayoutEffect)(function () { if (!imageRef.current) return; var image = imageRef.current; var borderSize = getBorderSize(image); image.style.height = "".concat(image.naturalHeight * scale + borderSize * 2, "px"); }, [imageRef, scale, dependency]); } function useCalcScale(diffImageRef, loaded) { var _useState3 = (0, _react.useState)(1), _useState4 = _slicedToArray(_useState3, 2), scale = _useState4[0], setScale = _useState4[1]; var calcScale = (0, _react.useCallback)(function () { var diffImage = diffImageRef.current; if (!diffImage || !loaded) return setScale(1); var borderSize = getBorderSize(diffImage); var ratio = (diffImage.getBoundingClientRect().width - borderSize * 2) / diffImage.naturalWidth; setScale(Math.min(1, ratio)); }, [diffImageRef, loaded]); useResizeObserver(diffImageRef, calcScale); (0, _react.useLayoutEffect)(calcScale, [calcScale]); return scale; } var CREEVEY_THEME = 'Creevey_theme'; function isTheme(theme) { return (0, _types.isDefined)(theme) && Object.prototype.hasOwnProperty.call(_theming.themes, theme); } function initialTheme() { var theme = localStorage.getItem(CREEVEY_THEME); return isTheme(theme) ? theme : 'light'; } function useTheme() { var _useState5 = (0, _react.useState)(initialTheme()), _useState6 = _slicedToArray(_useState5, 2), theme = _useState6[0], setTheme = _useState6[1]; (0, _react.useEffect)(function () { localStorage.setItem(CREEVEY_THEME, theme); }, [theme]); return [theme, setTheme]; } function setSearchParams(testPath) { var pageUrl = "?".concat((0, _qs.stringify)({ testPath: testPath })); window.history.pushState({ testPath: testPath }, '', pageUrl); } function getTestPathFromSearch() { var _parse = (0, _qs.parse)(window.location.search.slice(1)), testPath = _parse.testPath; //@ts-expect-error: This expression is not callable. if (Array.isArray(testPath) && testPath.every(function (token) { return typeof token == 'string'; })) { return testPath; } return []; } function useForceUpdate() { var _useState7 = (0, _react.useState)({}), _useState8 = _slicedToArray(_useState7, 2), update = _useState8[1]; return (0, _react.useCallback)(function () { return update({}); }, []); }