creevey 0.10.0-beta.3 → 0.10.0-beta.31
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/Addon.js +17 -7
- package/dist/client/addon/components/Addon.js.map +1 -1
- package/dist/client/addon/components/Panel.js +2 -2
- package/dist/client/addon/components/Panel.js.map +1 -1
- package/dist/client/addon/components/Tools.js +17 -7
- package/dist/client/addon/components/Tools.js.map +1 -1
- package/dist/client/addon/withCreevey.d.ts +1 -0
- package/dist/client/addon/withCreevey.js +10 -1
- package/dist/client/addon/withCreevey.js.map +1 -1
- package/dist/client/shared/components/ImagesView/BlendView.js +17 -7
- package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SideBySideView.js +17 -7
- package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SlideView.js +17 -7
- package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SwapView.js +29 -7
- 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/PageHeader/PageHeader.js +20 -8
- package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
- package/dist/client/shared/components/ResultsPage.js +43 -13
- package/dist/client/shared/components/ResultsPage.js.map +1 -1
- package/dist/client/shared/creeveyClientApi.js +8 -1
- package/dist/client/shared/creeveyClientApi.js.map +1 -1
- package/dist/client/shared/helpers.d.ts +1 -3
- package/dist/client/shared/helpers.js +4 -19
- package/dist/client/shared/helpers.js.map +1 -1
- package/dist/client/web/CreeveyApp.js +41 -14
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/client/web/CreeveyContext.d.ts +5 -0
- package/dist/client/web/CreeveyContext.js +20 -7
- package/dist/client/web/CreeveyContext.js.map +1 -1
- package/dist/client/web/CreeveyLoader.js +2 -2
- package/dist/client/web/CreeveyLoader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/Search.js +19 -9
- package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBar.js +18 -7
- package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +60 -7
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +17 -7
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +18 -10
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestLink.js +18 -10
- 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 +79 -64
- package/dist/client/web/KeyboardEventsContext.js.map +1 -1
- package/dist/client/web/assets/index-Cs8IUTQs.js +595 -0
- package/dist/client/web/index.html +1 -1
- package/dist/client/web/index.js +17 -7
- package/dist/client/web/index.js.map +1 -1
- package/dist/client/web/themes.d.ts +2 -0
- package/dist/client/web/themes.js +22 -0
- package/dist/client/web/themes.js.map +1 -0
- package/dist/creevey.js +16 -9
- package/dist/creevey.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/server/config.d.ts +1 -1
- package/dist/server/config.js +29 -7
- package/dist/server/config.js.map +1 -1
- package/dist/server/connection.d.ts +3 -0
- package/dist/server/connection.js +28 -0
- package/dist/server/connection.js.map +1 -0
- package/dist/server/docker.js +38 -21
- package/dist/server/docker.js.map +1 -1
- package/dist/server/index.js +63 -11
- 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 +4 -3
- package/dist/server/master/pool.js +12 -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 +71 -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 +13 -11
- package/dist/server/master/start.js.map +1 -1
- package/dist/server/playwright/docker-file.d.ts +2 -1
- package/dist/server/playwright/docker-file.js +7 -5
- package/dist/server/playwright/docker-file.js.map +1 -1
- package/dist/server/playwright/internal.d.ts +5 -4
- package/dist/server/playwright/internal.js +91 -71
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/playwright/webdriver.d.ts +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 +6 -4
- 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 +13 -9
- package/dist/server/reporter.js.map +1 -1
- package/dist/server/selenium/internal.d.ts +3 -4
- package/dist/server/selenium/internal.js +127 -99
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/selenoid.js +9 -6
- package/dist/server/selenium/selenoid.js.map +1 -1
- package/dist/server/selenium/webdriver.d.ts +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/testsFiles/parser.js +44 -2
- package/dist/server/testsFiles/parser.js.map +1 -1
- package/dist/server/utils.d.ts +20 -1
- package/dist/server/utils.js +82 -7
- package/dist/server/utils.js.map +1 -1
- package/dist/server/webdriver.d.ts +3 -4
- 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/match-image.d.ts +4 -4
- package/dist/server/worker/match-image.js +7 -4
- package/dist/server/worker/match-image.js.map +1 -1
- package/dist/server/worker/start.js +24 -14
- package/dist/server/worker/start.js.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/types.d.ts +38 -13
- package/dist/types.js.map +1 -1
- package/docs/config.md +3 -0
- package/package.json +65 -63
- package/src/client/addon/components/Panel.tsx +2 -2
- package/src/client/addon/withCreevey.ts +8 -1
- 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/PageHeader/PageHeader.tsx +4 -2
- package/src/client/shared/components/ResultsPage.tsx +31 -8
- package/src/client/shared/creeveyClientApi.ts +9 -1
- package/src/client/shared/helpers.ts +4 -24
- package/src/client/web/CreeveyApp.tsx +26 -8
- package/src/client/web/CreeveyContext.tsx +9 -0
- package/src/client/web/CreeveyLoader.tsx +1 -1
- 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/client/web/themes.ts +24 -0
- package/src/creevey.ts +16 -10
- package/src/server/config.ts +30 -8
- package/src/server/connection.ts +26 -0
- package/src/server/docker.ts +42 -24
- package/src/server/index.ts +73 -14
- package/src/server/logger.ts +6 -2
- package/src/server/master/api.ts +1 -1
- package/src/server/master/pool.ts +22 -56
- package/src/server/master/queue.ts +77 -0
- package/src/server/master/runner.ts +4 -1
- package/src/server/master/server.ts +1 -1
- package/src/server/master/start.ts +16 -11
- package/src/server/playwright/docker-file.ts +8 -5
- package/src/server/playwright/internal.ts +91 -78
- package/src/server/playwright/webdriver.ts +2 -2
- package/src/server/providers/browser.ts +6 -4
- package/src/server/providers/hybrid.ts +1 -1
- package/src/server/reporter.ts +15 -9
- package/src/server/selenium/internal.ts +131 -107
- package/src/server/selenium/selenoid.ts +9 -7
- package/src/server/selenium/webdriver.ts +2 -2
- package/src/server/telemetry.ts +7 -3
- package/src/server/testsFiles/parser.ts +51 -1
- package/src/server/utils.ts +87 -8
- package/src/server/webdriver.ts +11 -16
- package/src/server/worker/chai-image.ts +4 -4
- package/src/server/worker/match-image.ts +12 -8
- package/src/server/worker/start.ts +25 -16
- package/src/shared/index.ts +1 -1
- package/src/types.ts +40 -15
- package/types/global.d.ts +1 -0
- package/.yarnrc.yml +0 -1
- package/chromatic.config.json +0 -5
- package/dist/client/web/assets/index-DkmZfG9C.js +0 -591
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { JSX, useEffect } from 'react';
|
1
|
+
import React, { JSX, useContext, useEffect } from 'react';
|
2
2
|
import { Tabs } from '@storybook/components';
|
3
3
|
import { CloseAltIcon } from '@storybook/icons';
|
4
4
|
import { styled, withTheme, Theme } from '@storybook/theming';
|
@@ -6,6 +6,7 @@ import { ImagesViewMode, Images } from '../../../../types.js';
|
|
6
6
|
import { getImageUrl } from '../../helpers.js';
|
7
7
|
import { ImagePreview } from './ImagePreview.js';
|
8
8
|
import { viewModes } from '../../viewMode.js';
|
9
|
+
import { CreeveyContext } from '../../../web/CreeveyContext.js';
|
9
10
|
|
10
11
|
interface PageHeaderProps {
|
11
12
|
title: string[];
|
@@ -76,6 +77,7 @@ export function PageHeader({
|
|
76
77
|
onImageChange,
|
77
78
|
onViewModeChange,
|
78
79
|
}: PageHeaderProps): JSX.Element | null {
|
80
|
+
const { isReport } = useContext(CreeveyContext);
|
79
81
|
const imageEntires = Object.entries(images) as [string, Images][];
|
80
82
|
|
81
83
|
const handleViewModeChange = (mode: string): void => {
|
@@ -110,7 +112,7 @@ export function PageHeader({
|
|
110
112
|
<ImagePreview
|
111
113
|
key={name}
|
112
114
|
imageName={name}
|
113
|
-
url={`${getImageUrl(title, name)}/${image.actual}`}
|
115
|
+
url={`${getImageUrl(title, name, isReport)}/${image.actual}`}
|
114
116
|
isActive={name === imageName}
|
115
117
|
onClick={onImageChange}
|
116
118
|
error={imagesWithError.includes(name)}
|
@@ -1,12 +1,13 @@
|
|
1
|
-
import React, { JSX, useState } from 'react';
|
1
|
+
import React, { JSX, useCallback, useContext, 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
|
+
import { CreeveyContext } from '../../web/CreeveyContext.js';
|
10
11
|
|
11
12
|
interface ResultsPageProps {
|
12
13
|
path: string[];
|
@@ -65,8 +66,9 @@ export function ResultsPageInternal({
|
|
65
66
|
onRetryChange,
|
66
67
|
}: ResultsPageProps): JSX.Element {
|
67
68
|
const result = results[retry - 1] ?? {};
|
69
|
+
const { isReport } = useContext(CreeveyContext);
|
68
70
|
const [viewMode, setViewMode] = useState<ImagesViewMode>(getViewMode());
|
69
|
-
const url = getImageUrl(path, imageName);
|
71
|
+
const url = getImageUrl(path, imageName, isReport);
|
70
72
|
const image = result.images?.[imageName];
|
71
73
|
const canApprove = Boolean(image && approved?.[imageName] != retry - 1 && result.status != 'success');
|
72
74
|
const hasDiffAndExpect = canApprove && Boolean(image?.diff && image.expect);
|
@@ -77,10 +79,31 @@ export function ResultsPageInternal({
|
|
77
79
|
)
|
78
80
|
: [];
|
79
81
|
|
80
|
-
const
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
const handleKeyDown = useCallback(
|
83
|
+
(e: KeyboardEvent) => {
|
84
|
+
if (!canApprove) return;
|
85
|
+
if (e.code === 'Tab') {
|
86
|
+
e.preventDefault();
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
88
|
+
if (e.shiftKey) setViewMode((mode) => viewModes.at((viewModes.indexOf(mode) - 1) % viewModes.length)!);
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
90
|
+
else setViewMode((mode) => viewModes.at((viewModes.indexOf(mode) + 1) % viewModes.length)!);
|
91
|
+
}
|
92
|
+
},
|
93
|
+
[canApprove],
|
94
|
+
);
|
95
|
+
|
96
|
+
useEffect(() => {
|
97
|
+
localStorage.setItem(VIEW_MODE_KEY, viewMode);
|
98
|
+
}, [viewMode]);
|
99
|
+
|
100
|
+
useEffect(() => {
|
101
|
+
document.addEventListener('keydown', handleKeyDown, false);
|
102
|
+
|
103
|
+
return () => {
|
104
|
+
document.removeEventListener('keydown', handleKeyDown, false);
|
105
|
+
};
|
106
|
+
}, [handleKeyDown]);
|
84
107
|
|
85
108
|
return (
|
86
109
|
<Container height={height}>
|
@@ -92,7 +115,7 @@ export function ResultsPageInternal({
|
|
92
115
|
errorMessage={result.error}
|
93
116
|
showViewModes={hasDiffAndExpect}
|
94
117
|
viewMode={viewMode}
|
95
|
-
onViewModeChange={
|
118
|
+
onViewModeChange={setViewMode}
|
96
119
|
onImageChange={onImageChange}
|
97
120
|
imagesWithError={imagesWithError}
|
98
121
|
/>
|
@@ -12,6 +12,7 @@ export interface CreeveyClientApi {
|
|
12
12
|
|
13
13
|
export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
|
14
14
|
let clientApiResolver: (api: CreeveyClientApi) => void = noop;
|
15
|
+
let clientApiRejecter: (error: Error | Event) => void = noop;
|
15
16
|
const updateListeners = new Set<(update: CreeveyUpdate) => void>();
|
16
17
|
let statusRequest: Promise<CreeveyStatus> | null = null;
|
17
18
|
let statusResolver: (status: CreeveyStatus) => void = noop;
|
@@ -22,6 +23,10 @@ export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
|
|
22
23
|
ws.send(JSON.stringify(request));
|
23
24
|
}
|
24
25
|
|
26
|
+
ws.addEventListener('error', (event) => {
|
27
|
+
clientApiRejecter(event);
|
28
|
+
});
|
29
|
+
|
25
30
|
ws.addEventListener('open', () => {
|
26
31
|
clientApiResolver({
|
27
32
|
start(ids: string[]) {
|
@@ -64,5 +69,8 @@ export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
|
|
64
69
|
});
|
65
70
|
// TODO Reconnect
|
66
71
|
|
67
|
-
return new Promise((resolve) =>
|
72
|
+
return new Promise((resolve, reject) => {
|
73
|
+
clientApiResolver = resolve;
|
74
|
+
clientApiRejecter = reject;
|
75
|
+
});
|
68
76
|
}
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import { themes, ThemeVars } from '@storybook/theming';
|
2
1
|
import { parse, stringify } from 'qs';
|
3
2
|
import { RefObject, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
4
3
|
import { TestData, isTest, isDefined, TestStatus, CreeveySuite, CreeveyTest, CreeveyStatus } from '../../types.js';
|
@@ -300,12 +299,14 @@ export function getConnectionUrl(): string {
|
|
300
299
|
.join(':');
|
301
300
|
}
|
302
301
|
|
303
|
-
export function getImageUrl(path: string[], imageName: string): string {
|
302
|
+
export function getImageUrl(path: string[], imageName: string, isReport?: boolean): string {
|
304
303
|
// path => [title, story, test, browser]
|
305
304
|
const browser = path.slice(-1)[0];
|
306
305
|
const imagesUrl = window.location.host
|
307
306
|
? `${window.location.protocol}//${getConnectionUrl()}${
|
308
|
-
window.location.pathname == '/'
|
307
|
+
window.location.pathname == '/' && !isReport
|
308
|
+
? '/report'
|
309
|
+
: window.location.pathname.split('/').slice(0, -1).join('/')
|
309
310
|
}/${encodeURI(path.slice(0, -1).join('/'))}`
|
310
311
|
: encodeURI(path.slice(0, -1).join('/'));
|
311
312
|
|
@@ -393,27 +394,6 @@ export function useCalcScale(diffImageRef: RefObject<HTMLImageElement>, loaded:
|
|
393
394
|
return scale;
|
394
395
|
}
|
395
396
|
|
396
|
-
const CREEVEY_THEME = 'Creevey_theme';
|
397
|
-
|
398
|
-
function isTheme(theme?: string | null): theme is ThemeVars['base'] {
|
399
|
-
return isDefined(theme) && Object.prototype.hasOwnProperty.call(themes, theme);
|
400
|
-
}
|
401
|
-
|
402
|
-
function initialTheme(): ThemeVars['base'] {
|
403
|
-
const theme = localStorage.getItem(CREEVEY_THEME);
|
404
|
-
return isTheme(theme) ? theme : 'light';
|
405
|
-
}
|
406
|
-
|
407
|
-
export function useTheme(): [ThemeVars['base'], (theme: ThemeVars['base']) => void] {
|
408
|
-
const [theme, setTheme] = useState<ThemeVars['base']>(initialTheme());
|
409
|
-
|
410
|
-
useEffect(() => {
|
411
|
-
localStorage.setItem(CREEVEY_THEME, theme);
|
412
|
-
}, [theme]);
|
413
|
-
|
414
|
-
return [theme, setTheme];
|
415
|
-
}
|
416
|
-
|
417
397
|
export function setSearchParams(testPath: string[]): void {
|
418
398
|
const pageUrl = `?${stringify({ testPath })}`;
|
419
399
|
window.history.pushState({ testPath }, '', pageUrl);
|
@@ -11,17 +11,17 @@ import {
|
|
11
11
|
getTestByPath,
|
12
12
|
removeTests,
|
13
13
|
getTestPath,
|
14
|
-
useTheme,
|
15
14
|
setSearchParams,
|
16
15
|
getTestPathFromSearch,
|
17
16
|
CreeveyViewFilter,
|
18
17
|
getFailedTests,
|
19
18
|
} from '../shared/helpers.js';
|
20
|
-
import { CreeveyContext } from './CreeveyContext.js';
|
19
|
+
import { CreeveyContext, FocusableItem } from './CreeveyContext.js';
|
21
20
|
import { KeyboardEvents } from './KeyboardEventsContext.js';
|
22
21
|
import { SideBar } from './CreeveyView/SideBar/index.js';
|
23
22
|
import { ResultsPage } from '../shared/components/ResultsPage.js';
|
24
23
|
import { Toggle } from './CreeveyView/SideBar/Toggle.js';
|
24
|
+
import { useTheme } from './themes.js';
|
25
25
|
|
26
26
|
export interface CreeveyAppProps {
|
27
27
|
api?: CreeveyClientApi;
|
@@ -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,6 +1,6 @@
|
|
1
1
|
import React, { JSX } from 'react';
|
2
2
|
import { styled, withTheme, Theme, keyframes, ensure, ThemeProvider, themes, Keyframes } from '@storybook/theming';
|
3
|
-
import { useTheme } from '
|
3
|
+
import { useTheme } from './themes.js';
|
4
4
|
|
5
5
|
const Container = withTheme(
|
6
6
|
styled.div(({ theme }) => ({
|
@@ -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;
|
@@ -1,20 +1,7 @@
|
|
1
|
-
import React, { JSX,
|
2
|
-
import { CreeveySuite, isTest
|
1
|
+
import React, { JSX, useEffect, useCallback, PropsWithChildren } from 'react';
|
2
|
+
import { CreeveySuite, isTest } from '../../types.js';
|
3
3
|
import { CreeveyViewFilter, filterTests, flattenSuite, getSuiteByPath, getTestPath } from '../shared/helpers.js';
|
4
|
-
import {
|
5
|
-
|
6
|
-
export type SuitePath = string[];
|
7
|
-
export type FocusableItem = null | SuitePath;
|
8
|
-
|
9
|
-
export interface KeyboardEventsContextType {
|
10
|
-
sidebarFocusedItem: FocusableItem;
|
11
|
-
setSidebarFocusedItem: (item: FocusableItem) => void;
|
12
|
-
}
|
13
|
-
|
14
|
-
export const KeyboardEventsContext = React.createContext<KeyboardEventsContextType>({
|
15
|
-
sidebarFocusedItem: [],
|
16
|
-
setSidebarFocusedItem: noop,
|
17
|
-
});
|
4
|
+
import { useCreeveyContext } from './CreeveyContext.js';
|
18
5
|
|
19
6
|
export const KeyboardEvents = ({
|
20
7
|
children,
|
@@ -24,9 +11,7 @@ export const KeyboardEvents = ({
|
|
24
11
|
rootSuite: CreeveySuite;
|
25
12
|
filter: CreeveyViewFilter;
|
26
13
|
}>): JSX.Element => {
|
27
|
-
const
|
28
|
-
|
29
|
-
const { onSuiteOpen, onSuiteToggle } = useContext(CreeveyContext);
|
14
|
+
const { onSuiteOpen, onSuiteToggle, sidebarFocusedItem, setSidebarFocusedItem } = useCreeveyContext();
|
30
15
|
|
31
16
|
const suiteList = flattenSuite(filterTests(rootSuite, filter));
|
32
17
|
|
@@ -45,63 +30,70 @@ export const KeyboardEvents = ({
|
|
45
30
|
(e: KeyboardEvent) => {
|
46
31
|
if (sidebarFocusedItem === null) return;
|
47
32
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
33
|
+
switch (e.code) {
|
34
|
+
case 'Enter': {
|
35
|
+
if (sidebarFocusedItem.length === 0) return;
|
36
|
+
|
37
|
+
const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
|
38
|
+
if (!focusedSuite) return;
|
39
|
+
if (!isTest(focusedSuite)) {
|
40
|
+
e.preventDefault();
|
41
|
+
onSuiteOpen(focusedSuite.path, !focusedSuite.opened);
|
42
|
+
}
|
43
|
+
if (isTest(focusedSuite) && focusedSuite.results?.length == 0) {
|
44
|
+
e.preventDefault();
|
45
|
+
}
|
46
|
+
return;
|
56
47
|
}
|
57
|
-
|
48
|
+
case 'Space': {
|
58
49
|
e.preventDefault();
|
50
|
+
// TODO handle keys in one place
|
51
|
+
if (e.altKey) return;
|
52
|
+
const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
|
53
|
+
if (!focusedSuite) return;
|
54
|
+
const path = isTest(focusedSuite) ? getTestPath(focusedSuite) : focusedSuite.path;
|
55
|
+
onSuiteToggle(path, !focusedSuite.checked);
|
56
|
+
return;
|
59
57
|
}
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
const nextPath = isTest(nextSuite.suite) ? getTestPath(nextSuite.suite) : nextSuite.suite.path;
|
74
|
-
setSidebarFocusedItem(nextPath);
|
75
|
-
}
|
76
|
-
if (e.code === 'ArrowUp') {
|
77
|
-
const currentIndex = sidebarFocusedItem.length === 0 ? 0 : getFocusedItemIndex(sidebarFocusedItem);
|
78
|
-
const nextSuite = currentIndex > 0 ? suiteList[currentIndex - 1].suite : rootSuite;
|
79
|
-
const nextPath = isTest(nextSuite) ? getTestPath(nextSuite) : nextSuite.path;
|
80
|
-
setSidebarFocusedItem(nextPath);
|
81
|
-
}
|
82
|
-
|
83
|
-
if (e.code === 'ArrowRight') {
|
84
|
-
if (sidebarFocusedItem.length === 0) return;
|
85
|
-
const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
|
86
|
-
|
87
|
-
if (!focusedSuite || isTest(focusedSuite)) return;
|
88
|
-
onSuiteOpen(focusedSuite.path, true);
|
89
|
-
}
|
90
|
-
|
91
|
-
if (e.code === 'ArrowLeft') {
|
92
|
-
if (sidebarFocusedItem.length === 0) return;
|
93
|
-
const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
|
94
|
-
if (!focusedSuite) return;
|
95
|
-
if (!isTest(focusedSuite) && focusedSuite.opened) {
|
96
|
-
onSuiteOpen(focusedSuite.path, false);
|
58
|
+
case 'ArrowDown': {
|
59
|
+
const currentIndex = sidebarFocusedItem.length === 0 ? -1 : getFocusedItemIndex(sidebarFocusedItem);
|
60
|
+
if (currentIndex === suiteList.length - 1) return;
|
61
|
+
const nextSuite = suiteList[currentIndex + 1];
|
62
|
+
const nextPath = isTest(nextSuite.suite) ? getTestPath(nextSuite.suite) : nextSuite.suite.path;
|
63
|
+
setSidebarFocusedItem(nextPath);
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
case 'ArrowUp': {
|
67
|
+
const currentIndex = sidebarFocusedItem.length === 0 ? 0 : getFocusedItemIndex(sidebarFocusedItem);
|
68
|
+
const nextSuite = currentIndex > 0 ? suiteList[currentIndex - 1].suite : rootSuite;
|
69
|
+
const nextPath = isTest(nextSuite) ? getTestPath(nextSuite) : nextSuite.path;
|
70
|
+
setSidebarFocusedItem(nextPath);
|
97
71
|
return;
|
98
72
|
}
|
73
|
+
case 'ArrowRight': {
|
74
|
+
if (sidebarFocusedItem.length === 0) return;
|
75
|
+
const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
|
99
76
|
|
100
|
-
|
101
|
-
|
77
|
+
if (!focusedSuite || isTest(focusedSuite)) return;
|
78
|
+
onSuiteOpen(focusedSuite.path, true);
|
79
|
+
return;
|
80
|
+
}
|
81
|
+
case 'ArrowLeft': {
|
82
|
+
if (sidebarFocusedItem.length === 0) return;
|
83
|
+
const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
|
84
|
+
if (!focusedSuite) return;
|
85
|
+
if (!isTest(focusedSuite) && focusedSuite.opened) {
|
86
|
+
onSuiteOpen(focusedSuite.path, false);
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
|
90
|
+
const path = isTest(focusedSuite) ? getTestPath(focusedSuite) : focusedSuite.path;
|
91
|
+
setSidebarFocusedItem(path.slice(0, -1));
|
92
|
+
return;
|
93
|
+
}
|
102
94
|
}
|
103
95
|
},
|
104
|
-
[onSuiteOpen, onSuiteToggle, rootSuite, suiteList, getFocusedItemIndex, sidebarFocusedItem],
|
96
|
+
[onSuiteOpen, onSuiteToggle, rootSuite, suiteList, getFocusedItemIndex, sidebarFocusedItem, setSidebarFocusedItem],
|
105
97
|
);
|
106
98
|
|
107
99
|
useEffect(() => {
|
@@ -112,9 +104,5 @@ export const KeyboardEvents = ({
|
|
112
104
|
};
|
113
105
|
}, [handleKeyDown]);
|
114
106
|
|
115
|
-
return
|
116
|
-
<KeyboardEventsContext.Provider value={{ sidebarFocusedItem, setSidebarFocusedItem }}>
|
117
|
-
{children}
|
118
|
-
</KeyboardEventsContext.Provider>
|
119
|
-
);
|
107
|
+
return <>{children}</>;
|
120
108
|
};
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { useEffect, useState } from 'react';
|
2
|
+
import { themes, ThemeVars } from '@storybook/theming';
|
3
|
+
import { isDefined } from '../../types.js';
|
4
|
+
|
5
|
+
const CREEVEY_THEME = 'Creevey_theme';
|
6
|
+
|
7
|
+
function isTheme(theme?: string | null): theme is ThemeVars['base'] {
|
8
|
+
return isDefined(theme) && Object.prototype.hasOwnProperty.call(themes, theme);
|
9
|
+
}
|
10
|
+
|
11
|
+
function initialTheme(): ThemeVars['base'] {
|
12
|
+
const theme = localStorage.getItem(CREEVEY_THEME);
|
13
|
+
return isTheme(theme) ? theme : 'light';
|
14
|
+
}
|
15
|
+
|
16
|
+
export function useTheme(): [ThemeVars['base'], (theme: ThemeVars['base']) => void] {
|
17
|
+
const [theme, setTheme] = useState<ThemeVars['base']>(initialTheme());
|
18
|
+
|
19
|
+
useEffect(() => {
|
20
|
+
localStorage.setItem(CREEVEY_THEME, theme);
|
21
|
+
}, [theme]);
|
22
|
+
|
23
|
+
return [theme, setTheme];
|
24
|
+
}
|