diff --git a/lib/static/icons/empty-report.svg b/lib/static/icons/empty-report.svg new file mode 100644 index 000000000..a7ee97e5a --- /dev/null +++ b/lib/static/icons/empty-report.svg @@ -0,0 +1 @@ + diff --git a/lib/static/modules/reducers/static-image-accepter.js b/lib/static/modules/reducers/static-image-accepter.js index 3c66d1ebd..8944125ae 100644 --- a/lib/static/modules/reducers/static-image-accepter.js +++ b/lib/static/modules/reducers/static-image-accepter.js @@ -62,9 +62,9 @@ export default (state, action) => { const diff = set({}, ['staticImageAccepter', 'acceptableImages'], acceptableImagesDiff); const imagesToCommitCount = get(state, ['staticImageAccepter', 'imagesToCommitCount']); + set(diff, ['staticImageAccepter', 'imagesToCommitCount'], imagesToCommitCount - action.payload.length); for (const imageId of action.payload) { set(acceptableImagesDiff, [imageId, 'commitStatus'], null); - set(diff, ['staticImageAccepter', 'imagesToCommitCount'], imagesToCommitCount - 1); } return applyStateUpdate(state, diff); diff --git a/lib/static/modules/reducers/tree/index.js b/lib/static/modules/reducers/tree/index.js index 89834ba94..27eb67be4 100644 --- a/lib/static/modules/reducers/tree/index.js +++ b/lib/static/modules/reducers/tree/index.js @@ -257,6 +257,10 @@ export default ((state, action) => { const {tree, view} = state; const imageIdsToUnstage = action.payload; + const failedBrowserIds = []; + const failedSuiteIds = []; + const imageIds = []; + for (const imageId of imageIdsToUnstage) { const originalStatus = get(state, ['staticImageAccepter', 'acceptableImages', imageId, 'originalStatus']); @@ -269,15 +273,19 @@ export default ((state, action) => { changeImageStatus(tree, imageId, originalStatus, diff.tree); changeNodeState(tree.results.byId, failedResultId, {status: FAIL}, diff.tree.results.byId); - failSuites(tree, failedSuiteId, diff.tree); + failedBrowserIds.push(failedBrowserId); + failedSuiteIds.push(failedSuiteId); + imageIds.push(imageId); + } + + failSuites(tree, failedSuiteIds, diff.tree); - calcSuitesOpenness({tree, expand: view.expand, suiteIds: [failedSuiteId], diff: diff.tree}); - calcBrowsersOpenness({tree, expand: view.expand, browserIds: [failedBrowserId], diff: diff.tree}); - calcImagesOpenness({tree, expand: view.expand, imageIds: [imageId], diff: diff.tree}); + calcSuitesOpenness({tree, expand: view.expand, suiteIds: failedSuiteIds, diff: diff.tree}); + calcBrowsersOpenness({tree, expand: view.expand, browserIds: failedBrowserIds, diff: diff.tree}); + calcImagesOpenness({tree, expand: view.expand, imageIds, diff: diff.tree}); - calcBrowsersShowness({tree, view, browserIds: [failedBrowserId], diff: diff.tree}); - calcSuitesShowness({tree, suiteIds: [failedSuiteId], diff: diff.tree}); - } + calcBrowsersShowness({tree, view, browserIds: failedBrowserIds, diff: diff.tree}); + calcSuitesShowness({tree, suiteIds: failedSuiteIds, diff: diff.tree}); return applyStateUpdate(state, diff); } diff --git a/lib/static/new-ui/components/AssertViewResult/index.tsx b/lib/static/new-ui/components/AssertViewResult/index.tsx index e015b1dff..b4c90ffdf 100644 --- a/lib/static/new-ui/components/AssertViewResult/index.tsx +++ b/lib/static/new-ui/components/AssertViewResult/index.tsx @@ -1,7 +1,7 @@ import React, {ReactNode} from 'react'; import {connect} from 'react-redux'; -import {ImageEntity, State} from '@/static/new-ui/types/store'; +import {ImageEntity} from '@/static/new-ui/types/store'; import {DiffModeId, TestStatus} from '@/constants'; import {DiffViewer} from '../DiffViewer'; import {Screenshot} from '@/static/new-ui/components/Screenshot'; @@ -43,6 +43,6 @@ function AssertViewResultInternal({result, diffMode, style}: AssertViewResultPro return null; } -export const AssertViewResult = connect((state: State) => ({ +export const AssertViewResult = connect(state => ({ diffMode: state.view.diffMode }))(AssertViewResultInternal); diff --git a/lib/static/new-ui/components/AttemptPicker/index.tsx b/lib/static/new-ui/components/AttemptPicker/index.tsx index 9cbcdd155..51e0cf84b 100644 --- a/lib/static/new-ui/components/AttemptPicker/index.tsx +++ b/lib/static/new-ui/components/AttemptPicker/index.tsx @@ -2,7 +2,6 @@ import React, {ReactNode} from 'react'; import {connect, useDispatch, useSelector} from 'react-redux'; import {ArrowRotateRight} from '@gravity-ui/icons'; -import {State} from '@/static/new-ui/types/store'; import {AttemptPickerItem} from '@/static/new-ui/components/AttemptPickerItem'; import styles from './index.module.css'; import classNames from 'classnames'; @@ -26,9 +25,9 @@ function AttemptPickerInternal(props: AttemptPickerInternalProps): ReactNode { const dispatch = useDispatch(); const currentBrowser = useSelector(getCurrentBrowser); - const isRunTestsAvailable = useSelector((state: State) => state.app.availableFeatures) + const isRunTestsAvailable = useSelector(state => state.app.availableFeatures) .find(feature => feature.name === RunTestsFeature.name); - const isRunning = useSelector((state: State) => state.running); + const isRunning = useSelector(state => state.running); const onAttemptPickHandler = (resultId: string, attemptIndex: number): void => { if (!props.browserId || currentResultId === resultId) { @@ -64,7 +63,7 @@ function AttemptPickerInternal(props: AttemptPickerInternalProps): ReactNode { ; } -export const AttemptPicker = connect((state: State) => { +export const AttemptPicker = connect(state => { let resultIds: string[] = []; let currentResultId = ''; const browserId = state.app.suitesPage.currentBrowserId; diff --git a/lib/static/new-ui/components/Card/AnimatedAppearCard.tsx b/lib/static/new-ui/components/Card/AnimatedAppearCard.tsx index 0ce42be6f..c81b97aab 100644 --- a/lib/static/new-ui/components/Card/AnimatedAppearCard.tsx +++ b/lib/static/new-ui/components/Card/AnimatedAppearCard.tsx @@ -4,10 +4,9 @@ import cardStyles from './index.module.css'; import styles from './AnimatedAppearCard.module.css'; import classNames from 'classnames'; import {useSelector} from 'react-redux'; -import {State} from '@/static/new-ui/types/store'; export function AnimatedAppearCard(): ReactNode { - const isInitialized = useSelector((state: State) => state.app.isInitialized); + const isInitialized = useSelector(state => state.app.isInitialized); return
diff --git a/lib/static/new-ui/components/Card/EmptyReportCard.tsx b/lib/static/new-ui/components/Card/EmptyReportCard.tsx index 391cdebb0..4c038efcf 100644 --- a/lib/static/new-ui/components/Card/EmptyReportCard.tsx +++ b/lib/static/new-ui/components/Card/EmptyReportCard.tsx @@ -1,5 +1,5 @@ import {TextHintCard} from '@/static/new-ui/components/Card/TextHintCard'; -import EmptyReport from '@/static/icons/empty-report.svg'; +import EmptyReport from '../../../icons/empty-report.svg'; import classNames from 'classnames'; import {Icon} from '@gravity-ui/uikit'; import {Check} from '@gravity-ui/icons'; diff --git a/lib/static/new-ui/components/CompactAttemptPicker/index.tsx b/lib/static/new-ui/components/CompactAttemptPicker/index.tsx index c1c49cf31..86eb04cba 100644 --- a/lib/static/new-ui/components/CompactAttemptPicker/index.tsx +++ b/lib/static/new-ui/components/CompactAttemptPicker/index.tsx @@ -4,7 +4,6 @@ import React, {ReactNode} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import styles from './index.module.css'; -import {State} from '@/static/new-ui/types/store'; import {getCurrentNamedImage} from '@/static/new-ui/features/visual-checks/selectors'; import {getIconByStatus} from '@/static/new-ui/utils'; import {changeTestRetry} from '@/static/modules/actions'; @@ -13,11 +12,11 @@ export function CompactAttemptPicker(): ReactNode { const dispatch = useDispatch(); const currentImage = useSelector(getCurrentNamedImage); const currentBrowserId = currentImage?.browserId; - const currentBrowser = useSelector((state: State) => currentBrowserId && state.tree.browsers.byId[currentBrowserId]); - const resultsById = useSelector((state: State) => state.tree.results.byId); + const currentBrowser = useSelector(state => currentBrowserId && state.tree.browsers.byId[currentBrowserId]); + const resultsById = useSelector(state => state.tree.results.byId); const totalAttemptsCount = currentBrowser ? currentBrowser.resultIds.length : null; - const currentAttemptIndex = useSelector((state: State) => currentBrowser ? state.tree.browsers.stateById[currentBrowser.id].retryIndex : null); + const currentAttemptIndex = useSelector(state => currentBrowser ? state.tree.browsers.stateById[currentBrowser.id].retryIndex : null); const onUpdate = ([value]: string[]): void => { if (currentBrowserId) { diff --git a/lib/static/new-ui/components/GuiniToolbarOverlay/index.tsx b/lib/static/new-ui/components/GuiniToolbarOverlay/index.tsx index fb5c2cf9a..5f1473329 100644 --- a/lib/static/new-ui/components/GuiniToolbarOverlay/index.tsx +++ b/lib/static/new-ui/components/GuiniToolbarOverlay/index.tsx @@ -6,7 +6,6 @@ import {CloudArrowUpIn, TriangleExclamation} from '@gravity-ui/icons'; import styles from './index.module.css'; import classNames from 'classnames'; import {useDispatch, useSelector} from 'react-redux'; -import {State} from '@/static/new-ui/types/store'; import { CommitResult, staticAccepterCommitScreenshot, @@ -23,22 +22,22 @@ export function GuiniToolbarOverlay(): ReactNode { const dispatch = useDispatch(); const toaster = useToaster(); - const isInProgress = useSelector((state: State) => state.processing); - const allImagesById = useSelector((state: State) => state.tree.images.byId); - const acceptableImages = useSelector((state: State) => state.staticImageAccepter.acceptableImages); - const delayedImages = useSelector((state: State) => state.staticImageAccepter.accepterDelayedImages); + const isInProgress = useSelector(state => state.processing); + const allImagesById = useSelector(state => state.tree.images.byId); + const acceptableImages = useSelector(state => state.staticImageAccepter.acceptableImages); + const delayedImages = useSelector(state => state.staticImageAccepter.accepterDelayedImages); const stagedImages = Object.values(acceptableImages) .filter(image => image.commitStatus === TestStatus.STAGED); - const staticAccepterConfig = useSelector((state: State) => state.config.staticImageAccepter); - const pullRequestUrl = useSelector((state: State) => state.config.staticImageAccepter.pullRequestUrl); - const offset = useSelector((state: State) => state.ui.staticImageAccepterToolbar.offset); + const staticAccepterConfig = useSelector(state => state.config.staticImageAccepter); + const pullRequestUrl = useSelector(state => state.config.staticImageAccepter.pullRequestUrl); + const offset = useSelector(state => state.ui.staticImageAccepterToolbar.offset); const location = useLocation(); const [isVisible, setIsVisible] = useState(null); const [isModalVisible, setIsModalVisible] = useState(false); - const commitMessage = useSelector((state: State) => state.app.staticImageAccepterModal.commitMessage); + const commitMessage = useSelector(state => state.app.staticImageAccepterModal.commitMessage); useEffect(() => { const newIsVisible = stagedImages.length > 0 && diff --git a/lib/static/new-ui/components/LoadingBar/index.tsx b/lib/static/new-ui/components/LoadingBar/index.tsx index 9c597968c..f2e8534ff 100644 --- a/lib/static/new-ui/components/LoadingBar/index.tsx +++ b/lib/static/new-ui/components/LoadingBar/index.tsx @@ -3,15 +3,14 @@ import React, {ReactNode, useEffect, useRef} from 'react'; import styles from './index.module.css'; import {useSelector} from 'react-redux'; import {getTotalLoadingProgress} from '@/static/new-ui/app/selectors'; -import {State} from '@/static/new-ui/types/store'; import classNames from 'classnames'; export function LoadingBar(): ReactNode { - const isVisible = useSelector((state: State) => state.app.loading.isVisible); - const isInProgress = useSelector((state: State) => state.app.loading.isInProgress); + const isVisible = useSelector(state => state.app.loading.isVisible); + const isInProgress = useSelector(state => state.app.loading.isInProgress); const isVisibleRef = useRef(isVisible); const progress = useSelector(getTotalLoadingProgress); - const taskTitle = useSelector((state: State) => state.app.loading.taskTitle); + const taskTitle = useSelector(state => state.app.loading.taskTitle); const [hidden, setHidden] = React.useState(true); diff --git a/lib/static/new-ui/components/MainLayout/index.tsx b/lib/static/new-ui/components/MainLayout/index.tsx index 53f0b65c3..014b7323e 100644 --- a/lib/static/new-ui/components/MainLayout/index.tsx +++ b/lib/static/new-ui/components/MainLayout/index.tsx @@ -9,7 +9,6 @@ import {SettingsPanel} from '@/static/new-ui/components/SettingsPanel'; import TestplaneIcon from '../../../icons/testplane-mono.svg'; import styles from './index.module.css'; import {Footer} from './Footer'; -import {State} from '@/static/new-ui/types/store'; import {EmptyReportCard} from '@/static/new-ui/components/Card/EmptyReportCard'; export enum PanelId { @@ -41,7 +40,7 @@ export function MainLayout(props: MainLayoutProps): ReactNode { const isInitialized = useSelector(getIsInitialized); - const browsersById = useSelector((state: State) => state.tree.browsers.byId); + const browsersById = useSelector(state => state.tree.browsers.byId); const isReportEmpty = isInitialized && Object.keys(browsersById).length === 0; const [visiblePanel, setVisiblePanel] = useState(null); diff --git a/lib/static/new-ui/components/SettingsPanel/index.tsx b/lib/static/new-ui/components/SettingsPanel/index.tsx index 959cb6ecc..cc63a2bc8 100644 --- a/lib/static/new-ui/components/SettingsPanel/index.tsx +++ b/lib/static/new-ui/components/SettingsPanel/index.tsx @@ -6,14 +6,13 @@ import {useDispatch, useSelector} from 'react-redux'; import {LocalStorageKey, UiMode} from '@/constants/local-storage'; import * as actions from '@/static/modules/actions'; -import {State} from '@/static/new-ui/types/store'; import useLocalStorage from '@/static/hooks/useLocalStorage'; import styles from './index.module.css'; export function SettingsPanel(): ReactNode { const dispatch = useDispatch(); - const baseHost = useSelector((state: State) => state.view.baseHost); + const baseHost = useSelector(state => state.view.baseHost); const [, setUiMode] = useLocalStorage(LocalStorageKey.UIMode, UiMode.New); const onBaseHostChange = (event: React.ChangeEvent): void => { diff --git a/lib/static/new-ui/features/suites/components/BrowsersSelect/index.tsx b/lib/static/new-ui/features/suites/components/BrowsersSelect/index.tsx index a2e177d2c..9deb5cb1c 100644 --- a/lib/static/new-ui/features/suites/components/BrowsersSelect/index.tsx +++ b/lib/static/new-ui/features/suites/components/BrowsersSelect/index.tsx @@ -7,7 +7,6 @@ import {bindActionCreators} from 'redux'; import * as actions from '@/static/modules/actions'; import {BrowserIcon} from '@/static/new-ui/features/suites/components/BrowsersSelect/BrowserIcon'; import {getIsInitialized} from '@/static/new-ui/store/selectors'; -import {State} from '@/static/new-ui/types/store'; import {BrowserItem} from '@/types'; import styles from './index.module.css'; @@ -175,7 +174,7 @@ function BrowsersSelectInternal({browsers, filteredBrowsers, actions}: BrowsersS } export const BrowsersSelect = connect( - (state: State) => ({ + state => ({ filteredBrowsers: state.view.filteredBrowsers, browsers: state.browsers }), 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 d2edc4ad8..397d7f1f0 100644 --- a/lib/static/new-ui/features/suites/components/ScreenshotsTreeViewItem/index.tsx +++ b/lib/static/new-ui/features/suites/components/ScreenshotsTreeViewItem/index.tsx @@ -4,7 +4,7 @@ import React, {ReactNode} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import {AssertViewResult} from '@/static/new-ui/components/AssertViewResult'; -import {ImageEntity, State} from '@/static/new-ui/types/store'; +import {ImageEntity} from '@/static/new-ui/types/store'; import {DiffModeId, DiffModes, EditScreensFeature, TestStatus} from '@/constants'; import { acceptTest, @@ -25,13 +25,13 @@ interface ScreenshotsTreeViewItemProps { export function ScreenshotsTreeViewItem(props: ScreenshotsTreeViewItemProps): ReactNode { const dispatch = useDispatch(); - const diffMode = useSelector((state: State) => state.view.diffMode); - const isEditScreensAvailable = useSelector((state: State) => state.app.availableFeatures) + const diffMode = useSelector(state => state.view.diffMode); + const isEditScreensAvailable = useSelector(state => state.app.availableFeatures) .find(feature => feature.name === EditScreensFeature.name); - const isStaticImageAccepterEnabled = useSelector((state: State) => state.staticImageAccepter.enabled); - const isRunning = useSelector((state: State) => state.running); - const isProcessing = useSelector((state: State) => state.processing); - const isGui = useSelector((state: State) => state.gui); + const isStaticImageAccepterEnabled = useSelector(state => state.staticImageAccepter.enabled); + const isRunning = useSelector(state => state.running); + const isProcessing = useSelector(state => state.processing); + const isGui = useSelector(state => state.gui); const isDiffModeSwitcherVisible = props.image.status === TestStatus.FAIL && props.image.diffImg; 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 242e43fea..14e7f2aea 100644 --- a/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx +++ b/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx @@ -19,7 +19,7 @@ import * as actions from '@/static/modules/actions'; import {CollapsibleSection} from '@/static/new-ui/features/suites/components/CollapsibleSection'; import {MetaInfo} from '@/static/new-ui/components/MetaInfo'; import {getIsInitialized} from '@/static/new-ui/store/selectors'; -import {ResultEntity, State} from '@/static/new-ui/types/store'; +import {ResultEntity} from '@/static/new-ui/types/store'; import {AttemptPicker} from '../../../../components/AttemptPicker'; import styles from './index.module.css'; @@ -86,7 +86,7 @@ function SuitesPageInternal({currentResult, actions, visibleBrowserIds}: SuitesP } export const SuitesPage = connect( - (state: State) => { + state => { const currentResult = getCurrentResult(state); return { diff --git a/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx index 6f8287927..14676e0fc 100644 --- a/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx +++ b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx @@ -16,7 +16,6 @@ import { } from '@/static/new-ui/features/suites/components/SuitesPage/types'; import {TreeViewItemTitle} from '@/static/new-ui/features/suites/components/TreeViewItemTitle'; import {TreeViewItemSubtitle} from '@/static/new-ui/features/suites/components/TreeViewItemSubtitle'; -import {State} from '@/static/new-ui/types/store'; import { getTreeViewExpandedById, getTreeViewItems @@ -39,10 +38,10 @@ export const SuitesTreeView = forwardRef state.app.isInitialized); - const currentBrowserId = useSelector((state: State) => state.app.suitesPage.currentBrowserId); - const treeViewItems = useSelector((state: State) => getTreeViewItems(state).tree); - const treeViewExpandedById = useSelector((state: State) => getTreeViewExpandedById(state)); + const isInitialized = useSelector(state => state.app.isInitialized); + const currentBrowserId = useSelector(state => state.app.suitesPage.currentBrowserId); + const treeViewItems = useSelector(state => getTreeViewItems(state).tree); + const treeViewExpandedById = useSelector(state => getTreeViewExpandedById(state)); const list = useList({ items: treeViewItems, @@ -84,6 +83,8 @@ export const SuitesTreeView = forwardRef { virtualizer.scrollToIndex(list.structure.visibleFlattenIds.indexOf(suiteId), {align: 'center'}); }, 50); diff --git a/lib/static/new-ui/features/suites/components/TestNameFilter/index.tsx b/lib/static/new-ui/features/suites/components/TestNameFilter/index.tsx index 1273bc9dd..637d0d5d9 100644 --- a/lib/static/new-ui/features/suites/components/TestNameFilter/index.tsx +++ b/lib/static/new-ui/features/suites/components/TestNameFilter/index.tsx @@ -4,7 +4,6 @@ import {debounce} from 'lodash'; import {connect, useSelector} from 'react-redux'; import {bindActionCreators} from 'redux'; import * as actions from '@/static/modules/actions'; -import {State} from '@/static/new-ui/types/store'; import {getIsInitialized} from '@/static/new-ui/store/selectors'; interface TestNameFilterProps { @@ -32,6 +31,6 @@ function TestNameFilterInternal(props: TestNameFilterProps): ReactNode { } export const TestNameFilter = connect( - (state: State) => ({testNameFilter: state.view.testNameFilter}), + state => ({testNameFilter: state.view.testNameFilter}), (dispatch) => ({actions: bindActionCreators(actions, dispatch)}) )(TestNameFilterInternal); diff --git a/lib/static/new-ui/features/suites/components/TestStatusFilter/index.tsx b/lib/static/new-ui/features/suites/components/TestStatusFilter/index.tsx index 06ce7157d..f5fb7bdf9 100644 --- a/lib/static/new-ui/features/suites/components/TestStatusFilter/index.tsx +++ b/lib/static/new-ui/features/suites/components/TestStatusFilter/index.tsx @@ -6,7 +6,6 @@ import {bindActionCreators} from 'redux'; import {TestStatus, ViewMode} from '@/constants'; import * as actions from '@/static/modules/actions'; import {getStatusCounts, StatusCounts} from '@/static/new-ui/features/suites/components/TestStatusFilter/selectors'; -import {State} from '@/static/new-ui/types/store'; import {getIconByStatus} from '@/static/new-ui/utils'; import {getIsInitialized} from '@/static/new-ui/store/selectors'; import styles from './index.module.css'; @@ -41,7 +40,7 @@ function TestStatusFilterInternal({statusCounts, actions, viewMode}: TestStatusF } export const TestStatusFilter = connect( - (state: State) => ({ + state => ({ statusCounts: getStatusCounts(state), viewMode: state.view.viewMode }), 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 2cb033de2..ad9ef8b21 100644 --- a/lib/static/new-ui/features/suites/components/TestSteps/index.tsx +++ b/lib/static/new-ui/features/suites/components/TestSteps/index.tsx @@ -9,7 +9,6 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import {CollapsibleSection} from '@/static/new-ui/features/suites/components/CollapsibleSection'; -import {State} from '@/static/new-ui/types/store'; import {TreeViewItemIcon} from '@/static/new-ui/components/TreeViewItemIcon'; import {TestStepArgs} from '@/static/new-ui/features/suites/components/TestStepArgs'; import {getIconByStatus} from '@/static/new-ui/utils'; @@ -94,7 +93,7 @@ function TestStepsInternal(props: TestStepsProps): ReactNode { } />; } -export const TestSteps = connect((state: State) => ({ +export const TestSteps = connect(state => ({ resultId: getCurrentResultId(state) ?? '', testSteps: getTestSteps(state), stepsExpandedById: getStepsExpandedById(state) diff --git a/lib/static/new-ui/features/suites/components/TreeActionsToolbar/index.tsx b/lib/static/new-ui/features/suites/components/TreeActionsToolbar/index.tsx index 3f0ba2af0..e8a10e26d 100644 --- a/lib/static/new-ui/features/suites/components/TreeActionsToolbar/index.tsx +++ b/lib/static/new-ui/features/suites/components/TreeActionsToolbar/index.tsx @@ -24,7 +24,7 @@ import { selectAll, staticAccepterStageScreenshot, staticAccepterUnstageScreenshot, undoAcceptImages } from '@/static/modules/actions'; -import {ImageEntity, State} from '@/static/new-ui/types/store'; +import {ImageEntity} from '@/static/new-ui/types/store'; import {CHECKED, INDETERMINATE} from '@/constants/checked-statuses'; import {IconButton} from '@/static/new-ui/components/IconButton'; import { @@ -44,39 +44,32 @@ interface TreeActionsToolbarProps { export function TreeActionsToolbar(props: TreeActionsToolbarProps): ReactNode { const dispatch = useDispatch(); - const rootSuiteIds = useSelector((state: State) => state.tree.suites.allRootIds); - const suitesStateById = useSelector((state: State) => state.tree.suites.stateById); - const browsersStateById = useSelector((state: State) => state.tree.browsers.stateById); - const browsersById = useSelector((state: State) => state.tree.browsers.byId); + const rootSuiteIds = useSelector(state => state.tree.suites.allRootIds); + const suitesStateById = useSelector(state => state.tree.suites.stateById); + const browsersStateById = useSelector(state => state.tree.browsers.stateById); + const browsersById = useSelector(state => state.tree.browsers.byId); const selectedTests = useSelector(getCheckedTests); const visibleBrowserIds: string[] = useSelector(getVisibleBrowserIds); const isInitialized = useSelector(getIsInitialized); - const isRunTestsAvailable = useSelector((state: State) => state.app.availableFeatures) + const isRunTestsAvailable = useSelector(state => state.app.availableFeatures) .find(feature => feature.name === RunTestsFeature.name); - const isRunning = useSelector((state: State) => state.running); + const isRunning = useSelector(state => state.running); - const isEditScreensAvailable = useSelector((state: State) => state.app.availableFeatures) + const isEditScreensAvailable = useSelector(state => state.app.availableFeatures) .find(feature => feature.name === EditScreensFeature.name); const isSelectedAll = useMemo(() => { - for (const suiteId of rootSuiteIds) { - if (suitesStateById[suiteId].checkStatus !== CHECKED) { - return false; - } - } - - return true; + return rootSuiteIds.every(suiteId => suitesStateById[suiteId].checkStatus === CHECKED); }, [suitesStateById, rootSuiteIds]); const isSelectedAtLeastOne = useMemo(() => { - for (const suiteId of rootSuiteIds) { - if (suitesStateById[suiteId].shouldBeShown && (suitesStateById[suiteId].checkStatus === CHECKED || suitesStateById[suiteId].checkStatus === INDETERMINATE)) { - return true; - } - } + return rootSuiteIds.some(suiteId => { + const isShown = suitesStateById[suiteId].shouldBeShown; + const isChecked = suitesStateById[suiteId].checkStatus === CHECKED || suitesStateById[suiteId].checkStatus === INDETERMINATE; - return false; + return isShown && isChecked; + }); }, [suitesStateById, rootSuiteIds]); const isStaticImageAccepterEnabled = useSelector(getIsStaticImageAccepterEnabled); @@ -90,18 +83,12 @@ export function TreeActionsToolbar(props: TreeActionsToolbarProps): ReactNode { const isUndoButtonVisible = isAtLeastOneRevertable && !isAtLeastOneAcceptable; const selectedTestsCount = useMemo(() => { - let count = 0; + const browserStates = Object.values(browsersStateById); - for (const browser of Object.values(browsersStateById)) { - if (browser.checkStatus === CHECKED) { - count++; - } - } - - return count; + return browserStates.reduce((acc, state) => acc + Number(state.checkStatus === CHECKED), 0); }, [browsersStateById]); - const handleSelectAll = (): void => { + const handleToggleAll = (): void => { if (isSelectedAll) { dispatch(deselectAll()); } else { @@ -134,7 +121,7 @@ export function TreeActionsToolbar(props: TreeActionsToolbarProps): ReactNode { .filter(image => isScreenRevertable({image, gui: isGuiMode, isLastResult: true, isStaticImageAccepterEnabled})) .map(image => image.id); - if (isStaticImageAccepterEnabled && !isGuiMode) { + if (isStaticImageAccepterEnabled) { dispatch(staticAccepterUnstageScreenshot(acceptableImageIds)); } else { dispatch(undoAcceptImages(acceptableImageIds)); @@ -146,27 +133,30 @@ export function TreeActionsToolbar(props: TreeActionsToolbarProps): ReactNode { .filter(image => isAcceptable(image)) .map(image => image.id); - if (isStaticImageAccepterEnabled && !isGuiMode) { + if (isStaticImageAccepterEnabled) { dispatch(staticAccepterStageScreenshot(acceptableImageIds)); } else { dispatch(acceptOpened(acceptableImageIds)); } }; + const selectedOrVisible = isSelectedAtLeastOne ? 'selected' : 'visible'; + const areActionsDisabled = isRunning || !isInitialized; + const viewButtons = <> {isRunTestsAvailable && } - tooltip={isSelectedAtLeastOne ? 'Run selected' : 'Run visible'} view={'flat'} onClick={handleRun} + tooltip={`Run ${selectedOrVisible}`} view={'flat'} onClick={handleRun} disabled={isRunning || !isInitialized}>} {isEditScreensAvailable && ( isUndoButtonVisible ? - } tooltip={isSelectedAtLeastOne ? 'Undo accepting selected screenshots' : 'Undo accepting visible screenshots'} view={'flat'} onClick={handleUndo} disabled={isRunning || !isInitialized}> : - } tooltip={isSelectedAtLeastOne ? 'Accept selected screenshots' : 'Accept visible screenshots'} view={'flat'} onClick={handleAccept} disabled={isRunning || !isInitialized}> + } tooltip={`Undo accepting ${selectedOrVisible} screenshots`} view={'flat'} onClick={handleUndo} disabled={areActionsDisabled}> : + } tooltip={`Accept ${selectedOrVisible} screenshots`} view={'flat'} onClick={handleAccept} disabled={areActionsDisabled}> )}
} tooltip={'Focus on active test'} view={'flat'} onClick={props.onHighlightCurrentTest} disabled={!isInitialized}/> } tooltip={'Expand all'} view={'flat'} onClick={handleExpandAll} disabled={!isInitialized}/> } tooltip={'Collapse all'} view={'flat'} onClick={handleCollapseAll} disabled={!isInitialized}/> - } tooltip={isSelectedAll ? 'Deselect all' : 'Select all'} view={'flat'} onClick={handleSelectAll} disabled={!isInitialized}/> + } tooltip={isSelectedAll ? 'Deselect all' : 'Select all'} view={'flat'} onClick={handleToggleAll} disabled={!isInitialized}/> ; return
diff --git a/lib/static/new-ui/features/suites/components/TreeViewItemTitle/index.tsx b/lib/static/new-ui/features/suites/components/TreeViewItemTitle/index.tsx index e54145436..e248e2ab2 100644 --- a/lib/static/new-ui/features/suites/components/TreeViewItemTitle/index.tsx +++ b/lib/static/new-ui/features/suites/components/TreeViewItemTitle/index.tsx @@ -5,7 +5,6 @@ import classNames from 'classnames'; import {Checkbox} from '@gravity-ui/uikit'; import {useDispatch, useSelector} from 'react-redux'; import {toggleBrowserCheckbox, toggleSuiteCheckbox} from '@/static/modules/actions'; -import {State} from '@/static/new-ui/types/store'; import {getToggledCheckboxState, isCheckboxChecked, isCheckboxIndeterminate} from '@/common-utils'; interface TreeViewItemTitleProps { @@ -15,7 +14,7 @@ interface TreeViewItemTitleProps { export function TreeViewItemTitle({item, className}: TreeViewItemTitleProps): React.JSX.Element { const dispatch = useDispatch(); - const checkStatus = useSelector((state: State) => + const checkStatus = useSelector(state => item.type === TreeViewItemType.Suite ? state.tree.suites.stateById[item.id].checkStatus : state.tree.browsers.stateById[item.id].checkStatus); const ref = useRef(null); 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 90d3ade23..35efb0bfd 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 @@ -5,7 +5,6 @@ import React, {ReactNode} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import {SplitViewLayout} from '@/static/new-ui/components/SplitViewLayout'; -import {State} from '@/static/new-ui/types/store'; import {UiCard} from '@/static/new-ui/components/Card/UiCard'; import { getCurrentImage, @@ -40,17 +39,17 @@ export function VisualChecksPage(): ReactNode { const onPreviousImageHandler = (): void => void dispatch(visualChecksPageSetCurrentNamedImage(visibleNamedImageIds[currentNamedImageIndex - 1])); const onNextImageHandler = (): void => void dispatch(visualChecksPageSetCurrentNamedImage(visibleNamedImageIds[currentNamedImageIndex + 1])); - const diffMode = useSelector((state: State) => state.view.diffMode); + const diffMode = useSelector(state => state.view.diffMode); const onChangeHandler = (diffMode: DiffModeId): void => { dispatch(changeDiffMode(diffMode)); }; - const isStaticImageAccepterEnabled = useSelector((state: State) => state.staticImageAccepter.enabled); - const isEditScreensAvailable = useSelector((state: State) => state.app.availableFeatures) + const isStaticImageAccepterEnabled = useSelector(state => state.staticImageAccepter.enabled); + const isEditScreensAvailable = useSelector(state => state.app.availableFeatures) .find(feature => feature.name === EditScreensFeature.name); - const isRunning = useSelector((state: State) => state.running); - const isProcessing = useSelector((state: State) => state.processing); - const isGui = useSelector((state: State) => state.gui); + const isRunning = useSelector(state => state.running); + const isProcessing = useSelector(state => state.processing); + const isGui = useSelector(state => state.gui); const onScreenshotAccept = (): void => { if (!currentImage) { @@ -75,14 +74,14 @@ export function VisualChecksPage(): ReactNode { } }; - const currentBrowserId = useSelector((state: State) => state.tree.results.byId[currentImage?.parentId ?? '']?.parentId); - const currentBrowser = useSelector((state: State) => currentBrowserId && state.tree.browsers.byId[currentBrowserId]); + const currentBrowserId = useSelector(state => state.tree.results.byId[currentImage?.parentId ?? '']?.parentId); + const currentBrowser = useSelector(state => currentBrowserId && state.tree.browsers.byId[currentBrowserId]); const currentResultId = currentImage?.parentId; const isLastResult = Boolean(currentResultId && currentBrowser && currentResultId === currentBrowser.resultIds[currentBrowser.resultIds.length - 1]); const isUndoAvailable = isScreenRevertable({gui: isGui, image: currentImage ?? {}, isLastResult, isStaticImageAccepterEnabled}); - const isInitialized = useSelector((state: State) => state.app.isInitialized); + const isInitialized = useSelector(state => state.app.isInitialized); return
diff --git a/lib/static/new-ui/store/selectors.ts b/lib/static/new-ui/store/selectors.ts index 021f53431..5b025a212 100644 --- a/lib/static/new-ui/store/selectors.ts +++ b/lib/static/new-ui/store/selectors.ts @@ -18,5 +18,5 @@ export const getResults = (state: State): Record => state. export const getImages = (state: State): Record => state.tree.images.byId; export const getIsInitialized = (state: State): boolean => state.app.isInitialized; -export const getIsStaticImageAccepterEnabled = (state: State): boolean => state.config.staticImageAccepter.enabled; +export const getIsStaticImageAccepterEnabled = (state: State): boolean => state.staticImageAccepter.enabled; export const getIsGui = (state: State): boolean => state.gui; diff --git a/lib/static/new-ui/types/store.ts b/lib/static/new-ui/types/store.ts index 7e8c1891f..3134ef939 100644 --- a/lib/static/new-ui/types/store.ts +++ b/lib/static/new-ui/types/store.ts @@ -210,3 +210,8 @@ export interface State { imagesToCommitCount: number; }; } + +declare module 'react-redux' { + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface DefaultRootState extends State {} +} diff --git a/lib/static/new-ui/utils/assert-view-status.tsx b/lib/static/new-ui/utils/assert-view-status.tsx index f45f49260..aa445695e 100644 --- a/lib/static/new-ui/utils/assert-view-status.tsx +++ b/lib/static/new-ui/utils/assert-view-status.tsx @@ -17,20 +17,23 @@ import React, {ReactNode} from 'react'; export const getAssertViewStatusIcon = (image: ImageEntity | null): ReactNode => { if (image === null) { return ; - } else if (image.status === TestStatus.SUCCESS) { - return ; - } else if (image.status === TestStatus.STAGED) { - return ; - } else if (image.status === TestStatus.COMMITED) { - return ; } else if (isNoRefImageError((image as ImageEntityError).error)) { return ; } else if (isInvalidRefImageError((image as ImageEntityError).error)) { return ; - } else if (image.status === TestStatus.FAIL) { - return ; - } else if (image.status === TestStatus.UPDATED) { - return ; + } + + switch (image.status) { + case TestStatus.SUCCESS: + return ; + case TestStatus.STAGED: + return ; + case TestStatus.COMMITED: + return ; + case TestStatus.FAIL: + return ; + case TestStatus.UPDATED: + return ; } return ; @@ -39,20 +42,23 @@ export const getAssertViewStatusIcon = (image: ImageEntity | null): ReactNode => export const getAssertViewStatusMessage = (image: ImageEntity | null): string => { if (image === null) { return 'Image is absent'; - } else if (image.status === TestStatus.SUCCESS) { - return 'Images match'; - } else if (image.status === TestStatus.STAGED) { - return 'Image is staged'; - } else if (image.status === TestStatus.COMMITED) { - return 'Image was committed'; } else if (isNoRefImageError((image as ImageEntityError).error)) { return 'Reference not found'; } else if (isInvalidRefImageError((image as ImageEntityError).error)) { return 'Reference is broken'; - } else if (image.status === TestStatus.FAIL) { - return 'Difference detected'; - } else if (image.status === TestStatus.UPDATED) { - return 'Reference updated'; + } + + switch (image.status) { + case TestStatus.SUCCESS: + return 'Images match'; + case TestStatus.STAGED: + return 'Image is staged'; + case TestStatus.COMMITED: + return 'Image was committed'; + case TestStatus.FAIL: + return 'Difference detected'; + case TestStatus.UPDATED: + return 'Reference updated'; } return 'Failed to compare'; diff --git a/test/unit/lib/static/modules/reducers/static-image-accepter.ts b/test/unit/lib/static/modules/reducers/static-image-accepter.ts index 0757fea1e..fe2b3c12e 100644 --- a/test/unit/lib/static/modules/reducers/static-image-accepter.ts +++ b/test/unit/lib/static/modules/reducers/static-image-accepter.ts @@ -144,9 +144,11 @@ describe('lib/static/modules/reducers/static-image-accepter', () => { }); describe(actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, () => { - it('should unstage image, decrementing "imagesToCommitCount"', () => { + it('should unstage images, decrementing "imagesToCommitCount"', () => { const staticImageAccepter = mkStaticImageAccepter({acceptableImages: { - 'imageId': mkAcceptableImage({id: 'imageId'}) + 'imageId1': mkAcceptableImage({id: 'imageId1'}), + 'imageId2': mkAcceptableImage({id: 'imageId2'}), + 'imageId3': mkAcceptableImage({id: 'imageId3'}) }}); const imagesById = mkImage({id: 'imageId', stateName: 'state', parentId: 'resultId'}); @@ -156,11 +158,12 @@ describe('lib/static/modules/reducers/static-image-accepter', () => { const state = {staticImageAccepter, tree}; - const newState = reducer(state, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: ['imageId']}); + const newState = reducer(state, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: ['imageId1', 'imageId2']}); - assert.equal(newState.staticImageAccepter.acceptableImages['imageId'].commitStatus, null); + assert.equal(newState.staticImageAccepter.acceptableImages['imageId1'].commitStatus, null); + assert.equal(newState.staticImageAccepter.acceptableImages['imageId2'].commitStatus, null); - assert.equal(newState.staticImageAccepter.imagesToCommitCount, state.staticImageAccepter.imagesToCommitCount - 1); + assert.equal(newState.staticImageAccepter.imagesToCommitCount, state.staticImageAccepter.imagesToCommitCount - 2); }); });