creevey
Version:
Cross-browser screenshot testing tool for Storybook with fancy UI Runner
208 lines • 9.18 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreeveyController = exports.ADDON_ID = void 0;
const types_1 = require("@storybook/types");
const core_events_1 = require("@storybook/core-events");
const index_js_1 = require("../../shared/index.js");
const types_js_1 = require("../../types.js");
const creeveyClientApi_js_1 = require("../shared/creeveyClientApi.js");
const helpers_js_1 = require("../shared/helpers.js");
const utils_js_1 = require("./utils.js");
exports.ADDON_ID = 'creevey';
// TODO Add `useController` hook
// TODO use `import { useGlobals, useStorybookApi } from '@storybook/manager-api';`
class CreeveyController {
storybookApi;
storyId = '';
activeBrowser = '';
selectedTestId = '';
status = { isRunning: false, tests: {}, browsers: [] };
creeveyApi = null;
stories = {};
updateStatusListeners = [];
changeTestListeners = [];
constructor(storybookApi) {
this.storybookApi = storybookApi;
this.storybookApi = storybookApi;
}
initAll = async () => {
this.storybookApi.on(core_events_1.STORY_RENDERED, this.onStoryRendered);
this.storybookApi.on(core_events_1.SET_STORIES, this.onSetStories);
this.creeveyApi = await (0, creeveyClientApi_js_1.initCreeveyClientApi)();
this.creeveyApi.onUpdate(this.handleCreeveyUpdate);
this.status = await this.creeveyApi.status;
};
onUpdateStatus(listener) {
this.updateStatusListeners.push(listener);
return () => void (this.updateStatusListeners = this.updateStatusListeners.filter((x) => x != listener));
}
onChangeTest(listener) {
this.changeTestListeners.push(listener);
return () => void (this.changeTestListeners = this.changeTestListeners.filter((x) => x != listener));
}
handleCreeveyUpdate = (update) => {
const { tests, removedTests = [], isRunning } = update;
if ((0, types_js_1.isDefined)(isRunning)) {
this.status.isRunning = isRunning;
}
if ((0, types_js_1.isDefined)(tests)) {
const prevTests = this.status.tests;
const prevStories = this.stories;
Object.values(tests)
.filter(types_js_1.isDefined)
.forEach((update) => {
const { id, skip, status, results, approved, storyId } = update;
const test = prevTests[id];
if (!test) {
return (prevTests[id] = update);
}
if ((0, types_js_1.isDefined)(skip))
test.skip = skip;
if ((0, types_js_1.isDefined)(status)) {
test.status = status;
if ((0, types_js_1.isDefined)(storyId) && (0, types_js_1.isDefined)(prevStories[storyId])) {
const story = prevStories[storyId];
const storyStatus = this.getStoryTests(storyId);
const oldStatus = storyStatus
.map((x) => (x.id === id ? status : x.status))
.reduce((oldStatus, newStatus) => (0, helpers_js_1.calcStatus)(oldStatus, newStatus), undefined);
story.name = this.addStatusToStoryName(story.name, (0, helpers_js_1.calcStatus)(oldStatus, status), skip ?? false);
}
}
if ((0, types_js_1.isDefined)(results)) {
if (test.results)
test.results.push(...results);
else
test.results = results;
}
if ((0, types_js_1.isDefined)(approved)) {
Object.entries(approved).forEach(([image, retry]) => retry !== undefined && ((test.approved = test.approved ?? {})[image] = retry));
}
});
const nextTests = {};
const testsToRemove = new Set(removedTests.map(({ id }) => id));
for (const id in prevTests) {
if (testsToRemove.has(id))
continue;
nextTests[id] = prevTests[id];
}
this.status.tests = nextTests;
this.stories = prevStories;
this.setPanelsTitle();
// TODO Check setStories method in 6.x and migrate properly
this.storybookApi.emit(core_events_1.SET_STORIES, this.stories);
}
this.updateStatusListeners.forEach((x) => {
x(update);
});
};
getCurrentTest = () => {
return this.status.tests[this.selectedTestId];
};
onStoryRendered = (storyId) => {
if (this.storyId === '')
this.addStatusesToSideBar();
if (this.storyId !== storyId) {
this.storyId = storyId;
this.selectedTestId = this.getTestsByStoryIdAndBrowser(this.activeBrowser)[0]?.id ?? '';
this.setPanelsTitle();
this.changeTestListeners.forEach((x) => {
x(this.selectedTestId);
});
}
};
onStart = () => {
this.creeveyApi?.start([this.selectedTestId]);
};
onStop = () => {
this.creeveyApi?.stop();
};
onImageApprove = (id, retry, image) => this.creeveyApi?.approve(id, retry, image);
onStartAllStoryTests = () => {
const ids = Object.values(this.status.tests)
.filter(types_js_1.isDefined)
.filter((x) => x.storyId === this.storyId)
.map((x) => x.id);
this.creeveyApi?.start(ids);
};
onStartAllTests = () => {
const ids = Object.values(this.status.tests)
.filter(types_js_1.isDefined)
.map((x) => x.id);
this.creeveyApi?.start(ids);
};
onSetStories = (data) => {
const stories = data.v ? (0, index_js_1.denormalizeStoryParameters)(data) : data.stories;
this.stories = stories;
};
setActiveBrowser = (browser) => {
this.activeBrowser = browser;
this.selectedTestId = this.getTestsByStoryIdAndBrowser(this.activeBrowser)[0]?.id ?? '';
this.changeTestListeners.forEach((x) => {
x(this.selectedTestId);
});
};
setSelectedTestId = (testId) => {
this.selectedTestId = testId;
this.changeTestListeners.forEach((x) => {
x(this.selectedTestId);
});
};
getStoryTests = (storyId) => {
return Object.values(this.status.tests)
.filter((result) => result?.storyId === storyId)
.filter(types_js_1.isDefined);
};
getBrowsers = () => {
return this.status.browsers;
};
getTestsByStoryIdAndBrowser = (browser) => {
return Object.values(this.status.tests)
.filter((x) => x?.browser === browser && x.storyId === this.storyId)
.filter(types_js_1.isDefined);
};
getTabTitle = (browser) => {
const tests = this.getTestsByStoryIdAndBrowser(browser);
const browserStatus = tests
.map((x) => x.status)
.reduce((oldStatus, newStatus) => (0, helpers_js_1.calcStatus)(oldStatus, newStatus), undefined);
const browserSkip = tests.length > 0 ? tests.every((x) => x.skip) : false;
const emojiStatus = (0, utils_js_1.getEmojiByTestStatus)(browserStatus, browserSkip);
return `${emojiStatus ? `${emojiStatus} ` : ''}Creevey/${browser}`;
};
setPanelsTitle = () => {
const panels = this.storybookApi.getElements(types_1.Addon_TypesEnum.PANEL);
let firstPanelBrowser = this.activeBrowser;
for (const p in panels) {
const panel = panels[p];
if (panel.id?.indexOf(exports.ADDON_ID) === 0 && panel.paramKey) {
panel.title = this.getTabTitle(panel.paramKey);
if (!firstPanelBrowser)
firstPanelBrowser = panel.paramKey;
}
}
this.storybookApi.setSelectedPanel(`${exports.ADDON_ID}/panel/${firstPanelBrowser}`);
};
addStatusesToSideBar() {
if (!Object.keys(this.stories).length)
return;
const stories = this.stories;
Object.keys(this.stories).forEach((storyId) => {
const storyStatus = this.getStoryTests(storyId);
const status = storyStatus
.map((x) => x.status)
.reduce((oldStatus, newStatus) => (0, helpers_js_1.calcStatus)(oldStatus, newStatus), undefined);
const skip = storyStatus.length > 0 ? storyStatus.every((x) => x.skip) : false;
this.stories[storyId].name = this.addStatusToStoryName(stories[storyId].name, status, skip);
});
// TODO Check setStories method in 6.x and migrate properly
this.storybookApi.emit(core_events_1.SET_STORIES, this.stories);
}
addStatusToStoryName(name, status, skip) {
name = name.replace(/^(❌|✔|🟡|🕗|⏸) /, '');
const emojiStatus = (0, utils_js_1.getEmojiByTestStatus)(status, skip);
return `${emojiStatus ? `${emojiStatus} ` : ''} ${name}`;
}
}
exports.CreeveyController = CreeveyController;
//# sourceMappingURL=controller.js.map