creevey 0.10.0-beta.8 → 0.10.0-beta.9
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/withCreevey.js +9 -1
- 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/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-DB8lHlJw.js → index-BE9CL5_G.js} +94 -94
- package/dist/client/web/index.html +1 -1
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/playwright/internal.d.ts +2 -2
- package/dist/server/playwright/internal.js +10 -8
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/selenium/internal.d.ts +2 -2
- package/dist/server/selenium/internal.js +10 -8
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/utils.js +2 -3
- package/dist/server/utils.js.map +1 -1
- package/dist/server/worker/start.js +17 -4
- package/dist/server/worker/start.js.map +1 -1
- package/package.json +1 -1
- package/src/client/addon/components/Panel.tsx +2 -2
- package/src/client/addon/withCreevey.ts +6 -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/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/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/server/index.ts +5 -0
- package/src/server/playwright/internal.ts +9 -9
- package/src/server/selenium/internal.ts +9 -9
- package/src/server/utils.ts +2 -3
- package/src/server/worker/start.ts +18 -5
@@ -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;
|
@@ -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
|
};
|
package/src/server/index.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import cluster from 'cluster';
|
2
|
+
import path from 'path';
|
2
3
|
import { readConfig, defaultBrowser } from './config.js';
|
3
4
|
import { Options, Config, BrowserConfigObject, isWorkerMessage } from '../types.js';
|
4
5
|
import { logger } from './logger.js';
|
@@ -8,6 +9,7 @@ import { isInsideDocker } from './utils.js';
|
|
8
9
|
import { sendWorkerMessage } from './messages.js';
|
9
10
|
import { playwrightDockerFile } from './playwright/docker-file.js';
|
10
11
|
import { buildImage } from './docker.js';
|
12
|
+
import { writeFile } from 'fs/promises';
|
11
13
|
|
12
14
|
async function startWebdriverServer(browser: string, config: Config, options: Options): Promise<string | undefined> {
|
13
15
|
if (config.webdriver === SeleniumWebdriver) {
|
@@ -79,6 +81,9 @@ export default async function (options: Options): Promise<void> {
|
|
79
81
|
const { browser = defaultBrowser, update, ui, port } = options;
|
80
82
|
let gridUrl = cluster.isPrimary ? config.gridUrl : options.gridUrl;
|
81
83
|
|
84
|
+
// TODO Add package.json with `"type": "commonjs"` as workaround for esm packages to load `data.js`
|
85
|
+
await writeFile(path.join(config.reportDir, 'package.json'), '{"type": "commonjs"}');
|
86
|
+
|
82
87
|
// NOTE: We don't need docker nor selenoid for update option
|
83
88
|
if (
|
84
89
|
!(gridUrl || (Object.values(config.browsers) as BrowserConfigObject[]).every(({ gridUrl }) => gridUrl)) &&
|
@@ -56,11 +56,13 @@ export class InternalBrowser {
|
|
56
56
|
#sessionId: string = v4();
|
57
57
|
#serverHost: string | null = null;
|
58
58
|
#serverPort: number;
|
59
|
+
#storybookGlobals?: StorybookGlobals;
|
59
60
|
#unsubscribe: () => void = noop;
|
60
|
-
constructor(browser: Browser, page: Page, port: number) {
|
61
|
+
constructor(browser: Browser, page: Page, port: number, storybookGlobals?: StorybookGlobals) {
|
61
62
|
this.#browser = browser;
|
62
63
|
this.#page = page;
|
63
64
|
this.#serverPort = port;
|
65
|
+
this.#storybookGlobals = storybookGlobals;
|
64
66
|
this.#unsubscribe = subscribeOn('shutdown', () => {
|
65
67
|
void this.closeBrowser();
|
66
68
|
});
|
@@ -112,6 +114,7 @@ export class InternalBrowser {
|
|
112
114
|
|
113
115
|
async selectStory(id: string, waitForReady = false): Promise<boolean> {
|
114
116
|
// NOTE: Global variables might be reset after hot reload. I think it's workaround, maybe we need better solution
|
117
|
+
await this.updateStorybookGlobals();
|
115
118
|
await this.updateBrowserGlobalVariables();
|
116
119
|
await this.resetMousePosition();
|
117
120
|
|
@@ -224,7 +227,7 @@ export class InternalBrowser {
|
|
224
227
|
|
225
228
|
// TODO Add debug output
|
226
229
|
|
227
|
-
const internalBrowser = new InternalBrowser(browser, page, options.port);
|
230
|
+
const internalBrowser = new InternalBrowser(browser, page, options.port, _storybookGlobals);
|
228
231
|
|
229
232
|
try {
|
230
233
|
if (isShuttingDown.current) return null;
|
@@ -232,7 +235,6 @@ export class InternalBrowser {
|
|
232
235
|
browserName,
|
233
236
|
viewport,
|
234
237
|
storybookUrl: address,
|
235
|
-
storybookGlobals: _storybookGlobals,
|
236
238
|
resolveStorybookUrl: config.resolveStorybookUrl,
|
237
239
|
});
|
238
240
|
|
@@ -254,13 +256,11 @@ export class InternalBrowser {
|
|
254
256
|
browserName,
|
255
257
|
viewport,
|
256
258
|
storybookUrl,
|
257
|
-
storybookGlobals,
|
258
259
|
resolveStorybookUrl,
|
259
260
|
}: {
|
260
261
|
browserName: string;
|
261
262
|
viewport?: { width: number; height: number };
|
262
263
|
storybookUrl: string;
|
263
|
-
storybookGlobals?: StorybookGlobals;
|
264
264
|
resolveStorybookUrl?: () => Promise<string>;
|
265
265
|
}) {
|
266
266
|
const sessionId = this.#sessionId;
|
@@ -278,7 +278,7 @@ export class InternalBrowser {
|
|
278
278
|
[
|
279
279
|
() => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
|
280
280
|
() => this.waitForStorybook(),
|
281
|
-
() => this.updateStorybookGlobals(
|
281
|
+
() => this.updateStorybookGlobals(),
|
282
282
|
() => this.resolveCreeveyHost(),
|
283
283
|
() => this.updateBrowserGlobalVariables(),
|
284
284
|
() => this.resizeViewport(viewport),
|
@@ -357,13 +357,13 @@ export class InternalBrowser {
|
|
357
357
|
if (isTimeout) throw new Error('Failed to wait `setStories` event');
|
358
358
|
}
|
359
359
|
|
360
|
-
private async updateStorybookGlobals(
|
361
|
-
if (!
|
360
|
+
private async updateStorybookGlobals(): Promise<void> {
|
361
|
+
if (!this.#storybookGlobals) return;
|
362
362
|
|
363
363
|
logger().debug('Applying storybook globals');
|
364
364
|
await this.#page.evaluate((globals: StorybookGlobals) => {
|
365
365
|
window.__CREEVEY_UPDATE_GLOBALS__(globals);
|
366
|
-
},
|
366
|
+
}, this.#storybookGlobals);
|
367
367
|
}
|
368
368
|
|
369
369
|
private async resolveCreeveyHost(): Promise<void> {
|