diff --git a/lib/constants/index.ts b/lib/constants/index.ts index 9223c00ef..f266e2137 100644 --- a/lib/constants/index.ts +++ b/lib/constants/index.ts @@ -13,3 +13,5 @@ export * from './test-statuses'; export * from './tool-names'; export * from './two-up-modes'; export * from './view-modes'; +export * from './pages'; +export * from './expand-modes'; diff --git a/lib/constants/pages.ts b/lib/constants/pages.ts new file mode 100644 index 000000000..bb1ab6692 --- /dev/null +++ b/lib/constants/pages.ts @@ -0,0 +1,9 @@ +export enum Page { + suitesPage = 'suitesPage', + visualChecksPage = 'visualChecksPage', +} + +export enum PathNames { + suites = '/suites', + visualChecks = '/visual-checks', +} diff --git a/lib/static/modules/actions/filters.ts b/lib/static/modules/actions/filters.ts index 49393e5d2..2c57d9aeb 100644 --- a/lib/static/modules/actions/filters.ts +++ b/lib/static/modules/actions/filters.ts @@ -2,8 +2,7 @@ import actionNames from '@/static/modules/action-names'; import type {Action} from '@/static/modules/actions/types'; import {setFilteredBrowsers} from '@/static/modules/query-params'; import {BrowserItem} from '@/types'; -import {ViewMode} from '@/constants'; -import {Page} from '@/static/new-ui/types/store'; +import {Page, ViewMode} from '@/constants'; interface FilterPayload{ page: Page; diff --git a/lib/static/modules/actions/suites-page.ts b/lib/static/modules/actions/suites-page.ts index f93366806..01917d96c 100644 --- a/lib/static/modules/actions/suites-page.ts +++ b/lib/static/modules/actions/suites-page.ts @@ -1,6 +1,7 @@ import actionNames from '@/static/modules/action-names'; import {Action} from '@/static/modules/actions/types'; -import {Page, TreeViewMode} from '@/static/new-ui/types/store'; +import {TreeViewMode} from '@/static/new-ui/types/store'; +import {Page} from '@/constants'; export type SuitesPageSetCurrentTreeNodeAction = Action; +export type VisualChecksPageSetCurrentNamedImageData = { + currentBrowserId?: string; + stateName?: string; +}; + +export type VisualChecksPageSetCurrentNamedImageAction = Action; -export const visualChecksPageSetCurrentNamedImage = (namedImageId: string): VisualChecksPageSetCurrentNamedImageAction => { - return {type: actionNames.VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE, payload: {namedImageId}}; +export const visualChecksPageSetCurrentNamedImage = (data: VisualChecksPageSetCurrentNamedImageData): VisualChecksPageSetCurrentNamedImageAction => { + return {type: actionNames.VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE, payload: data}; }; export type Toggle2UpDiffVisibilityAction = Action { switch (action.type) { case actionNames.VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE: - return applyStateUpdate(state, {app: {visualChecksPage: {currentNamedImageId: action.payload.namedImageId}}}) as State; + return applyStateUpdate(state, {app: {visualChecksPage: action.payload}}) as State; case actionNames.VISUAL_CHECKS_TOGGLE_2UP_DIFF_VISIBILITY: localStorageWrapper.setItem(TWO_UP_DIFF_VISIBILITY_KEY, action.payload.isVisible); return applyStateUpdate(state, { diff --git a/lib/static/modules/search/index.ts b/lib/static/modules/search/index.ts index f047cc55e..da12bb5a4 100644 --- a/lib/static/modules/search/index.ts +++ b/lib/static/modules/search/index.ts @@ -1,5 +1,5 @@ import {setSearchLoading, updateNameFilter, setMatchCaseFilter} from '@/static/modules/actions'; -import {Page} from '@/static/new-ui/types/store'; +import {Page} from '@/constants'; let worker: Worker; let searchResult: Set = new Set([]); diff --git a/lib/static/new-ui/app/App.tsx b/lib/static/new-ui/app/App.tsx index 97b8b6998..dfd4dd1f8 100644 --- a/lib/static/new-ui/app/App.tsx +++ b/lib/static/new-ui/app/App.tsx @@ -19,27 +19,28 @@ import {AnalyticsProvider} from '@/static/new-ui/providers/analytics'; import {MetrikaScript} from '@/static/new-ui/components/MetrikaScript'; import {ErrorHandler} from '../features/error-handling/components/ErrorHandling'; import FaviconChanger from '../../components/favicon-changer'; +import {PathNames} from '@/constants'; const toaster = new Toaster(); -export function App(): ReactNode { - const pages = [ - { - title: 'Suites', - url: '/suites', - icon: ListCheck, - element: , - children: [} />] - }, - { - title: 'Visual Checks', - url: '/visual-checks', - icon: Eye, - element: , - children: [} />] - } - ]; +const pages = [ + { + title: 'Suites', + url: PathNames.suites, + icon: ListCheck, + element: , + children: [} />] + }, + { + title: 'Visual Checks', + url: PathNames.visualChecks, + icon: Eye, + element: , + children: [} />] + } +]; +export function App(): ReactNode { const customScripts = (store.getState() as State).config.customScripts; return @@ -56,7 +57,7 @@ export function App(): ReactNode { - } path={'/'}/> + } path={'/'}/> {pages.map(page => ( }> diff --git a/lib/static/new-ui/components/GuiniToolbarOverlay/index.tsx b/lib/static/new-ui/components/GuiniToolbarOverlay/index.tsx index 5f1473329..ec045fcb6 100644 --- a/lib/static/new-ui/components/GuiniToolbarOverlay/index.tsx +++ b/lib/static/new-ui/components/GuiniToolbarOverlay/index.tsx @@ -14,7 +14,7 @@ import { } from '@/static/modules/actions'; import {Point} from '@/static/new-ui/types'; import {useLocation} from 'react-router-dom'; -import {TestStatus} from '@/constants'; +import {TestStatus, PathNames} from '@/constants'; import {formatCommitPayload} from '@/static/modules/static-image-accepter'; import {pick} from 'lodash'; @@ -43,7 +43,7 @@ export function GuiniToolbarOverlay(): ReactNode { const newIsVisible = stagedImages.length > 0 && !isInProgress && !isModalVisible && - ['/suites', '/visual-checks'].some((path) => location.pathname.startsWith(path)); + [PathNames.suites, PathNames.visualChecks].some((path) => location.pathname.startsWith(path)); if (Boolean(newIsVisible) !== Boolean(isVisible)) { setIsVisible(newIsVisible); } diff --git a/lib/static/new-ui/components/MainLayout/index.tsx b/lib/static/new-ui/components/MainLayout/index.tsx index 720de0ed4..3148e292f 100644 --- a/lib/static/new-ui/components/MainLayout/index.tsx +++ b/lib/static/new-ui/components/MainLayout/index.tsx @@ -15,7 +15,7 @@ import {useAnalytics} from '@/static/new-ui/hooks/useAnalytics'; import {setSectionSizes} from '../../../modules/actions/suites-page'; import {ArrowLeftToLine, ArrowRightFromLine} from '@gravity-ui/icons'; import {isSectionHidden} from '../../features/suites/utils'; -import {Page} from '@/static/new-ui/types/store'; +import {Page, PathNames} from '@/constants'; export enum PanelId { Settings = 'settings', @@ -104,7 +104,7 @@ export function MainLayout(props: MainLayoutProps): ReactNode { return navigate('/suites')}} + logo={{text: 'Testplane UI', iconSrc: TestplaneIcon, iconSize: 32, onClick: () => navigate(PathNames.suites)}} compact={true} headerDecoration={false} menuItems={menuItems} 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 498869877..5ee7a8cbc 100644 --- a/lib/static/new-ui/features/suites/components/ScreenshotsTreeViewItem/index.tsx +++ b/lib/static/new-ui/features/suites/components/ScreenshotsTreeViewItem/index.tsx @@ -5,7 +5,7 @@ import {useDispatch, useSelector} from 'react-redux'; import {AssertViewResult} from '@/static/new-ui/components/AssertViewResult'; import {ImageEntity} from '@/static/new-ui/types/store'; -import {DiffModeId, EditScreensFeature, TestStatus} from '@/constants'; +import {DiffModeId, EditScreensFeature, TestStatus, Page} from '@/constants'; import {getAvailableDiffModes} from '@/static/new-ui/utils/diffModes'; import { setDiffMode, @@ -13,13 +13,14 @@ import { staticAccepterUnstageScreenshot } from '@/static/modules/actions'; import {isAcceptable, isScreenRevertable} from '@/static/modules/utils'; -import {getCurrentBrowser, getCurrentResult} from '@/static/new-ui/features/suites/selectors'; +import {getCurrentBrowser, getCurrentResult, getCurrentBrowserId} from '@/static/new-ui/features/suites/selectors'; import {AssertViewStatus} from '@/static/new-ui/components/AssertViewStatus'; import styles from './index.module.css'; import {thunkAcceptImages, thunkRevertImages} from '@/static/modules/actions/screenshots'; import {useAnalytics} from '@/static/new-ui/hooks/useAnalytics'; import {ErrorHandler} from '../../../error-handling/components/ErrorHandling'; import {useNavigate, useParams} from 'react-router-dom'; +import {getUrl} from '@/static/new-ui/utils/getUrl'; interface ScreenshotsTreeViewItemProps { image: ImageEntity; @@ -44,9 +45,10 @@ export function ScreenshotsTreeViewItem(props: ScreenshotsTreeViewItemProps): Re const dispatch = useDispatch(); const analytics = useAnalytics(); const navigate = useNavigate(); - const {suiteId, stateName} = useParams(); + const {hash, browser, stateName} = useParams(); const ref = createRef(); const inited = useRef(false); + const suiteId = useSelector(getCurrentBrowserId({hash, browser})); const diffMode = useSelector(state => state.view.diffMode); const isEditScreensAvailable = useSelector(state => state.app.availableFeatures) @@ -87,7 +89,13 @@ export function ScreenshotsTreeViewItem(props: ScreenshotsTreeViewItemProps): Re const imageId = `${currentResult?.parentId} ${props.image.stateName}`; const onVisualChecks = (): void => { - navigate(`/visual-checks/${encodeURIComponent(imageId)}/${currentResult?.attempt}`); + navigate(getUrl({ + page: Page.visualChecksPage, + hash, + browser, + stateName: props.image.stateName, + attempt: currentResult?.attempt + })); }; useEffect(() => { @@ -110,7 +118,7 @@ export function ScreenshotsTreeViewItem(props: ScreenshotsTreeViewItemProps): Re {isDiffModeSwitcherVisible && (
- {getAvailableDiffModes('suites').map(diffMode => + {getAvailableDiffModes(Page.suitesPage).map(diffMode => )} @@ -120,7 +128,7 @@ export function ScreenshotsTreeViewItem(props: ScreenshotsTreeViewItemProps): Re onUpdate={([diffMode]): void => onDiffModeChangeHandler(diffMode as DiffModeId)} multiple={false} > - {getAvailableDiffModes('suites').map(diffMode => + {getAvailableDiffModes(Page.suitesPage).map(diffMode => )} 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 8b198412b..bfe276254 100644 --- a/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx +++ b/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx @@ -2,15 +2,20 @@ import classNames from 'classnames'; import React, {ReactNode, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import {useNavigate, useParams} from 'react-router-dom'; - import {UiCard} from '@/static/new-ui/components/Card/UiCard'; -import {getAttempt, getCurrentResult, getCurrentResultImages} from '@/static/new-ui/features/suites/selectors'; +import { + getAttempt, + getCurrentResult, + getCurrentResultImages, + getCurrentBrowserId +} from '@/static/new-ui/features/suites/selectors'; import {SplitViewLayout} from '@/static/new-ui/components/SplitViewLayout'; import {TreeViewHandle} from '@/static/new-ui/components/TreeView'; import {SuiteTitle} from '@/static/new-ui/components/SuiteTitle'; import * as actions from '@/static/modules/actions'; import {getIsInitialized} from '@/static/new-ui/store/selectors'; import {TestControlPanel} from '@/static/new-ui/features/suites/components/TestControlPanel'; +import {TestStatusBar} from '@/static/new-ui/features/suites/components/TestStatusBar'; import styles from './index.module.css'; import {TestInfoSkeleton} from '@/static/new-ui/features/suites/components/SuitesPage/TestInfoSkeleton'; @@ -26,12 +31,12 @@ import {ErrorHandler} from '../../../error-handling/components/ErrorHandling'; import {TestInfo} from '@/static/new-ui/features/suites/components/TestInfo'; import {MIN_SECTION_SIZE_PERCENT} from '../../constants'; import {SideBar} from '@/static/new-ui/components/SideBar'; -import {getSuitesStatusCounts, getSuitesTreeViewData} from './selectors'; +import {getCurrentSuiteHash, getSuitesStatusCounts, getSuitesTreeViewData} from './selectors'; import {getIconByStatus} from '@/static/new-ui/utils'; -import {Page} from '@/static/new-ui/types/store'; +import {Page} from '@/constants'; import {usePage} from '@/static/new-ui/hooks/usePage'; import {changeTestRetry, setCurrentTreeNode, setStrictMatchFilter} from '@/static/modules/actions'; -import {TestStatusBar} from '@/static/new-ui/features/suites/components/TestStatusBar'; +import {getUrl} from '@/static/new-ui/utils/getUrl'; export function SuitesPage(): ReactNode { const page = usePage(); @@ -42,6 +47,8 @@ export function SuitesPage(): ReactNode { const params = useParams(); const attempt = useSelector(getAttempt); const currentBrowser = useSelector(state => state.app[Page.suitesPage].currentBrowserId); + const hash = useSelector(getCurrentSuiteHash); + const urlBrowserId = useSelector(getCurrentBrowserId(params)); const currentTreeNodeId = useSelector(state => state.app[Page.suitesPage].currentTreeNodeId); const currentIndex = visibleTreeNodeIds.indexOf(currentTreeNodeId as string); @@ -60,34 +67,41 @@ export function SuitesPage(): ReactNode { }, [page]); useEffect(() => { - if (currentResult?.parentId && attempt !== null && resultImages.length) { - navigate('/' + [ - 'suites', - currentResult.parentId as string, - params.stateName !== undefined ? params.stateName : resultImages[0].stateName as string, - attempt?.toString() as string - ].map(encodeURIComponent).join('/')); + const stateName = + (params.stateName && resultImages.some((item) => item.stateName === params.stateName)) ? + params.stateName : + (resultImages.length ? resultImages[0].stateName : undefined) + ; + + if (currentResult?.parentId && attempt !== null) { + navigate(getUrl({ + page: Page.suitesPage, + attempt, + hash, + browser: currentResult.name, + stateName + })); } - }, [currentResult, attempt]); + }, [currentResult, attempt, hash]); useEffect(() => { - if (currentBrowser === params.suiteId) { + if (currentBrowser === urlBrowserId) { return; } - if (isInitialized && params.suiteId) { + if (isInitialized && urlBrowserId) { dispatch(setStrictMatchFilter(false)); - const treeNode = findTreeNodeByBrowserId(treeData.tree, params.suiteId); + const treeNode = findTreeNodeByBrowserId(treeData.tree, urlBrowserId); if (!treeNode) { return; } - dispatch(setCurrentTreeNode({browserId: params.suiteId, treeNodeId: treeNode.id})); + dispatch(setCurrentTreeNode({browserId: urlBrowserId, treeNodeId: treeNode.id})); if (params.attempt !== undefined) { - dispatch(changeTestRetry({browserId: params.suiteId, retryIndex: Number(params.attempt)})); + dispatch(changeTestRetry({browserId: urlBrowserId, retryIndex: Number(params.attempt)})); } } }, [isInitialized, params]); @@ -215,7 +229,7 @@ export function SuitesPage(): ReactNode { onStatusChange={onStatusChange} /> - }> + }> {currentResult && <>
setStickyHeaderElement(ref)}> onPrevNextSuiteHandler(1)} - onPrevious={(): void => onPrevNextSuiteHandler(-1)} - /> + onPrevious={(): void => onPrevNextSuiteHandler(-1)}/>
+ } - {!params.suiteId && !currentResult &&
Select a test to see details
} - {params.suiteId && !isInitialized && } + {!urlBrowserId && !currentResult &&
Select a test to see details
} + {urlBrowserId && !isInitialized && }
diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/selectors.ts b/lib/static/new-ui/features/suites/components/SuitesPage/selectors.ts index 7826322b2..95fe0166f 100644 --- a/lib/static/new-ui/features/suites/components/SuitesPage/selectors.ts +++ b/lib/static/new-ui/features/suites/components/SuitesPage/selectors.ts @@ -13,6 +13,8 @@ import { import {buildTreeBottomUp, collectTreeLeafIds, formatEntityToTreeNodeData, sortTreeNodes} from './utils'; import {TestStatus} from '@/constants'; import {TreeViewData} from '@/static/new-ui/components/TreeView'; +import {getCurrentResult} from '@/static/new-ui/features/suites/selectors'; +import {State} from '@/static/new-ui/types/store'; // Converts the existing store structure to the one that can be consumed by GravityUI export const getSuitesTreeViewData = createSelector( @@ -74,6 +76,12 @@ export interface SuitesStatusCounts { idle: number; } +export const getCurrentSuiteHash = (state: State): string | null => { + const currentResult = getCurrentResult(state); + + return state.tree.suites.byId[currentResult?.suitePath?.join(' ') || '']?.hash; +}; + export const getSuitesStatusCounts = createSelector( [getResults, getBrowsersState], (results, browsersState) => { diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/types.ts b/lib/static/new-ui/features/suites/components/SuitesPage/types.ts index d93c2ab1a..efd16a89d 100644 --- a/lib/static/new-ui/features/suites/components/SuitesPage/types.ts +++ b/lib/static/new-ui/features/suites/components/SuitesPage/types.ts @@ -20,6 +20,8 @@ export interface TreeViewItemData { images?: ImageEntity[]; parentData?: TreeViewItemData; skipReason?: string; + browserId?: string; + stateName?: string; } export interface TreeRoot { diff --git a/lib/static/new-ui/features/suites/components/TestSteps/index.tsx b/lib/static/new-ui/features/suites/components/TestSteps/index.tsx index 9934664e7..8810c9ab5 100644 --- a/lib/static/new-ui/features/suites/components/TestSteps/index.tsx +++ b/lib/static/new-ui/features/suites/components/TestSteps/index.tsx @@ -27,6 +27,9 @@ import {ErrorHandler} from '../../../error-handling/components/ErrorHandling'; import {goToTimeInSnapshotsPlayer, setCurrentPlayerHighlightTime} from '@/static/modules/actions/snapshots'; import {setCurrentStep} from '@/static/modules/actions'; import {useNavigate} from 'react-router-dom'; +import {getUrl} from '@/static/new-ui/utils/getUrl'; +import {Page} from '@/constants'; +import {getCurrentSuiteHash} from '@/static/new-ui/features/suites/components/SuitesPage/selectors'; type TestStepClickHandler = (item: {id: string}) => void @@ -140,6 +143,7 @@ function TestStepsInternal(props: TestStepsProps): ReactNode { const dispatch = useDispatch(); const navigate = useNavigate(); const currentResult = useSelector(getCurrentResult); + const hash = useSelector(getCurrentSuiteHash); const currentStepId = useSelector(state => state.app.suitesPage.currentStepId); const currentHighlightStepId = useSelector(state => state.app.suitesPage.currentHighlightedStepId); @@ -171,13 +175,14 @@ function TestStepsInternal(props: TestStepsProps): ReactNode { expandedById: Object.assign({}, props.stepsExpandedById, {[id]: !props.stepsExpandedById[id]}) }); - if (step.type === StepType.Action) { - navigate('/' + [ - 'suites', - currentResult?.parentId as string, - step.args[0] as string, - currentResult?.attempt?.toString() as string - ].map(encodeURIComponent).join('/')); + if (step.type === StepType.Action && step.title === 'assertView') { + navigate(getUrl({ + page: Page.suitesPage, + hash, + browser: currentResult?.name, + attempt: currentResult?.attempt, + stateName: step.args[0] + })); } }, [items, props.actions, props.stepsExpandedById]); diff --git a/lib/static/new-ui/features/suites/selectors.ts b/lib/static/new-ui/features/suites/selectors.ts index 325065e84..d80507dd3 100644 --- a/lib/static/new-ui/features/suites/selectors.ts +++ b/lib/static/new-ui/features/suites/selectors.ts @@ -57,6 +57,20 @@ export const getCurrentResultImages = (state: State): ImageEntity[] => { return result?.imageIds.map(imageId => state.tree.images.byId[imageId]) ?? []; }; +export const getCurrentBrowserId = (params: Record): ((state: State) => string | null) => { + return (state: State) => { + if (params.hash && params.browser) { + const currentSuite = state.tree.suites.byHash[params.hash]; + + if (currentSuite) { + return currentSuite.id + ' ' + params.browser; + } + } + + return null; + }; +}; + export const isTimeTravelPlayerAvailable = (state: State): boolean => { const currentResult = getCurrentResult(state); if (!currentResult) { 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 2810f617b..30d0259f7 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 @@ -31,11 +31,16 @@ import {preloadImageEntity} from '../../../../../modules/utils/imageEntity'; import {useNavigate} from 'react-router-dom'; import {RunTestButton} from '../../../../components/RunTest'; import {IconButton} from '../../../../components/IconButton'; +import {getUrl} from '@/static/new-ui/utils/getUrl'; +import {Page} from '@/constants'; +import {TreeViewData} from '@/static/new-ui/components/TreeView'; +import {TreeViewItemData} from '@/static/new-ui/features/suites/components/SuitesPage/types'; +import {getCurrentImageSuiteHash} from '@/static/new-ui/features/visual-checks/components/VisualChecksPage/selectors'; interface VisualChecksStickyHeaderProps { currentNamedImage: NamedImageEntity | null; - visibleNamedImageIds: string[]; - onImageChange: (id: string) => void; + treeData: TreeViewData; + onImageChange: (item: TreeViewItemData) => void; } export const PRELOAD_IMAGES_COUNT = 3; @@ -63,16 +68,18 @@ const usePreloadImages = ( }, []); }; -export function VisualChecksStickyHeader({currentNamedImage, visibleNamedImageIds, onImageChange}: VisualChecksStickyHeaderProps): ReactNode { +export function VisualChecksStickyHeader({currentNamedImage, treeData, onImageChange}: VisualChecksStickyHeaderProps): ReactNode { + const visibleNamedImageIds = treeData.allTreeNodeIds; const dispatch = useDispatch(); const analytics = useAnalytics(); const currentImage = useSelector(getCurrentImage); const attempt = useSelector(getAttempt); const navigate = useNavigate(); + const hash = useSelector(getCurrentImageSuiteHash); const currentNamedImageIndex = visibleNamedImageIds.indexOf(currentNamedImage?.id as string); - const onPreviousImageHandler = (): void => onImageChange(visibleNamedImageIds[currentNamedImageIndex - 1]); - const onNextImageHandler = (): void => onImageChange(visibleNamedImageIds[currentNamedImageIndex + 1]); + const onPreviousImageHandler = (): void => onImageChange(treeData.tree[currentNamedImageIndex - 1].data); + const onNextImageHandler = (): void => onImageChange(treeData.tree[currentNamedImageIndex + 1].data); usePreloadImages(currentNamedImageIndex, visibleNamedImageIds); @@ -134,12 +141,13 @@ export function VisualChecksStickyHeader({currentNamedImage, visibleNamedImageId const onSuites = (): void => { if (currentNamedImage) { - navigate('/' + [ - 'suites', - currentNamedImage?.browserId as string, - currentNamedImage?.stateName as string, - attempt?.toString() as string - ].map(encodeURIComponent).join('/')); + navigate(getUrl({ + page: Page.suitesPage, + hash, + browser: currentNamedImage.browserName, + attempt, + stateName: currentNamedImage?.stateName + })); } }; @@ -165,7 +173,7 @@ export function VisualChecksStickyHeader({currentNamedImage, visibleNamedImageId diff --git a/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/index.tsx b/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/index.tsx index 6fd5f278f..bd1c56ee2 100644 --- a/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/index.tsx +++ b/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/index.tsx @@ -20,17 +20,18 @@ import {ErrorHandler} from '../../../error-handling/components/ErrorHandling'; import * as actions from '@/static/modules/actions'; import {changeTestRetry, visualChecksPageSetCurrentNamedImage} from '@/static/modules/actions'; import {SideBar} from '@/static/new-ui/components/SideBar'; -import {getVisualChecksViewMode, getVisualTreeViewData} from './selectors'; +import {getCurrentImageSuiteHash, getVisualChecksViewMode, getVisualTreeViewData} from './selectors'; import {TreeViewHandle} from '@/static/new-ui/components/TreeView'; import {TreeViewItemData} from '@/static/new-ui/features/suites/components/SuitesPage/types'; -import {TestStatus, ViewMode} from '@/constants'; +import {Page, TestStatus, ViewMode} from '@/constants'; import {getIconByStatus} from '@/static/new-ui/utils'; import {isSectionHidden} from '@/static/new-ui/features/suites/utils'; import {MIN_SECTION_SIZE_PERCENT} from '@/static/new-ui/features/suites/constants'; -import {Page} from '@/static/new-ui/types/store'; import {usePage} from '@/static/new-ui/hooks/usePage'; import {useNavigate, useParams} from 'react-router-dom'; import {RunTestLoading} from '@/static/new-ui/components/RunTestLoading'; +import {getUrl} from '@/static/new-ui/utils/getUrl'; +import {getCurrentBrowserId} from '@/static/new-ui/features/suites/selectors'; export function VisualChecksPage(): ReactNode { const dispatch = useDispatch(); @@ -48,8 +49,17 @@ export function VisualChecksPage(): ReactNode { const params = useParams(); const inited = useRef(false); const isRunning = currentNamedImage?.status === TestStatus.RUNNING; + const urlBrowserId = useSelector(getCurrentBrowserId(params)); + const currentImageSuiteId = useSelector((state) => ( + state.app.visualChecksPage.currentBrowserId + )); + const hash = useSelector(getCurrentImageSuiteHash); - const currentTreeNodeId = useSelector((state) => state.app.visualChecksPage.currentNamedImageId); + const currentImageStateName = useSelector((state) => ( + state.app.visualChecksPage.stateName + )); + + const currentTreeNodeId = `${currentImageSuiteId} ${currentImageStateName}`; const treeData = useSelector(getVisualTreeViewData); const suitesTreeViewRef = useRef(null); @@ -59,15 +69,14 @@ export function VisualChecksPage(): ReactNode { }, [suitesTreeViewRef, currentTreeNodeId]); const isInitialized = useSelector(state => state.app.isInitialized); - const onImageChange = useCallback((imageId: string) => { - dispatch(visualChecksPageSetCurrentNamedImage(imageId)); - setImageChanged(true); - }, [currentBrowser]); - const onTreeItemClick = useCallback((item: TreeViewItemData) => { - dispatch(visualChecksPageSetCurrentNamedImage(item.id)); + const onImageChange = useCallback((item: TreeViewItemData) => { + dispatch(visualChecksPageSetCurrentNamedImage({ + currentBrowserId: item.browserId, + stateName: item.stateName + })); setImageChanged(true); - }, [currentBrowser, attempt]); + }, [treeData]); useEffect(() => { if (imageChanged && currentBrowser) { @@ -77,25 +86,36 @@ export function VisualChecksPage(): ReactNode { }, [imageChanged, currentBrowser]); useEffect(() => { - if (currentTreeNodeId && attempt !== null) { - navigate(`${encodeURIComponent(currentTreeNodeId as string)}/${attempt}`); + if (hash && currentImageStateName && attempt !== null) { + navigate(getUrl({ + page: Page.visualChecksPage, + hash, + browser: currentBrowser?.name, + attempt: attempt, + stateName: currentImageStateName + })); } - }, [currentTreeNodeId, attempt]); + }, [hash, currentBrowser, currentImageStateName, attempt]); useEffect(() => { - if (params && isInitialized && !inited.current) { + if (isInitialized && !inited.current) { inited.current = true; - if (params.imageId) { - dispatch(visualChecksPageSetCurrentNamedImage(params.imageId)); - - if (params.attempt !== undefined) { - const browserId = params.imageId.split(' ').slice(0, -1).join(' '); - dispatch(changeTestRetry({browserId, retryIndex: Number(params.attempt)})); + if (params) { + if (urlBrowserId && params.stateName) { + dispatch(visualChecksPageSetCurrentNamedImage({ + currentBrowserId: urlBrowserId, + stateName: params.stateName + })); + } else if (currentNamedImage) { + dispatch(visualChecksPageSetCurrentNamedImage({ + currentBrowserId: currentNamedImage?.browserId, + stateName: currentNamedImage?.stateName + })); } } } - }, [params, isInitialized]); + }, [urlBrowserId, params, isInitialized, currentNamedImage]); const statusValue = useSelector(getVisualChecksViewMode); @@ -153,7 +173,7 @@ export function VisualChecksPage(): ReactNode { treeData={treeData} treeViewExpandedById={{}} currentTreeNodeId={currentTreeNodeId} - onClick={onTreeItemClick} + onClick={onImageChange} statusList={statusList} statusValue={statusValue} onStatusChange={onStatusChange} @@ -165,7 +185,7 @@ export function VisualChecksPage(): ReactNode { {currentNamedImage && ( )} diff --git a/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/selectors.ts b/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/selectors.ts index 48ae27293..49aa40451 100644 --- a/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/selectors.ts +++ b/lib/static/new-ui/features/visual-checks/components/VisualChecksPage/selectors.ts @@ -1,10 +1,10 @@ import {createSelector} from 'reselect'; import {getImages} from '@/static/new-ui/store/selectors'; import {EntityType, TreeRoot} from '@/static/new-ui/features/suites/components/SuitesPage/types'; -import {ImageEntity, Page, State} from '@/static/new-ui/types/store'; +import {ImageEntity, State} from '@/static/new-ui/types/store'; import {getNamedImages} from '@/static/new-ui/features/visual-checks/selectors'; import {TreeViewData} from '@/static/new-ui/components/TreeView'; -import {TestStatus, ViewMode} from '@/constants'; +import {Page, TestStatus, ViewMode} from '@/constants'; import {matchTestName} from '@/static/modules/utils'; import {checkSearchResultExits} from '@/static/modules/search'; @@ -16,6 +16,13 @@ interface VisualTreeViewData extends TreeViewData{ stats: Stats; } +export const getCurrentImageSuiteHash = (state: State): string | null => { + const browserId = state.app.visualChecksPage.currentBrowserId || ''; + const suiteId = state.tree.browsers.byId[browserId]?.parentId || ''; + + return state.tree.suites.byId[suiteId]?.hash; +}; + export const getVisualTreeViewData = createSelector( [ getImages, @@ -91,6 +98,8 @@ export const getVisualTreeViewData = createSelector( status: item.status === TestStatus.RUNNING ? item.status : images[item.imageIds[item.imageIds.length - 1]].status, tags: [], title: [...item.suitePath, item.browserName], + browserId: item.browserId, + stateName: item.stateName, images: [ images[item.imageIds[item.imageIds.length - 1]] as ImageEntity ] diff --git a/lib/static/new-ui/features/visual-checks/selectors.ts b/lib/static/new-ui/features/visual-checks/selectors.ts index 43ba23adb..a8cdc8456 100644 --- a/lib/static/new-ui/features/visual-checks/selectors.ts +++ b/lib/static/new-ui/features/visual-checks/selectors.ts @@ -88,11 +88,14 @@ export const getNamedImages = createSelector( ); export const getCurrentNamedImage = (state: State): NamedImageEntity | null => { - const currentNamedImageId = state.app.visualChecksPage.currentNamedImageId; + const currentNamedImageId = [state.app.visualChecksPage.currentBrowserId, state.app.visualChecksPage.stateName].join(' '); const namedImages = getNamedImages(state); - if (!currentNamedImageId) { - return Object.values(namedImages)[0]; + if (!currentNamedImageId || !namedImages[currentNamedImageId]) { + const list = Object.values(namedImages); + const firstFailed = list.find(({status}) => (status === TestStatus.FAIL || status === TestStatus.FAIL_ERROR)); + + return firstFailed || list[0]; } return namedImages[currentNamedImageId]; @@ -167,7 +170,7 @@ export const getLastAttempt = (state: State): number => { }; export const getCurrentBrowser = (state: State): BrowserEntity | null => { - const currentNamedImageId = state.app.visualChecksPage.currentNamedImageId; + const currentNamedImageId = getCurrentNamedImage(state)?.id; const namedImages = getNamedImages(state); if (currentNamedImageId) { diff --git a/lib/static/new-ui/hooks/usePage.ts b/lib/static/new-ui/hooks/usePage.ts index 1f61f9e5f..ab6a02503 100644 --- a/lib/static/new-ui/hooks/usePage.ts +++ b/lib/static/new-ui/hooks/usePage.ts @@ -1,17 +1,6 @@ import {useLocation} from 'react-router-dom'; -import {Page} from '@/static/new-ui/types/store'; - -function getPageByPathname(pathname: string): Page { - if (pathname.startsWith('/visual-checks')) { - return Page.visualChecksPage; - } - - if (pathname.startsWith('/suites')) { - return Page.suitesPage; - } - - return Page.suitesPage; -} +import {Page} from '@/constants'; +import {getPageByPathname} from '@/static/new-ui/utils/page'; export const usePage = (): Page => { const location = useLocation(); diff --git a/lib/static/new-ui/types/store.ts b/lib/static/new-ui/types/store.ts index 798abc450..255924015 100644 --- a/lib/static/new-ui/types/store.ts +++ b/lib/static/new-ui/types/store.ts @@ -1,5 +1,5 @@ import {CoordBounds} from 'looks-same'; -import {BrowserFeature, DiffModeId, Feature, TestStatus, ViewMode, TwoUpFitMode} from '@/constants'; +import {Page, BrowserFeature, DiffModeId, Feature, TestStatus, ViewMode, TwoUpFitMode} from '@/constants'; import { Attachment, BrowserItem, @@ -30,6 +30,7 @@ export interface GroupEntity { export interface SuiteEntityNode { id: string; + hash: string; name: string; parentId: string | null; status: TestStatus; @@ -39,6 +40,7 @@ export interface SuiteEntityNode { export interface SuiteEntityLeaf { id: string; + hash: string; name: string; parentId: string | null; status: TestStatus; @@ -184,6 +186,7 @@ export interface TreeEntity { suites: { allRootIds: string[]; byId: Record; + byHash: Record; stateById: Record; }; groups: { @@ -250,11 +253,6 @@ export interface SnapshotsPlayerHighlightState { goToTime: number; } -export enum Page { - suitesPage = 'suitesPage', - visualChecksPage = 'visualChecksPage', -} - export interface State { app: { isNewUi: boolean; @@ -277,7 +275,8 @@ export interface State { filteredBrowsers: BrowserItem[]; }; [Page.visualChecksPage]: { - currentNamedImageId: string | null; + currentBrowserId: string | null; + stateName: string | null; // Filters in top of sidebar nameFilter: string; diff --git a/lib/static/new-ui/utils/diffModes.ts b/lib/static/new-ui/utils/diffModes.ts index 2d4c49834..d983ab5c8 100644 --- a/lib/static/new-ui/utils/diffModes.ts +++ b/lib/static/new-ui/utils/diffModes.ts @@ -1,9 +1,10 @@ import {DiffMode, DiffModes} from '@/constants'; +import {Page} from '@/constants'; -export function getAvailableDiffModes(context: 'visual-checks' | 'suites'): DiffMode[] { +export function getAvailableDiffModes(page: Page): DiffMode[] { const allModes = Object.values(DiffModes); - if (context === 'visual-checks') { + if (page === Page.visualChecksPage) { return allModes; } diff --git a/lib/static/new-ui/utils/getUrl.ts b/lib/static/new-ui/utils/getUrl.ts new file mode 100644 index 000000000..0d39c0b57 --- /dev/null +++ b/lib/static/new-ui/utils/getUrl.ts @@ -0,0 +1,19 @@ +import {Page} from '@/constants'; +import {getPathnameByPage} from '@/static/new-ui/utils/page'; + +export type GetUrlParams = { + page: Page + hash?: string | null; + browser?: string | null; + attempt?: number | string | null; + stateName?: string | null; +}; + +export const getUrl = (params: GetUrlParams): string => ( + `${getPathnameByPage(params.page)}/` + [ + params.hash as string, + params.browser as string, + params.attempt?.toString() as string, + params.stateName as string + ].filter(Boolean).map(encodeURIComponent).join('/') +); diff --git a/lib/static/new-ui/utils/page.ts b/lib/static/new-ui/utils/page.ts new file mode 100644 index 000000000..f244f3f3a --- /dev/null +++ b/lib/static/new-ui/utils/page.ts @@ -0,0 +1,17 @@ +import {Page, PathNames} from '@/constants'; + +export function getPageByPathname(pathname: string): Page { + if (pathname.startsWith(PathNames.visualChecks)) { + return Page.visualChecksPage; + } + + return Page.suitesPage; +} + +export const getPathnameByPage = (page: Page): string => { + if (page === Page.visualChecksPage) { + return PathNames.visualChecks; + } + + return PathNames.suites; +}; diff --git a/lib/tests-tree-builder/base.ts b/lib/tests-tree-builder/base.ts index f0a152b7a..0826b4fe0 100644 --- a/lib/tests-tree-builder/base.ts +++ b/lib/tests-tree-builder/base.ts @@ -1,4 +1,4 @@ -import {determineFinalStatus} from '../common-utils'; +import {determineFinalStatus, getShortMD5} from '../common-utils'; import {BrowserVersions, DEFAULT_TITLE_DELIMITER, TestStatus} from '../constants'; import {ReporterTestResult} from '../adapters/test-result'; import {ErrorDetails, ImageInfoFull} from '../types'; @@ -28,6 +28,7 @@ interface TreeBrowser { export interface TreeSuite { status?: TestStatus; id: string; + hash: string; parentId: string | null; name: string; suitePath: string[]; @@ -44,6 +45,7 @@ export type TreeImage = { export interface Tree { suites: { byId: Record; + byHash: Record; allIds: string[]; allRootIds: string[]; }, @@ -98,7 +100,7 @@ export class BaseTestsTreeBuilder { this._transformer = new TreeTestResultTransformer(options); this._tree = { - suites: {byId: {}, allIds: [], allRootIds: []}, + suites: {byId: {}, byHash: {}, allIds: [], allRootIds: []}, browsers: {byId: {}, allIds: []}, results: {byId: {}, allIds: []}, images: {byId: {}, allIds: []} @@ -156,7 +158,8 @@ export class BaseTestsTreeBuilder { if (!suites.byId[id]) { const parentId = isRoot ? null : this._buildId(suitePath.slice(0, -1)); - const suite: TreeSuite = {id, parentId, name, suitePath, root: isRoot}; + const hash = getShortMD5(id); + const suite: TreeSuite = {id, hash, parentId, name, suitePath, root: isRoot}; this._addSuite(suite); } @@ -194,6 +197,7 @@ export class BaseTestsTreeBuilder { const {suites} = this._tree; suites.byId[suite.id] = suite; + suites.byHash[suite.hash] = suite; suites.allIds.push(suite.id); if (suite.root) { diff --git a/test/func/tests/common/new-ui/suites-page/common.testplane.js b/test/func/tests/common/new-ui/suites-page/common.testplane.js new file mode 100644 index 000000000..df32951d7 --- /dev/null +++ b/test/func/tests/common/new-ui/suites-page/common.testplane.js @@ -0,0 +1,54 @@ +if (process.env.TOOL === 'testplane') { + describe(process.env.TOOL || 'Default', () => { + describe('New UI', () => { + describe('Suites page', () => { + describe('Common tests', () => { + const changeHash = (hash) => { + window.location.hash = hash; + window.location.reload(); + }; + + const getHash = async (browser) => { + const currentUrl = await browser.getUrl(); + return currentUrl.split('#')[1]; + }; + + it('click to test', async ({browser}) => { + const testElement = await browser.$('[data-list-item="failed describe/successfully passed test/chrome"]'); + await testElement.click(); + const titleTestElement = await browser.$('h2'); + + await expect(titleTestElement).toHaveText('successfully passed test'); + await expect(await getHash(browser)).toBe('/suites/eee7841/chrome/1'); + }); + + it('open by url', async ({browser}) => { + await browser.execute(changeHash, '/suites/eee7841/chrome/1'); + + const titleTestElement = await browser.$('h2'); + await expect(titleTestElement).toHaveText('successfully passed test'); + }); + + it('open screenshot from test and go back', async ({browser}) => { + const testElement = await browser.$('[data-list-item="failed describe/test with image comparison diff/chrome"]'); + await testElement.click(); + + const goToVisualButtonElement = await browser.$('[data-qa="go-visual-button"]'); + await goToVisualButtonElement.click(); + + await expect(await getHash(browser)).toBe('/visual-checks/ba3c69a/chrome/1/header'); + + const goToSuitesButtonElement = await browser.$('[data-qa="go-suites-button"]'); + await goToSuitesButtonElement.click(); + + await expect(await getHash(browser)).toBe('/suites/ba3c69a/chrome/1/header'); + + const titleTestElement = await browser.$('h2'); + + await expect(titleTestElement).toHaveText('test with image comparison diff'); + }); + }); + }); + }); + }); +} diff --git a/test/func/tests/common/new-ui/visual-checks-page/main-functionality.testplane.js b/test/func/tests/common/new-ui/visual-checks-page/common.testplane.js similarity index 94% rename from test/func/tests/common/new-ui/visual-checks-page/main-functionality.testplane.js rename to test/func/tests/common/new-ui/visual-checks-page/common.testplane.js index d80467faf..083623b2d 100644 --- a/test/func/tests/common/new-ui/visual-checks-page/main-functionality.testplane.js +++ b/test/func/tests/common/new-ui/visual-checks-page/common.testplane.js @@ -2,16 +2,18 @@ if (process.env.TOOL === 'testplane') { describe(process.env.TOOL || 'Default', () => { describe('New UI', () => { describe('Visual checks page', () => { - describe('Expand/collapse visual checks list button', () => { + describe('Common tests', () => { beforeEach(async ({browser}) => { const menuItem = await browser.$('[data-qa="visual-checks-page-menu-item"]'); await menuItem.click(); }); - it('page open', async ({browser}) => { + it('page open first failed', async ({browser}) => { const pageTitle = await browser.$('[data-qa="sidebar-title"]'); + const titleTestElement = await browser.$('h2'); await expect(pageTitle).toHaveText('Visual Checks'); + await expect(titleTestElement).toHaveText('test without screenshot'); }); it('move to suites and back', async ({browser}) => { @@ -43,11 +45,11 @@ if (process.env.TOOL === 'testplane') { const currentUrl = await browser.getUrl(); const hash = currentUrl.split('#')[1]; - await expect(hash).toBe('/visual-checks/failed%20describe%20test%20with%20image%20comparison%20diff%20chrome%20header/1'); + await expect(hash).toBe('/visual-checks/ba3c69a/chrome/1/header'); }); it('open screenshot by url', async ({browser}) => { - await browser.url('/fixtures/testplane/report/new-ui.html#/visual-checks/failed%20describe%20test%20with%20image%20comparison%20diff%20chrome%20header/1'); + await browser.url('/fixtures/testplane/report/new-ui.html#/visual-checks/ba3c69a/chrome/1/header'); await browser.execute(() => window.location.reload()); // need for catch data from changed hash const rightSideTitle = await browser.$('h2.text-display-1'); diff --git a/test/func/tests/screens/1e052b3/chrome/button.png b/test/func/tests/screens/1e052b3/chrome/button.png new file mode 100644 index 000000000..7902bd96e Binary files /dev/null and b/test/func/tests/screens/1e052b3/chrome/button.png differ diff --git a/test/func/tests/screens/67d6533/chrome/button.png b/test/func/tests/screens/67d6533/chrome/button.png deleted file mode 100644 index bb04856f0..000000000 Binary files a/test/func/tests/screens/67d6533/chrome/button.png and /dev/null differ diff --git a/test/func/tests/screens/6a4b847/chrome/retry-selector.png b/test/func/tests/screens/6a4b847/chrome/retry-selector.png index 6274f4e57..154533b43 100644 Binary files a/test/func/tests/screens/6a4b847/chrome/retry-selector.png and b/test/func/tests/screens/6a4b847/chrome/retry-selector.png differ diff --git a/test/func/tests/screens/99f329b/chrome/button.png b/test/func/tests/screens/99f329b/chrome/button.png deleted file mode 100644 index 86e819f38..000000000 Binary files a/test/func/tests/screens/99f329b/chrome/button.png and /dev/null differ diff --git a/test/func/tests/screens/a12f501/chrome/button.png b/test/func/tests/screens/a12f501/chrome/button.png new file mode 100644 index 000000000..7902bd96e Binary files /dev/null and b/test/func/tests/screens/a12f501/chrome/button.png differ diff --git a/test/func/tests/screens/bb1ceae/chrome/details summary.png b/test/func/tests/screens/bb1ceae/chrome/details summary.png index cabed0a06..699628de7 100644 Binary files a/test/func/tests/screens/bb1ceae/chrome/details summary.png and b/test/func/tests/screens/bb1ceae/chrome/details summary.png differ diff --git a/test/func/tests/screens/bc098ae/chrome/retry-selector.png b/test/func/tests/screens/bc098ae/chrome/retry-selector.png index 5759bd8a9..a8188542b 100644 Binary files a/test/func/tests/screens/bc098ae/chrome/retry-selector.png and b/test/func/tests/screens/bc098ae/chrome/retry-selector.png differ diff --git a/test/func/tests/screens/befc47b/chrome/retry-selector.png b/test/func/tests/screens/befc47b/chrome/retry-selector.png index 2e291679c..317a9f47f 100644 Binary files a/test/func/tests/screens/befc47b/chrome/retry-selector.png and b/test/func/tests/screens/befc47b/chrome/retry-selector.png differ diff --git a/test/func/tests/screens/c05e410/chrome/button.png b/test/func/tests/screens/c05e410/chrome/button.png deleted file mode 100644 index 86e819f38..000000000 Binary files a/test/func/tests/screens/c05e410/chrome/button.png and /dev/null differ diff --git a/test/func/tests/screens/ea5770d/chrome/button.png b/test/func/tests/screens/ea5770d/chrome/button.png new file mode 100644 index 000000000..6c9276d9e Binary files /dev/null and b/test/func/tests/screens/ea5770d/chrome/button.png differ diff --git a/test/func/tests/screens/f23f882/chrome/retry-selector.png b/test/func/tests/screens/f23f882/chrome/retry-selector.png index 5759bd8a9..a8188542b 100644 Binary files a/test/func/tests/screens/f23f882/chrome/retry-selector.png and b/test/func/tests/screens/f23f882/chrome/retry-selector.png differ diff --git a/test/unit/lib/static/modules/reducers/filters.js b/test/unit/lib/static/modules/reducers/filters.js index ed8fe9fd1..9a07f5af1 100644 --- a/test/unit/lib/static/modules/reducers/filters.js +++ b/test/unit/lib/static/modules/reducers/filters.js @@ -4,7 +4,7 @@ const defaultState = require('lib/static/modules/default-state').default; const {appendQuery, encodeBrowsers} = require('lib/static/modules/query-params'); const {ViewMode} = require('lib/constants/view-modes'); const {mkStorage} = require('../../../../utils'); -const {Page} = require('@/static/new-ui/types/store'); +const {Page, PathNames} = require('@/constants'); describe('lib/static/modules/reducers/view', () => { let baseUrl; @@ -28,11 +28,11 @@ describe('lib/static/modules/reducers/view', () => { [ { page: Page.suitesPage, - hash: '#/suites' + hash: `#${PathNames.suites}` }, { page: Page.visualChecksPage, - hash: '#/visual-checks' + hash: `#${PathNames.visualChecks}` } ].forEach(({page, hash}) => { [actionNames.INIT_GUI_REPORT, actionNames.INIT_STATIC_REPORT].forEach((type) => { diff --git a/test/unit/lib/static/new-ui/features/visual-checks/components/VisualChecksStickyHeader.jsx b/test/unit/lib/static/new-ui/features/visual-checks/components/VisualChecksStickyHeader.jsx index f470805f6..92a4ab0fb 100644 --- a/test/unit/lib/static/new-ui/features/visual-checks/components/VisualChecksStickyHeader.jsx +++ b/test/unit/lib/static/new-ui/features/visual-checks/components/VisualChecksStickyHeader.jsx @@ -51,7 +51,7 @@ describe('', () => { renderWithStore(( - + ), store); }); diff --git a/test/unit/lib/static/utils.tsx b/test/unit/lib/static/utils.tsx index 561b3f5d4..4df566458 100644 --- a/test/unit/lib/static/utils.tsx +++ b/test/unit/lib/static/utils.tsx @@ -21,6 +21,7 @@ import { } from '@/static/new-ui/types/store'; import {UNCHECKED} from '@/constants/checked-statuses'; import {EntityType, TreeViewItemData} from '@/static/new-ui/features/suites/components/SuitesPage/types'; +import {getShortMD5} from '@/common-utils'; export const mkState = ({initialState}: { initialState: Partial }): State => { return _.defaultsDeep(initialState ?? {}, defaultState); @@ -41,6 +42,7 @@ export const mkGroupEntity = (name: string, overrides?: Partial): G export const mkSuiteEntityLeaf = (name: string, overrides?: Partial): SuiteEntityLeaf => (_.mergeWith({ id: name, + hash: getShortMD5(name), name, parentId: null, status: TestStatus.SUCCESS,