+export function TreeViewItemTitle({item, className}: TreeViewItemTitleProps): React.JSX.Element {
+ const dispatch = useDispatch();
+ const areCheckboxesNeeded = useSelector(getAreCheckboxesNeeded);
+ 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);
+
+ useEffect(() => {
+ if (ref.current) {
+ ref.current.onclick = (e): void => {
+ e.stopPropagation();
+
+ if (item.type === TreeViewItemType.Suite) {
+ dispatch(toggleSuiteCheckbox({
+ suiteId: item.id,
+ checkStatus: getToggledCheckboxState(checkStatus)
+ }));
+ } else if (item.type === TreeViewItemType.Browser) {
+ dispatch(toggleBrowserCheckbox({
+ suiteBrowserId: item.id,
+ checkStatus: getToggledCheckboxState(checkStatus)
+ }));
+ }
+ };
+ }
+ }, [ref, item, checkStatus]);
+
+ return
+ {item.title}
{
- props.item.type === TreeViewItemType.Browser &&
- props.item.errorTitle &&
- {props.item.errorTitle}
+ item.type === TreeViewItemType.Browser &&
+ item.errorTitle &&
+ {item.errorTitle}
}
+ {areCheckboxesNeeded && }
;
}
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 d4eb0985c..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) {
@@ -69,20 +68,20 @@ export function VisualChecksPage(): ReactNode {
}
if (isStaticImageAccepterEnabled) {
- dispatch(staticAccepterUnstageScreenshot(currentImage.id));
+ dispatch(staticAccepterUnstageScreenshot([currentImage.id]));
} else {
dispatch(undoAcceptImage(currentImage.id));
}
};
- 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 3d7dd17b9..af049e88a 100644
--- a/lib/static/new-ui/store/selectors.ts
+++ b/lib/static/new-ui/store/selectors.ts
@@ -7,6 +7,7 @@ import {
SuiteState,
BrowserState
} from '@/static/new-ui/types/store';
+import {EditScreensFeature, RunTestsFeature} from '@/constants';
export const getAllRootSuiteIds = (state: State): string[] => state.tree.suites.allRootIds;
export const getSuites = (state: State): Record => state.tree.suites.byId;
@@ -18,3 +19,7 @@ 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.staticImageAccepter.enabled;
+export const getIsGui = (state: State): boolean => state.gui;
+
+export const getAreCheckboxesNeeded = (state: State): boolean => state.app.availableFeatures.includes(RunTestsFeature) || state.app.availableFeatures.includes(EditScreensFeature);
diff --git a/lib/static/new-ui/types/store.ts b/lib/static/new-ui/types/store.ts
index 19b05d6ed..3134ef939 100644
--- a/lib/static/new-ui/types/store.ts
+++ b/lib/static/new-ui/types/store.ts
@@ -4,6 +4,7 @@ import {HtmlReporterValues} from '@/plugin-api';
import {CoordBounds} from 'looks-same';
import {Point} from '@/static/new-ui/types/index';
import {AcceptableImage} from '@/static/modules/static-image-accepter';
+import {CheckStatus} from '@/constants/checked-statuses';
export interface SuiteEntityNode {
id: string;
@@ -116,6 +117,7 @@ export const isImageEntityFail = (image: ImageEntity): image is ImageEntityFail
export interface SuiteState {
shouldBeOpened: boolean;
shouldBeShown: boolean;
+ checkStatus: CheckStatus;
}
export interface BrowserState {
@@ -123,6 +125,7 @@ export interface BrowserState {
retryIndex: number;
// True if test is not shown because of its status. Useful when computing counts by status.
isHiddenBecauseOfStatus?: boolean;
+ checkStatus: CheckStatus;
}
export interface ResultState {
@@ -207,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
new file mode 100644
index 000000000..aa445695e
--- /dev/null
+++ b/lib/static/new-ui/utils/assert-view-status.tsx
@@ -0,0 +1,65 @@
+import {ImageEntity, ImageEntityError} from '@/static/new-ui/types/store';
+import {Icon} from '@gravity-ui/uikit';
+import {
+ ArrowRightArrowLeft,
+ CircleCheck,
+ FileArrowUp, FileCheck,
+ FileExclamation,
+ FileLetterX,
+ FilePlus,
+ SquareExclamation,
+ SquareXmark
+} from '@gravity-ui/icons';
+import {TestStatus} from '@/constants';
+import {isInvalidRefImageError, isNoRefImageError} from '@/common-utils';
+import React, {ReactNode} from 'react';
+
+export const getAssertViewStatusIcon = (image: ImageEntity | null): ReactNode => {
+ if (image === null) {
+ return ;
+ } else if (isNoRefImageError((image as ImageEntityError).error)) {
+ return ;
+ } else if (isInvalidRefImageError((image as ImageEntityError).error)) {
+ 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 ;
+};
+
+export const getAssertViewStatusMessage = (image: ImageEntity | null): string => {
+ if (image === null) {
+ return 'Image is absent';
+ } else if (isNoRefImageError((image as ImageEntityError).error)) {
+ return 'Reference not found';
+ } else if (isInvalidRefImageError((image as ImageEntityError).error)) {
+ return 'Reference is broken';
+ }
+
+ 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 d2a4f08ab..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: '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);
});
});
diff --git a/test/unit/lib/static/modules/reducers/tree/index.js b/test/unit/lib/static/modules/reducers/tree/index.js
index 5150058ca..d701f082d 100644
--- a/test/unit/lib/static/modules/reducers/tree/index.js
+++ b/test/unit/lib/static/modules/reducers/tree/index.js
@@ -1854,7 +1854,7 @@ describe('lib/static/modules/reducers/tree', () => {
it('should return original image status', () => {
const newState = reducer(state, {type: actionNames.STATIC_ACCEPTER_STAGE_SCREENSHOT, payload: ['i1']});
- const nextState = reducer(newState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: {imageId: 'i1'}});
+ const nextState = reducer(newState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: ['i1']});
assert.equal(nextState.tree.images.byId['i1'].status, FAIL);
});
@@ -1862,8 +1862,8 @@ describe('lib/static/modules/reducers/tree', () => {
it('should return original result status', () => {
const firstState = reducer(state, {type: actionNames.STATIC_ACCEPTER_STAGE_SCREENSHOT, payload: ['i1', 'i2']});
- const secondState = reducer(firstState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: {imageId: 'i1'}});
- const thirdState = reducer(secondState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: {imageId: 'i2'}});
+ const secondState = reducer(firstState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: ['i1']});
+ const thirdState = reducer(secondState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: ['i2']});
assert.equal(thirdState.tree.results.byId['r1'].status, FAIL);
});
@@ -1871,9 +1871,9 @@ describe('lib/static/modules/reducers/tree', () => {
it('should return original suite status', () => {
const firstState = reducer(state, {type: actionNames.STATIC_ACCEPTER_STAGE_SCREENSHOT, payload: ['i1', 'i2', 'i3']});
- const secondState = reducer(firstState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: {imageId: 'i1'}});
- const thirdState = reducer(secondState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: {imageId: 'i2'}});
- const fourthState = reducer(thirdState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: {imageId: 'i3'}});
+ const secondState = reducer(firstState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: ['i1']});
+ const thirdState = reducer(secondState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: ['i2']});
+ const fourthState = reducer(thirdState, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: ['i3']});
assert.equal(fourthState.tree.suites.byId['s1'].status, FAIL);
});
@@ -1884,7 +1884,7 @@ describe('lib/static/modules/reducers/tree', () => {
state.tree.results.byId['r1'].status = STAGED;
state.tree.images.byId['i1'].status = STAGED;
- const nextState = reducer(state, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: {imageId: 'i1'}});
+ const nextState = reducer(state, {type: actionNames.STATIC_ACCEPTER_UNSTAGE_SCREENSHOT, payload: ['i1']});
assert.equal(nextState.tree.suites.byId['s0'].status, FAIL);
assert.equal(nextState.tree.suites.byId['s1'].status, FAIL);
diff --git a/test/unit/lib/static/utils.tsx b/test/unit/lib/static/utils.tsx
index d72ff0ae4..dcd617a61 100644
--- a/test/unit/lib/static/utils.tsx
+++ b/test/unit/lib/static/utils.tsx
@@ -12,6 +12,7 @@ import {TestStatus} from '@/constants';
import reducer from '@/static/modules/reducers';
import localStorage from '@/static/modules/middlewares/local-storage';
import {BrowserEntity, ImageEntityFail, State, SuiteEntity, TreeEntity} from '@/static/new-ui/types/store';
+import {UNCHECKED} from '@/constants/checked-statuses';
export const mkState = ({initialState}: { initialState: Partial }): State => {
return _.defaultsDeep(initialState ?? {}, defaultState);
@@ -77,6 +78,7 @@ export const addBrowserToTree = ({tree, suiteName, browserName}: AddBrowserToTre
} as BrowserEntity;
tree.browsers.stateById[fullId] = {
shouldBeShown: true,
+ checkStatus: UNCHECKED,
retryIndex: 0,
isHiddenBecauseOfStatus: false
};