diff --git a/doc/newsfragments/2725_new.status_icons.rst b/doc/newsfragments/2725_new.status_icons.rst new file mode 100644 index 000000000..12185063d --- /dev/null +++ b/doc/newsfragments/2725_new.status_icons.rst @@ -0,0 +1 @@ +Status-indicating icons have been introduced to improve accessibility for colour vision deficiency. \ No newline at end of file diff --git a/testplan/web_ui/testing/src/AssertionPane/Assertion.js b/testplan/web_ui/testing/src/AssertionPane/Assertion.js index f2e255160..30900a351 100644 --- a/testplan/web_ui/testing/src/AssertionPane/Assertion.js +++ b/testplan/web_ui/testing/src/AssertionPane/Assertion.js @@ -1,8 +1,9 @@ import PropTypes from "prop-types"; import { Card, CardBody, Collapse } from "reactstrap"; import { css, StyleSheet } from "aphrodite"; -import { ErrorBoundary } from "../Common/ErrorBoundary"; +import { useAtomValue } from "jotai"; +import { ErrorBoundary } from "../Common/ErrorBoundary"; import BasicAssertion from "./AssertionTypes/BasicAssertion"; import MarkdownAssertion from "./AssertionTypes/MarkdownAssertion"; import CodeLogAssertion from "./AssertionTypes/CodeLogAssertion"; @@ -30,6 +31,7 @@ import { import LogfileMatchAssertion from "./AssertionTypes/LogfileMatchAssertion"; import { EXPAND_STATUS } from "../Common/defaults"; import XMLCheckAssertion from "./AssertionTypes/XMLCheckAssertion"; +import { showStatusIconsPreference } from "../UserSettings/UserSettings"; /** * Component to render one assertion. @@ -127,6 +129,7 @@ function Assertion({ } } } + let showStatusIcons = useAtomValue(showStatusIconsPreference); return ( @@ -136,6 +139,7 @@ function Assertion({ toggleExpand={toggleExpand} index={index} displayPath={displayPath} + showStatusIcons={showStatusIcons} /> {timeInfoArray[2]}    @@ -159,6 +179,17 @@ function AssertionHeader({ "" ); + const statusIcon = showStatusIcons ? ( + + + + ) : null; + return (
@@ -172,10 +203,10 @@ function AssertionHeader({ ...assertion.custom_style, }} > + {statusIcon} {description} ({assertion.type}) - {component} {pathButton} {/* @@ -273,6 +304,11 @@ const styles = StyleSheet.create({ icon: { margin: "0rem .25rem 0rem 0rem", }, + statusIcon: { + display: "inline-flex", + width: "1rem", + justifyContent: "center", + }, }); export default AssertionHeader; diff --git a/testplan/web_ui/testing/src/AssertionPane/__tests__/AssertionHeader.test.js b/testplan/web_ui/testing/src/AssertionPane/__tests__/AssertionHeader.test.js index 11602e366..9d91d2990 100644 --- a/testplan/web_ui/testing/src/AssertionPane/__tests__/AssertionHeader.test.js +++ b/testplan/web_ui/testing/src/AssertionPane/__tests__/AssertionHeader.test.js @@ -1,8 +1,10 @@ import React from "react"; import { shallow } from "enzyme"; import { StyleSheetTestUtils } from "aphrodite"; +import { getDefaultStore } from "jotai"; import AssertionHeader from "../AssertionHeader"; +import { showStatusIconsPreference } from "../../UserSettings/UserSettings"; function defaultProps() { return { @@ -39,4 +41,10 @@ describe("AssertionHeader", () => { shallowComponent = shallow(); expect(shallowComponent).toMatchSnapshot(); }); + + it("shallow renders the correct HTML structure with status icons enabled", () => { + getDefaultStore().set(showStatusIconsPreference, true); + shallowComponent = shallow(); + expect(shallowComponent).toMatchSnapshot(); + }); }); diff --git a/testplan/web_ui/testing/src/AssertionPane/__tests__/__snapshots__/Assertion.test.js.snap b/testplan/web_ui/testing/src/AssertionPane/__tests__/__snapshots__/Assertion.test.js.snap index d98130ec7..92e15ab93 100644 --- a/testplan/web_ui/testing/src/AssertionPane/__tests__/__snapshots__/Assertion.test.js.snap +++ b/testplan/web_ui/testing/src/AssertionPane/__tests__/__snapshots__/Assertion.test.js.snap @@ -22,6 +22,7 @@ exports[`Assertion shallow renders the correct HTML structure 1`] = ` } } index={0} + showStatusIcons={false} /> `; + +exports[`AssertionHeader shallow renders the correct HTML structure with status icons enabled 1`] = ` + +
+ + + + ( + Equal + ) + + + +
+
+`; diff --git a/testplan/web_ui/testing/src/Common/Styles.js b/testplan/web_ui/testing/src/Common/Styles.js index c5c882ed7..03ff450e4 100644 --- a/testplan/web_ui/testing/src/Common/Styles.js +++ b/testplan/web_ui/testing/src/Common/Styles.js @@ -2,7 +2,21 @@ * Common aphrodite styles. */ import { StyleSheet } from "aphrodite"; -import { RED, GREEN, ORANGE, BLACK } from "../Common/defaults"; +import { + faCheck, + faInfo, + faQuestionCircle, + faTimes, +} from "@fortawesome/free-solid-svg-icons"; + +import { + RED, + GREEN, + ORANGE, + BLACK, + LIGHT_GREY, + MEDIUM_GREY +} from "../Common/defaults"; export const unselectable = { "moz-user-select": "-moz-none", @@ -15,26 +29,33 @@ export const unselectable = { export const statusStyles = { passed: { color: GREEN, + icon: faCheck, }, failed: { color: RED, + icon: faTimes, }, error: { color: RED, + icon: faTimes, }, skipped: { color: ORANGE, + icon: faQuestionCircle, }, unstable: { color: ORANGE, + icon: faQuestionCircle, }, unknown: { color: BLACK, + icon: faInfo, }, }; export const navStyles = StyleSheet.create({ entryName: { + display: "flex", overflow: "hidden", "text-overflow": "ellipsis", "white-space": "nowrap", @@ -82,6 +103,33 @@ export const navStyles = StyleSheet.create({ flexDirection: "column", alignItems: "end", }, + navButton: { + position: "relative", + display: "block", + border: "none", + backgroundColor: LIGHT_GREY, + cursor: "pointer", + }, + navButtonInteract: { + ":hover": { + backgroundColor: MEDIUM_GREY, + }, + }, + navButtonInteractFocus: { + backgroundColor: MEDIUM_GREY, + outline: "none", + }, + buttonList: { + "overflow-y": "auto", + height: "100%", + }, + statusIcon: { + display: "inline-flex", + width: "1rem", + justifyContent: "center", + marginRight: "0.3rem", + alignSelf: "center", + }, environmentToggle: { padding: "0.65em 0em 0.65em 0em", }, diff --git a/testplan/web_ui/testing/src/Nav/InteractiveNavEntry.js b/testplan/web_ui/testing/src/Nav/InteractiveNavEntry.js index 76d8aa603..bc380bba3 100644 --- a/testplan/web_ui/testing/src/Nav/InteractiveNavEntry.js +++ b/testplan/web_ui/testing/src/Nav/InteractiveNavEntry.js @@ -11,7 +11,7 @@ import { faToggleOn, faFastBackward, } from "@fortawesome/free-solid-svg-icons"; -import { useAtom } from "jotai"; +import { useAtom, useAtomValue } from "jotai"; import { CATEGORY_ICONS, @@ -23,8 +23,9 @@ import { NAV_ENTRY_ACTIONS, } from "../Common/defaults"; import { navStyles } from "../Common/Styles"; -import { generateNavTimeInfo } from "./navUtils"; +import { generateNavTimeInfo, GetStatusIcon } from "./navUtils"; import { pendingEnvRequestAtom } from "../Report/InteractiveReport"; +import { showStatusIconsPreference } from "../UserSettings/UserSettings"; /** * Display interactive NavEntry information: @@ -67,6 +68,10 @@ const InteractiveNavEntry = (props) => { props.teardownTime, props.executionTime, ) : null; + + const statusIcon2 = useAtomValue(showStatusIconsPreference) + ? GetStatusIcon(props.status) + : null; return (
{ } title={props.description || props.name} > + {statusIcon2} {props.name}
diff --git a/testplan/web_ui/testing/src/Nav/NavEntry.js b/testplan/web_ui/testing/src/Nav/NavEntry.js index 5bc8d40cc..7717b3283 100644 --- a/testplan/web_ui/testing/src/Nav/NavEntry.js +++ b/testplan/web_ui/testing/src/Nav/NavEntry.js @@ -2,6 +2,7 @@ import React from "react"; import PropTypes from "prop-types"; import { Badge } from "reactstrap"; import { css } from "aphrodite"; +import { useAtomValue } from "jotai"; import { CATEGORY_ICONS, @@ -10,7 +11,8 @@ import { STATUS_CATEGORY, } from "../Common/defaults"; import { navStyles } from "../Common/Styles"; -import { generateNavTimeInfo } from "./navUtils"; +import { generateNavTimeInfo, GetStatusIcon } from "./navUtils"; +import { showStatusIconsPreference } from "../UserSettings/UserSettings"; /** * Display NavEntry information: @@ -26,6 +28,9 @@ const NavEntry = (props) => { props.teardownTime, props.executionTime, ) : null; + const statusIcon = + useAtomValue(showStatusIconsPreference) ? GetStatusIcon(props.status) + : null; return (
{ } title={`${props.description || props.name} - ${props.status}`} > + {statusIcon} {props.name}
diff --git a/testplan/web_ui/testing/src/Nav/__tests__/NavEntry.test.js b/testplan/web_ui/testing/src/Nav/__tests__/NavEntry.test.js index d474335e7..3bb5cd7d6 100644 --- a/testplan/web_ui/testing/src/Nav/__tests__/NavEntry.test.js +++ b/testplan/web_ui/testing/src/Nav/__tests__/NavEntry.test.js @@ -1,9 +1,10 @@ import React from "react"; import { shallow } from "enzyme"; import { StyleSheetTestUtils } from "aphrodite"; -import { Badge } from "reactstrap"; +import { getDefaultStore } from "jotai"; import NavEntry from "../NavEntry"; +import { showStatusIconsPreference } from "../../UserSettings/UserSettings"; function defaultProps() { return { @@ -45,4 +46,10 @@ describe("NavEntry", () => { const navEntry = shallow(); expect(navEntry).toMatchSnapshot(); }); + + it("shallow renders the correct HTML structure with status icons enabled", () => { + getDefaultStore().set(showStatusIconsPreference, true); + const navEntry = shallow(); + expect(navEntry).toMatchSnapshot(); + }); }); diff --git a/testplan/web_ui/testing/src/Nav/__tests__/__snapshots__/InteractiveNavEntry.test.js.snap b/testplan/web_ui/testing/src/Nav/__tests__/__snapshots__/InteractiveNavEntry.test.js.snap index 925b26050..27895804f 100644 --- a/testplan/web_ui/testing/src/Nav/__tests__/__snapshots__/InteractiveNavEntry.test.js.snap +++ b/testplan/web_ui/testing/src/Nav/__tests__/__snapshots__/InteractiveNavEntry.test.js.snap @@ -20,7 +20,7 @@ exports[`InteractiveNavEntry renders a testcase in "failed" state 1`] = ` C
FakeTestcase @@ -29,20 +29,20 @@ exports[`InteractiveNavEntry renders a testcase in "failed" state 1`] = ` className="entryIcons_9stzon" > 8 / 1 @@ -112,7 +112,7 @@ exports[`InteractiveNavEntry renders a testcase in "not_run" state 1`] = ` C
FakeTestcase @@ -121,20 +121,20 @@ exports[`InteractiveNavEntry renders a testcase in "not_run" state 1`] = ` className="entryIcons_9stzon" > 0 / 0 @@ -204,7 +204,7 @@ exports[`InteractiveNavEntry renders a testcase in "passed" state 1`] = ` C
FakeTestcase @@ -213,20 +213,20 @@ exports[`InteractiveNavEntry renders a testcase in "passed" state 1`] = ` className="entryIcons_9stzon" > 9 / 0 @@ -296,7 +296,7 @@ exports[`InteractiveNavEntry renders a testcase in "ready" state 1`] = ` C
FakeTestcase @@ -305,20 +305,20 @@ exports[`InteractiveNavEntry renders a testcase in "ready" state 1`] = ` className="entryIcons_9stzon" > 0 / 0 @@ -388,7 +388,7 @@ exports[`InteractiveNavEntry renders a testcase in "resetting" state 1`] = ` C
FakeTestcase @@ -397,20 +397,20 @@ exports[`InteractiveNavEntry renders a testcase in "resetting" state 1`] = ` className="entryIcons_9stzon" > 0 / 0 @@ -480,7 +480,7 @@ exports[`InteractiveNavEntry renders a testcase in "running" state 1`] = ` C
FakeTestcase @@ -489,20 +489,20 @@ exports[`InteractiveNavEntry renders a testcase in "running" state 1`] = ` className="entryIcons_9stzon" > 0 / 0 @@ -572,7 +572,7 @@ exports[`InteractiveNavEntry renders a testcase in "waiting" state 1`] = ` C
FakeTestcase @@ -581,20 +581,20 @@ exports[`InteractiveNavEntry renders a testcase in "waiting" state 1`] = ` className="entryIcons_9stzon" > 0 / 0 @@ -664,7 +664,7 @@ exports[`InteractiveNavEntry renders an entry with environment status STARTED 1` MT
FakeTestcase @@ -673,20 +673,20 @@ exports[`InteractiveNavEntry renders an entry with environment status STARTED 1` className="entryIcons_9stzon" > 0 / 0 @@ -838,7 +838,7 @@ exports[`InteractiveNavEntry renders an entry with environment status STARTING 1 MT
FakeTestcase @@ -847,20 +847,20 @@ exports[`InteractiveNavEntry renders an entry with environment status STARTING 1 className="entryIcons_9stzon" > 0 / 0 @@ -1015,7 +1015,7 @@ exports[`InteractiveNavEntry renders an entry with environment status STOPPED 1` MT
FakeTestcase @@ -1024,20 +1024,20 @@ exports[`InteractiveNavEntry renders an entry with environment status STOPPED 1` className="entryIcons_9stzon" > 0 / 0 @@ -1189,7 +1189,7 @@ exports[`InteractiveNavEntry renders an entry with environment status STOPPING 1 MT
FakeTestcase @@ -1198,20 +1198,20 @@ exports[`InteractiveNavEntry renders an entry with environment status STOPPING 1 className="entryIcons_9stzon" > 0 / 0 diff --git a/testplan/web_ui/testing/src/Nav/__tests__/__snapshots__/NavEntry.test.js.snap b/testplan/web_ui/testing/src/Nav/__tests__/__snapshots__/NavEntry.test.js.snap index 78bfe33be..9fa4a21a1 100644 --- a/testplan/web_ui/testing/src/Nav/__tests__/__snapshots__/NavEntry.test.js.snap +++ b/testplan/web_ui/testing/src/Nav/__tests__/__snapshots__/NavEntry.test.js.snap @@ -20,7 +20,7 @@ exports[`NavEntry shallow renders the correct HTML structure 1`] = ` TP
entry name @@ -29,20 +29,115 @@ exports[`NavEntry shallow renders the correct HTML structure 1`] = ` className="entryIcons_9stzon" > 0 / + 0 + + +
+
+`; + +exports[`NavEntry shallow renders the correct HTML structure with status icons enabled 1`] = ` +
+ + TP + +
+ + + + entry name +
+
+ + + + 0 + + / + 0 @@ -71,7 +166,7 @@ exports[`NavEntry when prop status="failed" name div and Badge have correct styl TP
entry name @@ -80,20 +175,20 @@ exports[`NavEntry when prop status="failed" name div and Badge have correct styl className="entryIcons_9stzon" > 0 / 0 @@ -122,7 +217,7 @@ exports[`NavEntry when prop status="xfail" name div and Badge have correct style TP
entry name @@ -131,20 +226,20 @@ exports[`NavEntry when prop status="xfail" name div and Badge have correct style className="entryIcons_9stzon" > 0 / 0 diff --git a/testplan/web_ui/testing/src/Nav/navUtils.js b/testplan/web_ui/testing/src/Nav/navUtils.js index 53165892d..2662c523e 100644 --- a/testplan/web_ui/testing/src/Nav/navUtils.js +++ b/testplan/web_ui/testing/src/Nav/navUtils.js @@ -3,16 +3,16 @@ */ import React from "react"; import { ListGroup, ListGroupItem } from "reactstrap"; -import { StyleSheet, css } from "aphrodite"; +import { css } from "aphrodite"; import _ from "lodash"; +import { NavLink } from "react-router-dom"; +import { generatePath } from "react-router"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import TagList from "./TagList"; import Column from "./Column"; -import { LIGHT_GREY, MEDIUM_GREY } from "../Common/defaults"; -import CommonStyles from "../Common/Styles.js"; -import { statusStyles } from "../Common/Styles"; -import { NavLink } from "react-router-dom"; -import { generatePath } from "react-router"; +import CommonStyles, { statusStyles } from "../Common/Styles.js"; +import { navStyles } from "../Common/Styles"; import { generateURLWithParameters, formatShortDuration, @@ -43,11 +43,11 @@ const CreateNavButtons = (props, createEntryComponent, uidEncoder) => { const tabIndex = entryIndex + 1; const cssClass = [ - styles.navButton, - styles.navButtonInteract, + navStyles.navButton, + navStyles.navButtonInteract, CommonStyles.unselectable, ]; - const cssActiveClass = [...cssClass, styles.navButtonInteractFocus]; + const cssActiveClass = [...cssClass, navStyles.navButtonInteractFocus]; let [reportuid, ...selectionuids] = uidEncoder ? entry.uids.map(uidEncoder) @@ -77,7 +77,7 @@ const CreateNavButtons = (props, createEntryComponent, uidEncoder) => { }); const navButtonsEmpty = ( - + No entries to display... ); @@ -144,34 +144,6 @@ const applyNamedFilter = (entries, filter) => { } }; -export const styles = StyleSheet.create({ - entryIcon: { - fontSize: "x-small", - margin: "0em 0.5em 0em 0.5em", - }, - navButton: { - position: "relative", - display: "block", - border: "none", - backgroundColor: LIGHT_GREY, - cursor: "pointer", - }, - navButtonInteract: { - ":hover": { - backgroundColor: MEDIUM_GREY, - }, - }, - navButtonInteractFocus: { - backgroundColor: MEDIUM_GREY, - outline: "none", - }, - buttonList: { - "overflow-y": "auto", - height: "100%", - }, - ...statusStyles, -}); - /** * Return the UID of the currently selected entry, or null if there is no * entry selected. @@ -323,10 +295,21 @@ const GetNavBreadcrumbs = (selected) => { const GetNavColumn = (props, navButtons) => ( - {navButtons} + {navButtons} ); +const GetStatusIcon = (status) => ( + + + +); + const generateNavTimeInfo = ( setupTimeProp, teardownTimeProp, @@ -342,7 +325,7 @@ const generateNavTimeInfo = ( const detailedTimeElement = (_.isNumber(setupTimeProp) || _.isNumber(teardownTimeProp)) ? ( - ( { @@ -359,7 +342,7 @@ const generateNavTimeInfo = ( const totalTimeElement = _.isNumber(totalTime) ? ( - + {formatShortDuration(totalTime)} ) @@ -374,6 +357,7 @@ const generateNavTimeInfo = ( export { CreateNavButtons, generateNavTimeInfo, + GetStatusIcon, GetSelectedUid, GetNavEntries, GetInteractiveNavEntries, diff --git a/testplan/web_ui/testing/src/Report/__tests__/__snapshots__/BatchReport.test.js.snap b/testplan/web_ui/testing/src/Report/__tests__/__snapshots__/BatchReport.test.js.snap index 3890f0ad1..e1935080e 100644 --- a/testplan/web_ui/testing/src/Report/__tests__/__snapshots__/BatchReport.test.js.snap +++ b/testplan/web_ui/testing/src/Report/__tests__/__snapshots__/BatchReport.test.js.snap @@ -26259,6 +26259,62 @@ ZeroDivisionError: integer division or modulo by zero + + + + + { Display file path of assertions + + Show status icons + Navigation preferences diff --git a/testplan/web_ui/testing/src/UserSettings/UserSettings.js b/testplan/web_ui/testing/src/UserSettings/UserSettings.js index 9795c6b0f..cadfca1c5 100644 --- a/testplan/web_ui/testing/src/UserSettings/UserSettings.js +++ b/testplan/web_ui/testing/src/UserSettings/UserSettings.js @@ -15,3 +15,7 @@ export const hideSkippedTestcasesPreference = atomWithStorage( "hideSkippedTestcases", false ); +export const showStatusIconsPreference = atomWithStorage( + "showStatusIcons", + false +);