UNPKG

jest-html

Version:

Preview Jest snapshots right in your browser

555 lines (469 loc) 18.9 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.saveAsBaseline = exports.getSnapshotSuite = exports.getFolder = exports.start = exports.configure = undefined; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_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"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _globby = require('globby'); var _globby2 = _interopRequireDefault(_globby); var _chokidar = require('chokidar'); var _chokidar2 = _interopRequireDefault(_chokidar); var _lodash = require('lodash.debounce'); var _lodash2 = _interopRequireDefault(_lodash); var _timm = require('timm'); var _storyboard = require('storyboard'); var _serializer = require('../serializer'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } var FOLDER_PATH_ATTR = '__folderPath'; var DIRTY_ATTR = '__dirty'; var DELETED_ATTR = '__deleted'; var _config = void 0; var _folderDict = {}; var _watchers = null; var configure = function configure(newConfig) { if (!_config) { _config = (0, _timm.clone)(newConfig); return; } _config = (0, _timm.merge)(_config, newConfig); }; var start = function () { var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() { var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref2$story = _ref2.story, story = _ref2$story === undefined ? _storyboard.mainStory : _ref2$story; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return loadCommonCss(story); case 2: _context.next = 4; return loadAllSnapshots(story); case 4: if (_config.watch) watchStart(story); broadcastSignal(); case 6: case 'end': return _context.stop(); } } }, _callee, undefined); })); return function start() { return _ref.apply(this, arguments); }; }(); // --------------------------------- // Snapshots // --------------------------------- var _snapshotSuiteDict = {}; var loadAllSnapshots = function () { var _ref3 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() { var story = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _storyboard.mainStory; var childStory, filePaths, commonCss, i; return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: childStory = story.child({ src: 'extractor', title: 'Refresh snapshots' }); _context2.prev = 1; _context2.next = 4; return (0, _globby2.default)(_config.snapshotPatterns); case 4: filePaths = _context2.sent; childStory.info('extractor', 'Reading snapshot files...'); commonCss = getCommonCss(); _snapshotSuiteDict = {}; i = 0; case 9: if (!(i < filePaths.length)) { _context2.next = 15; break; } _context2.next = 12; return loadSuite(filePaths[i], commonCss, childStory); case 12: i++; _context2.next = 9; break; case 15: buildFolderDict(childStory); childStory.debug('extractor', 'Snapshot tree:', { attach: _folderDict }); case 17: _context2.prev = 17; childStory.close(); return _context2.finish(17); case 20: case 'end': return _context2.stop(); } } }, _callee2, undefined, [[1,, 17, 20]]); })); return function loadAllSnapshots() { return _ref3.apply(this, arguments); }; }(); var updateSnapshotCss = function updateSnapshotCss() { var story = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _storyboard.mainStory; var commonCss = getCommonCss(); Object.keys(_snapshotSuiteDict).forEach(function (filePath) { var suite = _snapshotSuiteDict[filePath]; var suiteCss = getSuiteCss(filePath, story); var css = suiteCss != null ? (0, _timm.addLast)(commonCss, suiteCss) : commonCss; forEachSnapshot(suite, function (snapshot) { snapshot.css = css; // eslint-disable-line no-param-reassign }); }); }; var loadSuite = function () { var _ref4 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(filePath, commonCss, story) { var absPath, rawSnapshots, suiteCss, finalFilePath, prevSuite, suite, suiteDirty, nextIds, i, id, rawSnapshot, _rawSnapshot$split, _rawSnapshot$split2, snap, html, css, _snapshot, prevSnapshot, prevBaseline; return regeneratorRuntime.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: story.info('extractor', 'Processing ' + _storyboard.chalk.cyan.bold(filePath) + '...'); absPath = _path2.default.resolve(process.cwd(), filePath); rawSnapshots = loadSnapshot(absPath); suiteCss = getSuiteCss(filePath, story); finalFilePath = '-/' + filePath.normalize(); prevSuite = _snapshotSuiteDict[finalFilePath]; // $FlowFixMe suite = {}; suiteDirty = false; nextIds = Object.keys(rawSnapshots); for (i = 0; i < nextIds.length; i++) { id = nextIds[i]; rawSnapshot = rawSnapshots[id]; _rawSnapshot$split = rawSnapshot.split(_serializer.HTML_PREVIEW_SEPARATOR), _rawSnapshot$split2 = _slicedToArray(_rawSnapshot$split, 2), snap = _rawSnapshot$split2[0], html = _rawSnapshot$split2[1]; css = suiteCss != null ? (0, _timm.addLast)(commonCss, suiteCss) : commonCss; _snapshot = { id: id, snap: snap, html: html, css: css, dirty: false, deleted: false }; if (prevSuite && prevSuite[id]) { prevSnapshot = prevSuite[id]; prevBaseline = prevSnapshot.baseline; // Copy the previous baseline, if any if (prevBaseline != null) { _snapshot.baseline = prevBaseline; _snapshot.dirty = html !== prevBaseline.html || snap !== prevBaseline.snap; // Create a new baseline if the snapshot's SNAP or HTML have changed } else if (html !== prevSnapshot.html || snap !== prevSnapshot.snap) { _snapshot.baseline = { html: prevSnapshot.html, snap: prevSnapshot.snap }; _snapshot.dirty = true; } } suite[id] = _snapshot; suiteDirty = suiteDirty || _snapshot.dirty; } if (prevSuite != null) { forEachSnapshot(prevSuite, function (snapshot) { var id = snapshot.id; if (nextIds.indexOf(id) < 0) { snapshot.deleted = true; // eslint-disable-line no-param-reassign suite[id] = snapshot; suiteDirty = true; } }); } story.debug('extractor', 'Found ' + Object.keys(rawSnapshots).length + ' snapshots'); suite[FOLDER_PATH_ATTR] = _path2.default.dirname(finalFilePath); suite[DIRTY_ATTR] = suiteDirty; suite[DELETED_ATTR] = false; _snapshotSuiteDict[finalFilePath] = sortSnapshots(suite); case 16: case 'end': return _context3.stop(); } } }, _callee3, undefined); })); return function loadSuite(_x4, _x5, _x6) { return _ref4.apply(this, arguments); }; }(); var saveAsBaseline = function saveAsBaseline(filePath, id) { var suite = _snapshotSuiteDict[filePath.normalize()]; if (suite == null) return; var snapshot = suite[id]; if (snapshot == null || !snapshot.baseline) return; delete snapshot.baseline; snapshot.dirty = false; suite.__dirty = isSuiteDirty(suite); buildFolderDict(_storyboard.mainStory); broadcastSignal(); }; var loadSnapshot = function loadSnapshot(absPath) { /* eslint-disable global-require, import/no-dynamic-require */ delete require.cache[require.resolve(absPath)]; return require(absPath); /* eslint-enable global-require */ }; var sortSnapshots = function sortSnapshots(suite) { var out = {}; Object.keys(suite).sort().forEach(function (id) { out[id] = suite[id]; }); return out; }; // --------------------------------- // CSS // --------------------------------- var _commonCss = []; var getCommonCss = function getCommonCss() { return _commonCss; }; var loadCommonCss = function () { var _ref5 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() { var story = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _storyboard.mainStory; var cssPaths; return regeneratorRuntime.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: story.info('extractor', 'Extracting common CSS...'); _context4.next = 3; return (0, _globby2.default)(_config.cssPatterns); case 3: cssPaths = _context4.sent; _commonCss = cssPaths.map(function (cssPath) { story.info('extractor', 'Processing ' + _storyboard.chalk.cyan.bold(cssPath) + '...'); return _fs2.default.readFileSync(cssPath, 'utf8'); }); case 5: case 'end': return _context4.stop(); } } }, _callee4, undefined); })); return function loadCommonCss() { return _ref5.apply(this, arguments); }; }(); var getSuiteCss = function getSuiteCss(filePath, story) { var suiteCss = void 0; try { var cssPath = getCssPathForSuite(filePath); story.debug('extractor', 'Trying to read ' + _storyboard.chalk.cyan.bold(cssPath) + '...'); suiteCss = _fs2.default.readFileSync(cssPath, 'utf8'); story.info('extractor', 'Found custom CSS in ' + _storyboard.chalk.cyan.bold(cssPath)); } catch (err) { /* no file exists */ } return suiteCss; }; var getCssPathForSuite = function getCssPathForSuite(filePath) { var _path$parse = _path2.default.parse(filePath), dir = _path$parse.dir, name = _path$parse.name; return _path2.default.join(dir, name + '.css'); }; // --------------------------------- // Folder dictionary // --------------------------------- var buildFolderDict = function buildFolderDict(story) { story.info('extractor', 'Building folder tree...'); var nextDict = {}; // Create root node var curFolderPath = '-'; var curFolder = { parentFolderPath: null, folderPath: '-', dirty: false, filePaths: [], suiteDirtyFlags: [], childrenFolderPaths: [], childrenFolderDirtyFlags: [] }; nextDict['-'] = curFolder; story.debug('extractor', 'Snapshot tree:', { attach: nextDict }); // Sort file paths to simplify tree generation var filePaths = Object.keys(_snapshotSuiteDict).sort(); // Process all file paths filePaths.forEach(function (filePath) { story.debug('extractor', 'File: ' + filePath); var folderPath = _path2.default.dirname(filePath); if (folderPath === curFolderPath) { curFolder.filePaths.push(filePath); } else { var parentFolderPath = folderPath; var fFound = false; while (!fFound) { if (parentFolderPath === '-') break; parentFolderPath = _path2.default.dirname(parentFolderPath); if (nextDict[parentFolderPath]) { fFound = true; break; } } if (!fFound) throw new Error('Error building path tree'); nextDict[parentFolderPath].childrenFolderPaths.push(folderPath); nextDict[folderPath] = { parentFolderPath: parentFolderPath, folderPath: folderPath, dirty: false, filePaths: [filePath], suiteDirtyFlags: [], childrenFolderPaths: [], childrenFolderDirtyFlags: [] }; curFolder = nextDict[folderPath]; curFolderPath = folderPath; } }); // Update children dirty flags for each folder updateChildrenDirtyFlags(nextDict, _snapshotSuiteDict, '-'); _folderDict = nextDict; }; // Update dirty flags, recursively, top-down var updateChildrenDirtyFlags = function updateChildrenDirtyFlags(folderDict, suiteDict, folderPath) { var curFolder = folderDict[folderPath]; curFolder.suiteDirtyFlags = curFolder.filePaths.map(function (filePath) { return _snapshotSuiteDict[filePath][DIRTY_ATTR]; }); curFolder.childrenFolderDirtyFlags = curFolder.childrenFolderPaths.map(function (subFolderPath) { return updateChildrenDirtyFlags(folderDict, suiteDict, subFolderPath); }); curFolder.dirty = curFolder.suiteDirtyFlags.some(Boolean) || curFolder.childrenFolderDirtyFlags.some(Boolean); return curFolder.dirty; }; // --------------------------------- // Watching and real-time // --------------------------------- var watchStart = function watchStart(story) { if (_watchers) return; _watchers = {}; // const glob = _config.snapshotPatterns.concat(_config.cssPatterns); var options = { ignored: /[/\\]\./, ignoreInitial: true }; _watchers.css = _chokidar2.default.watch(_config.cssPatterns, options); _watchers.snap = _chokidar2.default.watch(_config.snapshotPatterns, options); ['change', 'add', 'unlink'].forEach(function (type) { // $FlowFixMe _watchers.css.on(type, function (filePath) { cssWatchEvent(type, filePath); }); // $FlowFixMe _watchers.snap.on(type, function (filePath) { snapWatchEvent(type, filePath); }); }); story.info('extractor', 'Started watching over snapshot and CSS files'); }; // const watchStop = (story: StoryT) => { // if (_watchers == null) return; // _watchers.close(); // _watchers = null; // story.info('extractor', 'Stopped file watcher'); // }; var cssWatchEvent = function cssWatchEvent(type, filePath0) { var filePath = slash(filePath0); _storyboard.mainStory.debug('extractor', 'CSS watch fired: ' + (_storyboard.chalk.bold(type.toUpperCase()) + ' ' + _storyboard.chalk.cyan.bold(filePath))); debouncedCssRefresh(); }; var debouncedCssRefresh = (0, _lodash2.default)(function () { return Promise.resolve().then(function () { return loadCommonCss(); }).then(function () { return updateSnapshotCss(); }).then(function () { return broadcastSignal(); }); }, 300); var snapWatchEvent = function snapWatchEvent(type, filePath0) { var filePath = slash(filePath0); _storyboard.mainStory.debug('extractor', 'Snapshot watch fired: ' + (_storyboard.chalk.bold(type.toUpperCase()) + ' ' + _storyboard.chalk.cyan.bold(filePath))); Promise.resolve().then(function () { var commonCss = getCommonCss(); switch (type) { case 'change': case 'add': return Promise.resolve().then(function () { return loadSuite(filePath, commonCss, _storyboard.mainStory); }).then(function () { return buildFolderDict(_storyboard.mainStory); }); case 'unlink': return Promise.resolve().then(function () { var suite = _snapshotSuiteDict['-/' + filePath.normalize()]; if (suite) { suite[DIRTY_ATTR] = true; suite[DELETED_ATTR] = true; return buildFolderDict(_storyboard.mainStory); } return null; }); default: break; } return null; }).then(function () { return broadcastSignal(); }); }; var broadcastSignal = function broadcastSignal() { if (!_config.socketioServer) return; _config.socketioServer.emit('REFRESH'); }; // --------------------------------- // Others // --------------------------------- var getFolder = function getFolder(folderPath) { return _folderDict[folderPath.normalize()]; }; var getSnapshotSuite = function getSnapshotSuite(filePath) { return _snapshotSuiteDict[filePath.normalize()]; }; var forEachSnapshot = function forEachSnapshot(suite, cb) { Object.keys(suite).forEach(function (id) { if (id === FOLDER_PATH_ATTR || id === DIRTY_ATTR || id === DELETED_ATTR) return; var snapshot = suite[id]; if ((typeof snapshot === 'undefined' ? 'undefined' : _typeof(snapshot)) !== 'object') return; cb(snapshot); }); }; var isSuiteDirty = function isSuiteDirty(suite) { var keys = Object.keys(suite); for (var i = 0; i < keys.length; i++) { var key = keys[i]; if (key === FOLDER_PATH_ATTR || key === DIRTY_ATTR || key === DELETED_ATTR) { continue; } if (suite[key].dirty) return true; } return false; }; var slash = function slash(str) { return str.replace(/\\/g, '/'); }; // ============================================= // Public API // ============================================= exports.configure = configure; exports.start = start; exports.getFolder = getFolder; exports.getSnapshotSuite = getSnapshotSuite; exports.saveAsBaseline = saveAsBaseline;