UNPKG

creevey

Version:

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

227 lines 8.67 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.selectStory = selectStory; exports.insertIgnoreStyles = insertIgnoreStyles; exports.removeIgnoreStyles = removeIgnoreStyles; exports.getStories = getStories; // TODO Use StorybookEvents.STORY_RENDER_PHASE_CHANGED: `loading/rendering/completed` with storyId // TODO Check other statuses and statuses with play function function selectStory(storyId, callback) { const STORYBOOK_EVENTS = { SET_STORIES: 'setStories', SET_CURRENT_STORY: 'setCurrentStory', FORCE_REMOUNT: 'forceRemount', STORY_RENDERED: 'storyRendered', STORY_FINISHED: 'storyFinished', STORY_ERRORED: 'storyErrored', // STORY_MISSING: 'storyMissing', STORY_THREW_EXCEPTION: 'storyThrewException', PLAY_FUNCTION_THREW_EXCEPTION: 'playFunctionThrewException', UPDATE_STORY_ARGS: 'updateStoryArgs', SET_GLOBALS: 'setGlobals', UPDATE_GLOBALS: 'updateGlobals', GLOBALS_UPDATED: 'globalsUpdated', }; const addonsChannel = () => window.__STORYBOOK_ADDONS_CHANNEL__; const disableAnimationsStyles = ` *, *:hover, *::before, *::after { animation-delay: -0.0001ms !important; animation-duration: 0s !important; animation-play-state: paused !important; cursor: none !important; caret-color: transparent !important; transition: 0s !important; } `; function isObject(x) { return typeof x == 'object' && x != null; } function disableAnimation() { window.__CREEVEY_ANIMATION_DISABLED__ = true; const style = document.createElement('style'); const textNode = document.createTextNode(disableAnimationsStyles); style.setAttribute('type', 'text/css'); style.appendChild(textNode); document.head.appendChild(style); } function waitForFontsLoaded() { // TODO Use document.fonts.ready instead const areFontsLoading = Array.from(document.fonts).some((font) => font.status == 'loading'); if (areFontsLoading) { return new Promise((resolve) => { const fontsLoadedHandler = () => { document.fonts.removeEventListener('loadingdone', fontsLoadedHandler); resolve(); }; document.fonts.addEventListener('loadingdone', fontsLoadedHandler); }); } } function catchRenderError(channel) { let rejectCallback; const promise = new Promise((_resolve, reject) => (rejectCallback = reject)); function errorHandler({ title, description }) { rejectCallback({ message: title, stack: description, }); } function exceptionHandler(exception) { rejectCallback(exception); } function removeHandlers() { channel.off(STORYBOOK_EVENTS.STORY_ERRORED, errorHandler); channel.off(STORYBOOK_EVENTS.STORY_THREW_EXCEPTION, exceptionHandler); channel.off(STORYBOOK_EVENTS.PLAY_FUNCTION_THREW_EXCEPTION, exceptionHandler); } channel.once(STORYBOOK_EVENTS.STORY_ERRORED, errorHandler); channel.once(STORYBOOK_EVENTS.STORY_THREW_EXCEPTION, exceptionHandler); if (window.__STORYBOOK_MODULE_CORE_EVENTS__.PLAY_FUNCTION_THREW_EXCEPTION) { channel.once(STORYBOOK_EVENTS.PLAY_FUNCTION_THREW_EXCEPTION, exceptionHandler); } return Object.assign(promise, { cancel: removeHandlers }); } function waitForStoryRendered(channel) { let resolveCallback; const promise = new Promise((resolve) => (resolveCallback = resolve)); function renderHandler() { resolveCallback(); } function removeHandlers() { channel.off(STORYBOOK_EVENTS.STORY_FINISHED, renderHandler); channel.off(STORYBOOK_EVENTS.STORY_RENDERED, renderHandler); } if (window.__STORYBOOK_MODULE_CORE_EVENTS__.STORY_FINISHED) { channel.once(STORYBOOK_EVENTS.STORY_FINISHED, renderHandler); } else { // NOTE: Earlier versions of Storybook don't have STORY_FINISHED event channel.once(STORYBOOK_EVENTS.STORY_RENDERED, renderHandler); } return Object.assign(promise, { cancel: removeHandlers }); } let currentStory = ''; const currentSelection = window.__STORYBOOK_PREVIEW__.currentSelection; if (currentSelection) { currentStory = currentSelection.storyId; } if (!window.__CREEVEY_ANIMATION_DISABLED__) disableAnimation(); const channel = addonsChannel(); const renderPromise = waitForStoryRendered(channel); const errorPromise = catchRenderError(channel); setTimeout(() => { if (storyId == currentStory) channel.emit(STORYBOOK_EVENTS.FORCE_REMOUNT, { storyId }); else channel.emit(STORYBOOK_EVENTS.SET_CURRENT_STORY, { storyId }); }, 0); void (async () => { try { await Promise.race([ (async () => { await renderPromise; await waitForFontsLoaded(); })(), errorPromise, ]); if (callback) callback(null); window.__CREEVEY_SELECT_STORY_RESULT__ = { status: 'success' }; } catch (reason) { // NOTE Event `STORY_THREW_EXCEPTION` triggered only in react and vue frameworks and return Error instance // NOTE Event `STORY_ERRORED` return error-like object without `name` field const errorMessage = reason instanceof Error ? // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing reason.stack || reason.message : isObject(reason) ? `${reason.message}\n ${reason.stack}` : reason; if (callback) callback(errorMessage); window.__CREEVEY_SELECT_STORY_RESULT__ = { status: 'error', message: errorMessage }; } finally { renderPromise.cancel(); errorPromise.cancel(); } })(); } function insertIgnoreStyles(ignoreSelectors) { const stylesElement = document.createElement('style'); stylesElement.setAttribute('type', 'text/css'); document.head.appendChild(stylesElement); ignoreSelectors.forEach((selector) => { stylesElement.innerHTML += ` ${selector} { background: #000 !important; box-shadow: none !important; text-shadow: none !important; outline: 0 !important; color: rgba(0,0,0,0) !important; } ${selector} *, ${selector}::before, ${selector}::after { visibility: hidden !important; } `; }); return stylesElement; } function removeIgnoreStyles(ignoreStyles) { ignoreStyles.remove(); } // TODO Find a way to send stories updates to the server async function getStories(callback) { function isRegExp(exp) { return exp instanceof RegExp; } function serializeRegExp(exp) { const { source, flags } = exp; return { __regexp: true, source, flags, }; } function cloneDeepWith(value, customizer) { const customized = customizer(value); if (customized !== undefined) return customized; if (Array.isArray(value)) { return value.map((item) => cloneDeepWith(item, customizer)); } if (value && typeof value === 'object') { const out = {}; for (const [k, v] of Object.entries(value)) { out[k] = cloneDeepWith(v, customizer); } return out; } return value; } function serializeRawStories(stories) { for (const storyId in stories) { const story = stories[storyId]; const creevey = story.parameters.creevey; if (creevey && 'skip' in creevey && creevey.skip) { creevey.skip = cloneDeepWith(creevey.skip, (value) => { if (isRegExp(value)) { return serializeRegExp(value); } return undefined; }); } } } const stories = await window.__STORYBOOK_PREVIEW__.extract(); serializeRawStories(stories); window.__CREEVEY_STORYBOOK_STORIES__ = stories; if (callback) callback(stories); return stories; } //# sourceMappingURL=storybook-helpers.js.map