+ return
{title}
{subtitle && {subtitle}}
;
diff --git a/lib/static/new-ui/components/RunTest/index.module.css b/lib/static/new-ui/components/RunTest/index.module.css
index 533071a9f..d26aa4c62 100644
--- a/lib/static/new-ui/components/RunTest/index.module.css
+++ b/lib/static/new-ui/components/RunTest/index.module.css
@@ -1,14 +1,3 @@
-.buttons-container {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-left: auto;
-}
-
-.divider {
- height: 24px;
-}
-
.retry-button {
composes: regular-button from global, action-button from global;
}
diff --git a/lib/static/new-ui/components/RunTest/index.tsx b/lib/static/new-ui/components/RunTest/index.tsx
index 648192a97..7f53624c7 100644
--- a/lib/static/new-ui/components/RunTest/index.tsx
+++ b/lib/static/new-ui/components/RunTest/index.tsx
@@ -1,66 +1,45 @@
-import React, {ReactNode} from 'react';
+import React, {forwardRef} from 'react';
import styles from './index.module.css';
-import {IconButton} from '@/static/new-ui/components/IconButton';
-import {Button, Divider, Icon, Spin} from '@gravity-ui/uikit';
-import {ArrowRotateRight, CirclePlay} from '@gravity-ui/icons';
+import {Button, ButtonProps, Icon, Spin} from '@gravity-ui/uikit';
+import {ArrowRotateRight} from '@gravity-ui/icons';
import {thunkRunTest} from '@/static/modules/actions';
-import {toggleTimeTravelPlayerVisibility} from '@/static/modules/actions/snapshots';
import {useDispatch, useSelector} from 'react-redux';
-import {isTimeTravelPlayerAvailable} from '../../features/suites/selectors';
import {RunTestsFeature} from '@/constants';
import {useAnalytics} from '../../hooks/useAnalytics';
import type {BrowserEntity} from '@/static/new-ui/types/store';
+import {isFeatureAvailable} from '../../utils/features';
interface RunTestProps {
- showPlayer?: boolean;
browser: BrowserEntity | null;
+ buttonText?: string | null;
+ buttonProps?: ButtonProps;
}
-export const RunTest = ({showPlayer = true, browser}: RunTestProps): ReactNode => {
- const isPlayerVisible = useSelector(state => state.ui.suitesPage.isSnapshotsPlayerVisible);
- const isRunning = useSelector(state => state.running);
+export const RunTestButton = forwardRef
(
+ ({browser, buttonProps, buttonText}, ref) => {
+ const isRunning = useSelector(state => state.running);
- const analytics = useAnalytics();
- const dispatch = useDispatch();
- const isRunTestsAvailable = useSelector(state => state.app.availableFeatures)
- .find(feature => feature.name === RunTestsFeature.name);
+ const analytics = useAnalytics();
+ const dispatch = useDispatch();
+ const isRunTestsAvailable = isFeatureAvailable(RunTestsFeature);
- const isPlayerAvailable = useSelector(isTimeTravelPlayerAvailable);
+ const onRetryTestHandler = (): void => {
+ if (browser) {
+ analytics?.trackFeatureUsage({featureName: 'Retry test button click in test control panel'});
+ dispatch(thunkRunTest({test: {testName: browser.parentId, browserName: browser.name}}));
+ }
+ };
- const onRetryTestHandler = (): void => {
- if (browser) {
- analytics?.trackFeatureUsage({featureName: 'Retry test button click in test control panel'});
- dispatch(thunkRunTest({test: {testName: browser.parentId, browserName: browser.name}}));
+ if (!isRunTestsAvailable) {
+ return null;
}
- };
- const onTogglePlayerVisibility = (): void => {
- analytics?.trackFeatureUsage({featureName: 'Toggle time travel player visibility'});
- dispatch(toggleTimeTravelPlayerVisibility(!isPlayerVisible));
- };
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return ;
+ }
+);
- const showRetryButton = Boolean(isRunTestsAvailable);
- const showPlayerButton = isPlayerAvailable && showPlayer;
- const showDivider = showRetryButton && showPlayerButton;
-
- return (
-
- {showPlayerButton && (
-
}
- onClick={onTogglePlayerVisibility}
- view='outlined'
- selected={isPlayerVisible}
- />
- )}
- {showDivider && }
- {showRetryButton && (
-
- )}
-
- );
-};
+RunTestButton.displayName = 'RunTestButton';
diff --git a/lib/static/new-ui/features/suites/components/ScreenshotsTreeViewItem/index.tsx b/lib/static/new-ui/features/suites/components/ScreenshotsTreeViewItem/index.tsx
index 7fefe09e0..498869877 100644
--- a/lib/static/new-ui/features/suites/components/ScreenshotsTreeViewItem/index.tsx
+++ b/lib/static/new-ui/features/suites/components/ScreenshotsTreeViewItem/index.tsx
@@ -1,11 +1,12 @@
-import {ArrowUturnCcwLeft, Check, Eye} from '@gravity-ui/icons';
+import {ArrowRightArrowLeft, ArrowUturnCcwLeft, Check, Eye} from '@gravity-ui/icons';
import {Button, Icon, SegmentedRadioGroup as RadioButton, Select, Flex} from '@gravity-ui/uikit';
import React, {ReactNode, createRef, useEffect, useRef} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {AssertViewResult} from '@/static/new-ui/components/AssertViewResult';
import {ImageEntity} from '@/static/new-ui/types/store';
-import {DiffModeId, DiffModes, EditScreensFeature, TestStatus} from '@/constants';
+import {DiffModeId, EditScreensFeature, TestStatus} from '@/constants';
+import {getAvailableDiffModes} from '@/static/new-ui/utils/diffModes';
import {
setDiffMode,
staticAccepterStageScreenshot,
@@ -109,17 +110,17 @@ export function ScreenshotsTreeViewItem(props: ScreenshotsTreeViewItemProps): Re
{isDiffModeSwitcherVisible && (
- {Object.values(DiffModes).map(diffMode =>
+ {getAvailableDiffModes('suites').map(diffMode =>
)}
@@ -164,7 +165,7 @@ export function ScreenshotsTreeViewItem(props: ScreenshotsTreeViewItemProps): Re
)}
}>
-
+
);
diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx b/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx
index 4b984dceb..8e1487cbf 100644
--- a/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx
+++ b/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx
@@ -74,9 +74,9 @@ export function SuitesPage(): ReactNode {
return;
}
- dispatch(setStrictMatchFilter(false));
-
if (isInitialized && params.suiteId) {
+ dispatch(setStrictMatchFilter(false));
+
const treeNode = findTreeNodeByBrowserId(treeData.tree, params.suiteId);
if (!treeNode) {
diff --git a/lib/static/new-ui/features/suites/components/TestControlPanel/index.module.css b/lib/static/new-ui/features/suites/components/TestControlPanel/index.module.css
index 836cf7f1c..720493579 100644
--- a/lib/static/new-ui/features/suites/components/TestControlPanel/index.module.css
+++ b/lib/static/new-ui/features/suites/components/TestControlPanel/index.module.css
@@ -12,3 +12,14 @@
flex-wrap: wrap;
gap: 4px;
}
+
+.buttons-container {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-left: auto;
+}
+
+.divider {
+ height: 24px;
+}
diff --git a/lib/static/new-ui/features/suites/components/TestControlPanel/index.tsx b/lib/static/new-ui/features/suites/components/TestControlPanel/index.tsx
index 62a055854..d24f588cc 100644
--- a/lib/static/new-ui/features/suites/components/TestControlPanel/index.tsx
+++ b/lib/static/new-ui/features/suites/components/TestControlPanel/index.tsx
@@ -1,11 +1,18 @@
+import {Divider, Icon} from '@gravity-ui/uikit';
+import {CirclePlay} from '@gravity-ui/icons';
import React, {ReactNode} from 'react';
-import {useSelector} from 'react-redux';
+import {useDispatch, useSelector} from 'react-redux';
import {AttemptPickerItem} from '@/static/new-ui/components/AttemptPickerItem';
import styles from './index.module.css';
import classNames from 'classnames';
-import {getCurrentBrowser, getCurrentResultId} from '@/static/new-ui/features/suites/selectors';
-import {RunTest} from '@/static/new-ui/components/RunTest';
+import {getCurrentBrowser, getCurrentResultId, isTimeTravelPlayerAvailable} from '@/static/new-ui/features/suites/selectors';
+import {RunTestButton} from '@/static/new-ui/components/RunTest';
+import {useAnalytics} from '../../../../hooks/useAnalytics';
+import {IconButton} from '../../../../components/IconButton';
+import {isFeatureAvailable} from '../../../../utils/features';
+import {RunTestsFeature} from '@/constants';
+import {toggleTimeTravelPlayerVisibility} from '@/static/modules/actions/snapshots';
interface TestControlPanelProps {
onAttemptChange?: (browserId: string, resultId: string, attemptIndex: number) => unknown;
@@ -14,6 +21,9 @@ interface TestControlPanelProps {
export function TestControlPanel(props: TestControlPanelProps): ReactNode {
const {onAttemptChange} = props;
+ const dispatch = useDispatch();
+ const analytics = useAnalytics();
+
const browserId = useSelector(state => state.app.suitesPage.currentBrowserId);
const resultIds = useSelector(state => {
if (browserId && state.tree.browsers.byId[browserId]) {
@@ -32,6 +42,17 @@ export function TestControlPanel(props: TestControlPanelProps): ReactNode {
onAttemptChange?.(browserId, resultId, attemptIndex);
};
+ const isRunTestsAvailable = isFeatureAvailable(RunTestsFeature);
+ const isPlayerAvailable = useSelector(isTimeTravelPlayerAvailable);
+ const isPlayerVisible = useSelector(state => state.ui.suitesPage.isSnapshotsPlayerVisible);
+
+ const showDivider = isRunTestsAvailable && isPlayerAvailable;
+
+ const onTogglePlayerVisibility = (): void => {
+ analytics?.trackFeatureUsage({featureName: 'Toggle time travel player visibility'});
+ dispatch(toggleTimeTravelPlayerVisibility(!isPlayerVisible));
+ };
+
return (
Attempts
@@ -45,7 +66,19 @@ export function TestControlPanel(props: TestControlPanelProps): ReactNode {
/>
))}
-
+
+ {isPlayerAvailable && (
+
}
+ onClick={onTogglePlayerVisibility}
+ view='outlined'
+ selected={isPlayerVisible}
+ />
+ )}
+ {showDivider && }
+
+
);
}
diff --git a/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/VisualChecksStickyHeader.tsx b/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/VisualChecksStickyHeader.tsx
index fc24c0a92..2810f617b 100644
--- a/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/VisualChecksStickyHeader.tsx
+++ b/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/VisualChecksStickyHeader.tsx
@@ -1,5 +1,5 @@
-import {ArrowUturnCcwLeft, Check, ListCheck} from '@gravity-ui/icons';
-import {Button, Divider, Icon, Select, Flex} from '@gravity-ui/uikit';
+import {ArrowRightArrowLeft, ArrowUturnCcwLeft, Check, LayersVertical, ListCheck, SquareDashed, ChevronsExpandToLines} from '@gravity-ui/icons';
+import {Button, Divider, Icon, Select, Flex, Tooltip} from '@gravity-ui/uikit';
import React, {ReactNode, useEffect, useRef} from 'react';
import {useDispatch, useSelector} from 'react-redux';
@@ -13,12 +13,15 @@ import {
import {SuiteTitle} from '@/static/new-ui/components/SuiteTitle';
import styles from './index.module.css';
import {CompactAttemptPicker} from '@/static/new-ui/components/CompactAttemptPicker';
-import {DiffModeId, DiffModes, EditScreensFeature} from '@/constants';
+import {DiffModeId, EditScreensFeature, RunTestsFeature, TwoUpFitMode} from '@/constants';
+import {getAvailableDiffModes} from '@/static/new-ui/utils/diffModes';
import {
- setDiffMode,
staticAccepterStageScreenshot,
- staticAccepterUnstageScreenshot
+ staticAccepterUnstageScreenshot,
+ toggle2UpDiffVisibility,
+ set2UpFitMode
} from '@/static/modules/actions';
+import {setVisualChecksDiffMode} from '@/static/modules/actions/visual-checks-page';
import {isAcceptable, isScreenRevertable} from '@/static/modules/utils';
import {AssertViewStatus} from '@/static/new-ui/components/AssertViewStatus';
import {thunkAcceptImages, thunkRevertImages} from '@/static/modules/actions/screenshots';
@@ -26,7 +29,8 @@ import {useAnalytics} from '@/static/new-ui/hooks/useAnalytics';
import {preloadImageEntity} from '../../../../../modules/utils/imageEntity';
import {useNavigate} from 'react-router-dom';
-import {RunTest} from '../../../../components/RunTest';
+import {RunTestButton} from '../../../../components/RunTest';
+import {IconButton} from '../../../../components/IconButton';
interface VisualChecksStickyHeaderProps {
currentNamedImage: NamedImageEntity | null;
@@ -72,9 +76,20 @@ export function VisualChecksStickyHeader({currentNamedImage, visibleNamedImageId
usePreloadImages(currentNamedImageIndex, visibleNamedImageIds);
- const diffMode = useSelector(state => state.view.diffMode);
+ const diffMode = useSelector(state => state.app.visualChecksPage.diffMode);
+ const is2UpDiffVisible = useSelector(state => state.ui.visualChecksPage.is2UpDiffVisible);
+ const twoUpFitMode = useSelector(state => state.ui.visualChecksPage.twoUpFitMode);
const onChangeHandler = (diffModeId: DiffModeId): void => {
- dispatch(setDiffMode({diffModeId}));
+ dispatch(setVisualChecksDiffMode(diffModeId));
+ };
+ const onToggle2UpDiffVisibility = (): void => {
+ analytics?.trackFeatureUsage({featureName: 'Toggle 2-up diff visibility'});
+ dispatch(toggle2UpDiffVisibility(!is2UpDiffVisible));
+ };
+ const onToggle2UpFitMode = (): void => {
+ const newFitMode = twoUpFitMode === TwoUpFitMode.FitToView ? TwoUpFitMode.FitToWidth : TwoUpFitMode.FitToView;
+ analytics?.trackFeatureUsage({featureName: 'Toggle 2-up fit mode'});
+ dispatch(set2UpFitMode(newFitMode));
};
const isStaticImageAccepterEnabled = useSelector(state => state.staticImageAccepter.enabled);
@@ -114,6 +129,9 @@ export function VisualChecksStickyHeader({currentNamedImage, visibleNamedImageId
const isLastResult = Boolean(currentResultId && currentBrowser && currentResultId === currentBrowser.resultIds[currentBrowser.resultIds.length - 1]);
const isUndoAvailable = isScreenRevertable({gui: isGui, image: currentImage ?? {}, isLastResult, isStaticImageAccepterEnabled});
+ const isRunTestsAvailable = Boolean(useSelector(state => state.app.availableFeatures)
+ .find(feature => feature.name === RunTestsFeature.name));
+
const onSuites = (): void => {
if (currentNamedImage) {
navigate('/' + [
@@ -145,22 +163,44 @@ export function VisualChecksStickyHeader({currentNamedImage, visibleNamedImageId