creevey 0.10.0-beta.1 → 0.10.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/addon/components/Panel.js +2 -2
- package/dist/client/addon/components/Panel.js.map +1 -1
- package/dist/client/addon/controller.js +4 -5
- package/dist/client/addon/controller.js.map +1 -1
- package/dist/client/addon/withCreevey.js +18 -34
- package/dist/client/addon/withCreevey.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SwapView.js +12 -0
- package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
- package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -0
- package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
- package/dist/client/shared/components/ResultsPage.js +23 -5
- package/dist/client/shared/components/ResultsPage.js.map +1 -1
- package/dist/client/web/CreeveyApp.js +22 -6
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/client/web/CreeveyContext.d.ts +5 -0
- package/dist/client/web/CreeveyContext.js +3 -0
- package/dist/client/web/CreeveyContext.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/Search.js +2 -2
- package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBar.js +1 -0
- package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +49 -6
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +1 -3
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestLink.js +1 -3
- package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
- package/dist/client/web/KeyboardEventsContext.d.ts +1 -8
- package/dist/client/web/KeyboardEventsContext.js +62 -57
- package/dist/client/web/KeyboardEventsContext.js.map +1 -1
- package/dist/client/web/assets/{index-DkmZfG9C.js → index-BE9CL5_G.js} +94 -94
- package/dist/client/web/index.html +1 -1
- package/dist/creevey.js +13 -5
- package/dist/creevey.js.map +1 -1
- package/dist/server/config.js +4 -3
- package/dist/server/config.js.map +1 -1
- package/dist/server/docker.js +2 -2
- package/dist/server/docker.js.map +1 -1
- package/dist/server/index.js +29 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +2 -1
- package/dist/server/logger.js +7 -3
- package/dist/server/logger.js.map +1 -1
- package/dist/server/master/api.js +1 -1
- package/dist/server/master/api.js.map +1 -1
- package/dist/server/master/pool.d.ts +3 -3
- package/dist/server/master/pool.js +10 -63
- package/dist/server/master/pool.js.map +1 -1
- package/dist/server/master/queue.d.ts +13 -0
- package/dist/server/master/queue.js +64 -0
- package/dist/server/master/queue.js.map +1 -0
- package/dist/server/master/runner.d.ts +1 -0
- package/dist/server/master/runner.js +4 -1
- package/dist/server/master/runner.js.map +1 -1
- package/dist/server/master/server.js +1 -1
- package/dist/server/master/server.js.map +1 -1
- package/dist/server/master/start.js +4 -4
- package/dist/server/master/start.js.map +1 -1
- package/dist/server/playwright/docker-file.js +12 -2
- package/dist/server/playwright/docker-file.js.map +1 -1
- package/dist/server/playwright/internal.d.ts +2 -2
- package/dist/server/playwright/internal.js +56 -44
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/playwright/webdriver.js +1 -1
- package/dist/server/playwright/webdriver.js.map +1 -1
- package/dist/server/providers/browser.js +2 -1
- package/dist/server/providers/browser.js.map +1 -1
- package/dist/server/providers/hybrid.js +1 -1
- package/dist/server/providers/hybrid.js.map +1 -1
- package/dist/server/reporter.js +4 -4
- package/dist/server/reporter.js.map +1 -1
- package/dist/server/selenium/internal.d.ts +2 -3
- package/dist/server/selenium/internal.js +116 -90
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/selenoid.js +2 -2
- package/dist/server/selenium/selenoid.js.map +1 -1
- package/dist/server/selenium/webdriver.js +1 -1
- package/dist/server/selenium/webdriver.js.map +1 -1
- package/dist/server/telemetry.js +7 -3
- package/dist/server/telemetry.js.map +1 -1
- package/dist/server/utils.d.ts +2 -1
- package/dist/server/utils.js +13 -3
- package/dist/server/utils.js.map +1 -1
- package/dist/server/webdriver.d.ts +2 -3
- package/dist/server/webdriver.js +10 -9
- package/dist/server/webdriver.js.map +1 -1
- package/dist/server/worker/chai-image.d.ts +1 -2
- package/dist/server/worker/chai-image.js +4 -3
- package/dist/server/worker/chai-image.js.map +1 -1
- package/dist/server/worker/start.js +24 -14
- package/dist/server/worker/start.js.map +1 -1
- package/dist/types.d.ts +30 -11
- package/dist/types.js +13 -1
- package/dist/types.js.map +1 -1
- package/package.json +36 -42
- package/src/client/addon/components/Panel.tsx +2 -2
- package/src/client/addon/controller.ts +13 -6
- package/src/client/addon/withCreevey.ts +25 -13
- package/src/client/shared/components/ImagesView/SwapView.tsx +18 -0
- package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -0
- package/src/client/shared/components/ResultsPage.tsx +28 -7
- package/src/client/web/CreeveyApp.tsx +25 -7
- package/src/client/web/CreeveyContext.tsx +9 -0
- package/src/client/web/CreeveyView/SideBar/Search.tsx +3 -3
- package/src/client/web/CreeveyView/SideBar/SideBar.tsx +1 -0
- package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +37 -6
- package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +3 -5
- package/src/client/web/CreeveyView/SideBar/TestLink.tsx +2 -4
- package/src/client/web/KeyboardEventsContext.tsx +61 -73
- package/src/creevey.ts +13 -6
- package/src/server/config.ts +4 -3
- package/src/server/docker.ts +2 -2
- package/src/server/index.ts +27 -4
- package/src/server/logger.ts +6 -2
- package/src/server/master/api.ts +1 -1
- package/src/server/master/pool.ts +18 -56
- package/src/server/master/queue.ts +64 -0
- package/src/server/master/runner.ts +4 -1
- package/src/server/master/server.ts +1 -1
- package/src/server/master/start.ts +7 -4
- package/src/server/playwright/docker-file.ts +14 -2
- package/src/server/playwright/internal.ts +76 -49
- package/src/server/playwright/webdriver.ts +1 -1
- package/src/server/providers/browser.ts +2 -1
- package/src/server/providers/hybrid.ts +1 -1
- package/src/server/reporter.ts +4 -3
- package/src/server/selenium/internal.ts +119 -92
- package/src/server/selenium/selenoid.ts +2 -2
- package/src/server/selenium/webdriver.ts +1 -1
- package/src/server/telemetry.ts +7 -3
- package/src/server/utils.ts +14 -4
- package/src/server/webdriver.ts +10 -15
- package/src/server/worker/chai-image.ts +4 -4
- package/src/server/worker/start.ts +25 -16
- package/src/types.ts +32 -13
- package/.yarnrc.yml +0 -1
- package/chromatic.config.json +0 -5
package/package.json
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
{
|
2
2
|
"name": "creevey",
|
3
3
|
"description": "Cross-browser screenshot testing tool for Storybook with fancy UI Runner",
|
4
|
-
"version": "0.10.0-beta.
|
4
|
+
"version": "0.10.0-beta.10",
|
5
5
|
"type": "commonjs",
|
6
|
-
"bin":
|
7
|
-
"creevey": "dist/cli.js"
|
8
|
-
},
|
6
|
+
"bin": "dist/cli.js",
|
9
7
|
"main": "./dist/index.js",
|
10
8
|
"types": "./dist/index.d.ts",
|
11
9
|
"exports": {
|
12
10
|
".": "./dist/index.js",
|
13
11
|
"./playwright": {
|
14
|
-
"
|
15
|
-
"
|
12
|
+
"types": "./dist/playwright.d.ts",
|
13
|
+
"default": "./dist/playwright.js"
|
16
14
|
},
|
17
15
|
"./selenium": {
|
18
|
-
"
|
19
|
-
"
|
16
|
+
"types": "./dist/selenium.d.ts",
|
17
|
+
"default": "./dist/selenium.js"
|
20
18
|
},
|
21
19
|
"./manager": "./dist/client/addon/manager.js",
|
22
20
|
"./preview": "./dist/client/addon/preview.js",
|
@@ -90,16 +88,9 @@
|
|
90
88
|
"dependencies": {
|
91
89
|
"@koa/cors": "^5.0.0",
|
92
90
|
"@octokit/core": "^6.1.2",
|
93
|
-
"@storybook/components": "^8.4.1",
|
94
|
-
"@storybook/core-events": "^8.4.1",
|
95
|
-
"@storybook/csf": "^0.1.11",
|
96
91
|
"@storybook/icons": "^1.2.12",
|
97
|
-
"@storybook/manager-api": "^8.4.1",
|
98
|
-
"@storybook/preview-api": "^8.4.1",
|
99
|
-
"@storybook/theming": "^8.4.1",
|
100
|
-
"@storybook/types": "^8.4.1",
|
101
92
|
"@types/chai": "^4.3.20",
|
102
|
-
"@types/dockerode": "^3.3.
|
93
|
+
"@types/dockerode": "^3.3.32",
|
103
94
|
"@types/koa": "^2.15.0",
|
104
95
|
"@types/koa-bodyparser": "^4.3.12",
|
105
96
|
"@types/koa-mount": "^4.0.5",
|
@@ -110,7 +101,7 @@
|
|
110
101
|
"@types/minimist": "^1.2.5",
|
111
102
|
"@types/pixelmatch": "^5.2.6",
|
112
103
|
"@types/pngjs": "^6.0.5",
|
113
|
-
"@types/qs": "^6.9.
|
104
|
+
"@types/qs": "^6.9.17",
|
114
105
|
"@types/react": "^18.3.12",
|
115
106
|
"@types/react-dom": "^18.3.1",
|
116
107
|
"@types/selenium-webdriver": "^4.1.27",
|
@@ -131,69 +122,72 @@
|
|
131
122
|
"loglevel-plugin-prefix": "^0.8.4",
|
132
123
|
"micromatch": "^4.0.8",
|
133
124
|
"minimist": "^1.2.8",
|
134
|
-
"odiff-bin": "^3.
|
125
|
+
"odiff-bin": "^3.2.0",
|
135
126
|
"pixelmatch": "^6.0.0",
|
136
127
|
"pngjs": "^7.0.0",
|
137
128
|
"polished": "^4.3.1",
|
138
|
-
"qs": "^6.13.
|
129
|
+
"qs": "^6.13.1",
|
139
130
|
"semver": "^7.6.3",
|
140
131
|
"shelljs": "^0.8.5",
|
141
132
|
"tar-stream": "^3.1.7",
|
142
133
|
"tsx": "^4.19.2",
|
143
|
-
"uuid": "^11.0.
|
134
|
+
"uuid": "^11.0.3",
|
144
135
|
"ws": "^8.18.0",
|
145
136
|
"yocto-spinner": "^0.1.1"
|
146
137
|
},
|
147
138
|
"devDependencies": {
|
148
139
|
"@chromatic-com/storybook": "^3.2.2",
|
149
|
-
"@eslint/js": "^9.
|
150
|
-
"@storybook/addon-essentials": "^8.4.
|
151
|
-
"@storybook/addon-interactions": "^8.4.
|
152
|
-
"@storybook/blocks": "^8.4.
|
153
|
-
"@storybook/channels": "^8.4.
|
154
|
-
"@storybook/
|
155
|
-
"@storybook/
|
156
|
-
"@storybook/
|
157
|
-
"@storybook/
|
158
|
-
"@storybook/react
|
159
|
-
"@storybook/
|
140
|
+
"@eslint/js": "^9.15.0",
|
141
|
+
"@storybook/addon-essentials": "^8.4.5",
|
142
|
+
"@storybook/addon-interactions": "^8.4.5",
|
143
|
+
"@storybook/blocks": "^8.4.5",
|
144
|
+
"@storybook/channels": "^8.4.5",
|
145
|
+
"@storybook/components": "^8.4.5",
|
146
|
+
"@storybook/csf": "^0.1.11",
|
147
|
+
"@storybook/manager-api": "^8.4.5",
|
148
|
+
"@storybook/preview-api": "^8.4.5",
|
149
|
+
"@storybook/react": "^8.4.5",
|
150
|
+
"@storybook/react-vite": "^8.4.5",
|
151
|
+
"@storybook/test": "^8.4.5",
|
152
|
+
"@storybook/theming": "^8.4.5",
|
153
|
+
"@storybook/types": "^8.4.5",
|
160
154
|
"@types/eslint": "^9.6.1",
|
161
155
|
"@types/eslint__js": "^8.42.3",
|
162
156
|
"@types/node": "^18.19.64",
|
163
157
|
"@types/resize-observer-browser": "^0.1.11",
|
164
|
-
"@types/semver": "^7",
|
158
|
+
"@types/semver": "^7.5.8",
|
165
159
|
"@types/tar-stream": "^3.1.3",
|
166
160
|
"@types/tmp": "^0.2.6",
|
167
161
|
"@vitejs/plugin-react-swc": "^3.7.1",
|
168
162
|
"browserstack-local": "^1.5.5",
|
169
|
-
"chromatic": "^11.
|
170
|
-
"concurrently": "^9.0
|
163
|
+
"chromatic": "^11.18.1",
|
164
|
+
"concurrently": "^9.1.0",
|
171
165
|
"conventional-changelog-cli": "^5.0.0",
|
172
166
|
"dotenv": "^16.4.5",
|
173
|
-
"eslint": "^9.
|
167
|
+
"eslint": "^9.15.0",
|
174
168
|
"eslint-config-prettier": "^9.1.0",
|
175
169
|
"eslint-plugin-prettier": "^5.2.1",
|
176
170
|
"eslint-plugin-react": "^7.37.2",
|
177
171
|
"eslint-plugin-react-hooks": "^5.0.0",
|
178
172
|
"git-cz": "^4.9.0",
|
179
|
-
"globals": "^15.
|
180
|
-
"husky": "^9.1.
|
173
|
+
"globals": "^15.12.0",
|
174
|
+
"husky": "^9.1.7",
|
181
175
|
"immer": "^10.1.1",
|
182
176
|
"lint-staged": "^15.2.10",
|
183
177
|
"pinst": "^3.0.0",
|
184
|
-
"playwright-core": "^1.
|
178
|
+
"playwright-core": "^1.49.0",
|
185
179
|
"prettier": "^3.3.3",
|
186
180
|
"react": "^18.3.1",
|
187
181
|
"react-dom": "^18.3.1",
|
188
182
|
"react-is": "^18.3.1",
|
189
183
|
"selenium-webdriver": "^4.26.0",
|
190
|
-
"storybook": "^8.4.
|
184
|
+
"storybook": "^8.4.5",
|
191
185
|
"tmp": "^0.2.3",
|
192
186
|
"typescript": "^5.6.3",
|
193
|
-
"typescript-eslint": "^8.
|
187
|
+
"typescript-eslint": "^8.15.0",
|
194
188
|
"use-immer": "^0.10.0",
|
195
|
-
"vite": "^5.4.
|
196
|
-
"vitest": "^2.1.
|
189
|
+
"vite": "^5.4.11",
|
190
|
+
"vitest": "^2.1.5"
|
197
191
|
},
|
198
192
|
"config": {
|
199
193
|
"commitizen": {
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import React, { JSX } from 'react';
|
2
2
|
import { Loader } from '@storybook/components';
|
3
3
|
import { styled } from '@storybook/theming';
|
4
|
-
import { TestData } from '../../../types.js';
|
4
|
+
import { noop, TestData } from '../../../types.js';
|
5
5
|
import { ResultsPage } from '../../shared/components/ResultsPage.js';
|
6
6
|
import { getTestPath } from '../../shared/helpers.js';
|
7
7
|
import TestSelect from './TestSelect.js';
|
8
|
-
|
8
|
+
|
9
9
|
interface PanelProps {
|
10
10
|
tests: TestData[];
|
11
11
|
selectedTestId: string;
|
@@ -1,7 +1,14 @@
|
|
1
1
|
import { Addon_TypesEnum } from '@storybook/types';
|
2
|
-
import { SET_STORIES, STORY_RENDERED } from '@storybook/core-events';
|
3
2
|
import { denormalizeStoryParameters } from '../../shared/index.js';
|
4
|
-
import {
|
3
|
+
import {
|
4
|
+
CreeveyStatus,
|
5
|
+
CreeveyUpdate,
|
6
|
+
isDefined,
|
7
|
+
TestData,
|
8
|
+
TestStatus,
|
9
|
+
StoriesRaw,
|
10
|
+
StorybookEvents,
|
11
|
+
} from '../../types.js';
|
5
12
|
import { initCreeveyClientApi, CreeveyClientApi } from '../shared/creeveyClientApi.js';
|
6
13
|
import { calcStatus } from '../shared/helpers.js';
|
7
14
|
import { getEmojiByTestStatus } from './utils.js';
|
@@ -25,8 +32,8 @@ export class CreeveyController {
|
|
25
32
|
this.storybookApi = storybookApi;
|
26
33
|
}
|
27
34
|
initAll = async (): Promise<void> => {
|
28
|
-
this.storybookApi.on(STORY_RENDERED, this.onStoryRendered);
|
29
|
-
this.storybookApi.on(SET_STORIES, this.onSetStories);
|
35
|
+
this.storybookApi.on(StorybookEvents.STORY_RENDERED, this.onStoryRendered);
|
36
|
+
this.storybookApi.on(StorybookEvents.SET_STORIES, this.onSetStories);
|
30
37
|
this.creeveyApi = await initCreeveyClientApi();
|
31
38
|
this.creeveyApi.onUpdate(this.handleCreeveyUpdate);
|
32
39
|
this.status = await this.creeveyApi.status;
|
@@ -93,7 +100,7 @@ export class CreeveyController {
|
|
93
100
|
this.stories = prevStories;
|
94
101
|
this.setPanelsTitle();
|
95
102
|
// TODO Check setStories method in 6.x and migrate properly
|
96
|
-
this.storybookApi.emit(SET_STORIES, this.stories);
|
103
|
+
this.storybookApi.emit(StorybookEvents.SET_STORIES, this.stories);
|
97
104
|
}
|
98
105
|
this.updateStatusListeners.forEach((x) => {
|
99
106
|
x(update);
|
@@ -213,7 +220,7 @@ export class CreeveyController {
|
|
213
220
|
});
|
214
221
|
|
215
222
|
// TODO Check setStories method in 6.x and migrate properly
|
216
|
-
this.storybookApi.emit(SET_STORIES, this.stories);
|
223
|
+
this.storybookApi.emit(StorybookEvents.SET_STORIES, this.stories);
|
217
224
|
}
|
218
225
|
|
219
226
|
addStatusToStoryName(name: string, status: TestStatus | undefined, skip: string | boolean): string {
|
@@ -1,10 +1,18 @@
|
|
1
|
-
import * as Events from '@storybook/core-events';
|
2
1
|
import type { Renderer, StoryContextForEnhancers } from '@storybook/csf';
|
3
2
|
import { makeDecorator, PreviewWeb, StoryStore } from '@storybook/preview-api';
|
4
3
|
import { Channel } from '@storybook/channels';
|
5
|
-
import {
|
4
|
+
import {
|
5
|
+
CaptureOptions,
|
6
|
+
CreeveyStoryParams,
|
7
|
+
isObject,
|
8
|
+
noop,
|
9
|
+
StoriesRaw,
|
10
|
+
StorybookEvents,
|
11
|
+
StorybookGlobals,
|
12
|
+
} from '../../types.js';
|
6
13
|
import { serializeRawStories } from '../../shared/index.js';
|
7
14
|
import { getConnectionUrl } from '../shared/helpers.js';
|
15
|
+
import isEqual from 'lodash/isEqual.js';
|
8
16
|
|
9
17
|
declare global {
|
10
18
|
interface Window {
|
@@ -62,12 +70,12 @@ function catchRenderError(channel: Channel): Promise<void> & { cancel: () => voi
|
|
62
70
|
rejectCallback(exception);
|
63
71
|
}
|
64
72
|
function removeHandlers(): void {
|
65
|
-
channel.off(
|
66
|
-
channel.off(
|
73
|
+
channel.off(StorybookEvents.STORY_ERRORED, errorHandler);
|
74
|
+
channel.off(StorybookEvents.STORY_THREW_EXCEPTION, errorHandler);
|
67
75
|
}
|
68
76
|
|
69
|
-
channel.once(
|
70
|
-
channel.once(
|
77
|
+
channel.once(StorybookEvents.STORY_ERRORED, errorHandler);
|
78
|
+
channel.once(StorybookEvents.STORY_THREW_EXCEPTION, exceptionHandler);
|
71
79
|
|
72
80
|
return Object.assign(promise, { cancel: removeHandlers });
|
73
81
|
}
|
@@ -79,10 +87,10 @@ function waitForStoryRendered(channel: Channel): Promise<void> & { cancel: () =>
|
|
79
87
|
resolveCallback();
|
80
88
|
}
|
81
89
|
function removeHandlers(): void {
|
82
|
-
channel.off(
|
90
|
+
channel.off(StorybookEvents.STORY_RENDERED, renderHandler);
|
83
91
|
}
|
84
92
|
|
85
|
-
channel.once(
|
93
|
+
channel.once(StorybookEvents.STORY_RENDERED, renderHandler);
|
86
94
|
|
87
95
|
return Object.assign(promise, { cancel: removeHandlers });
|
88
96
|
}
|
@@ -132,6 +140,7 @@ let captureResolver: () => void;
|
|
132
140
|
let waitForCreevey: Promise<void>;
|
133
141
|
let creeveyReady: () => void;
|
134
142
|
let setStoriesCounter = 0;
|
143
|
+
let globals = {};
|
135
144
|
|
136
145
|
export function withCreevey(): ReturnType<typeof makeDecorator> {
|
137
146
|
const addonsChannel = (): Channel => window.__STORYBOOK_ADDONS_CHANNEL__;
|
@@ -166,7 +175,7 @@ export function withCreevey(): ReturnType<typeof makeDecorator> {
|
|
166
175
|
return stories;
|
167
176
|
}
|
168
177
|
|
169
|
-
// TODO Use
|
178
|
+
// TODO Use StorybookEvents.STORY_RENDER_PHASE_CHANGED: `loading/rendering/completed` with storyId
|
170
179
|
// TODO Check other statuses and statuses with play function
|
171
180
|
async function selectStory(
|
172
181
|
storyId: string,
|
@@ -188,8 +197,8 @@ export function withCreevey(): ReturnType<typeof makeDecorator> {
|
|
188
197
|
const capturePromise = waitForCaptureCall().then(() => (isCaptureCalled = true));
|
189
198
|
|
190
199
|
setTimeout(() => {
|
191
|
-
if (storyId == currentStory) channel.emit(
|
192
|
-
else channel.emit(
|
200
|
+
if (storyId == currentStory) channel.emit(StorybookEvents.FORCE_REMOUNT, { storyId });
|
201
|
+
else channel.emit(StorybookEvents.SET_CURRENT_STORY, { storyId });
|
193
202
|
}, 0);
|
194
203
|
|
195
204
|
try {
|
@@ -218,8 +227,11 @@ export function withCreevey(): ReturnType<typeof makeDecorator> {
|
|
218
227
|
}
|
219
228
|
}
|
220
229
|
|
221
|
-
function updateGlobals(
|
222
|
-
|
230
|
+
function updateGlobals(newGlobals: StorybookGlobals): void {
|
231
|
+
if (isEqual(globals, newGlobals)) return;
|
232
|
+
|
233
|
+
globals = newGlobals;
|
234
|
+
addonsChannel().emit(StorybookEvents.UPDATE_GLOBALS, { globals });
|
223
235
|
}
|
224
236
|
|
225
237
|
function insertIgnoreStyles(ignoreSelectors: string[]): HTMLStyleElement {
|
@@ -46,6 +46,24 @@ export const SwapView = withTheme(({ theme, expect, actual, diff }: ViewPropsWit
|
|
46
46
|
setImage((prevImage) => (prevImage == 'actual' ? 'expect' : 'actual'));
|
47
47
|
}, []);
|
48
48
|
|
49
|
+
const handleKeyDown = useCallback(
|
50
|
+
(e: KeyboardEvent) => {
|
51
|
+
if (e.code === 'Space' && e.altKey) {
|
52
|
+
e.preventDefault();
|
53
|
+
handleChangeView();
|
54
|
+
}
|
55
|
+
},
|
56
|
+
[handleChangeView],
|
57
|
+
);
|
58
|
+
|
59
|
+
useEffect(() => {
|
60
|
+
document.addEventListener('keydown', handleKeyDown, false);
|
61
|
+
|
62
|
+
return () => {
|
63
|
+
document.removeEventListener('keydown', handleKeyDown, false);
|
64
|
+
};
|
65
|
+
}, [handleKeyDown]);
|
66
|
+
|
49
67
|
useEffect(() => {
|
50
68
|
if (loaded) readyForCapture();
|
51
69
|
}, [loaded]);
|
@@ -1,11 +1,11 @@
|
|
1
|
-
import React, { JSX, useState } from 'react';
|
1
|
+
import React, { JSX, useCallback, useEffect, useState } from 'react';
|
2
2
|
import { Placeholder, ScrollArea } from '@storybook/components';
|
3
3
|
import { styled, withTheme, Theme } from '@storybook/theming';
|
4
4
|
import { ImagesView } from './ImagesView/ImagesView.js';
|
5
5
|
import { PageHeader } from './PageHeader/PageHeader.js';
|
6
6
|
import { PageFooter } from './PageFooter/PageFooter.js';
|
7
7
|
import { getImageUrl } from '../helpers.js';
|
8
|
-
import { getViewMode, VIEW_MODE_KEY } from '../viewMode.js';
|
8
|
+
import { getViewMode, VIEW_MODE_KEY, viewModes } from '../viewMode.js';
|
9
9
|
import { ImagesViewMode, TestResult } from '../../../types.js';
|
10
10
|
|
11
11
|
interface ResultsPageProps {
|
@@ -77,10 +77,31 @@ export function ResultsPageInternal({
|
|
77
77
|
)
|
78
78
|
: [];
|
79
79
|
|
80
|
-
const
|
81
|
-
|
82
|
-
|
83
|
-
|
80
|
+
const handleKeyDown = useCallback(
|
81
|
+
(e: KeyboardEvent) => {
|
82
|
+
if (!canApprove) return;
|
83
|
+
if (e.code === 'Tab') {
|
84
|
+
e.preventDefault();
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
86
|
+
if (e.shiftKey) setViewMode((mode) => viewModes.at((viewModes.indexOf(mode) - 1) % viewModes.length)!);
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
88
|
+
else setViewMode((mode) => viewModes.at((viewModes.indexOf(mode) + 1) % viewModes.length)!);
|
89
|
+
}
|
90
|
+
},
|
91
|
+
[canApprove],
|
92
|
+
);
|
93
|
+
|
94
|
+
useEffect(() => {
|
95
|
+
localStorage.setItem(VIEW_MODE_KEY, viewMode);
|
96
|
+
}, [viewMode]);
|
97
|
+
|
98
|
+
useEffect(() => {
|
99
|
+
document.addEventListener('keydown', handleKeyDown, false);
|
100
|
+
|
101
|
+
return () => {
|
102
|
+
document.removeEventListener('keydown', handleKeyDown, false);
|
103
|
+
};
|
104
|
+
}, [handleKeyDown]);
|
84
105
|
|
85
106
|
return (
|
86
107
|
<Container height={height}>
|
@@ -92,7 +113,7 @@ export function ResultsPageInternal({
|
|
92
113
|
errorMessage={result.error}
|
93
114
|
showViewModes={hasDiffAndExpect}
|
94
115
|
viewMode={viewMode}
|
95
|
-
onViewModeChange={
|
116
|
+
onViewModeChange={setViewMode}
|
96
117
|
onImageChange={onImageChange}
|
97
118
|
imagesWithError={imagesWithError}
|
98
119
|
/>
|
@@ -17,7 +17,7 @@ import {
|
|
17
17
|
CreeveyViewFilter,
|
18
18
|
getFailedTests,
|
19
19
|
} from '../shared/helpers.js';
|
20
|
-
import { CreeveyContext } from './CreeveyContext.js';
|
20
|
+
import { CreeveyContext, FocusableItem } from './CreeveyContext.js';
|
21
21
|
import { KeyboardEvents } from './KeyboardEventsContext.js';
|
22
22
|
import { SideBar } from './CreeveyView/SideBar/index.js';
|
23
23
|
import { ResultsPage } from '../shared/components/ResultsPage.js';
|
@@ -59,8 +59,9 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
|
|
59
59
|
const failedTests = useMemo(() => getFailedTests(tests), [tests]);
|
60
60
|
|
61
61
|
const [retry, setRetry] = useState(openedTest?.results?.length ?? 0);
|
62
|
-
const result = openedTest?.results?.[retry - 1]
|
63
|
-
const [imageName, setImageName] = useState(Object.keys(result
|
62
|
+
const result = useMemo(() => openedTest?.results?.[retry - 1], [openedTest, retry]);
|
63
|
+
const [imageName, setImageName] = useState(Object.keys(result?.images ?? {})[0] ?? '');
|
64
|
+
const [sidebarFocusedItem, setSidebarFocusedItem] = useState<FocusableItem>([]);
|
64
65
|
const canApprove = useMemo(
|
65
66
|
() =>
|
66
67
|
Boolean(
|
@@ -94,6 +95,7 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
|
|
94
95
|
(test: CreeveyTest): void => {
|
95
96
|
const testPath = getTestPath(test);
|
96
97
|
setSearchParams(testPath);
|
98
|
+
setSidebarFocusedItem(testPath);
|
97
99
|
updateTests((draft) => {
|
98
100
|
openSuite(draft, testPath, true);
|
99
101
|
openTest(testPath);
|
@@ -103,11 +105,24 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
|
|
103
105
|
);
|
104
106
|
|
105
107
|
const handleGoToNextFailedTest = useCallback(() => {
|
106
|
-
if (failedTests.length
|
108
|
+
if (failedTests.length == 0) return;
|
107
109
|
const currentTest = failedTests.findIndex((t) => t.id === openedTest?.id);
|
108
|
-
const
|
109
|
-
|
110
|
-
|
110
|
+
const failedImages = Object.entries(result?.images ?? {})
|
111
|
+
.filter(([name, image]) =>
|
112
|
+
// TODO Move to helpers, it duplicates in a few places
|
113
|
+
Boolean(image?.error != null && openedTest?.approved?.[name] != retry - 1 && result?.status != 'success'),
|
114
|
+
)
|
115
|
+
.map(([name]) => name);
|
116
|
+
if (
|
117
|
+
failedImages.length > 1 &&
|
118
|
+
(failedTests.length == 1 || failedImages.indexOf(imageName) < failedImages.length - 1)
|
119
|
+
) {
|
120
|
+
setImageName((name) => failedImages[failedImages.indexOf(name) + 1] ?? failedImages[0]);
|
121
|
+
} else {
|
122
|
+
const nextFailedTest = failedTests[currentTest + 1] ?? failedTests[0];
|
123
|
+
handleOpenTest(nextFailedTest);
|
124
|
+
}
|
125
|
+
}, [failedTests, handleOpenTest, openedTest, retry, result, imageName]);
|
111
126
|
|
112
127
|
const handleImageApproveNew = useCallback((): void => {
|
113
128
|
const id = openedTest?.id;
|
@@ -189,12 +204,15 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
|
|
189
204
|
value={{
|
190
205
|
isReport: initialState.isReport,
|
191
206
|
isRunning,
|
207
|
+
onImageNext: canApprove ? handleGoToNextFailedTest : undefined,
|
192
208
|
onImageApprove: canApprove ? handleImageApproveAndGoNext : undefined,
|
193
209
|
onApproveAll: handleApproveAll,
|
194
210
|
onStart: handleStart,
|
195
211
|
onStop: handleStop,
|
196
212
|
onSuiteOpen: handleSuiteOpen,
|
197
213
|
onSuiteToggle: handleSuiteToggle,
|
214
|
+
sidebarFocusedItem,
|
215
|
+
setSidebarFocusedItem,
|
198
216
|
}}
|
199
217
|
>
|
200
218
|
<ThemeProvider theme={ensure(themes[theme])}>
|
@@ -1,26 +1,35 @@
|
|
1
1
|
import React, { useContext } from 'react';
|
2
2
|
import { CreeveySuite, noop } from '../../types.js';
|
3
3
|
|
4
|
+
export type SuitePath = string[];
|
5
|
+
export type FocusableItem = null | SuitePath;
|
6
|
+
|
4
7
|
export interface CreeveyContextType {
|
5
8
|
isReport: boolean;
|
6
9
|
isRunning: boolean;
|
7
10
|
onStop: () => void;
|
11
|
+
onImageNext?: () => void;
|
8
12
|
onImageApprove?: () => void;
|
9
13
|
onApproveAll: () => void;
|
10
14
|
onStart: (rootSuite: CreeveySuite) => void;
|
11
15
|
onSuiteOpen: (path: string[], opened: boolean) => void;
|
12
16
|
onSuiteToggle: (path: string[], checked: boolean) => void;
|
17
|
+
sidebarFocusedItem: FocusableItem;
|
18
|
+
setSidebarFocusedItem: (item: FocusableItem) => void;
|
13
19
|
}
|
14
20
|
|
15
21
|
export const CreeveyContext = React.createContext<CreeveyContextType>({
|
16
22
|
isReport: true,
|
17
23
|
isRunning: false,
|
24
|
+
onImageNext: noop,
|
18
25
|
onImageApprove: noop,
|
19
26
|
onApproveAll: noop,
|
20
27
|
onStop: noop,
|
21
28
|
onStart: noop,
|
22
29
|
onSuiteOpen: noop,
|
23
30
|
onSuiteToggle: noop,
|
31
|
+
sidebarFocusedItem: [],
|
32
|
+
setSidebarFocusedItem: noop,
|
24
33
|
});
|
25
34
|
|
26
35
|
export const useCreeveyContext = () => useContext(CreeveyContext);
|
@@ -1,7 +1,7 @@
|
|
1
|
-
import React, { JSX, ChangeEvent,
|
1
|
+
import React, { JSX, ChangeEvent, useRef, useState } from 'react';
|
2
2
|
import { SearchIcon, CloseAltIcon } from '@storybook/icons';
|
3
3
|
import { styled, Theme, withTheme } from '@storybook/theming';
|
4
|
-
import {
|
4
|
+
import { useCreeveyContext } from '../../CreeveyContext.js';
|
5
5
|
|
6
6
|
interface SearchProps {
|
7
7
|
onChange: (arg: string) => void;
|
@@ -100,7 +100,7 @@ const FilterForm = withTheme(
|
|
100
100
|
);
|
101
101
|
|
102
102
|
export const Search = ({ onChange, value }: SearchProps): JSX.Element => {
|
103
|
-
const { setSidebarFocusedItem } =
|
103
|
+
const { setSidebarFocusedItem } = useCreeveyContext();
|
104
104
|
const [focussed, onSetFocussed] = useState(false);
|
105
105
|
const searchRef = useRef<HTMLInputElement>(null);
|
106
106
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { JSX } from 'react';
|
1
|
+
import React, { JSX, useCallback, useEffect, useState } from 'react';
|
2
2
|
import { styled, withTheme } from '@storybook/theming';
|
3
3
|
import { Button } from '@storybook/components';
|
4
4
|
import { ChevronRightIcon } from '@storybook/icons';
|
@@ -21,15 +21,46 @@ const Container = styled.div({
|
|
21
21
|
});
|
22
22
|
|
23
23
|
export function SideBarFooter(): JSX.Element {
|
24
|
-
const { onApproveAll, onImageApprove } = useCreeveyContext();
|
24
|
+
const { onApproveAll, onImageApprove, onImageNext } = useCreeveyContext();
|
25
|
+
const [isAlt, setIsAlt] = useState(false);
|
26
|
+
|
27
|
+
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
28
|
+
if (e.code === 'AltLeft') {
|
29
|
+
e.preventDefault();
|
30
|
+
setIsAlt(true);
|
31
|
+
}
|
32
|
+
}, []);
|
33
|
+
const handleKeyUp = useCallback((e: KeyboardEvent) => {
|
34
|
+
if (e.code === 'AltLeft') {
|
35
|
+
e.preventDefault();
|
36
|
+
setIsAlt(false);
|
37
|
+
}
|
38
|
+
}, []);
|
39
|
+
|
40
|
+
useEffect(() => {
|
41
|
+
document.addEventListener('keydown', handleKeyDown, false);
|
42
|
+
document.addEventListener('keyup', handleKeyUp, false);
|
43
|
+
|
44
|
+
return () => {
|
45
|
+
document.removeEventListener('keydown', handleKeyDown, false);
|
46
|
+
document.removeEventListener('keyup', handleKeyUp, false);
|
47
|
+
};
|
48
|
+
}, [handleKeyDown, handleKeyUp]);
|
25
49
|
|
26
50
|
return (
|
27
51
|
<Sticky>
|
28
52
|
<Container>
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
53
|
+
{isAlt ? (
|
54
|
+
<Button variant="outline" size="medium" onClick={onImageNext} disabled={!onImageApprove}>
|
55
|
+
Next
|
56
|
+
<ChevronRightIcon />
|
57
|
+
</Button>
|
58
|
+
) : (
|
59
|
+
<Button variant="solid" size="medium" onClick={onImageApprove} disabled={!onImageApprove}>
|
60
|
+
Approve
|
61
|
+
<ChevronRightIcon />
|
62
|
+
</Button>
|
63
|
+
)}
|
33
64
|
<Button variant="outline" size="medium" onClick={onApproveAll}>
|
34
65
|
Approve all
|
35
66
|
</Button>
|
@@ -1,11 +1,10 @@
|
|
1
|
-
import React, { JSX, useRef,
|
1
|
+
import React, { JSX, useRef, useEffect, useMemo } from 'react';
|
2
2
|
import { ChevronDownIcon, ChevronRightIcon } from '@storybook/icons';
|
3
3
|
import { styled, withTheme, Theme } from '@storybook/theming';
|
4
4
|
import { Checkbox, CheckboxContainer } from './Checkbox.js';
|
5
5
|
import { TestStatusIcon } from './TestStatusIcon.js';
|
6
6
|
import { CreeveySuite, isTest } from '../../../../types.js';
|
7
|
-
import {
|
8
|
-
import { KeyboardEventsContext } from '../../KeyboardEventsContext.js';
|
7
|
+
import { useCreeveyContext } from '../../CreeveyContext.js';
|
9
8
|
|
10
9
|
export interface SuiteLinkProps {
|
11
10
|
title: string;
|
@@ -79,8 +78,7 @@ export const SuiteTitle = styled.span({
|
|
79
78
|
});
|
80
79
|
|
81
80
|
export function SuiteLink({ title, suite, 'data-testid': dataTid }: SuiteLinkProps): JSX.Element {
|
82
|
-
const { onSuiteOpen, onSuiteToggle } =
|
83
|
-
const { sidebarFocusedItem, setSidebarFocusedItem } = useContext(KeyboardEventsContext);
|
81
|
+
const { onSuiteOpen, onSuiteToggle, sidebarFocusedItem, setSidebarFocusedItem } = useCreeveyContext();
|
84
82
|
const checkboxRef = useRef<Checkbox>(null);
|
85
83
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
86
84
|
|
@@ -1,9 +1,8 @@
|
|
1
1
|
import React, { JSX, useEffect, useCallback, useContext, useMemo, useRef } from 'react';
|
2
2
|
import { CreeveyTest } from '../../../../types.js';
|
3
3
|
import { TestStatusIcon } from './TestStatusIcon.js';
|
4
|
-
import {
|
4
|
+
import { useCreeveyContext } from '../../CreeveyContext.js';
|
5
5
|
import { SideBarContext } from './SideBar.js';
|
6
|
-
import { KeyboardEventsContext } from '../../KeyboardEventsContext.js';
|
7
6
|
import { Button, Container, SuiteContainer, SuiteTitle } from './SuiteLink.js';
|
8
7
|
import { Checkbox, CheckboxContainer } from './Checkbox.js';
|
9
8
|
import { getTestPath } from '../../../shared/helpers.js';
|
@@ -20,9 +19,8 @@ const TestContainer = styled(SuiteContainer)({
|
|
20
19
|
});
|
21
20
|
|
22
21
|
export function TestLink({ title, opened, test }: TestLinkProps): JSX.Element {
|
23
|
-
const { onSuiteToggle } =
|
22
|
+
const { onSuiteToggle, sidebarFocusedItem, setSidebarFocusedItem } = useCreeveyContext();
|
24
23
|
const { onOpenTest } = useContext(SideBarContext);
|
25
|
-
const { sidebarFocusedItem, setSidebarFocusedItem } = useContext(KeyboardEventsContext);
|
26
24
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
27
25
|
|
28
26
|
const emptyResults = (test.results?.length ?? 0) == 0;
|