diff --git a/testplan/web_ui/testing/.env b/testplan/web_ui/testing/.env deleted file mode 100644 index 11e4a7122..000000000 --- a/testplan/web_ui/testing/.env +++ /dev/null @@ -1,4 +0,0 @@ -EXTEND_ESLINT=true -# make 'serve' skip checking its servers for updates -# see: node_modules/serve/bin/serve.js:365 -NO_UPDATE_CHECK=1 diff --git a/testplan/web_ui/testing/.env.production b/testplan/web_ui/testing/.env.production deleted file mode 100644 index ccfcf41f0..000000000 --- a/testplan/web_ui/testing/.env.production +++ /dev/null @@ -1,5 +0,0 @@ -#REACT_APP_THREAD=thread -#REACT_APP_THREADJS=thread.js -#REACT_APP_RELTHREAD=./thread -#REACT_APP_USE_THREAD=1 -#EXTEND_ESLINT=1 diff --git a/testplan/web_ui/testing/.env.test b/testplan/web_ui/testing/.env.test deleted file mode 100644 index d37fccd16..000000000 --- a/testplan/web_ui/testing/.env.test +++ /dev/null @@ -1,2 +0,0 @@ -SKIP_PREFLIGHT_CHECK=true -BROWSER=none diff --git a/testplan/web_ui/testing/.eslintignore b/testplan/web_ui/testing/.eslintignore index b449a9bfb..e81312e58 100644 --- a/testplan/web_ui/testing/.eslintignore +++ b/testplan/web_ui/testing/.eslintignore @@ -1,3 +1,4 @@ +__tests__ __snapshots__ -/build/ -/node_modules/* +sampleReports.js +fakeReport.js diff --git a/testplan/web_ui/testing/.eslintrc.json b/testplan/web_ui/testing/.eslintrc.json index 0d9c41109..36a8cbdbf 100644 --- a/testplan/web_ui/testing/.eslintrc.json +++ b/testplan/web_ui/testing/.eslintrc.json @@ -1,42 +1,21 @@ { "env": { - "browser": true, - "es6": true, - "commonjs": true + "browser": 2, + "es6": 2, + "node": 2, + "commonjs": 2 }, "extends": [ "react-app" ], - "overrides": [ - { - "files": [ - "*.test.js", - "*.test.jsx", - "*.test.ts", - "*.test.tsx", - "./src/setupTests.js", - "./jest-puppeteer.config.js" - ], - "env": { - "jest": true, - "node": true - }, - "globals": { - "page": true, - "browser": true, - "context": true, - "jestPuppeteer": true - } - } - ], "parserOptions": { "ecmaFeatures": { - "experimentalObjectRestSpread": true, - "jsx": true, - "arrowFunctions": true, - "classes": true, - "modules": true, - "defaultParams": true + "experimentalObjectRestSpread": 2, + "jsx": 2, + "arrowFunctions": 2, + "classes": 2, + "modules": 2, + "defaultParams": 2 }, "sourceType": "module" }, @@ -45,45 +24,27 @@ "react" ], "rules": { - "max-len": [ - "error", - { - "code": 80, - "comments": 120 - } - ], + "max-len": ["error", { "code": 80, "comments": 120 }], "linebreak-style": [ "error", "unix" ], - "semi": [ - "error", - "always" - ], + "semi": ["error", "always"], "no-const-assign": 2, "no-dupe-class-members": 2, "no-duplicate-case": 2, - "no-extra-parens": [ - 2, - "functions" - ], + "no-extra-parens": [2, "functions"], "no-self-compare": 2, "accessor-pairs": 2, - "comma-spacing": [ - 2, - { - "before": false, - "after": true - } - ], + "comma-spacing": [2, { + "before": false, + "after": true + }], "constructor-super": 2, - "new-cap": [ - 2, - { - "newIsCap": true, - "capIsNew": false - } - ], + "new-cap": [2, { + "newIsCap": true, + "capIsNew": false + }], "new-parens": 2, "no-array-constructor": 2, "no-class-assign": 2, diff --git a/testplan/web_ui/testing/.gitignore b/testplan/web_ui/testing/.gitignore index 46fd3f783..129208cd3 100644 --- a/testplan/web_ui/testing/.gitignore +++ b/testplan/web_ui/testing/.gitignore @@ -1,6 +1,5 @@ node_modules/ .env.local -.env.production.local -.env.development.local +.env.*.local yarn-error.log -/build/ +build/ diff --git a/testplan/web_ui/testing/DEVELOPMENT.md b/testplan/web_ui/testing/DEVELOPMENT.md deleted file mode 100644 index ec85d87e4..000000000 --- a/testplan/web_ui/testing/DEVELOPMENT.md +++ /dev/null @@ -1,14 +0,0 @@ -# Testplan Web UI - -- Recommended Development Setup - * [IntelliJ-based IDEs](#intellij-based-ides) - * [VSCode](#vscode) - - -#### IntelliJ-based IDEs - -TODO - -### VSCode - -TODO diff --git a/testplan/web_ui/testing/README.md b/testplan/web_ui/testing/README.md deleted file mode 100644 index aefef544b..000000000 --- a/testplan/web_ui/testing/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Web UI - -- [Recommended development setup](DEVELOPMENT.md) -- [Report fetch](src/Report/BatchReport/state/reportWorker) -- diff --git a/testplan/web_ui/testing/jest-puppeteer.config.js b/testplan/web_ui/testing/jest-puppeteer.config.js deleted file mode 100644 index 05ba94dd2..000000000 --- a/testplan/web_ui/testing/jest-puppeteer.config.js +++ /dev/null @@ -1,40 +0,0 @@ -const - { dirname, resolve, delimiter } = require('path'), - { appPackageJson, appNodeModules } = require('react-scripts/config/paths'), - { getServers } = require('jest-dev-server'), - { env: { PATH, SKIP_BUILD, CI, HEADLESS }, execPath } = process, - // serve production build for puppeteer integration tests - { scripts: { build, serve } } = require(appPackageJson), - isSkipBuild = !!JSON.parse(SKIP_BUILD || '0'), - isHeadless = !!JSON.parse(CI || '0') && !!JSON.parse(HEADLESS || '1'); - -module.exports = { - // see github.com/smooth-code/jest-puppeteer/issues/120#issuecomment-464185653 - launch: { - dumpio: true, - ...(isHeadless ? {} : { headless: false, devtools: true }), - }, - server: { - debug: true, - proto: 'http', - host: '127.0.0.1', - port: 5000, - usedPortAction: 'error', - launchTimeout: 1000 * 60 * 5, - command: (isSkipBuild ? '' : `${build} && `) + serve, - options: { - env: { - PATH: [ - dirname(execPath), - resolve(appNodeModules, '.bin'), - PATH - ].join(delimiter), - }, - windowsVerbatimArguments: true, - }, - }, - getOrigin() { - return `${this.server.proto}://${this.server.host}:${this.server.port}`; - }, - getProcesses: getServers, -}; diff --git a/testplan/web_ui/testing/package.json b/testplan/web_ui/testing/package.json index 5823c6aba..09d045b35 100644 --- a/testplan/web_ui/testing/package.json +++ b/testplan/web_ui/testing/package.json @@ -11,16 +11,16 @@ "@fortawesome/fontawesome-svg-core": "1.2.2", "@fortawesome/free-solid-svg-icons": "5.2.0", "@fortawesome/react-fontawesome": "0.1.3", - "@reduxjs/toolkit": "^1.3.4", + "@reduxjs/toolkit": "~1.3.4", "ag-grid-community": "^21.2.1", "ag-grid-react": "^21.2.1", "aphrodite": "2.2.3", "axios": "0.19.0", "bootstrap": "4.3.1", "eslint-plugin-react": "^7.14.3", - "history": "^4.10.1", - "lodash": "^4.17.15", - "prop-types": "^15.7.2", + "history": "^~4.10.1", + "lodash": "~4.17.15", + "qs": "^6.9.1", "react": "~16.12.0", "react-copy-html-to-clipboard": "6.0.4", "react-custom-scrollbars": "4.2.1", @@ -32,6 +32,7 @@ "react-scripts": "^3.4.0", "react-spinners": "^0.6.0", "react-syntax-highlighter": "^11.0.2", + "react-test-renderer": "16.6.0", "react-vis": "^1.11.7", "reactstrap": "6.3.0", "redux": "^4.0.5" @@ -43,18 +44,10 @@ "enzyme": "3.7.0", "enzyme-adapter-react-16": "1.6.0", "enzyme-to-json": "^3.3.5", - "expect-puppeteer": "^4.4.0", - "jest": "^24.9.0", - "jest-dev-server": "^4.4.0", - "jest-environment-node": "^25.3.0", - "jest-environment-puppeteer": "^4.4.0", - "jest-puppeteer": "^4.4.0", "moxios": "0.4.0", - "puppeteer": "^2.0.0", - "serve": "^11.0.0" + "npm-force-resolutions": "0.0.3" }, "scripts": { - "serve": "serve -d -s -l 5000 build", "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", @@ -76,49 +69,8 @@ ] }, "jest": { - "globalSetup": "jest-environment-puppeteer/setup", - "globalTeardown": "jest-environment-puppeteer/teardown", "snapshotSerializers": [ "enzyme-to-json/serializer" ] - }, - "optionalDependencies": { - "@babel/cli": "^7.4.4", - "@babel/node": "^7.5.0", - "@babel/register": "^7.9.0", - "@types/bootstrap": "^3.3.37", - "@types/cheerio": "^0.22.11", - "@types/enzyme": "^3.1.16", - "@types/enzyme-adapter-react-16": "^1.0.3", - "@types/enzyme-to-json": "^1.5.1", - "@types/expect-puppeteer": "^3.3.1", - "@types/history": "^4.7.5", - "@types/jest": "^24.9.0", - "@types/jest-environment-puppeteer": "^4.3.1", - "@types/jquery": "^3.3.32", - "@types/lodash": "^4.14.149", - "@types/node": "^13.11.0", - "@types/prop-types": "^15.7.3", - "@types/puppeteer": "^2.0.1", - "@types/react": "^16.9.32", - "@types/react-dom": "^16.9.5", - "@types/react-redux": "^7.1.0", - "@types/react-router": "^5.1.4", - "@types/react-router-dom": "^5.1.3", - "@types/react-syntax-highlighter": "^11.0.2", - "@types/react-test-renderer": "^16.0.2", - "@types/reactstrap": "^8.0.1", - "@types/sizzle": "^2.3.2", - "@types/testing-library__dom": "^6.12.0", - "@types/testing-library__react": "^9.1.2", - "@types/webpack-dev-server": "^3.9.0", - "@types/webpack-env": "^1.14.0", - "@typescript-eslint/eslint-plugin": "^2.27.0", - "@typescript-eslint/parser": "^2.27.0", - "eslint": "^6.8.0", - "qs": "^6.9.1", - "react-devtools": "^3.6.1", - "redux-mock-store": "^1.5.3", - "typescript": "^3.8.3" } } diff --git a/testplan/web_ui/testing/public/index.html b/testplan/web_ui/testing/public/index.html index 787b44095..7a4a98d73 100644 --- a/testplan/web_ui/testing/public/index.html +++ b/testplan/web_ui/testing/public/index.html @@ -4,6 +4,7 @@ + line[0])); - const allItemsAreSameLevel = set.size === 1; + const set = _uniq(origFlattenedJSON.map(line => line[0])); + const allItemsAreSameLevel = set.length === 1; // if all remaining items of the list are on the same depth level, // they can be sorted and returned diff --git a/testplan/web_ui/testing/src/AssertionPane/AssertionTypes/GraphAssertions/graphUtils.js b/testplan/web_ui/testing/src/AssertionPane/AssertionTypes/GraphAssertions/graphUtils.js index d95a707de..ee36633eb 100644 --- a/testplan/web_ui/testing/src/AssertionPane/AssertionTypes/GraphAssertions/graphUtils.js +++ b/testplan/web_ui/testing/src/AssertionPane/AssertionTypes/GraphAssertions/graphUtils.js @@ -7,8 +7,8 @@ * Return the JSX for the 'style' parameter for the graph component * to help render nicer graphs, not currently set by the user * - * @param {string} graph_type - The type of graph being rendered - * @return {Object.|undefined} Returns any style required for the graph + * @param {str} graph_type - The type of graph being rendered + * @return {dict[key: object]} Returns any style required for the graph */ export function returnStyle(graph_type){ if(graph_type === 'Contour'){ @@ -25,8 +25,8 @@ export function returnStyle(graph_type){ * Return the JSX for the 'XType' parameter for the XYPlot * component to be make the x axis increment either numerical or ordinal * - * @param {string} graph_type - The type of graph being rendered - * @return {string|undefined} Returns ordinal if x-axis should be + * @param {str} graph_type - The type of graph being rendered + * @return {str} Returns ordinal if x-axis should be * letters instead of numerical */ export function returnXType(graph_type){ @@ -43,12 +43,11 @@ const COLOUR_PALETTE=['#1c5c9c', '#68caea', '#7448c5', '#633836', * otherwise return from a colour scheme/palette, then random colours * (tinted darker/blue) * - * @param {Object.>} series_options - - * dictionary with series name and user specified options - * @param {Object.} data - - * every data series name along with the relative list of data - * @return {Object.} - * Every series name and it's display colour + * @param {dict[str, dict[str, object]]} series_options - dictionary with + * series name and user specified options + * @param {dict[str, list]} data - every data series name along + * with the relative list of data + * @return {dict[str, str]} Every series name and it's display colour */ export function returnColour(series_options, data){ const series_names = Object.keys(data); @@ -93,10 +92,10 @@ export function returnColour(series_options, data){ * Return an xAxisTitle for the graph component * or nothing if it has not been set * - * @param {Object.} graph_options - user specified options + * @param {dict[str: object]} graph_options - user specified options * for the entire graph * - * @return {string | undefined} The axis title, or null if not set + * @return {str/null} The axis title, or null if not set */ export function returnXAxisTitle(graph_options){ if(graph_options == null){ @@ -110,10 +109,10 @@ export function returnXAxisTitle(graph_options){ /** * Return an yAxisTitle for the graph component * or nothing if it has not been set - * @param {Object.} graph_options - user specified options + * @param {dict[str: object]} graph_options - user specified options * for the entire graph * - * @return {string | undefined} The axis title, or null if not set + * @return {str/null} The axis title, or null if not set */ export function returnYAxisTitle(graph_options){ if(graph_options == null){ diff --git a/testplan/web_ui/testing/src/AssertionPane/AssertionTypes/NotImplementedAssertion.js b/testplan/web_ui/testing/src/AssertionPane/AssertionTypes/NotImplementedAssertion.js index d36fac255..a31ec6869 100644 --- a/testplan/web_ui/testing/src/AssertionPane/AssertionTypes/NotImplementedAssertion.js +++ b/testplan/web_ui/testing/src/AssertionPane/AssertionTypes/NotImplementedAssertion.js @@ -1,6 +1,6 @@ import React, {Component, Fragment} from 'react'; import {library} from '@fortawesome/fontawesome-svg-core'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome/index.es'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faFrown} from '@fortawesome/free-solid-svg-icons'; import {css, StyleSheet} from 'aphrodite'; @@ -34,4 +34,4 @@ const styles = StyleSheet.create({ }, }); -export default NotImplementedAssertion; +export default NotImplementedAssertion; \ No newline at end of file diff --git a/testplan/web_ui/testing/src/Common/ErrorCatch.jsx b/testplan/web_ui/testing/src/Common/ErrorCatch.jsx deleted file mode 100644 index 2f426176f..000000000 --- a/testplan/web_ui/testing/src/Common/ErrorCatch.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { compose } from '@reduxjs/toolkit/dist/redux-toolkit.esm'; - -const __DEV__ = 'production' !== process.env.NODE_ENV; - -export default class ErrorCatch extends React.Component { - constructor(props) { - super(props); - this.state = { - hasError: false, - errName: '', - errMsg: '', - errMiscEntries: {}, - }; - } - static getDerivedStateFromError(error) { - const { name: errName, message: errMsg } = error, - nonMiscProps = ['name', 'message']; - let errMiscEntries = ''; - try { - errMiscEntries = compose( - Object.fromEntries, - filtered => filtered.map(entry => [entry[0], entry[1].value]), - entries => entries.filter(entry => !nonMiscProps.includes(entry[0])), - Object.entries, - Object.getOwnPropertyDescriptors(error), - ); - // errMiscEntries = - // Object.fromEntries( - // Object.entries( - // Object.getOwnPropertyDescriptors(error) - // ).filter(entry => !nonMiscProps.includes(entry[0])) - // .map(entry => [entry[0], entry[1].value]) - // ); - } catch(err2) { console.error(err2); } - return { hasError: true, errName, errMsg, errMiscEntries }; - } - componentDidCatch(error, errorInfo) { - console.error(error, errorInfo); - } - render() { - if (this.state.hasError) { - if(__DEV__) { - const headline = `A ${this.state.errName} was thrown` + ( - this.props.level ? ` from the ${this.props.level} level:` : ':' - ); - const preBlocks = Array.isArray(this.state.errMiscEntries) ? ( - this.state.errMiscEntries.map(([attr, val], idx) => ( -
-

{attr}

-
{val}
-
- )) - ) : null; - return ( - <> -

{headline}

-

{this.state.errMsg}

- {preBlocks} - - ); - } - return ( -

An unexpected error occurred. Please try refreshing the page.

- ); - } - return this.props.children; - } -} - -ErrorCatch.propTypes = { - level: PropTypes.string.isRequired, - children: PropTypes.element.isRequired, -}; diff --git a/testplan/web_ui/testing/src/Common/Home.jsx b/testplan/web_ui/testing/src/Common/Home.jsx deleted file mode 100644 index eebc501c7..000000000 --- a/testplan/web_ui/testing/src/Common/Home.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { Redirect, useLocation } from 'react-router-dom'; - -const NoReport = () => ( -

NO TEST REPORT TO RENDER

-); - -/** - * When NODE_ENV === 'development' we can choose an actual report to render - * by setting the environment variable REACT_APP_REPORT_UID_OVERRIDE - * to an actual report UID. - * - * Note that process.env.REACT_APP_* vars are elided during compilation. See - * node_modules/react-scripts/config/env.js:73. - */ -const DevHome = () => { - const REPORT_UID_OVERRIDE = process.env.REACT_APP_REPORT_UID_OVERRIDE; - const { search: query, hash} = useLocation(); - if(typeof REPORT_UID_OVERRIDE !== 'undefined') { - // see CodeSandbox 'example.js' here: - // https://reacttraining.com/react-router/web/example/query-parameters - const dest = `/testplan/${REPORT_UID_OVERRIDE}${query}${hash}`; - console.log(`REACT_APP_REPORT_UID_OVERRIDE='${REPORT_UID_OVERRIDE}' ` + - `so redirecting to '${dest}'`); - return ; - } else { - return ( - <> - -

- Set these environment variables before compiling: -
    -
  • - REACT_APP_REPORT_UID_OVERRIDE - UID of an existing report -
  • -
  • - REACT_APP_API_BASE_URL - Full 'scheme://host:port/path' of your - API server, e.g. 'http://couch.example.com/devel' -
  • -
- If your database server has a different origin than your development - server and doesn't have CORS configured, you'll need to launch a - newer Chrome / Chromium with the '--disable-web-security' flag to - allow cross-origin requests. -

- - ); - } -}; - -const ProdHome = () => ( - <> - -

- Navigate to the test report link printed at the end of your testplan - run. -

- -); - -// TODO: Make a homepage that is unconditionally useful -export default function Home() { - return ( - <> - {process.env.NODE_ENV === 'development' ? : } - - ); -} diff --git a/testplan/web_ui/testing/src/Common/LoadingAnimation.jsx b/testplan/web_ui/testing/src/Common/LoadingAnimation.jsx deleted file mode 100644 index 7a3ecda1a..000000000 --- a/testplan/web_ui/testing/src/Common/LoadingAnimation.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import FadeLoader from 'react-spinners/FadeLoader'; - -// Used to center spinner on page,suggested by -// github.com/davidhu2000/react-spinners/issues/53#issuecomment-472369554 -const divStyle = { - position: 'fixed', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', -}; - -const defaultProps = { - height: 15, - width: 5, - radius: 10, -}; - -export default props => ( -
- -
-); diff --git a/testplan/web_ui/testing/src/Common/Message.jsx b/testplan/web_ui/testing/src/Common/Message.js similarity index 84% rename from testplan/web_ui/testing/src/Common/Message.jsx rename to testplan/web_ui/testing/src/Common/Message.js index 6fd49c99f..c560f5b64 100644 --- a/testplan/web_ui/testing/src/Common/Message.jsx +++ b/testplan/web_ui/testing/src/Common/Message.js @@ -1,20 +1,8 @@ -import React, { Component } from 'react'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; -import { StyleSheet, css } from 'aphrodite/es'; +import {StyleSheet, css} from 'aphrodite'; -import { MEDIUM_GREY } from './defaults'; - -const styles = StyleSheet.create({ - message: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - textAlign: 'center', - minHeight: '100vh', - color: MEDIUM_GREY, - }, -}); +import {MEDIUM_GREY} from "./defaults"; /** * Displayed a message in the center of the container. @@ -41,4 +29,16 @@ Message.propTypes = { left: PropTypes.string, }; -export default Message; +const styles = StyleSheet.create({ + message: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + textAlign: 'center', + minHeight: '100vh', + color: MEDIUM_GREY, + }, +}); + +export default Message; \ No newline at end of file diff --git a/testplan/web_ui/testing/src/Common/Styles.js b/testplan/web_ui/testing/src/Common/Styles.js index 0fd316d41..2212d6dfc 100644 --- a/testplan/web_ui/testing/src/Common/Styles.js +++ b/testplan/web_ui/testing/src/Common/Styles.js @@ -1,6 +1,9 @@ -/** Common aphrodite styles. */ +/** + * Common aphrodite styles. + */ import {StyleSheet} from 'aphrodite'; + const Styles = StyleSheet.create({ unselectable: { "moz-user-select": "-moz-none", diff --git a/testplan/web_ui/testing/src/Common/SwitchRequireSlash.jsx b/testplan/web_ui/testing/src/Common/SwitchRequireSlash.jsx deleted file mode 100644 index e6ec72faa..000000000 --- a/testplan/web_ui/testing/src/Common/SwitchRequireSlash.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { Route, Switch, Redirect } from 'react-router-dom'; - -export default ({ children = null, location = null }) => ( - - {/* Must be first - require trailing slash */} - ( - - )} - /> - {children} - -); diff --git a/testplan/web_ui/testing/src/Common/URLParamRegistry.js b/testplan/web_ui/testing/src/Common/URLParamRegistry.js index bba6fb029..35864f570 100644 --- a/testplan/web_ui/testing/src/Common/URLParamRegistry.js +++ b/testplan/web_ui/testing/src/Common/URLParamRegistry.js @@ -1,17 +1,39 @@ +import { nanoid } from '@reduxjs/toolkit/dist/redux-toolkit.esm'; +import { locationsAreEqual } from 'history'; +import { batch } from 'react-redux/es'; import * as _qsStringify from 'qs/lib/stringify'; import * as _qsParse from 'qs/lib/parse'; import _difference from 'lodash/difference'; import _intersection from 'lodash/intersection'; import _isEqual from 'lodash/isEqual'; import _cloneDeep from 'lodash/cloneDeep'; +import { isNonemptyArray } from '../Common/utils'; -const __DEV__ = process.env.NODE_ENV !== 'production'; +/** @typedef {import("redux").Middleware} Middleware */ const qsParse = queryString => _qsParse(queryString, { ignoreQueryPrefix: true, allowDots: true, charset: 'utf-8', strictNullHandling: true, + decoder: (str, defaultDecoder, charset, type) => { + const decodedStr = defaultDecoder(str); + if(type === 'value') { + if(/^\s*(true|false)\s*$/.test(decodedStr)) { + return decodedStr.trim() === 'true'; + } + if(/^\s*null\s*$/.test(decodedStr)) { + return null; + } + if(/^\s*\d+\s*$/.test(decodedStr)) { + return parseInt(decodedStr.trim()); + } + if(/^\s*(\d+\.\d*|\d*\.\d+)\s*$/.test(decodedStr)) { + return parseFloat(decodedStr.trim()); + } + } + return decodedStr; + }, }); const qsStringify = obj => _qsStringify(obj, { @@ -22,30 +44,39 @@ const qsStringify = obj => _qsStringify(obj, { sort: (s1, s2) => s1.localeCompare(s2), // alphabetical }); -const isArray = Array.isArray; -const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; -const defineProperty = Object.defineProperty.bind(Object); +const registryTagProp = `__${nanoid()}`; +const tagAction = (action, tag) => Object.defineProperty( + action, + registryTagProp, + { value: tag, configurable: true } +); +const untagAction = action => { + delete action[registryTagProp]; + return action; +}; +const actionHasTag = (action, tag) => tag === action[registryTagProp]; export default class URLParamRegistry { + _id = nanoid(); _prevSearchString = ''; _prevSearchObj = {}; _prevSearchKeys = []; + _forcedURLRefresh = false; _unlistenHistory = null; - /** @type {{ [queryParam: string]: { defaultVal: any; actionCreators: Array; }}} */ + /** @type {{ [queryParam: string]: { defaultVal: any, actionCreators: Array }}} */ _param2ActionCreatorsMap = {}; - /** @type {{ [actionType: string]: { defaultVal: any; params: string[]; }}} */ + /** @type {{ [actionType: string]: { defaultVal: any, params: string[] }}} */ _actionType2ParamsMap = {}; _actionInterceptor(action) { const { defaultVal, params } = this._actionType2ParamsMap[action.type]; - if(isArray(params) && params.length > 0) { + if(isNonemptyArray(params)) { const currLocation = this._history.location; - const currSearchString = currLocation.search; - const currSearchObj = qsParse(currSearchString); + const currSearchObj = qsParse(currLocation.search); const newSearchObj = _cloneDeep(currSearchObj); for(const param of params) { - if(action.payload === defaultVal) { + if(_isEqual(action.payload, defaultVal)) { delete newSearchObj[param]; } else { newSearchObj[param] = action.payload; @@ -54,96 +85,104 @@ export default class URLParamRegistry { if(!_isEqual(newSearchObj, currSearchObj)) { const newLocation = { ...currLocation, - search: qsStringify(newSearchObj), + search: qsStringify(newSearchObj) }; - this._history.push(newLocation, undefined, this._history.length); + if(!locationsAreEqual(currLocation, newLocation)) { + this._history.push(newLocation, undefined, this._history.length); + } } } + return action; } - _createHistoryListener(store) { - return (location, action, programmaticPushLength) => { + /** + * This method dispatches registered actions when the corresponding query + * params change in some way. + */ + _createHistoryListener(dispatch) { + // Wrap the store's dispatch function to annotate that the action is being + // sent from this class so that we don't intercept it in our middleware + const wrappedDispatch = action => dispatch(tagAction(action, this._id)); + return (location, navAction, programmaticPushLength) => { const wasProgrammaticPush = ( - action === 'PUSH' && + navAction === 'PUSH' && typeof programmaticPushLength === 'number' && this._history.length === (programmaticPushLength + 1) ); - if(!wasProgrammaticPush) { - if(this._unlistenHistory && location.search !== - this._prevSearchString) { - const searchObj = qsParse(location.search); - const searchKeys = Object.keys(searchObj); - const addedKeys = _difference(searchKeys, this._prevSearchKeys); + const newSearchStr = location.search !== this._prevSearchString; + if(this._forcedURLRefresh || (!wasProgrammaticPush && newSearchStr)) { + this._forcedURLRefresh = false; + const searchObj = qsParse(location.search); + const searchKeys = Object.keys(searchObj); + const addedKeys = _difference(searchKeys, this._prevSearchKeys); + const removedKeys = _difference(this._prevSearchKeys, searchKeys); + const commonKeys = _intersection(searchKeys, this._prevSearchKeys); + // only rerender once for all dispatches + batch(() => { + // dispatch actions for query params that have been added for(const newKey of addedKeys) { const { actionCreators } = this._param2ActionCreatorsMap[newKey]; - if(actionCreators) { + if(isNonemptyArray(actionCreators)) { const payloadVal = searchObj[newKey]; for(const ac of actionCreators) { - store.dispatch(ac(payloadVal)); + wrappedDispatch(ac(payloadVal)); } } } - const removedKeys = _difference(this._prevSearchKeys, searchKeys); + // dispatch actions for query params that have been removed for(const oldKey of removedKeys) { const { defaultVal, actionCreators } = this._param2ActionCreatorsMap[oldKey]; - if(actionCreators) { + if(isNonemptyArray(actionCreators)) { for(const ac of actionCreators) { - store.dispatch(ac(defaultVal)); + wrappedDispatch(ac(defaultVal)); } } } - const commonKeys = _intersection(searchKeys, this._prevSearchKeys); + // dispatch actions for query params whose values have changed for(const key of commonKeys) { const newVal = searchObj[key]; const oldVal = this._prevSearchObj[key]; if(!_isEqual(newVal, oldVal)) { const { actionCreators } = this._param2ActionCreatorsMap[key]; - if(actionCreators) { - for(const actionCreator of actionCreators) { - store.dispatch(actionCreator(newVal)); + if(isNonemptyArray(actionCreators)) { + for(const ac of actionCreators) { + wrappedDispatch(ac(newVal)); } } } } - this._prevSearchString = location.search; - this._prevSearchObj = searchObj; - this._prevSearchKeys = searchKeys; - } + }); + this._prevSearchObj = searchObj; + this._prevSearchKeys = searchKeys; } + this._prevSearchString = location.search; }; } static _tagHistory(history) { const oldPush = history.push.bind(history); - function newPush(path, state, programmaticPushLength) { - this._programmaticPushLength = programmaticPushLength; - return oldPush(path, state); - } - const pushDescriptor = getOwnPropertyDescriptor(history, 'push'); - defineProperty(history, 'push', { - ...pushDescriptor, - value: newPush.bind(history), - }); const oldListen = history.listen.bind(history); - function newListen(listener) { - return oldListen((location, action) => { - const res = listener( - location, - action, - this._programmaticPushLength, - ); - this._programmaticPushLength = undefined; - return res; - }); - } - const _listenDescriptor = getOwnPropertyDescriptor(history, 'listen'); - defineProperty(history, 'listen', { - ..._listenDescriptor, - value: newListen.bind(history), + return Object.defineProperties(history, { + push: { + ...Object.getOwnPropertyDescriptor(history, 'push'), + value: function(path, state, programmaticPushLength) { + this._programmaticPushLength = programmaticPushLength; + return oldPush(path, state); + }.bind(history), + }, + listen: { + ...Object.getOwnPropertyDescriptor(history, 'listen'), + value: function(listener) { + return oldListen((location, action) => { + const r = listener(location, action, this._programmaticPushLength); + this._programmaticPushLength = undefined; + return r; + }); + }.bind(history), + }, }); - return history; } constructor(history) { @@ -158,27 +197,15 @@ export default class URLParamRegistry { * @param {function} actionCreator */ registerQueryParamListener(queryParam, actionCreator) { - const typeofQueryParam = typeof queryParam; - const typeofActionCreator = typeof actionCreator; - if(typeofQueryParam === 'string' && typeofActionCreator === 'function') { - const defaultVal = actionCreator().payload; - let currACObj = this._param2ActionCreatorsMap[queryParam]; - if(!currACObj) { - this._param2ActionCreatorsMap[queryParam] = { - defaultVal, - actionCreators: [], - }; - currACObj = this._param2ActionCreatorsMap[queryParam]; - } - const currACSet = new Set(currACObj.actionCreators); - if(!currACSet.has(actionCreator)) { - currACObj.actionCreators.push(actionCreator); - } - } else if(__DEV__) { - throw new Error( - 'Expected (, ), received ' + - `(<${typeofQueryParam}>, <${typeofActionCreator}>).` - ); + if(!(queryParam in this._param2ActionCreatorsMap)) { + this._param2ActionCreatorsMap[queryParam] = { + defaultVal: actionCreator().payload, + actionCreators: [], + }; + } + const currACObj = this._param2ActionCreatorsMap[queryParam]; + if(!currACObj.actionCreators.includes(actionCreator)) { + currACObj.actionCreators.push(actionCreator); } return this; } @@ -190,34 +217,13 @@ export default class URLParamRegistry { * @param {function} actionCreator */ registerActionListener(queryParam, actionCreator) { - const typeofQueryParam = typeof queryParam; - const typeofActionCreator = typeof actionCreator; - if(typeofQueryParam === 'string' && typeofActionCreator === 'function') { - const { type: actionType, payload: defaultVal } = actionCreator(); - const typeofActionType = typeof actionType; - if(typeofActionType === 'string') { - let currParamsObj = this._actionType2ParamsMap[actionType]; - if(!currParamsObj) { - this._actionType2ParamsMap[actionType] = { - defaultVal, - params: [], - }; - currParamsObj = this._actionType2ParamsMap[actionType]; - } - if(!(queryParam in currParamsObj.params)) { - currParamsObj.params.push(queryParam); - } - } else if(__DEV__) { - throw new Error( - 'Expected `actionCreator().type` to be type , ' + - `instead got type <${typeofActionType}>.` - ); - } - } else if(__DEV__) { - throw new Error( - 'Expected (, ), received ' + - `(<${typeofQueryParam}>, <${typeofActionCreator}>).` - ); + const { type: actionType, payload: defaultVal } = actionCreator(); + if(!(actionType in this._actionType2ParamsMap)) { + this._actionType2ParamsMap[actionType] = { defaultVal, params: [] }; + } + const currParamsObj = this._actionType2ParamsMap[actionType]; + if(!currParamsObj.params.includes(queryParam)) { + currParamsObj.params.push(queryParam); } return this; } @@ -227,35 +233,34 @@ export default class URLParamRegistry { * @param {function} actionCreator */ registerBidirectionalListener(queryParam, actionCreator) { - const typeofQueryParam = typeof queryParam; - const typeofActionCreator = typeof actionCreator; - if(typeofQueryParam === 'string' && typeofActionCreator === 'function') { - this.registerActionListener(queryParam, actionCreator); - this.registerQueryParamListener(queryParam, actionCreator); - } else if(__DEV__) { - throw new Error( - 'Expected (, ), received ' + - `(<${typeofQueryParam}>, <${typeofActionCreator}>).` - ); - } + this.registerActionListener(queryParam, actionCreator); + this.registerQueryParamListener(queryParam, actionCreator); return this; } /** * This should creates the middleware that should be passed to the store * creator. + * @returns {Middleware} */ createMiddleware() { return store => { this._unlistenHistory = this._history.listen( - this._createHistoryListener(store) + this._createHistoryListener(store.dispatch) + ); + return next => action => ( + (action.type in this._actionType2ParamsMap) ? + actionHasTag(action, this._id) ? + // we've already intercepted the action + next(untagAction(action)) : + // add the corresponding query param to the URL + // use `batch` so the dispatch + history push + // only results in one rerender + batch(() => + next(this._actionInterceptor(tagAction(action, this._id))) + ) + : next(action) ); - return next => { - return action => { - this._actionInterceptor(action); - next(action); - }; - }; }; } } diff --git a/testplan/web_ui/testing/src/__tests__/testUtils.test.js b/testplan/web_ui/testing/src/Common/__tests__/testUtils.test.js similarity index 96% rename from testplan/web_ui/testing/src/__tests__/testUtils.test.js rename to testplan/web_ui/testing/src/Common/__tests__/testUtils.test.js index fc92aa484..67558f251 100644 --- a/testplan/web_ui/testing/src/__tests__/testUtils.test.js +++ b/testplan/web_ui/testing/src/Common/__tests__/testUtils.test.js @@ -1,10 +1,10 @@ /** @jest-environment node */ -import { randomSamples } from './testUtils'; -import { getPaths } from './testUtils'; -import { reverseMap } from './testUtils'; -import { filterObjectDeep } from './testUtils'; -import { deriveURLPathsFromReport } from './testUtils'; -import { TESTPLAN_REPORT_2 as REPORT } from './documents'; +import { randomSamples } from '../testUtils'; +import { getPaths } from '../testUtils'; +import { reverseMap } from '../testUtils'; +import { filterObjectDeep } from '../testUtils'; +import { deriveURLPathsFromReport } from '../testUtils'; +import { TESTPLAN_REPORT } from '../fakeReport'; describe('randomSamples', () => { @@ -38,7 +38,7 @@ describe('randomSamples', () => { }); const TESTPLAN_REPORT_SLIM = filterObjectDeep( - REPORT, + TESTPLAN_REPORT, [ 'name', 'entries', 'category' ], ); @@ -323,7 +323,7 @@ describe('reverseMap', () => { describe('deriveURLPathsFromReport', () => { - const TESTPLAN_REPORT_1 = require('./documents/TESTPLAN_REPORT_1.json'); + const TESTPLAN_REPORT_1 = require('../../__tests__/documents/TESTPLAN_REPORT_1.json'); it('does the jsdoc example', () => { const aliasMap = new Map(); diff --git a/testplan/web_ui/testing/src/Common/__tests__/uriComponentCodec.test.js b/testplan/web_ui/testing/src/Common/__tests__/uriComponentCodec.test.js index c6479411f..058fa3728 100644 --- a/testplan/web_ui/testing/src/Common/__tests__/uriComponentCodec.test.js +++ b/testplan/web_ui/testing/src/Common/__tests__/uriComponentCodec.test.js @@ -7,7 +7,7 @@ const reserved2PctEncodedMap = new Map([ [ '#woke', '%23woke' ], [ 'buy:5', 'buy%3a5' ], [ 'In / Out', 'In %2f Out' ], - [ '@realDonaldTrump', '%40realDonaldTrump' ], + [ '@gmail.com', '%40gmail.com' ], [ '>=]', '>%3d%5d' ], [ '<:-[', '<%3a-%5b' ], [ 'help!', 'help%21' ], diff --git a/testplan/web_ui/testing/src/Common/__tests__/utils.test.js b/testplan/web_ui/testing/src/Common/__tests__/utils.test.js index 8f6d7e2ef..0c8a9a38a 100644 --- a/testplan/web_ui/testing/src/Common/__tests__/utils.test.js +++ b/testplan/web_ui/testing/src/Common/__tests__/utils.test.js @@ -1,25 +1,35 @@ -import { NAV_ENTRY_DISPLAY_DATA } from '../defaults'; -import { getNavEntryDisplayData } from '../utils'; +import React from 'react'; + +import {NAV_ENTRY_DISPLAY_DATA} from "../defaults"; +import {getNavEntryDisplayData} from '../utils'; describe('Common/utils', () => { + describe('getNavEntryDisplayData', () => { + it('returns an empty object when given an empty object.', () => { const displayData = getNavEntryDisplayData({}); + for (const attribute of NAV_ENTRY_DISPLAY_DATA) { expect(displayData.hasOwnProperty(attribute)).toBeFalsy(); } }); - it( - 'returns an object with the expected keys when given an object with them', - () => { - const entry = Object.fromEntries( - NAV_ENTRY_DISPLAY_DATA.concat('unknown').map((val, i) => [val, i]) - ); - const displayData = getNavEntryDisplayData(entry); - for (const attribute of NAV_ENTRY_DISPLAY_DATA) { - expect(displayData[attribute]).toEqual(entry[attribute]); - } + + it('returns an object with the expected keys when given an object with them.', () => { + let index = 0; + let entry = {}; + for (const attribute of NAV_ENTRY_DISPLAY_DATA) { + entry[attribute] = index; + index++; } - ); + entry['unknown'] = index; + + const displayData = getNavEntryDisplayData(entry); + for (const attribute of NAV_ENTRY_DISPLAY_DATA) { + expect(displayData[attribute]).toEqual(entry[attribute]); + } + }) + }); + }); diff --git a/testplan/web_ui/testing/src/Common/defaults.js b/testplan/web_ui/testing/src/Common/defaults.js index bd5ac40f0..fd906b1b6 100644 --- a/testplan/web_ui/testing/src/Common/defaults.js +++ b/testplan/web_ui/testing/src/Common/defaults.js @@ -1,22 +1,25 @@ -/** Constants used across the entire application. */ -export const GREEN = '#228F1D'; -export const DARK_GREEN = '#1A721D'; -export const RED = '#A2000C'; -export const DARK_RED = '#840008'; -export const ORANGE = '#FFA500'; -export const DARK_ORANGE = '#DD8800'; -export const LIGHT_GREY = '#F3F3F3'; -export const MEDIUM_GREY = '#D0D0D0'; -export const DARK_GREY = '#ADADAD'; -export const BLACK = '#404040'; +/** + * Constants used across the entire application. + */ +const GREEN = '#228F1D'; +const DARK_GREEN = '#1A721D'; +const RED = '#A2000C'; +const DARK_RED = '#840008'; +const ORANGE = '#FFA500'; +const DARK_ORANGE = '#DD8800'; +const LIGHT_GREY = '#F3F3F3'; +const MEDIUM_GREY = '#D0D0D0'; +const DARK_GREY = '#ADADAD'; +const BLACK = '#404040'; export const BOTTOMMOST_ENTRY_CATEGORY = 'testcase'; -export const COLUMN_WIDTH = 22; // unit: em -export const MIN_COLUMN_WIDTH = 180; // unit: px -export const INTERACTIVE_COL_WIDTH = 28; // wider to fit interactive buttons -export const INDENT_MULTIPLIER = 1.5; +const COLUMN_WIDTH = 22; // unit: em +const MIN_COLUMN_WIDTH = 180; // unit: px +const INTERACTIVE_COL_WIDTH = 28; // wider to fit interactive buttons -export const TOOLBAR_BUTTONS_BATCH = [ +const INDENT_MULTIPLIER = 1.5; + +const TOOLBAR_BUTTONS_BATCH = [ {name: 'cl', type: 'clock'}, {name: 'pr', type: 'print'}, {name: 'if', type: 'info-circle'}, @@ -29,7 +32,7 @@ export const TOOLBAR_BUTTONS_BATCH = [ {name: 'mo', type: 'chart-bar'} ]; -export const TOOLBAR_BUTTONS_INTERACTIVE = [ +const TOOLBAR_BUTTONS_INTERACTIVE = [ {name: 'po', type: 'fa-power-off'}, {name: 'bg', type: 'fa-bug'}, {name: 'rf', type: 'fa-refresh'}, @@ -38,7 +41,7 @@ export const TOOLBAR_BUTTONS_INTERACTIVE = [ {name: 'fp', type: 'fa-floppy-o'} ]; -export const CATEGORIES = { +const CATEGORIES = { 'test': 'test', 'multitest': 'test', 'cppunit': 'test', @@ -54,7 +57,7 @@ export const CATEGORIES = { 'testcase': 'testcase' }; -export const CATEGORY_ICONS = { +const CATEGORY_ICONS = { 'testplan': 'TP', 'test': 'T', 'multitest': 'MT', @@ -71,7 +74,7 @@ export const CATEGORY_ICONS = { 'testcase': 'C' }; -export const ENTRY_TYPES = [ +const ENTRY_TYPES = [ 'testplan', 'multitest', 'gtest', @@ -84,7 +87,7 @@ export const ENTRY_TYPES = [ 'testcase', ]; -export const STATUS = [ +const STATUS = [ 'error', 'failed', 'passed', @@ -99,7 +102,7 @@ export const STATUS = [ 'unknown', ]; -export const STATUS_CATEGORY = { +const STATUS_CATEGORY = { 'error': 'error', 'failed': 'failed', 'incomplete': 'failed', @@ -112,14 +115,14 @@ export const STATUS_CATEGORY = { 'unknown': 'unknown', }; -export const RUNTIME_STATUS = [ +const RUNTIME_STATUS = [ 'ready', 'running', 'finished', ]; -export const NAV_ENTRY_DISPLAY_DATA = [ +const NAV_ENTRY_DISPLAY_DATA = [ 'name', 'uid', 'type', @@ -132,7 +135,7 @@ export const NAV_ENTRY_DISPLAY_DATA = [ 'logs', ]; -export const BASIC_ASSERTION_TYPES = [ +const BASIC_ASSERTION_TYPES = [ 'Log', 'Equal', 'NotEqual', 'Greater', 'GreaterEqual', 'Less', 'LessEqual', 'IsClose', 'IsTrue', 'IsFalse', @@ -147,7 +150,7 @@ export const BASIC_ASSERTION_TYPES = [ 'RawAssertion', ]; -export const SORT_TYPES = { +const SORT_TYPES = { NONE: 0, ALPHABETICAL: 1, REVERSE_ALPHABETICAL: 2, @@ -155,7 +158,7 @@ export const SORT_TYPES = { ONLY_FAILURES: 4, }; -export const DICT_GRID_STYLE = { +const DICT_GRID_STYLE = { MAX_VISIBLE_ROW: 20, ROW_HEIGHT: 28, EMPTY_ROW_HEIGHT: 5, @@ -169,4 +172,34 @@ export const DICT_GRID_STYLE = { // NOTE: currently we poll for updates using HTTP for simplicity but in future // it might be better to use websockets or SSEs to allow the backend to notify // us when updates are available. -export const POLL_MS = 1000; +const POLL_MS = 1000; + +export { + GREEN, + DARK_GREEN, + RED, + DARK_RED, + ORANGE, + DARK_ORANGE, + LIGHT_GREY, + MEDIUM_GREY, + DARK_GREY, + BLACK, + COLUMN_WIDTH, + MIN_COLUMN_WIDTH, + INTERACTIVE_COL_WIDTH, + INDENT_MULTIPLIER, + TOOLBAR_BUTTONS_BATCH, + TOOLBAR_BUTTONS_INTERACTIVE, + CATEGORIES, + CATEGORY_ICONS, + ENTRY_TYPES, + STATUS, + STATUS_CATEGORY, + RUNTIME_STATUS, + NAV_ENTRY_DISPLAY_DATA, + BASIC_ASSERTION_TYPES, + SORT_TYPES, + DICT_GRID_STYLE, + POLL_MS, +}; diff --git a/testplan/web_ui/testing/src/Common/fakeReport.js b/testplan/web_ui/testing/src/Common/fakeReport.js new file mode 100644 index 000000000..1f4c93816 --- /dev/null +++ b/testplan/web_ui/testing/src/Common/fakeReport.js @@ -0,0 +1,3938 @@ +/** + * Sample Testplan reports to be used in development & testing. + */ +const TESTPLAN_REPORT = { + "name": "Sample Testplan", + "status": "failed", + "uid": "520a92e4-325e-4077-93e6-55d7091a3f83", + "tags_index": {}, + "information": [ + [ + "user", + "unknown" + ], + [ + "command_line_string", + "/home/unknown/path_to_testplan_script/testplan.py" + ], + ], + "status_override": null, + "meta": {}, + "timer": { + "run": { + "start": "2018-10-15T14:30:10.998071+00:00", + "end": "2018-10-15T14:30:11.296158+00:00" + } + }, + "entries": [ + { + "name": "Primary", + "status": "failed", + "category": "multitest", + "description": null, + "status_override": null, + "uid": "21739167-b30f-4c13-a315-ef6ae52fd1f7", + "type": "TestGroupReport", + "logs": [], + "tags": { + "simple": ["server"] + }, + "timer": { + "run": { + "start": "2018-10-15T14:30:11.009705+00:00", + "end": "2018-10-15T14:30:11.159661+00:00" + } + }, + "entries": [ + { + "status": "failed", + "category": "testsuite", + "name": "AlphaSuite", + "status_override": null, + "description": "This is a failed testsuite", + "uid": "cb144b10-bdb0-44d3-9170-d8016dd19ee7", + "type": "TestGroupReport", + "logs": [], + "tags": { + "simple": ["server"] + }, + "timer": { + "run": { + "start": "2018-10-15T14:30:11.009872+00:00", + "end": "2018-10-15T14:30:11.158224+00:00" + } + }, + "entries": [ + { + "name": "test_equality_passing", + "category": "testcase", + "status": "passed", + "status_override": null, + "description": "A testcase example", + "uid": "736706ef-ba65-475d-96c5-f2855f431028", + "type": "TestCaseReport", + "logs": [], + "tags": { + "colour": ["white"] + }, + "timer": { + "run": { + "start": "2018-10-15T14:30:11.010072+00:00", + "end": "2018-10-15T14:30:11.132214+00:00" + } + }, + "entries": [ + { + "category": "DEFAULT", + "machine_time": "2018-10-15T15:30:11.010098+00:00", + "description": "passing equality", + "line_no": 24, + "label": "==", + "second": 1, + "meta_type": "assertion", + "passed": true, + "type": "Equal", + "utc_time": "2018-10-15T14:30:11.010094+00:00", + "first": 1 + } + ], + }, + { + "name": "test_equality_passing2", + "category": "testcase", + "status": "failed", + "tags": {}, + "status_override": null, + "description": null, + "uid": "78686a4d-7b94-4ae6-ab50-d9960a7fb714", + "type": "TestCaseReport", + "logs": [], + "timer": { + "run": { + "start": "2018-10-15T14:30:11.510072+00:00", + "end": "2018-10-15T14:30:11.632214+00:00" + } + }, + "entries": [ + { + "category": "DEFAULT", + "machine_time": "2018-10-15T15:30:11.510098+00:00", + "description": "passing equality", + "line_no": 24, + "label": "==", + "second": 1, + "meta_type": "assertion", + "passed": true, + "type": "Equal", + "utc_time": "2018-10-15T14:30:11.510094+00:00", + "first": 1 + } + ], + }, + ], + }, + { + "status": "passed", + "category": "testsuite", + "name": "BetaSuite", + "status_override": null, + "description": null, + "uid": "6fc5c008-4d1a-454e-80b6-74bdc9bca49e", + "type": "TestGroupReport", + "logs": [], + "tags": { + "simple": ["client"] + }, + "timer": { + "run": { + "start": "2018-10-15T14:30:11.009872+00:00", + "end": "2018-10-15T14:30:11.158224+00:00" + } + }, + "entries": [ + { + "name": "test_equality_passing", + "category": "testcase", + "status": "passed", + "tags": {}, + "status_override": null, + "description": null, + "uid": "8865a23d-1823-4c8d-ab37-58d24fc8ac05", + "type": "TestCaseReport", + "logs": [], + "timer": { + "run": { + "start": "2018-10-15T14:30:11.010072+00:00", + "end": "2018-10-15T14:30:11.132214+00:00" + } + }, + "entries": [ + { + "category": "DEFAULT", + "machine_time": "2018-10-15T15:30:11.010098+00:00", + "description": "passing equality", + "line_no": 24, + "label": "==", + "second": 1, + "meta_type": "assertion", + "passed": true, + "type": "Equal", + "utc_time": "2018-10-15T14:30:11.010094+00:00", + "first": 1 + } + ], + }, + ], + }, + ], + }, + { + "name": "Secondary", + "status": "passed", + "category": "multitest", + "tags": {}, + "description": null, + "status_override": null, + "uid": "8c3c7e6b-48e8-40cd-86db-8c8aed2592c8", + "type": "TestGroupReport", + "logs": [], + "timer": { + "run": { + "start": "2018-10-15T14:30:12.009705+00:00", + "end": "2018-10-15T14:30:12.159661+00:00" + } + }, + "entries": [ + { + "status": "passed", + "category": "testsuite", + "name": "GammaSuite", + "tags": {}, + "status_override": null, + "description": null, + "uid": "08d4c671-d55d-49d4-96ba-dc654d12be26", + "type": "TestGroupReport", + "logs": [], + "timer": { + "run": { + "start": "2018-10-15T14:30:12.009872+00:00", + "end": "2018-10-15T14:30:12.158224+00:00" + } + }, + "entries": [ + { + "name": "test_equality_passing", + "category": "testcase", + "status": "passed", + "tags": {}, + "status_override": null, + "description": null, + "uid": "f73bd6ea-d378-437b-a5db-00d9e427f36a", + "type": "TestCaseReport", + "logs": [], + "timer": { + "run": { + "start": "2018-10-15T14:30:12.010072+00:00", + "end": "2018-10-15T14:30:12.132214+00:00" + } + }, + "entries": [ + { + "category": "DEFAULT", + "machine_time": "2018-10-15T15:30:12.010098+00:00", + "description": "passing equality", + "line_no": 24, + "label": "==", + "second": 1, + "meta_type": "assertion", + "passed": true, + "type": "Equal", + "utc_time": "2018-10-15T14:30:12.010094+00:00", + "first": 1 + } + ], + }, + ], + } + ], + }, + ], +}; + +var fakeReportAssertions = { + "category": "testplan", + "tags_index": {}, + "meta": {}, + "information": [ + [ + "user", + "yifan" + ], + [ + "command_line_string", + "oss/examples/Assertions/Basic/test_plan.py --json example.json" + ], + [ + "python_version", + "3.7.1" + ] + ], + "counter": { + "passed": 2, + "failed": 6, + "total": 8 + }, + "uid": "c648a283-22f3-4503-ae6d-c982b4c7cca0", + "attachments": {}, + "status": "failed", + "timer": { + "run": { + "end": "2020-01-10T03:06:59.348924+00:00", + "start": "2020-01-10T03:06:58.537339+00:00" + } + }, + "runtime_status": "finished", + "name": "Assertions Example", + "status_override": null, + "entries": [ + { + "description": null, + "counter": { + "passed": 2, + "failed": 6, + "total": 8 + }, + "name": "Assertions Test", + "tags": {}, + "env_status": "STOPPED", + "type": "TestGroupReport", + "status_reason": null, + "runtime_status": "finished", + "fix_spec_path": null, + "part": null, + "uid": "99aef9f5-6957-4842-a6fa-e0cd9e358473", + "status": "failed", + "parent_uids": [ + "Assertions Example" + ], + "timer": { + "run": { + "end": "2020-01-10T03:06:59.141338+00:00", + "start": "2020-01-10T03:06:58.629871+00:00" + } + }, + "hash": 3697482064019099674, + "status_override": null, + "logs": [], + "category": "multitest", + "entries": [ + { + "description": null, + "counter": { + "passed": 2, + "failed": 6, + "total": 8 + }, + "name": "SampleSuite", + "tags": {}, + "env_status": null, + "type": "TestGroupReport", + "status_reason": null, + "runtime_status": "finished", + "fix_spec_path": null, + "part": null, + "uid": "9f98c732-d040-4a13-84e1-563adcd9dd32", + "status": "failed", + "parent_uids": [ + "Assertions Example", + "Assertions Test" + ], + "timer": { + "run": { + "end": "2020-01-10T03:06:59.135813+00:00", + "start": "2020-01-10T03:06:58.629972+00:00" + } + }, + "hash": -4958192469702756289, + "status_override": null, + "logs": [], + "category": "testsuite", + "entries": [ + { + "category": "testcase", + "logs": [], + "description": "Description test\nSecond description", + "suite_related": false, + "counter": { + "passed": 0, + "failed": 1, + "total": 1 + }, + "status_reason": null, + "type": "TestCaseReport", + "uid": "25d0115f-91c4-481b-ad0f-37382d95fabd", + "status": "failed", + "parent_uids": [ + "Assertions Example", + "Assertions Test", + "SampleSuite" + ], + "timer": { + "run": { + "end": "2020-01-10T03:06:58.939142+00:00", + "start": "2020-01-10T03:06:58.630091+00:00" + } + }, + "hash": 4069384282795794238, + "runtime_status": "finished", + "name": "test_basic_assertions", + "status_override": null, + "tags": {}, + "entries": [ + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": "==", + "type": "Equal", + "utc_time": "2020-01-10T03:06:58.630121+00:00", + "second": "foo", + "passed": true, + "first": "foo", + "machine_time": "2020-01-10T11:06:58.630129+00:00", + "line_no": 25 + }, + { + "category": "DEFAULT", + "description": "Description for failing equality", + "meta_type": "assertion", + "label": "==", + "type": "Equal", + "utc_time": "2020-01-10T03:06:58.893461+00:00", + "second": 2, + "passed": false, + "first": 1, + "machine_time": "2020-01-10T11:06:58.893477+00:00", + "line_no": 28 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": "!=", + "type": "NotEqual", + "utc_time": "2020-01-10T03:06:58.895795+00:00", + "second": "bar", + "passed": true, + "first": "foo", + "machine_time": "2020-01-10T11:06:58.895806+00:00", + "line_no": 30 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": ">", + "type": "Greater", + "utc_time": "2020-01-10T03:06:58.898075+00:00", + "second": 2, + "passed": true, + "first": 5, + "machine_time": "2020-01-10T11:06:58.898084+00:00", + "line_no": 31 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": ">=", + "type": "GreaterEqual", + "utc_time": "2020-01-10T03:06:58.899619+00:00", + "second": 2, + "passed": true, + "first": 2, + "machine_time": "2020-01-10T11:06:58.899627+00:00", + "line_no": 32 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": ">=", + "type": "GreaterEqual", + "utc_time": "2020-01-10T03:06:58.901156+00:00", + "second": 1, + "passed": true, + "first": 2, + "machine_time": "2020-01-10T11:06:58.901163+00:00", + "line_no": 33 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": "<", + "type": "Less", + "utc_time": "2020-01-10T03:06:58.902604+00:00", + "second": 20, + "passed": true, + "first": 10, + "machine_time": "2020-01-10T11:06:58.902613+00:00", + "line_no": 34 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": "<=", + "type": "LessEqual", + "utc_time": "2020-01-10T03:06:58.904109+00:00", + "second": 10, + "passed": true, + "first": 10, + "machine_time": "2020-01-10T11:06:58.904117+00:00", + "line_no": 35 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": "<=", + "type": "LessEqual", + "utc_time": "2020-01-10T03:06:58.905543+00:00", + "second": 30, + "passed": true, + "first": 10, + "machine_time": "2020-01-10T11:06:58.905550+00:00", + "line_no": 36 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": "==", + "type": "Equal", + "utc_time": "2020-01-10T03:06:58.906994+00:00", + "second": 15, + "passed": true, + "first": 15, + "machine_time": "2020-01-10T11:06:58.907002+00:00", + "line_no": 41 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": "!=", + "type": "NotEqual", + "utc_time": "2020-01-10T03:06:58.908433+00:00", + "second": 20, + "passed": true, + "first": 10, + "machine_time": "2020-01-10T11:06:58.908440+00:00", + "line_no": 42 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": "<", + "type": "Less", + "utc_time": "2020-01-10T03:06:58.909946+00:00", + "second": 3, + "passed": true, + "first": 2, + "machine_time": "2020-01-10T11:06:58.909954+00:00", + "line_no": 43 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": ">", + "type": "Greater", + "utc_time": "2020-01-10T03:06:58.911441+00:00", + "second": 2, + "passed": true, + "first": 3, + "machine_time": "2020-01-10T11:06:58.911449+00:00", + "line_no": 44 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": "<=", + "type": "LessEqual", + "utc_time": "2020-01-10T03:06:58.912920+00:00", + "second": 15, + "passed": true, + "first": 10, + "machine_time": "2020-01-10T11:06:58.912928+00:00", + "line_no": 45 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": ">=", + "type": "GreaterEqual", + "utc_time": "2020-01-10T03:06:58.914465+00:00", + "second": 10, + "passed": true, + "first": 15, + "machine_time": "2020-01-10T11:06:58.914473+00:00", + "line_no": 46 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "rel_tol": 0.1, + "label": "~=", + "type": "IsClose", + "utc_time": "2020-01-10T03:06:58.915976+00:00", + "second": 95, + "abs_tol": 0.0, + "passed": true, + "first": 100, + "machine_time": "2020-01-10T11:06:58.915984+00:00", + "line_no": 50 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "rel_tol": 0.01, + "label": "~=", + "type": "IsClose", + "utc_time": "2020-01-10T03:06:58.917481+00:00", + "second": 95, + "abs_tol": 0.0, + "passed": false, + "first": 100, + "machine_time": "2020-01-10T11:06:58.917489+00:00", + "line_no": 51 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "entry", + "type": "Log", + "utc_time": "2020-01-10T03:06:58.919181+00:00", + "machine_time": "2020-01-10T11:06:58.919189+00:00", + "line_no": 56, + "message": "This is a log message, it will be displayed along with other assertion details." + }, + { + "category": "DEFAULT", + "description": "Boolean Truthiness check", + "meta_type": "assertion", + "type": "IsTrue", + "utc_time": "2020-01-10T03:06:58.921013+00:00", + "expr": true, + "passed": true, + "machine_time": "2020-01-10T11:06:58.921021+00:00", + "line_no": 61 + }, + { + "category": "DEFAULT", + "description": "Boolean Falseness check", + "meta_type": "assertion", + "type": "IsFalse", + "utc_time": "2020-01-10T03:06:58.923056+00:00", + "expr": false, + "passed": true, + "machine_time": "2020-01-10T11:06:58.923064+00:00", + "line_no": 62 + }, + { + "category": "DEFAULT", + "description": "This is an explicit failure.", + "meta_type": "assertion", + "type": "Fail", + "utc_time": "2020-01-10T03:06:58.924595+00:00", + "passed": false, + "machine_time": "2020-01-10T11:06:58.924621+00:00", + "line_no": 64 + }, + { + "category": "DEFAULT", + "description": "Passing membership", + "meta_type": "assertion", + "type": "Contain", + "utc_time": "2020-01-10T03:06:58.926405+00:00", + "container": "foobar", + "passed": true, + "machine_time": "2020-01-10T11:06:58.926413+00:00", + "line_no": 67, + "member": "foo" + }, + { + "category": "DEFAULT", + "description": "Failing membership", + "meta_type": "assertion", + "type": "NotContain", + "utc_time": "2020-01-10T03:06:58.928507+00:00", + "container": "{'a': 1, 'b': 2}", + "passed": true, + "machine_time": "2020-01-10T11:06:58.928515+00:00", + "line_no": 71, + "member": 10 + }, + { + "category": "DEFAULT", + "description": "Comparison of slices", + "meta_type": "assertion", + "type": "EqualSlices", + "utc_time": "2020-01-10T03:06:58.930479+00:00", + "data": [ + [ + "slice(2, 4, None)", + [ + 2, + 3 + ], + [], + [ + 3, + 4 + ], + [ + 3, + 4 + ] + ], + [ + "slice(6, 8, None)", + [ + 6, + 7 + ], + [], + [ + 7, + 8 + ], + [ + 7, + 8 + ] + ] + ], + "passed": true, + "included_indices": [], + "machine_time": "2020-01-10T11:06:58.930488+00:00", + "expected": [ + "a", + "b", + 3, + 4, + "c", + "d", + 7, + 8 + ], + "line_no": 79, + "actual": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "category": "DEFAULT", + "description": "Comparison of slices (exclusion)", + "meta_type": "assertion", + "type": "EqualExcludeSlices", + "utc_time": "2020-01-10T03:06:58.932694+00:00", + "data": [ + [ + "slice(0, 2, None)", + [ + 2, + 3, + 4, + 5, + 6, + 7 + ], + [ + 4, + 5, + 6, + 7 + ], + [ + 3, + 4, + 5, + 6, + 7, + 8 + ], + [ + 3, + 4, + "c", + "d", + "e", + "f" + ] + ], + [ + "slice(4, 8, None)", + [ + 0, + 1, + 2, + 3 + ], + [ + 0, + 1 + ], + [ + 1, + 2, + 3, + 4 + ], + [ + "a", + "b", + 3, + 4 + ] + ] + ], + "passed": true, + "included_indices": [ + 2, + 3 + ], + "machine_time": "2020-01-10T11:06:58.932703+00:00", + "expected": [ + "a", + "b", + 3, + 4, + "c", + "d", + "e", + "f" + ], + "line_no": 91, + "actual": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "unified": false, + "category": "DEFAULT", + "ignore_blank_lines": true, + "description": null, + "meta_type": "assertion", + "type": "LineDiff", + "utc_time": "2020-01-10T03:06:58.934779+00:00", + "delta": [], + "second": [ + "abc\n", + "xyz\n", + "\n" + ], + "context": false, + "passed": true, + "first": [ + "abc\n", + "xyz\n" + ], + "machine_time": "2020-01-10T11:06:58.934786+00:00", + "ignore_space_change": false, + "line_no": 98, + "ignore_whitespaces": false + }, + { + "unified": 3, + "category": "DEFAULT", + "ignore_blank_lines": false, + "description": null, + "meta_type": "assertion", + "type": "LineDiff", + "utc_time": "2020-01-10T03:06:58.936975+00:00", + "delta": [], + "second": [ + "1\n", + "1\n", + "1\n", + "abc \n", + "xy\t\tz\n", + "2\n", + "2\n", + "2\n" + ], + "context": false, + "passed": true, + "first": [ + "1\r\n", + "1\r\n", + "1\r\n", + "abc\r\n", + "xy z\r\n", + "2\r\n", + "2\r\n", + "2\r\n" + ], + "machine_time": "2020-01-10T11:06:58.936983+00:00", + "ignore_space_change": true, + "line_no": 102, + "ignore_whitespaces": false + } + ] + }, + { + "category": "testcase", + "logs": [], + "description": null, + "suite_related": false, + "counter": { + "passed": 1, + "failed": 0, + "total": 1 + }, + "status_reason": null, + "type": "TestCaseReport", + "uid": "cd31b565-3702-4540-a140-ff9fd480e8ce", + "status": "passed", + "parent_uids": [ + "Assertions Example", + "Assertions Test", + "SampleSuite" + ], + "timer": { + "run": { + "end": "2020-01-10T03:06:58.963478+00:00", + "start": "2020-01-10T03:06:58.954190+00:00" + } + }, + "hash": -6066149844839810607, + "runtime_status": "finished", + "name": "test_raised_exceptions", + "status_override": null, + "tags": {}, + "entries": [ + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "pattern": null, + "type": "ExceptionRaised", + "utc_time": "2020-01-10T03:06:58.954270+00:00", + "func_match": true, + "raised_exception": [ + "", + "'bar'" + ], + "exception_match": true, + "expected_exceptions": [ + "KeyError" + ], + "passed": true, + "pattern_match": true, + "machine_time": "2020-01-10T11:06:58.954275+00:00", + "func": null, + "line_no": 112 + }, + { + "category": "DEFAULT", + "description": "Exception raised with custom pattern.", + "meta_type": "assertion", + "pattern": "foobar", + "type": "ExceptionRaised", + "utc_time": "2020-01-10T03:06:58.955863+00:00", + "func_match": true, + "raised_exception": [ + "", + "abc foobar xyz" + ], + "exception_match": true, + "expected_exceptions": [ + "ValueError" + ], + "passed": true, + "pattern_match": true, + "machine_time": "2020-01-10T11:06:58.955871+00:00", + "func": null, + "line_no": 121 + }, + { + "category": "DEFAULT", + "description": "Exception raised with custom func.", + "meta_type": "assertion", + "pattern": null, + "type": "ExceptionRaised", + "utc_time": "2020-01-10T03:06:58.957489+00:00", + "func_match": true, + "raised_exception": [ + ".MyException'>", + "4" + ], + "exception_match": true, + "expected_exceptions": [ + "MyException" + ], + "passed": true, + "pattern_match": true, + "machine_time": "2020-01-10T11:06:58.957497+00:00", + "func": ".custom_func at 0x7f9cfc64fea0>", + "line_no": 139 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "pattern": null, + "type": "ExceptionNotRaised", + "utc_time": "2020-01-10T03:06:58.958956+00:00", + "func_match": true, + "raised_exception": [ + "", + "'bar'" + ], + "exception_match": false, + "expected_exceptions": [ + "TypeError" + ], + "passed": true, + "pattern_match": true, + "machine_time": "2020-01-10T11:06:58.958964+00:00", + "func": null, + "line_no": 146 + }, + { + "category": "DEFAULT", + "description": "Exception not raised with custom pattern.", + "meta_type": "assertion", + "pattern": "foobar", + "type": "ExceptionNotRaised", + "utc_time": "2020-01-10T03:06:58.960503+00:00", + "func_match": true, + "raised_exception": [ + "", + "abc" + ], + "exception_match": true, + "expected_exceptions": [ + "ValueError" + ], + "passed": true, + "pattern_match": null, + "machine_time": "2020-01-10T11:06:58.960510+00:00", + "func": null, + "line_no": 157 + }, + { + "category": "DEFAULT", + "description": "Exception not raised with custom func.", + "meta_type": "assertion", + "pattern": null, + "type": "ExceptionNotRaised", + "utc_time": "2020-01-10T03:06:58.962023+00:00", + "func_match": false, + "raised_exception": [ + ".MyException'>", + "5" + ], + "exception_match": true, + "expected_exceptions": [ + "MyException" + ], + "passed": true, + "pattern_match": true, + "machine_time": "2020-01-10T11:06:58.962031+00:00", + "func": ".custom_func at 0x7f9cfc64fea0>", + "line_no": 165 + } + ] + }, + { + "category": "testcase", + "logs": [], + "description": null, + "suite_related": false, + "counter": { + "passed": 0, + "failed": 1, + "total": 1 + }, + "status_reason": null, + "type": "TestCaseReport", + "uid": "fca0596d-c220-4267-9a38-57968aca92d5", + "status": "failed", + "parent_uids": [ + "Assertions Example", + "Assertions Test", + "SampleSuite" + ], + "timer": { + "run": { + "end": "2020-01-10T03:06:58.979777+00:00", + "start": "2020-01-10T03:06:58.971424+00:00" + } + }, + "hash": -2707574492059523373, + "runtime_status": "finished", + "name": "test_assertion_group -- very long long long long long long long longlong long long longlong long long long name", + "status_override": null, + "tags": {}, + "entries": [ + { + "category": "DEFAULT", + "description": "Equality assertion outside the group", + "meta_type": "assertion", + "label": "==", + "type": "Equal", + "utc_time": "2020-01-10T03:06:58.971447+00:00", + "second": 1, + "passed": true, + "first": 1, + "machine_time": "2020-01-10T11:06:58.971451+00:00", + "line_no": 173 + }, + { + "description": "Custom group description", + "meta_type": "assertion", + "type": "Group", + "passed": false, + "entries": [ + { + "category": "DEFAULT", + "description": "Assertion within a group", + "meta_type": "assertion", + "label": "!=", + "type": "NotEqual", + "utc_time": "2020-01-10T03:06:58.973038+00:00", + "second": 3, + "passed": true, + "first": 2, + "machine_time": "2020-01-10T11:06:58.973047+00:00", + "line_no": 176 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "label": ">", + "type": "Greater", + "utc_time": "2020-01-10T03:06:58.974577+00:00", + "second": 3, + "passed": true, + "first": 5, + "machine_time": "2020-01-10T11:06:58.974586+00:00", + "line_no": 177 + }, + { + "description": "This is a sub group", + "meta_type": "assertion", + "type": "Group", + "passed": false, + "entries": [ + { + "category": "DEFAULT", + "description": "Assertion within sub group", + "meta_type": "assertion", + "label": "<", + "type": "Less", + "utc_time": "2020-01-10T03:06:58.976376+00:00", + "second": 3, + "passed": false, + "first": 6, + "machine_time": "2020-01-10T11:06:58.976384+00:00", + "line_no": 181 + } + ] + } + ] + }, + { + "category": "DEFAULT", + "description": "Final assertion outside all groups", + "meta_type": "assertion", + "label": "==", + "type": "Equal", + "utc_time": "2020-01-10T03:06:58.978219+00:00", + "second": "foo", + "passed": true, + "first": "foo", + "machine_time": "2020-01-10T11:06:58.978227+00:00", + "line_no": 184 + } + ] + }, + { + "category": "testcase", + "logs": [], + "description": null, + "suite_related": false, + "counter": { + "passed": 0, + "failed": 1, + "total": 1 + }, + "status_reason": null, + "type": "TestCaseReport", + "uid": "a3fd1023-b150-487a-bc7d-c0f64e326e63", + "status": "failed", + "parent_uids": [ + "Assertions Example", + "Assertions Test", + "SampleSuite" + ], + "timer": { + "run": { + "end": "2020-01-10T03:06:59.006101+00:00", + "start": "2020-01-10T03:06:58.987035+00:00" + } + }, + "hash": -8719069130512673532, + "runtime_status": "finished", + "name": "test_regex_namespace", + "status_override": null, + "tags": {}, + "entries": [ + { + "category": "DEFAULT", + "description": "string pattern match", + "meta_type": "assertion", + "pattern": "foo", + "type": "RegexMatch", + "utc_time": "2020-01-10T03:06:58.987140+00:00", + "match_indexes": [ + [ + 0, + 3 + ] + ], + "passed": true, + "string": "foobar", + "machine_time": "2020-01-10T11:06:58.987146+00:00", + "line_no": 196 + }, + { + "category": "DEFAULT", + "description": "SRE match", + "meta_type": "assertion", + "pattern": "foo", + "type": "RegexMatch", + "utc_time": "2020-01-10T03:06:58.988905+00:00", + "match_indexes": [ + [ + 0, + 3 + ] + ], + "passed": true, + "string": "foobar", + "machine_time": "2020-01-10T11:06:58.988913+00:00", + "line_no": 201 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "pattern": "first line.*second", + "type": "RegexMatch", + "utc_time": "2020-01-10T03:06:58.991277+00:00", + "match_indexes": [ + [ + 0, + 17 + ] + ], + "passed": true, + "string": "first line\nsecond line\nthird line", + "machine_time": "2020-01-10T11:06:58.991285+00:00", + "line_no": 212 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "pattern": "baz", + "type": "RegexMatchNotExists", + "utc_time": "2020-01-10T03:06:58.992937+00:00", + "match_indexes": [], + "passed": true, + "string": "foobar", + "machine_time": "2020-01-10T11:06:58.992945+00:00", + "line_no": 217 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "pattern": "foobar", + "type": "RegexMatchNotExists", + "utc_time": "2020-01-10T03:06:58.994520+00:00", + "match_indexes": [], + "passed": true, + "string": "first line\nsecond line\nthird line", + "machine_time": "2020-01-10T11:06:58.994527+00:00", + "line_no": 222 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "pattern": "second", + "type": "RegexSearch", + "utc_time": "2020-01-10T03:06:58.996148+00:00", + "match_indexes": [ + [ + 11, + 17 + ] + ], + "passed": true, + "string": "first line\nsecond line\nthird line", + "machine_time": "2020-01-10T11:06:58.996156+00:00", + "line_no": 225 + }, + { + "category": "DEFAULT", + "description": "Passing search empty", + "meta_type": "assertion", + "pattern": "foobar", + "type": "RegexSearchNotExists", + "utc_time": "2020-01-10T03:06:58.997760+00:00", + "match_indexes": [], + "passed": true, + "string": "first line\nsecond line\nthird line", + "machine_time": "2020-01-10T11:06:58.997768+00:00", + "line_no": 230 + }, + { + "category": "DEFAULT", + "description": "Failing search_empty", + "meta_type": "assertion", + "pattern": "second", + "type": "RegexSearchNotExists", + "utc_time": "2020-01-10T03:06:58.999296+00:00", + "match_indexes": [ + [ + 11, + 17 + ] + ], + "passed": false, + "string": "first line\nsecond line\nthird line", + "machine_time": "2020-01-10T11:06:58.999303+00:00", + "line_no": 233 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "pattern": "foo", + "type": "RegexFindIter", + "utc_time": "2020-01-10T03:06:59.000852+00:00", + "match_indexes": [ + [ + 0, + 3 + ], + [ + 4, + 7 + ], + [ + 8, + 11 + ], + [ + 20, + 23 + ] + ], + "condition": "", + "passed": true, + "string": "foo foo foo bar bar foo bar", + "machine_time": "2020-01-10T11:06:59.000860+00:00", + "condition_match": true, + "line_no": 243 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "pattern": "foo", + "type": "RegexFindIter", + "utc_time": "2020-01-10T03:06:59.002669+00:00", + "match_indexes": [ + [ + 0, + 3 + ], + [ + 4, + 7 + ], + [ + 8, + 11 + ], + [ + 20, + 23 + ] + ], + "condition": "(VAL > 2 and VAL < 5)", + "passed": true, + "string": "foo foo foo bar bar foo bar", + "machine_time": "2020-01-10T11:06:59.002676+00:00", + "condition_match": true, + "line_no": 250 + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "pattern": "\\w+ line$", + "type": "RegexMatchLine", + "utc_time": "2020-01-10T03:06:59.004622+00:00", + "match_indexes": [ + [ + 0, + 0, + 10 + ], + [ + 1, + 0, + 11 + ], + [ + 2, + 0, + 10 + ] + ], + "passed": true, + "string": "first line\nsecond line\nthird line", + "machine_time": "2020-01-10T11:06:59.004630+00:00", + "line_no": 257 + } + ] + }, + { + "category": "testcase", + "logs": [], + "description": null, + "suite_related": false, + "counter": { + "passed": 0, + "failed": 1, + "total": 1 + }, + "status_reason": null, + "type": "TestCaseReport", + "uid": "e8fb2848-cc83-4df3-83e0-82fe839d6526", + "status": "failed", + "parent_uids": [ + "Assertions Example", + "Assertions Test", + "SampleSuite" + ], + "timer": { + "run": { + "end": "2020-01-10T03:06:59.072704+00:00", + "start": "2020-01-10T03:06:59.016322+00:00" + } + }, + "hash": -8829886055223884393, + "runtime_status": "finished", + "name": "test_table_namespace", + "status_override": null, + "tags": {}, + "entries": [ + { + "category": "DEFAULT", + "description": "Table Match: list of list vs list of list", + "meta_type": "assertion", + "type": "TableMatch", + "utc_time": "2020-01-10T03:06:59.016418+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "name", + "age" + ], + "data": [ + [ + 0, + [ + "Bob", + 32 + ], + {}, + {}, + {} + ], + [ + 1, + [ + "Susan", + 24 + ], + {}, + {}, + {} + ], + [ + 2, + [ + "Rick", + 67 + ], + {}, + {}, + {} + ] + ], + "report_fails_only": false, + "passed": true, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.016424+00:00", + "line_no": 284, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Match: list of dict vs list of dict", + "meta_type": "assertion", + "type": "TableMatch", + "utc_time": "2020-01-10T03:06:59.018525+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "name", + "age" + ], + "data": [ + [ + 0, + [ + "Bob", + 32 + ], + {}, + {}, + {} + ], + [ + 1, + [ + "Susan", + 24 + ], + {}, + {}, + {} + ], + [ + 2, + [ + "Rick", + 67 + ], + {}, + {}, + {} + ] + ], + "report_fails_only": false, + "passed": true, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.018533+00:00", + "line_no": 289, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Match: list of dict vs list of list", + "meta_type": "assertion", + "type": "TableMatch", + "utc_time": "2020-01-10T03:06:59.020629+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "name", + "age" + ], + "data": [ + [ + 0, + [ + "Bob", + 32 + ], + {}, + {}, + {} + ], + [ + 1, + [ + "Susan", + 24 + ], + {}, + {}, + {} + ], + [ + 2, + [ + "Rick", + 67 + ], + {}, + {}, + {} + ] + ], + "report_fails_only": false, + "passed": true, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.020640+00:00", + "line_no": 294, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Diff: list of list vs list of list", + "meta_type": "assertion", + "type": "TableDiff", + "utc_time": "2020-01-10T03:06:59.023695+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "name", + "age" + ], + "data": [], + "report_fails_only": true, + "passed": true, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.023703+00:00", + "line_no": 299, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Diff: list of dict vs list of dict", + "meta_type": "assertion", + "type": "TableDiff", + "utc_time": "2020-01-10T03:06:59.026093+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "name", + "age" + ], + "data": [], + "report_fails_only": true, + "passed": true, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.026102+00:00", + "line_no": 304, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Diff: list of dict vs list of list", + "meta_type": "assertion", + "type": "TableDiff", + "utc_time": "2020-01-10T03:06:59.027835+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "name", + "age" + ], + "data": [], + "report_fails_only": true, + "passed": true, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.027843+00:00", + "line_no": 309, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Match: simple comparators", + "meta_type": "assertion", + "type": "TableMatch", + "utc_time": "2020-01-10T03:06:59.029541+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "name", + "age" + ], + "data": [ + [ + 0, + [ + "Bob", + 32 + ], + {}, + {}, + { + "name": "REGEX(\\w{3})", + "age": "" + } + ], + [ + 1, + [ + "Susan", + 24 + ], + {}, + {}, + {} + ], + [ + 2, + [ + "Rick", + 67 + ], + { + "name": "" + }, + {}, + {} + ] + ], + "report_fails_only": false, + "passed": false, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.029549+00:00", + "line_no": 338, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Diff: simple comparators", + "meta_type": "assertion", + "type": "TableDiff", + "utc_time": "2020-01-10T03:06:59.031666+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "name", + "age" + ], + "data": [ + [ + 2, + [ + "Rick", + 67 + ], + { + "name": "" + }, + {}, + {} + ] + ], + "report_fails_only": true, + "passed": false, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.031674+00:00", + "line_no": 343, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Match: readable comparators", + "meta_type": "assertion", + "type": "TableMatch", + "utc_time": "2020-01-10T03:06:59.034598+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "name", + "age" + ], + "data": [ + [ + 0, + [ + "Bob", + 32 + ], + {}, + {}, + { + "name": "REGEX(\\w{3})", + "age": "(VAL > 30 and VAL < 40)" + } + ], + [ + 1, + [ + "Susan", + 24 + ], + {}, + {}, + {} + ], + [ + 2, + [ + "Rick", + 67 + ], + { + "name": "VAL in ['David', 'Helen', 'Pablo']" + }, + {}, + {} + ] + ], + "report_fails_only": false, + "passed": false, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.034625+00:00", + "line_no": 361, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Diff: readable comparators", + "meta_type": "assertion", + "type": "TableDiff", + "utc_time": "2020-01-10T03:06:59.037495+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "name", + "age" + ], + "data": [ + [ + 2, + [ + "Rick", + 67 + ], + { + "name": "VAL in ['David', 'Helen', 'Pablo']" + }, + {}, + {} + ] + ], + "report_fails_only": true, + "passed": false, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.037502+00:00", + "line_no": 366, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Match: Trimmed columns", + "meta_type": "assertion", + "type": "TableMatch", + "utc_time": "2020-01-10T03:06:59.040045+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "column_1", + "column_2" + ], + "data": [ + [ + 0, + [ + 0, + 0 + ], + {}, + {}, + {} + ], + [ + 1, + [ + 1, + 2 + ], + {}, + {}, + {} + ], + [ + 2, + [ + 2, + 4 + ], + {}, + {}, + {} + ], + [ + 3, + [ + 3, + 6 + ], + {}, + {}, + {} + ], + [ + 4, + [ + 4, + 8 + ], + {}, + {}, + {} + ], + [ + 5, + [ + 5, + 10 + ], + {}, + {}, + {} + ], + [ + 6, + [ + 6, + 12 + ], + {}, + {}, + {} + ], + [ + 7, + [ + 7, + 14 + ], + {}, + {}, + {} + ], + [ + 8, + [ + 8, + 16 + ], + {}, + {}, + {} + ], + [ + 9, + [ + 9, + 18 + ], + {}, + {}, + {} + ] + ], + "report_fails_only": false, + "passed": true, + "include_columns": [ + "column_1", + "column_2" + ], + "machine_time": "2020-01-10T11:06:59.040052+00:00", + "line_no": 383, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Diff: Trimmed columns", + "meta_type": "assertion", + "type": "TableDiff", + "utc_time": "2020-01-10T03:06:59.042860+00:00", + "exclude_columns": null, + "fail_limit": 0, + "columns": [ + "column_1", + "column_2" + ], + "data": [], + "report_fails_only": true, + "passed": true, + "include_columns": [ + "column_1", + "column_2" + ], + "machine_time": "2020-01-10T11:06:59.042869+00:00", + "line_no": 391, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Match: Trimmed rows", + "meta_type": "assertion", + "type": "TableMatch", + "utc_time": "2020-01-10T03:06:59.046590+00:00", + "exclude_columns": null, + "fail_limit": 2, + "columns": [ + "amount", + "product_id" + ], + "data": [ + [ + 0, + [ + 0, + 4240 + ], + {}, + {}, + {} + ], + [ + 1, + [ + 10, + 3961 + ], + {}, + {}, + {} + ], + [ + 2, + [ + 20, + 1627 + ], + {}, + {}, + {} + ], + [ + 3, + [ + 30, + 1351 + ], + {}, + {}, + {} + ], + [ + 4, + [ + 40, + 2123 + ], + {}, + {}, + {} + ], + [ + 5, + [ + 25, + 1111 + ], + { + "amount": 35 + }, + {}, + {} + ], + [ + 6, + [ + 20, + 2222 + ], + { + "product_id": 1234 + }, + {}, + {} + ] + ], + "report_fails_only": false, + "passed": false, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.046598+00:00", + "line_no": 428, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": "Table Diff: Trimmed rows", + "meta_type": "assertion", + "type": "TableDiff", + "utc_time": "2020-01-10T03:06:59.049590+00:00", + "exclude_columns": null, + "fail_limit": 2, + "columns": [ + "amount", + "product_id" + ], + "data": [ + [ + 5, + [ + 25, + 1111 + ], + { + "amount": 35 + }, + {}, + {} + ], + [ + 6, + [ + 20, + 2222 + ], + { + "product_id": 1234 + }, + {}, + {} + ] + ], + "report_fails_only": true, + "passed": false, + "include_columns": null, + "machine_time": "2020-01-10T11:06:59.049598+00:00", + "line_no": 437, + "message": null, + "strict": false + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "column": "symbol", + "limit": null, + "type": "ColumnContain", + "utc_time": "2020-01-10T03:06:59.051390+00:00", + "data": [ + [ + 0, + "AAPL", + true + ], + [ + 1, + "GOOG", + false + ], + [ + 2, + "FB", + false + ], + [ + 3, + "AMZN", + true + ], + [ + 4, + "MSFT", + false + ] + ], + "passed": false, + "machine_time": "2020-01-10T11:06:59.051397+00:00", + "values": [ + "AAPL", + "AMZN" + ], + "line_no": 454, + "report_fails_only": false + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "column": "symbol", + "limit": 20, + "type": "ColumnContain", + "utc_time": "2020-01-10T03:06:59.057037+00:00", + "data": [ + [ + 1, + "GOOG", + false + ], + [ + 2, + "FB", + false + ], + [ + 4, + "MSFT", + false + ], + [ + 6, + "GOOG", + false + ], + [ + 7, + "FB", + false + ], + [ + 9, + "MSFT", + false + ], + [ + 11, + "GOOG", + false + ], + [ + 12, + "FB", + false + ], + [ + 14, + "MSFT", + false + ], + [ + 16, + "GOOG", + false + ], + [ + 17, + "FB", + false + ], + [ + 19, + "MSFT", + false + ], + [ + 21, + "GOOG", + false + ], + [ + 22, + "FB", + false + ], + [ + 24, + "MSFT", + false + ], + [ + 26, + "GOOG", + false + ], + [ + 27, + "FB", + false + ], + [ + 29, + "MSFT", + false + ], + [ + 31, + "GOOG", + false + ], + [ + 32, + "FB", + false + ] + ], + "passed": false, + "machine_time": "2020-01-10T11:06:59.057048+00:00", + "values": [ + "AAPL", + "AMZN" + ], + "line_no": 467, + "report_fails_only": true + }, + { + "category": "DEFAULT", + "description": "Table Log: list of dicts", + "meta_type": "entry", + "type": "TableLog", + "utc_time": "2020-01-10T03:06:59.060012+00:00", + "table": [ + { + "name": "Bob", + "age": 32 + }, + { + "name": "Susan", + "age": 24 + }, + { + "name": "Rick", + "age": 67 + } + ], + "display_index": false, + "columns": [ + "name", + "age" + ], + "machine_time": "2020-01-10T11:06:59.060020+00:00", + "line_no": 472, + "indices": [ + 0, + 1, + 2 + ] + }, + { + "category": "DEFAULT", + "description": "Table Log: list of lists", + "meta_type": "entry", + "type": "TableLog", + "utc_time": "2020-01-10T03:06:59.061711+00:00", + "table": [ + { + "name": "Bob", + "age": 32 + }, + { + "name": "Susan", + "age": 24 + }, + { + "name": "Rick", + "age": 67 + } + ], + "display_index": false, + "columns": [ + "name", + "age" + ], + "machine_time": "2020-01-10T11:06:59.061718+00:00", + "line_no": 473, + "indices": [ + 0, + 1, + 2 + ] + }, + { + "category": "DEFAULT", + "description": "Table Log: many rows", + "meta_type": "entry", + "type": "TableLog", + "utc_time": "2020-01-10T03:06:59.063421+00:00", + "table": [ + { + "symbol": "AAPL", + "amount": 12 + }, + { + "symbol": "GOOG", + "amount": 21 + }, + { + "symbol": "FB", + "amount": 32 + }, + { + "symbol": "AMZN", + "amount": 5 + }, + { + "symbol": "MSFT", + "amount": 42 + }, + { + "symbol": "AAPL", + "amount": 12 + }, + { + "symbol": "GOOG", + "amount": 21 + }, + { + "symbol": "FB", + "amount": 32 + }, + { + "symbol": "AMZN", + "amount": 5 + }, + { + "symbol": "MSFT", + "amount": 42 + }, + { + "symbol": "AAPL", + "amount": 12 + }, + { + "symbol": "GOOG", + "amount": 21 + }, + { + "symbol": "FB", + "amount": 32 + }, + { + "symbol": "AMZN", + "amount": 5 + }, + { + "symbol": "MSFT", + "amount": 42 + }, + { + "symbol": "AAPL", + "amount": 12 + }, + { + "symbol": "GOOG", + "amount": 21 + }, + { + "symbol": "FB", + "amount": 32 + }, + { + "symbol": "AMZN", + "amount": 5 + }, + { + "symbol": "MSFT", + "amount": 42 + } + ], + "display_index": false, + "columns": [ + "symbol", + "amount" + ], + "machine_time": "2020-01-10T11:06:59.063429+00:00", + "line_no": 479, + "indices": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19 + ] + }, + { + "category": "DEFAULT", + "description": "Table Log: many columns", + "meta_type": "entry", + "type": "TableLog", + "utc_time": "2020-01-10T03:06:59.065884+00:00", + "table": [ + { + "col_0": "row 0 col 0", + "col_1": "row 0 col 1", + "col_2": "row 0 col 2", + "col_3": "row 0 col 3", + "col_4": "row 0 col 4", + "col_5": "row 0 col 5", + "col_6": "row 0 col 6", + "col_7": "row 0 col 7", + "col_8": "row 0 col 8", + "col_9": "row 0 col 9", + "col_10": "row 0 col 10", + "col_11": "row 0 col 11", + "col_12": "row 0 col 12", + "col_13": "row 0 col 13", + "col_14": "row 0 col 14", + "col_15": "row 0 col 15", + "col_16": "row 0 col 16", + "col_17": "row 0 col 17", + "col_18": "row 0 col 18", + "col_19": "row 0 col 19" + }, + { + "col_0": "row 1 col 0", + "col_1": "row 1 col 1", + "col_2": "row 1 col 2", + "col_3": "row 1 col 3", + "col_4": "row 1 col 4", + "col_5": "row 1 col 5", + "col_6": "row 1 col 6", + "col_7": "row 1 col 7", + "col_8": "row 1 col 8", + "col_9": "row 1 col 9", + "col_10": "row 1 col 10", + "col_11": "row 1 col 11", + "col_12": "row 1 col 12", + "col_13": "row 1 col 13", + "col_14": "row 1 col 14", + "col_15": "row 1 col 15", + "col_16": "row 1 col 16", + "col_17": "row 1 col 17", + "col_18": "row 1 col 18", + "col_19": "row 1 col 19" + }, + { + "col_0": "row 2 col 0", + "col_1": "row 2 col 1", + "col_2": "row 2 col 2", + "col_3": "row 2 col 3", + "col_4": "row 2 col 4", + "col_5": "row 2 col 5", + "col_6": "row 2 col 6", + "col_7": "row 2 col 7", + "col_8": "row 2 col 8", + "col_9": "row 2 col 9", + "col_10": "row 2 col 10", + "col_11": "row 2 col 11", + "col_12": "row 2 col 12", + "col_13": "row 2 col 13", + "col_14": "row 2 col 14", + "col_15": "row 2 col 15", + "col_16": "row 2 col 16", + "col_17": "row 2 col 17", + "col_18": "row 2 col 18", + "col_19": "row 2 col 19" + }, + { + "col_0": "row 3 col 0", + "col_1": "row 3 col 1", + "col_2": "row 3 col 2", + "col_3": "row 3 col 3", + "col_4": "row 3 col 4", + "col_5": "row 3 col 5", + "col_6": "row 3 col 6", + "col_7": "row 3 col 7", + "col_8": "row 3 col 8", + "col_9": "row 3 col 9", + "col_10": "row 3 col 10", + "col_11": "row 3 col 11", + "col_12": "row 3 col 12", + "col_13": "row 3 col 13", + "col_14": "row 3 col 14", + "col_15": "row 3 col 15", + "col_16": "row 3 col 16", + "col_17": "row 3 col 17", + "col_18": "row 3 col 18", + "col_19": "row 3 col 19" + }, + { + "col_0": "row 4 col 0", + "col_1": "row 4 col 1", + "col_2": "row 4 col 2", + "col_3": "row 4 col 3", + "col_4": "row 4 col 4", + "col_5": "row 4 col 5", + "col_6": "row 4 col 6", + "col_7": "row 4 col 7", + "col_8": "row 4 col 8", + "col_9": "row 4 col 9", + "col_10": "row 4 col 10", + "col_11": "row 4 col 11", + "col_12": "row 4 col 12", + "col_13": "row 4 col 13", + "col_14": "row 4 col 14", + "col_15": "row 4 col 15", + "col_16": "row 4 col 16", + "col_17": "row 4 col 17", + "col_18": "row 4 col 18", + "col_19": "row 4 col 19" + }, + { + "col_0": "row 5 col 0", + "col_1": "row 5 col 1", + "col_2": "row 5 col 2", + "col_3": "row 5 col 3", + "col_4": "row 5 col 4", + "col_5": "row 5 col 5", + "col_6": "row 5 col 6", + "col_7": "row 5 col 7", + "col_8": "row 5 col 8", + "col_9": "row 5 col 9", + "col_10": "row 5 col 10", + "col_11": "row 5 col 11", + "col_12": "row 5 col 12", + "col_13": "row 5 col 13", + "col_14": "row 5 col 14", + "col_15": "row 5 col 15", + "col_16": "row 5 col 16", + "col_17": "row 5 col 17", + "col_18": "row 5 col 18", + "col_19": "row 5 col 19" + }, + { + "col_0": "row 6 col 0", + "col_1": "row 6 col 1", + "col_2": "row 6 col 2", + "col_3": "row 6 col 3", + "col_4": "row 6 col 4", + "col_5": "row 6 col 5", + "col_6": "row 6 col 6", + "col_7": "row 6 col 7", + "col_8": "row 6 col 8", + "col_9": "row 6 col 9", + "col_10": "row 6 col 10", + "col_11": "row 6 col 11", + "col_12": "row 6 col 12", + "col_13": "row 6 col 13", + "col_14": "row 6 col 14", + "col_15": "row 6 col 15", + "col_16": "row 6 col 16", + "col_17": "row 6 col 17", + "col_18": "row 6 col 18", + "col_19": "row 6 col 19" + }, + { + "col_0": "row 7 col 0", + "col_1": "row 7 col 1", + "col_2": "row 7 col 2", + "col_3": "row 7 col 3", + "col_4": "row 7 col 4", + "col_5": "row 7 col 5", + "col_6": "row 7 col 6", + "col_7": "row 7 col 7", + "col_8": "row 7 col 8", + "col_9": "row 7 col 9", + "col_10": "row 7 col 10", + "col_11": "row 7 col 11", + "col_12": "row 7 col 12", + "col_13": "row 7 col 13", + "col_14": "row 7 col 14", + "col_15": "row 7 col 15", + "col_16": "row 7 col 16", + "col_17": "row 7 col 17", + "col_18": "row 7 col 18", + "col_19": "row 7 col 19" + }, + { + "col_0": "row 8 col 0", + "col_1": "row 8 col 1", + "col_2": "row 8 col 2", + "col_3": "row 8 col 3", + "col_4": "row 8 col 4", + "col_5": "row 8 col 5", + "col_6": "row 8 col 6", + "col_7": "row 8 col 7", + "col_8": "row 8 col 8", + "col_9": "row 8 col 9", + "col_10": "row 8 col 10", + "col_11": "row 8 col 11", + "col_12": "row 8 col 12", + "col_13": "row 8 col 13", + "col_14": "row 8 col 14", + "col_15": "row 8 col 15", + "col_16": "row 8 col 16", + "col_17": "row 8 col 17", + "col_18": "row 8 col 18", + "col_19": "row 8 col 19" + }, + { + "col_0": "row 9 col 0", + "col_1": "row 9 col 1", + "col_2": "row 9 col 2", + "col_3": "row 9 col 3", + "col_4": "row 9 col 4", + "col_5": "row 9 col 5", + "col_6": "row 9 col 6", + "col_7": "row 9 col 7", + "col_8": "row 9 col 8", + "col_9": "row 9 col 9", + "col_10": "row 9 col 10", + "col_11": "row 9 col 11", + "col_12": "row 9 col 12", + "col_13": "row 9 col 13", + "col_14": "row 9 col 14", + "col_15": "row 9 col 15", + "col_16": "row 9 col 16", + "col_17": "row 9 col 17", + "col_18": "row 9 col 18", + "col_19": "row 9 col 19" + } + ], + "display_index": false, + "columns": [ + "col_0", + "col_1", + "col_2", + "col_3", + "col_4", + "col_5", + "col_6", + "col_7", + "col_8", + "col_9", + "col_10", + "col_11", + "col_12", + "col_13", + "col_14", + "col_15", + "col_16", + "col_17", + "col_18", + "col_19" + ], + "machine_time": "2020-01-10T11:06:59.065891+00:00", + "line_no": 490, + "indices": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + { + "category": "DEFAULT", + "description": "Table Log: long cells", + "meta_type": "entry", + "type": "TableLog", + "utc_time": "2020-01-10T03:06:59.070773+00:00", + "table": [ + { + "Name": "Bob Stevens", + "Age": "33", + "Address": "89 Trinsdale Avenue, LONDON, E8 0XW" + }, + { + "Name": "Susan Evans", + "Age": "21", + "Address": "100 Loop Road, SWANSEA, U8 12JK" + }, + { + "Name": "Trevor Dune", + "Age": "88", + "Address": "28 Kings Lane, MANCHESTER, MT16 2YT" + }, + { + "Name": "Belinda Baggins", + "Age": "38", + "Address": "31 Prospect Hill, DOYNTON, BS30 9DN" + }, + { + "Name": "Cosimo Hornblower", + "Age": "89", + "Address": "65 Prospect Hill, SURREY, PH33 4TY" + }, + { + "Name": "Sabine Wurfel", + "Age": "31", + "Address": "88 Clasper Way, HEXWORTHY, PL20 4BG" + } + ], + "display_index": false, + "columns": [ + "Name", + "Age", + "Address" + ], + "machine_time": "2020-01-10T11:06:59.070780+00:00", + "line_no": 504, + "indices": [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + } + ] + }, + { + "category": "testcase", + "logs": [], + "description": null, + "suite_related": false, + "counter": { + "passed": 0, + "failed": 1, + "total": 1 + }, + "status_reason": null, + "type": "TestCaseReport", + "uid": "ca8979be-8eb3-4ff4-8c18-aba4c8348bac", + "status": "failed", + "parent_uids": [ + "Assertions Example", + "Assertions Test", + "SampleSuite" + ], + "timer": { + "run": { + "end": "2020-01-10T03:06:59.102866+00:00", + "start": "2020-01-10T03:06:59.087638+00:00" + } + }, + "hash": -6007544999293600650, + "runtime_status": "finished", + "name": "test_dict_namespace", + "status_override": null, + "tags": {}, + "entries": [ + { + "category": "DEFAULT", + "description": "Simple dict match", + "meta_type": "assertion", + "type": "DictMatch", + "include_keys": null, + "utc_time": "2020-01-10T03:06:59.087672+00:00", + "actual_description": null, + "expected_description": null, + "comparison": [ + [ + 0, + "foo", + "Passed", + [ + "int", + "1" + ], + [ + "int", + "1" + ] + ], + [ + 0, + "bar", + "Failed", + [ + "int", + "2" + ], + [ + "int", + "5" + ] + ], + [ + 0, + "extra-key", + "Failed", + [ + null, + "ABSENT" + ], + [ + "int", + "10" + ] + ] + ], + "passed": false, + "machine_time": "2020-01-10T11:06:59.087677+00:00", + "exclude_keys": null, + "line_no": 524 + }, + { + "category": "DEFAULT", + "description": "Nested dict match", + "meta_type": "assertion", + "type": "DictMatch", + "include_keys": null, + "utc_time": "2020-01-10T03:06:59.089583+00:00", + "actual_description": null, + "expected_description": null, + "comparison": [ + [ + 0, + "foo", + "Failed", + "", + "" + ], + [ + 1, + "alpha", + "Failed", + "", + "" + ], + [ + 1, + "", + "Passed", + [ + "int", + "1" + ], + [ + "int", + "1" + ] + ], + [ + 1, + "", + "Passed", + [ + "int", + "2" + ], + [ + "int", + "2" + ] + ], + [ + 1, + "", + "Failed", + [ + "int", + "3" + ], + [ + null, + null + ] + ], + [ + 1, + "beta", + "Failed", + "", + "" + ], + [ + 2, + "color", + "Failed", + [ + "str", + "red" + ], + [ + "str", + "blue" + ] + ] + ], + "passed": false, + "machine_time": "2020-01-10T11:06:59.089619+00:00", + "exclude_keys": null, + "line_no": 542 + }, + { + "category": "DEFAULT", + "description": "Dict match: Custom comparators", + "meta_type": "assertion", + "type": "DictMatch", + "include_keys": null, + "utc_time": "2020-01-10T03:06:59.091710+00:00", + "actual_description": null, + "expected_description": null, + "comparison": [ + [ + 0, + "foo", + "Passed", + "", + "" + ], + [ + 0, + "", + "Passed", + [ + "int", + "1" + ], + [ + "int", + "1" + ] + ], + [ + 0, + "", + "Passed", + [ + "int", + "2" + ], + [ + "int", + "2" + ] + ], + [ + 0, + "", + "Passed", + [ + "int", + "3" + ], + [ + "func", + "" + ] + ], + [ + 0, + "bar", + "Passed", + "", + "" + ], + [ + 1, + "color", + "Passed", + [ + "str", + "blue" + ], + [ + "func", + "VAL in ['blue', 'red', 'yellow']" + ] + ], + [ + 0, + "baz", + "Passed", + [ + "str", + "hello world" + ], + [ + "REGEX", + "\\w+ world" + ] + ] + ], + "passed": true, + "machine_time": "2020-01-10T11:06:59.091718+00:00", + "exclude_keys": null, + "line_no": 560 + }, + { + "category": "DEFAULT", + "description": "default assertion passes because the values are numerically equal", + "meta_type": "assertion", + "type": "DictMatch", + "include_keys": null, + "utc_time": "2020-01-10T03:06:59.093424+00:00", + "actual_description": null, + "expected_description": null, + "comparison": [ + [ + 0, + "foo", + "Passed", + [ + "int", + "1" + ], + [ + "float", + 1.0 + ] + ], + [ + 0, + "bar", + "Passed", + [ + "int", + "2" + ], + [ + "float", + 2.0 + ] + ], + [ + 0, + "baz", + "Passed", + [ + "int", + "3" + ], + [ + "float", + 3.0 + ] + ] + ], + "passed": true, + "machine_time": "2020-01-10T11:06:59.093432+00:00", + "exclude_keys": null, + "line_no": 572 + }, + { + "category": "DEFAULT", + "description": "when we check types the assertion will fail", + "meta_type": "assertion", + "type": "DictMatch", + "include_keys": null, + "utc_time": "2020-01-10T03:06:59.094973+00:00", + "actual_description": null, + "expected_description": null, + "comparison": [ + [ + 0, + "foo", + "Failed", + [ + "int", + "1" + ], + [ + "float", + 1.0 + ] + ], + [ + 0, + "bar", + "Failed", + [ + "int", + "2" + ], + [ + "float", + 2.0 + ] + ], + [ + 0, + "baz", + "Failed", + [ + "int", + "3" + ], + [ + "float", + 3.0 + ] + ] + ], + "passed": false, + "machine_time": "2020-01-10T11:06:59.094981+00:00", + "exclude_keys": null, + "line_no": 578 + }, + { + "category": "DEFAULT", + "description": "use a custom comparison function to check within a tolerance", + "meta_type": "assertion", + "type": "DictMatch", + "include_keys": null, + "utc_time": "2020-01-10T03:06:59.096547+00:00", + "actual_description": null, + "expected_description": null, + "comparison": [ + [ + 0, + "foo", + "Passed", + [ + "float", + 1.02 + ], + [ + "float", + 0.98 + ] + ], + [ + 0, + "bar", + "Passed", + [ + "float", + 2.28 + ], + [ + "float", + 2.33 + ] + ], + [ + 0, + "baz", + "Passed", + [ + "float", + 3.5 + ], + [ + "float", + 3.46 + ] + ] + ], + "passed": true, + "machine_time": "2020-01-10T11:06:59.096554+00:00", + "exclude_keys": null, + "line_no": 587 + }, + { + "category": "DEFAULT", + "description": "only report the failing comparison", + "meta_type": "assertion", + "type": "DictMatch", + "include_keys": null, + "utc_time": "2020-01-10T03:06:59.098102+00:00", + "actual_description": null, + "expected_description": null, + "comparison": [ + [ + 0, + "bad_key", + "Failed", + [ + "str", + "actual" + ], + [ + "str", + "expected" + ] + ] + ], + "passed": false, + "machine_time": "2020-01-10T11:06:59.098109+00:00", + "exclude_keys": null, + "line_no": 601 + }, + { + "absent_keys_diff": [ + "bar" + ], + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "has_keys_diff": [ + "alpha" + ], + "type": "DictCheck", + "utc_time": "2020-01-10T03:06:59.099751+00:00", + "passed": false, + "absent_keys": [ + "bar", + "beta" + ], + "machine_time": "2020-01-10T11:06:59.099760+00:00", + "line_no": 611, + "has_keys": [ + "foo", + "alpha" + ] + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "entry", + "type": "DictLog", + "utc_time": "2020-01-10T03:06:59.101282+00:00", + "flattened_dict": [ + [ + 0, + "foo", + "" + ], + [ + 0, + "", + [ + "int", + "1" + ] + ], + [ + 0, + "", + [ + "int", + "2" + ] + ], + [ + 0, + "", + [ + "int", + "3" + ] + ], + [ + 0, + "bar", + "" + ], + [ + 1, + "color", + [ + "str", + "blue" + ] + ], + [ + 0, + "baz", + [ + "str", + "hello world" + ] + ] + ], + "machine_time": "2020-01-10T11:06:59.101290+00:00", + "line_no": 620 + } + ] + }, + { + "category": "testcase", + "logs": [], + "description": null, + "suite_related": false, + "counter": { + "passed": 0, + "failed": 1, + "total": 1 + }, + "status_reason": null, + "type": "TestCaseReport", + "uid": "826ee3d4-0dea-412b-9652-86f5847706d9", + "status": "failed", + "parent_uids": [ + "Assertions Example", + "Assertions Test", + "SampleSuite" + ], + "timer": { + "run": { + "end": "2020-01-10T03:06:59.116938+00:00", + "start": "2020-01-10T03:06:59.111312+00:00" + } + }, + "hash": 3253704292606433761, + "runtime_status": "finished", + "name": "test_fix_namespace", + "status_override": null, + "tags": {}, + "entries": [ + { + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "type": "FixMatch", + "include_keys": null, + "utc_time": "2020-01-10T03:06:59.111446+00:00", + "actual_description": null, + "expected_description": null, + "comparison": [ + [ + 0, + 36, + "Passed", + [ + "int", + "6" + ], + [ + "int", + "6" + ] + ], + [ + 0, + 22, + "Passed", + [ + "int", + "5" + ], + [ + "int", + "5" + ] + ], + [ + 0, + 55, + "Passed", + [ + "int", + "2" + ], + [ + "int", + "2" + ] + ], + [ + 0, + 38, + "Passed", + [ + "int", + "5" + ], + [ + "func", + "VAL >= 4" + ] + ], + [ + 0, + 555, + "Failed", + "", + "" + ], + [ + 0, + "", + "Failed", + "", + "" + ], + [ + 1, + 600, + "Passed", + [ + "str", + "A" + ], + [ + "str", + "A" + ] + ], + [ + 1, + 601, + "Failed", + [ + "str", + "A" + ], + [ + "str", + "B" + ] + ], + [ + 1, + 683, + "Passed", + "", + "" + ], + [ + 1, + "", + "Passed", + "", + "" + ], + [ + 2, + 688, + "Passed", + [ + "str", + "a" + ], + [ + "str", + "a" + ] + ], + [ + 2, + 689, + "Passed", + [ + "str", + "a" + ], + [ + "REGEX", + "[a-z]" + ] + ], + [ + 1, + "", + "Passed", + "", + "" + ], + [ + 2, + 688, + "Passed", + [ + "str", + "b" + ], + [ + "str", + "b" + ] + ], + [ + 2, + 689, + "Passed", + [ + "str", + "b" + ], + [ + "str", + "b" + ] + ], + [ + 0, + "", + "Failed", + "", + "" + ], + [ + 1, + 600, + "Failed", + [ + "str", + "B" + ], + [ + "str", + "C" + ] + ], + [ + 1, + 601, + "Passed", + [ + "str", + "B" + ], + [ + "str", + "B" + ] + ], + [ + 1, + 683, + "Passed", + "", + "" + ], + [ + 1, + "", + "Passed", + "", + "" + ], + [ + 2, + 688, + "Passed", + [ + "str", + "c" + ], + [ + "str", + "c" + ] + ], + [ + 2, + 689, + "Passed", + [ + "str", + "c" + ], + [ + "func", + "VAL in ('c', 'd')" + ] + ], + [ + 1, + "", + "Passed", + "", + "" + ], + [ + 2, + 688, + "Passed", + [ + "str", + "d" + ], + [ + "str", + "d" + ] + ], + [ + 2, + 689, + "Passed", + [ + "str", + "d" + ], + [ + "str", + "d" + ] + ] + ], + "passed": false, + "machine_time": "2020-01-10T11:06:59.111452+00:00", + "exclude_keys": null, + "line_no": 708 + }, + { + "absent_keys_diff": [ + 555 + ], + "category": "DEFAULT", + "description": null, + "meta_type": "assertion", + "has_keys_diff": [ + 26, + 11 + ], + "type": "FixCheck", + "utc_time": "2020-01-10T03:06:59.113689+00:00", + "passed": false, + "absent_keys": [ + 444, + 555 + ], + "machine_time": "2020-01-10T11:06:59.113697+00:00", + "line_no": 716, + "has_keys": [ + 26, + 22, + 11 + ] + }, + { + "category": "DEFAULT", + "description": null, + "meta_type": "entry", + "type": "FixLog", + "utc_time": "2020-01-10T03:06:59.115483+00:00", + "flattened_dict": [ + [ + 0, + 36, + [ + "int", + "6" + ] + ], + [ + 0, + 22, + [ + "int", + "5" + ] + ], + [ + 0, + 55, + [ + "int", + "2" + ] + ], + [ + 0, + 38, + [ + "int", + "5" + ] + ], + [ + 0, + 555, + "" + ], + [ + 0, + "", + "" + ], + [ + 1, + 556, + [ + "str", + "USD" + ] + ], + [ + 1, + 624, + [ + "int", + "1" + ] + ], + [ + 0, + "", + "" + ], + [ + 1, + 556, + [ + "str", + "EUR" + ] + ], + [ + 1, + 624, + [ + "int", + "2" + ] + ] + ], + "machine_time": "2020-01-10T11:06:59.115490+00:00", + "line_no": 729 + } + ] + }, + { + "category": "testcase", + "logs": [], + "description": null, + "suite_related": false, + "counter": { + "passed": 1, + "failed": 0, + "total": 1 + }, + "status_reason": null, + "type": "TestCaseReport", + "uid": "52a8a7d9-80e6-4f7f-8eef-065bb25d38f8", + "status": "passed", + "parent_uids": [ + "Assertions Example", + "Assertions Test", + "SampleSuite" + ], + "timer": { + "run": { + "end": "2020-01-10T03:06:59.129247+00:00", + "start": "2020-01-10T03:06:59.123570+00:00" + } + }, + "hash": -5041530229790182508, + "runtime_status": "finished", + "name": "test_xml_namespace", + "status_override": null, + "tags": {}, + "entries": [ + { + "category": "DEFAULT", + "description": "Simple XML check for existence of xpath.", + "meta_type": "assertion", + "type": "XMLCheck", + "utc_time": "2020-01-10T03:06:59.123813+00:00", + "namespaces": null, + "data": [], + "passed": true, + "xml": "\n Foo\n \n", + "machine_time": "2020-01-10T11:06:59.123821+00:00", + "tags": null, + "line_no": 751, + "message": "xpath: `/Root/Test` exists in the XML.", + "xpath": "/Root/Test" + }, + { + "category": "DEFAULT", + "description": "XML check for tags in the given xpath.", + "meta_type": "assertion", + "type": "XMLCheck", + "utc_time": "2020-01-10T03:06:59.125438+00:00", + "namespaces": null, + "data": [ + [ + "Value1", + null, + null, + null + ], + [ + "Value2", + null, + null, + null + ] + ], + "passed": true, + "xml": "\n Value1\n Value2\n \n", + "machine_time": "2020-01-10T11:06:59.125447+00:00", + "tags": [ + "Value1", + "Value2" + ], + "line_no": 765, + "message": null, + "xpath": "/Root/Test" + }, + { + "category": "DEFAULT", + "description": "XML check with namespace matching.", + "meta_type": "assertion", + "type": "XMLCheck", + "utc_time": "2020-01-10T03:06:59.127250+00:00", + "namespaces": { + "a": "http://testplan" + }, + "data": [ + [ + "Hello world!", + null, + null, + "REGEX(Hello*)" + ] + ], + "passed": true, + "xml": "\n \n \n Hello world!\n \n \n", + "machine_time": "2020-01-10T11:06:59.127259+00:00", + "tags": [ + "re.compile('Hello*')" + ], + "line_no": 784, + "message": null, + "xpath": "//*/a:message" + } + ] + } + ] + } + ] + } + ] +} + + +export { + TESTPLAN_REPORT, + fakeReportAssertions, +} diff --git a/testplan/web_ui/testing/src/__tests__/documents/TESTPLAN_REPORT_1.json b/testplan/web_ui/testing/src/Common/sampleReports.js similarity index 61% rename from testplan/web_ui/testing/src/__tests__/documents/TESTPLAN_REPORT_1.json rename to testplan/web_ui/testing/src/Common/sampleReports.js index 74bbf5f7e..52f705b56 100644 --- a/testplan/web_ui/testing/src/__tests__/documents/TESTPLAN_REPORT_1.json +++ b/testplan/web_ui/testing/src/Common/sampleReports.js @@ -1,4 +1,7 @@ -{ +/** + * Sample Testplan reports to be used in development & testing. + */ +const TESTPLAN_REPORT = { "category": "testplan", "name": "Sample Testplan", "status": "failed", @@ -23,9 +26,7 @@ "type": "TestGroupReport", "logs": [], "tags": { - "simple": [ - "server" - ] + "simple": ["server"] }, "timer": { "run": { @@ -44,9 +45,7 @@ "type": "TestGroupReport", "logs": [], "tags": { - "simple": [ - "server" - ] + "simple": ["server"] }, "timer": { "run": { @@ -56,7 +55,7 @@ }, "entries": [ { - "category": "testcase", + "category": 'testcase', "name": "test_equality_passing", "status": "passed", "status_override": null, @@ -65,9 +64,7 @@ "type": "TestCaseReport", "logs": [], "tags": { - "colour": [ - "white" - ] + "colour": ["white"] }, "timer": { "run": { @@ -89,10 +86,10 @@ "utc_time": "2018-10-15T14:30:11.010094+00:00", "first": 1 } - ] + ], }, { - "category": "testcase", + "category": 'testcase', "name": "test_equality_passing2", "status": "failed", "tags": {}, @@ -121,9 +118,9 @@ "utc_time": "2018-10-15T14:30:11.510094+00:00", "first": 1 } - ] - } - ] + ], + }, + ], }, { "status": "passed", @@ -135,9 +132,7 @@ "type": "TestGroupReport", "logs": [], "tags": { - "simple": [ - "client" - ] + "simple": ["client"] }, "timer": { "run": { @@ -147,7 +142,7 @@ }, "entries": [ { - "category": "testcase", + "category": 'testcase', "name": "test_equality_passing", "status": "passed", "tags": {}, @@ -176,11 +171,11 @@ "utc_time": "2018-10-15T14:30:11.010094+00:00", "first": 1 } - ] - } - ] - } - ] + ], + }, + ], + }, + ], }, { "name": "Secondary", @@ -217,7 +212,7 @@ }, "entries": [ { - "category": "testcase", + "category": 'testcase', "name": "test_equality_passing", "status": "passed", "tags": {}, @@ -246,11 +241,171 @@ "utc_time": "2018-10-15T14:30:12.010094+00:00", "first": 1 } - ] - } - ] + ], + }, + ], } - ] + ], + }, + ], +}; + +// Simple report that only contains one MultiTest and one suite. +const SIMPLE_REPORT = { + "category": "testplan", + "name": "Sample Testplan", + "status": "failed", + "uid": "520a92e4-325e-4077-93e6-55d7091a3f83", + "tags_index": {}, + "status_override": null, + "meta": {}, + "timer": { + "run": { + "start": "2018-10-15T14:30:10.998071+00:00", + "end": "2018-10-15T14:30:11.296158+00:00" } - ] + }, + "entries": [{ + "name": "Primary", + "status": "failed", + "category": "multitest", + "description": null, + "status_override": null, + "uid": "21739167-b30f-4c13-a315-ef6ae52fd1f7", + "type": "TestGroupReport", + "logs": [], + "tags": { + "simple": ["server"] + }, + "timer": { + "run": { + "start": "2018-10-15T14:30:11.009705+00:00", + "end": "2018-10-15T14:30:11.159661+00:00" + } + }, + "entries": [{ + "status": "failed", + "category": "testsuite", + "name": "AlphaSuite", + "status_override": null, + "description": null, + "uid": "cb144b10-bdb0-44d3-9170-d8016dd19ee7", + "type": "TestGroupReport", + "logs": [], + "tags": { + "simple": ["server"] + }, + "timer": { + "run": { + "start": "2018-10-15T14:30:11.009872+00:00", + "end": "2018-10-15T14:30:11.158224+00:00" + } + }, + "entries": [{ + "category": 'testcase', + "name": "test_equality_passing", + "status": "passed", + "status_override": null, + "description": null, + "uid": "736706ef-ba65-475d-96c5-f2855f431028", + "type": "TestCaseReport", + "logs": [], + "tags": { + "colour": ["white"] + }, + "timer": { + "run": { + "start": "2018-10-15T14:30:11.010072+00:00", + "end": "2018-10-15T14:30:11.132214+00:00" + } + }, + "entries": [{ + "category": "DEFAULT", + "machine_time": "2018-10-15T15:30:11.010098+00:00", + "description": "passing equality", + "line_no": 24, + "label": "==", + "second": 1, + "meta_type": "assertion", + "passed": true, + "type": "Equal", + "utc_time": "2018-10-15T14:30:11.010094+00:00", + "first": 1 + }], + }], + }], + }], +}; + +/** + * Fake interactive report. All entries start with a status of "ready". + */ +const FakeInteractiveReport = { + counter: {passed: 0, failed: 0}, + entries: [{ + counter: {passed: 0, failed: 0}, + category: "multitest", + description: null, + entries: [{ + counter: {passed: 0, failed: 0}, + category: "testsuite", + description: null, + entries: [{ + counter: {passed: 0, failed: 0}, + description: null, + entries: [], + logs: [], + name: "test_interactive", + name_type_index: [], + status: 'unknown', + runtime_status: 'ready', + status_override: null, + suite_related: false, + tags: {}, + tags_index: {}, + timer: {}, + type: "TestCaseReport", + uid: "ddd", + }], + logs: [], + name: "Interactive Suite", + name_type_index: [], + part: null, + status: 'unknown', + runtime_status: 'ready', + status_override: null, + tags: {}, + tags_index: {}, + timer: {}, + type: "TestGroupReport", + uid: "ccc", + }], + logs: [], + name: "Interactive MTest", + name_type_index: [], + part: null, + status: 'unknown', + runtime_status: 'ready', + status_override: null, + tags: {}, + tags_index: {}, + timer: {}, + type: "TestGroupReport", + uid: "bbb", + }], + meta: {}, + name: "Fake Interactive Report", + name_type_index: [], + status: 'unknown', + runtime_status: 'ready', + status_override: null, + tags_index: {}, + timer: null, + uid: "aaa", +}; + +export { + TESTPLAN_REPORT, + SIMPLE_REPORT, + FakeInteractiveReport, } diff --git a/testplan/web_ui/testing/src/__tests__/testUtils.js b/testplan/web_ui/testing/src/Common/testUtils.js similarity index 99% rename from testplan/web_ui/testing/src/__tests__/testUtils.js rename to testplan/web_ui/testing/src/Common/testUtils.js index 0fd52b323..d0ac0326a 100644 --- a/testplan/web_ui/testing/src/__tests__/testUtils.js +++ b/testplan/web_ui/testing/src/Common/testUtils.js @@ -6,8 +6,8 @@ * {@link './../Common/utils.js'} and reexport it from here. */ import _ from 'lodash'; -import uriComponentCodec from '../Common/uriComponentCodec'; -export { reverseMap } from '../Common/utils'; +import uriComponentCodec from './uriComponentCodec'; +export { reverseMap } from './utils'; // `react-scripts test` sets NODE_ENV to "test". This module shouldn't be // used at any other time. Thus we'll throw an error to ensure this. diff --git a/testplan/web_ui/testing/src/Common/uriComponentCodec.js b/testplan/web_ui/testing/src/Common/uriComponentCodec.js index 33f2544c9..b273cf300 100644 --- a/testplan/web_ui/testing/src/Common/uriComponentCodec.js +++ b/testplan/web_ui/testing/src/Common/uriComponentCodec.js @@ -15,11 +15,12 @@ const originalToPctEscapedMap = new Map([ '&', "'", '(', ')', '=', ';', '+', '*', ',', ].map(c => [ c, `%${c.charCodeAt(0).toString(16)}` ])); -const mkTranslator = charmap => uriComponent => ( - [ charmap ].reduce((prevStr, [oldChar, newChar]) => - prevStr.replace(RegExp(_escapeRegExp(oldChar), 'g'), newChar), - uriComponent - ) +const mkTranslator = charmap => uriComponent => [ ...charmap ].reduce( + (prevStr, [oldChar, newChar]) => prevStr.replace( + RegExp(_escapeRegExp(oldChar), 'g'), + newChar + ), + uriComponent ); export default { diff --git a/testplan/web_ui/testing/src/Common/utils.js b/testplan/web_ui/testing/src/Common/utils.js index ebc5c10be..60475bcba 100644 --- a/testplan/web_ui/testing/src/Common/utils.js +++ b/testplan/web_ui/testing/src/Common/utils.js @@ -1,7 +1,7 @@ /** * Common utility functions. */ -import { NAV_ENTRY_DISPLAY_DATA } from './defaults'; +import {NAV_ENTRY_DISPLAY_DATA} from "./defaults"; /** * Get the data to be used when displaying the nav entry. @@ -9,8 +9,8 @@ import { NAV_ENTRY_DISPLAY_DATA } from './defaults'; * @param {object} entry - nav entry. * @returns {Object} */ -export function getNavEntryDisplayData(entry) { - const metadata = {}; +function getNavEntryDisplayData(entry) { + let metadata = {}; for (const attribute of NAV_ENTRY_DISPLAY_DATA) { if (entry.hasOwnProperty(attribute)) { metadata[attribute] = entry[attribute]; @@ -25,7 +25,13 @@ export function getNavEntryDisplayData(entry) { * @param iterable * @returns {boolean} */ -export const any = iterable => Array.from(iterable).some(e => !!e); +function any(iterable) { + for (let index = 0; index < iterable.length; ++index) { + if (iterable[index]) return true; + } + + return false; +} /** * Returns a sorted array of the given iterable. @@ -35,9 +41,9 @@ export const any = iterable => Array.from(iterable).some(e => !!e); * @param {boolean} reverse - if true, the sorted list is reversed * @returns {Array} */ -export function sorted(iterable, key=(item) => (item), reverse=false) { +function sorted(iterable, key=(item) => (item), reverse=false) { return iterable.sort((firstMember, secondMember) => { - const reverser = reverse ? 1 : -1; + let reverser = reverse ? 1 : -1; return ((key(firstMember) < key(secondMember)) ? reverser @@ -52,7 +58,7 @@ export function sorted(iterable, key=(item) => (item), reverse=false) { * Example: "id-so7567s1pcpojemi" * @returns {string} */ -export function uniqueId() { +function uniqueId() { return 'id-' + Math.random().toString(36).substr(2, 16); } @@ -61,8 +67,8 @@ export function uniqueId() { * @param {string} str - string that generate hash code * @returns {number} */ -export function hashCode(str) { - let hash = 0, i, chr, len; +function hashCode(str) { + var hash = 0, i, chr, len; if (str.length === 0) return hash; for (i = 0, len = str.length; i < len; i++) { chr = str.charCodeAt(i); @@ -77,12 +83,21 @@ export function hashCode(str) { * @param {object} dom - HTML DOM node * @returns {string} */ -export function domToString(dom) { - const tmp = document.createElement("div"); +function domToString(dom) { + let tmp = document.createElement("div"); tmp.appendChild(dom); return tmp.innerHTML; } +export { + getNavEntryDisplayData, + any, + sorted, + uniqueId, + hashCode, + domToString, +}; + /** * Reverses a Map. * @template T, U @@ -93,36 +108,4 @@ export const reverseMap = aMap => new Map( Array.from(aMap).map(([newVal, newKey]) => [ newKey, newVal ]) ); -export const SI_FILE_SIZES = [ - 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' -]; -export const BINARY_FILE_SIZES = [ - 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB' -]; - -/** - * Taken from {@link https://stackoverflow.com/a/14919494 this great SO answer}. - * @example - > toHumanReadableSize(5000, true) - '5.0 kB' - > toHumanReadableSize(5000, false) - '4.9 KiB' - > toHumanReadableSize(-10000000000000000000000000000) - '-8271.8 YiB' - * - * @param {number} numBytes - * @param {boolean} useSI - * @param {number} decimals - * @returns {string} - */ -export function humanReadableSize(numBytes, useSI = false, decimals = 1) { - numBytes = typeof numBytes !== 'number' ? 0 : numBytes; - decimals = typeof decimals !== 'number' ? 1 : decimals; - const div = useSI ? 10 ** 3 : 2 ** 10; - if(Math.abs(numBytes) < div) return `${numBytes} B`; - const units = useSI ? SI_FILE_SIZES : BINARY_FILE_SIZES; - const lastUnitIdx = units.length - 1; - let u = -1; - do numBytes /= div; while(Math.abs(numBytes) >= div && ++u < lastUnitIdx); - return `${numBytes.toFixed(decimals)} ${units[u]}`; -} +export const isNonemptyArray = x => Array.isArray(x) && x.length; diff --git a/testplan/web_ui/testing/src/Nav/InteractiveNavEntry.js b/testplan/web_ui/testing/src/Nav/InteractiveNavEntry.js index 53bc15116..e875562f9 100644 --- a/testplan/web_ui/testing/src/Nav/InteractiveNavEntry.js +++ b/testplan/web_ui/testing/src/Nav/InteractiveNavEntry.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Badge} from 'reactstrap'; import {StyleSheet, css} from "aphrodite"; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome/index.es'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import { faPlay, faRedo, diff --git a/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNav.test.js b/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNav.test.js index 2e7b028d5..aef9856e2 100644 --- a/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNav.test.js +++ b/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNav.test.js @@ -3,8 +3,8 @@ import React from 'react'; import {shallow} from 'enzyme'; import {StyleSheetTestUtils} from "aphrodite"; -import InteractiveNav from '../InteractiveNav'; -import { FakeInteractiveReport } from '../../__tests__/documents'; +import InteractiveNav from '../InteractiveNav.js'; +import {FakeInteractiveReport} from '../../Common/sampleReports.js'; describe('InteractiveNav', () => { beforeEach(() => { diff --git a/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNavEntry.test.js b/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNavEntry.test.js index 898eab498..9d84e82b1 100644 --- a/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNavEntry.test.js +++ b/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNavEntry.test.js @@ -2,9 +2,10 @@ import React from 'react'; import {shallow} from 'enzyme'; import {StyleSheetTestUtils} from "aphrodite"; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome/index.es'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import InteractiveNavEntry from '../InteractiveNavEntry'; +import InteractiveNavEntry from '../InteractiveNavEntry.js'; +import {FakeInteractiveReport} from '../../Common/sampleReports.js'; describe('InteractiveNavEntry', () => { beforeEach(() => { diff --git a/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNavList.test.js b/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNavList.test.js index c624c6243..16826c8ac 100644 --- a/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNavList.test.js +++ b/testplan/web_ui/testing/src/Nav/__tests__/InteractiveNavList.test.js @@ -3,8 +3,8 @@ import React from 'react'; import {shallow} from 'enzyme'; import {StyleSheetTestUtils} from "aphrodite"; -import InteractiveNavList from '../InteractiveNavList'; -import { FakeInteractiveReport } from '../../__tests__/documents'; +import InteractiveNavList from '../InteractiveNavList.js'; +import {FakeInteractiveReport} from '../../Common/sampleReports.js'; describe('InteractiveNavList', () => { beforeEach(() => { diff --git a/testplan/web_ui/testing/src/Nav/__tests__/Nav.test.js b/testplan/web_ui/testing/src/Nav/__tests__/Nav.test.js index 96715a118..ef4892fa0 100644 --- a/testplan/web_ui/testing/src/Nav/__tests__/Nav.test.js +++ b/testplan/web_ui/testing/src/Nav/__tests__/Nav.test.js @@ -1,12 +1,13 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import {shallow} from 'enzyme'; import {StyleSheetTestUtils} from "aphrodite"; + import Nav from '../Nav'; -import { TESTPLAN_REPORT_1 as REPORT } from '../../__tests__/documents'; +import {TESTPLAN_REPORT} from '../../Common/sampleReports'; const defaultProps = { - report: REPORT, - selected: [REPORT], + report: TESTPLAN_REPORT, + selected: [TESTPLAN_REPORT], }; describe('Nav', () => { diff --git a/testplan/web_ui/testing/src/Nav/__tests__/navUtils.test.js b/testplan/web_ui/testing/src/Nav/__tests__/navUtils.test.js index d84408ee8..ff43fc744 100644 --- a/testplan/web_ui/testing/src/Nav/__tests__/navUtils.test.js +++ b/testplan/web_ui/testing/src/Nav/__tests__/navUtils.test.js @@ -1,6 +1,8 @@ +import React from 'react'; import {StyleSheetTestUtils} from "aphrodite"; + import {CreateNavButtons, GetSelectedUid} from '../navUtils'; -import { TESTPLAN_REPORT_1 as REPORT } from '../../__tests__/documents'; +import {TESTPLAN_REPORT} from '../../Common/sampleReports'; describe('navUtils', () => { @@ -23,11 +25,11 @@ describe('navUtils', () => { displayTime: false, displayEmpty: true, handleNavClick: jest.fn(), - entries: REPORT.entries, + entries: TESTPLAN_REPORT.entries, filter: null, } const createEntryComponent = jest.fn(); - const selectedUid = REPORT.uid; + const selectedUid = TESTPLAN_REPORT.uid; const navButtons = CreateNavButtons( props, createEntryComponent, selectedUid @@ -39,9 +41,9 @@ describe('navUtils', () => { describe('GetSelectedUid', () => { it('gets the selected UID', () => { - const selected = [REPORT]; + const selected = [TESTPLAN_REPORT]; const uid = GetSelectedUid(selected); - expect(uid).toBe(REPORT.uid); + expect(uid).toBe(TESTPLAN_REPORT.uid); }); }); diff --git a/testplan/web_ui/testing/src/Nav/navUtils.js b/testplan/web_ui/testing/src/Nav/navUtils.js index 5144aa6e7..2cde66b6a 100644 --- a/testplan/web_ui/testing/src/Nav/navUtils.js +++ b/testplan/web_ui/testing/src/Nav/navUtils.js @@ -148,8 +148,8 @@ const GetSelectedUid = (selected) => { * we do not drill down any further and instead display all entries in the * suite that testcase belongs to. * - * @param {Array} selected - Current selection hierarchy. - * @return {Array} Report nodes to display in the navigation + * @param {Array[ReportNode]} selected - Current selection hierarchy. + * @return {Array[ReportNode]} Report nodes to display in the navigation * column. */ const GetNavEntries = (selected) => { @@ -178,8 +178,8 @@ const GetNavEntries = (selected) => { * this is just the selection hierarchy. As a special case, when a testcase * is selected, we only display up to the suite level in the breadcrumb bar. * - * @param {Array} selected - Current selection hierarchy. - * @return {Array} Report nodes to display in the breadcrumb bar. + * @param {Array[ReportNode]} selected - Current selection hierarchy. + * @return {Array[ReportNode]} Report nodes to display in the breadcrumb bar. */ const GetNavBreadcrumbs = (selected) => { const selectedEntry = selected[selected.length - 1]; diff --git a/testplan/web_ui/testing/src/Report/BatchReport.js b/testplan/web_ui/testing/src/Report/BatchReport.js new file mode 100644 index 000000000..29bf4870d --- /dev/null +++ b/testplan/web_ui/testing/src/Report/BatchReport.js @@ -0,0 +1,235 @@ +import React from 'react'; +import {StyleSheet, css} from 'aphrodite'; +import axios from 'axios'; + +import Toolbar from '../Toolbar/Toolbar'; +import {TimeButton} from '../Toolbar/Buttons'; +import Nav from '../Nav/Nav'; +import { + PropagateIndices, + UpdateSelectedState, + GetReportState, + GetCenterPane, + GetSelectedEntries, +} from "./reportUtils"; +import {COLUMN_WIDTH} from "../Common/defaults"; +import {fakeReportAssertions} from "../Common/fakeReport"; + +/** + * BatchReport component: + * * fetch Testplan report. + * * display messages when loading report or error in report. + * * render toolbar, nav & assertion components. + */ +class BatchReport extends React.Component { + constructor(props) { + super(props); + this.handleNavFilter = this.handleNavFilter.bind(this); + this.updateFilter = this.updateFilter.bind(this); + this.updateTagsDisplay = this.updateTagsDisplay.bind(this); + this.updateTimeDisplay = this.updateTimeDisplay.bind(this); + this.updateDisplayEmpty = this.updateDisplayEmpty.bind(this); + this.handleNavClick = this.handleNavClick.bind(this); + this.handleColumnResizing = this.handleColumnResizing.bind(this); + + this.state = { + navWidth: `${COLUMN_WIDTH}em`, + report: null, + testcaseUid: null, + loading: false, + error: null, + filter: null, + displayTags: false, + displayTime: false, + displayEmpty: true, + selectedUIDs: [], + }; + } + + /** + * Fetch the Testplan report. + * * Get the UID from the URL. + * * Handle UID errors. + * * Make a GET request for the Testplan report. + * * Handle error response. + * @public + */ + getReport() { + // Inspect the UID to determine the report to render. As a special case, + // we will display a fake report for development purposes. + const uid = this.props.match.params.uid; + if (uid === "_dev") { + const processedReport = PropagateIndices(fakeReportAssertions); + setTimeout( + () => this.setState({ + report: processedReport, + selectedUIDs: this.autoSelect(processedReport), + loading: false, + }), + 1500); + } else { + axios.get(`/api/v1/reports/${uid}`) + .then(response => { + const processedReport = PropagateIndices(response.data); + this.setState({ + report: processedReport, + selectedUIDs: this.autoSelect(processedReport), + loading: false, + }); + }) + .catch(error => this.setState({ + error: error, + loading: false, + })); + } + } + + /** + * Auto-select an entry in the report when it is first loaded. + * @param {reportNode} reportEntry - the current report entry to select from. + * @return {Array[string]} List of UIDs of the currently selected entries. + */ + autoSelect(reportEntry) { + const selection = [reportEntry.uid]; + + // If the current report entry has only one child entry and that entry is + // not a testcase, we automatically expand it. + if ((reportEntry.entries.length === 1) && + (reportEntry.entries[0].category!== "testcase")) { + return selection.concat(this.autoSelect(reportEntry.entries[0])); + } else { + return selection; + } + } + + /** + * Fetch the Testplan report once the component has mounted. + * @public + */ + componentDidMount() { + this.setState({loading: true}, this.getReport); + } + + /** + * Handle filter expressions being typed into the filter box. Placeholder. + * + * @param {Object} e - keyup event. + * @public + */ + handleNavFilter(e) { // eslint-disable-line no-unused-vars + // Save expressions to state. + } + + /** + * Update the global filter state of the entry. + * + * @param {string} filter - null, all, pass or fail. + * @public + */ + updateFilter(filter) { + this.setState({filter: filter}); + } + + /** + * Update tag display of each navigation entry. + * + * @param {boolean} displayTags. + * @public + */ + updateTagsDisplay(displayTags) { + this.setState({displayTags: displayTags}); + } + + /** + * Update navigation pane to show/hide entries of empty testcases. + * + * @param {boolean} displayEmpty. + * @public + */ + updateDisplayEmpty(displayEmpty) { + this.setState({displayEmpty: displayEmpty}); + } + + /** + * Update execution time display of each navigation entry and each assertion. + * + * @param {boolean} displayTime. + * @public + */ + updateTimeDisplay(displayTime) { + this.setState({displayTime: displayTime}); + } + + /** + * Handle resizing event and update NavList & Center Pane. + */ + handleColumnResizing(navWidth) { + this.setState({navWidth: navWidth}); + } + + /** + * Handle a navigation entry being clicked. + */ + handleNavClick(e, entry, depth) { + e.stopPropagation(); + this.setState((state, props) => UpdateSelectedState(state, entry, depth)); + } + + render() { + const {reportStatus, reportFetchMessage} = GetReportState(this.state); + + if (this.state.report && this.state.report.name) { + window.document.title = this.state.report.name; + } + + const selectedEntries = GetSelectedEntries( + this.state.selectedUIDs, this.state.report + ); + const centerPane = GetCenterPane( + this.state, + reportFetchMessage, + this.props.match.params.uid, + selectedEntries, + ); + + return ( +
+ ]} + /> +
+ ); + } +} + +const styles = StyleSheet.create({ + batchReport: { + /** overflow will hide dropdown div */ + // overflow: 'hidden' + } +}); + +export default BatchReport; diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/AutoSelectRedirect.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/AutoSelectRedirect.jsx deleted file mode 100644 index 0d1e50361..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/AutoSelectRedirect.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { Redirect } from 'react-router'; -import connect from 'react-redux/es/connect/connect'; -import { mkGetUIDoAutoSelect } from '../state/uiSelectors'; - -const connector = connect( - () => { - const getDoAutoSelect = mkGetUIDoAutoSelect(); - return state => ({ - doAutoSelect: getDoAutoSelect(state), - }); - }, - null, - (stateProps, _, ownProps) => { - const { doAutoSelect } = stateProps; - const { basePath } = ownProps; - let { entry } = ownProps; - // trim trailing slashes from basePath and join with the first entry's name - let toPath = `${basePath.replace(/\/+$/, '')}/${entry.name || ''}`; - if(doAutoSelect) { - while(entry.category !== 'testcase' - && Array.isArray(entry.entries) - && entry.entries.length === 1 - && typeof (entry = entry.entries[0] || {}) === 'object' - && typeof (entry.name) === 'string' - ) { - toPath = `${toPath}/${entry.name}`; - } - } - return { - toPath, - }; - }, -); - -export default connector(({ toPath }) => ( - -)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/BoundStyledListGroupItemLink.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/BoundStyledListGroupItemLink.jsx deleted file mode 100644 index c8f516d8b..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/BoundStyledListGroupItemLink.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import { withRouter } from 'react-router'; -import connect from 'react-redux/es/connect/connect'; -import { mkGetUIIsShowTags } from '../state/uiSelectors'; -import { setHashComponentAlias } from '../state/uiActions'; -import { setSelectedTestCase } from '../state/uiActions'; -import { BOTTOMMOST_ENTRY_CATEGORY } from '../../../Common/defaults'; -import { uriComponentCodec } from '../utils'; -import StyledListGroupItemLink from './StyledListGroupItemLink'; -import TagList from '../../../Nav/TagList'; -import NavEntry from '../../../Nav/NavEntry'; - -const connector = connect( - () => { - const getIsShowTags = mkGetUIIsShowTags(); - return state => ({ - isShowTags: getIsShowTags(state), - }); - }, - { - setSelectedTestCase, - setHashComponentAlias, - }, - (stateProps, dispatchProps, ownProps) => { - const { isShowTags } = stateProps; - const { setSelectedTestCase, setHashComponentAlias } = dispatchProps; - const { entry, idx, nPass, nFail, match: { url: matchedUrl } } = ownProps; - return { - isShowTags, - setSelectedTestCase, - setHashComponentAlias, - entry, - idx, - nPass, - nFail, - matchedUrl, - }; - } -); - -export default connector(withRouter(({ - entry, idx, nPass, nFail, isShowTags, setHashComponentAlias, - setSelectedTestCase, matchedUrl -}) => { - const { name, status, category, tags, uid } = entry, - isBottommost = category === BOTTOMMOST_ENTRY_CATEGORY, - encodedName = uriComponentCodec.encode(name), - nextPathname = isBottommost ? matchedUrl : `${matchedUrl}/${encodedName}`, - onClickOverride = !isBottommost ? { - onClick() { setSelectedTestCase(null); }, - } : { - onClick(evt) { - evt.preventDefault(); - evt.stopPropagation(); - setSelectedTestCase(entry); - }, - }; - setHashComponentAlias(encodedName, name); - return ( - - { - isShowTags && tags - ? - : null - } - - - ); -})); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/CenterPane/Placeholder.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/CenterPane/Placeholder.jsx deleted file mode 100644 index b5ba94c9c..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/CenterPane/Placeholder.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import Progress from 'reactstrap/lib/Progress'; -import connect from 'react-redux/es/connect/connect'; -import _debounce from 'lodash/debounce'; -import _isObject from 'lodash/isObject'; -import { - mkGetReportDownloadProgress, - mkGetReportIsFetching, - mkGetReportLastFetchError, - mkGetReportDocument, -} from '../../state/reportSelectors'; -import Message from '../../../../Common/Message'; -import { humanReadableSize } from '../../../../Common/utils'; -import { COLUMN_WIDTH } from '../../../../Common/defaults'; - -const STARTING_MSG = 'Waiting to fetch Testplan report...'; -const FETCHING_MSG = 'Fetching Testplan report...'; -const FINISHED_MSG = 'Please select an entry.'; -const ERRORED_PREFIX_MSG = 'Error fetching Testplan report.'; - -const MessageStyled = props => ( - -); - -const connector = connect( - () => { - const getDocument = mkGetReportDocument(); - const getIsFetching = mkGetReportIsFetching(); - const getError = mkGetReportLastFetchError(); - const getProgress = _debounce(mkGetReportDownloadProgress(), 100); - return state => ({ - progress: getProgress(state), - isFetching: getIsFetching(state), - error: getError(state), - document: getDocument(state), - }); - }, -); - -export default connector(({ progress, isFetching, error, document }) => { - if(!isFetching && error) { - const sfx = (typeof error === 'object' ? error.message : 0) || ''; - return ( - - ); - } - if(isFetching) { - if(progress.lengthComputable) { - const pct = 100 * progress.loaded / progress.total; - const color = pct > 99 ? 'success' : 'info'; - const pctStr = pct.toFixed(1).padStart(5); - const MessageFilled = () => ( - <> -

{`${FETCHING_MSG} ${pctStr}%`}

- - - { - `${humanReadableSize(progress.loaded)}` + - ` / ` + - `${humanReadableSize(progress.total)}` - } - - - - ); - return (); - } - return (); - } - if(!isFetching && !error && _isObject(document)) { - return (); - } - return (); -}); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/CenterPane/index.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/CenterPane/index.jsx deleted file mode 100644 index 31aef8fa4..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/CenterPane/index.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import connect from 'react-redux/es/connect/connect'; -import { createSelector } from '@reduxjs/toolkit/dist/redux-toolkit.esm'; -import { mkGetUIFilter } from '../../state/uiSelectors'; -import { mkGetUISelectedTestCase } from '../../state/uiSelectors'; -import { mkGetReportDocument } from '../../state/reportSelectors'; -import AssertionPane from '../../../../AssertionPane/AssertionPane'; -import { COLUMN_WIDTH } from '../../../../Common/defaults'; -import Placeholder from './Placeholder'; - -const isNonemptyArray = v => Array.isArray(v) && v.length > 0; - -const connector = connect( - () => { - const getFilter = mkGetUIFilter(); - const getSelectedTestCase = createSelector( - mkGetUISelectedTestCase(), - tc => tc || {} - ); - const getDocument = createSelector( - mkGetReportDocument(), - doc => doc || {} - ); - return state => { - return { - filter: getFilter(state), - selectedTestCase: getSelectedTestCase(state), - document: getDocument(state), - leftWidth: `${(COLUMN_WIDTH || 0) + 1.5}`, - }; - }; - }, -); - -export default connector(({ - selectedTestCase, document, filter, leftWidth -}) => { - const { uid: reportUID } = document; - const { uid: testcaseUID, logs, entries, description } = selectedTestCase; - const descriptionEntries = React.useMemo( - () => isNonemptyArray(description) ? description : [ description ], - [ description ] - ); - if(isNonemptyArray(entries) || isNonemptyArray(entries)) { - return ( - - ); - } - return ; -}); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/DocumentationButton.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/DocumentationButton.jsx deleted file mode 100644 index 0748d8398..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/DocumentationButton.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import NavItem from 'reactstrap/lib/NavItem'; -import { css } from 'aphrodite/es'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome/index.es'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faBook } from '@fortawesome/free-solid-svg-icons'; -import connect from 'react-redux/es/connect/connect'; -import { mkGetDocumentationURL } from '../../../state/appSelectors'; -import navStyles from '../../../Toolbar/navStyles'; - -library.add(faBook); - -const connector = connect( - () => { - const getDocsURL = mkGetDocumentationURL(); - return state => ({ - docsURL: getDocsURL(state), - docsIconName: faBook.iconName, - docsIconClasses: css(navStyles.toolbarButton), - docsAnchorClasses: css(navStyles.buttonsBar), - }); - }, -); - -export default connector(({ - docsURL, docsIconName, docsIconClasses, docsAnchorClasses -}) => ( - - - - - -)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/EmptyListGroupItem.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/EmptyListGroupItem.jsx deleted file mode 100644 index 06bb2720a..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/EmptyListGroupItem.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import ListGroupItem from 'reactstrap/lib/ListGroupItem'; -import { css } from 'aphrodite/es'; -import { navUtilsStyles } from '../style'; - -const lgiClasses = css(navUtilsStyles.navButton); - -export default () => ( - - No entries to display... - -); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/FilterButton.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/FilterButton.jsx deleted file mode 100644 index 17766162b..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/FilterButton.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import DropdownItem from 'reactstrap/lib/DropdownItem'; -import DropdownMenu from 'reactstrap/lib/DropdownMenu'; -import DropdownToggle from 'reactstrap/lib/DropdownToggle'; -import UncontrolledDropdown from 'reactstrap/lib/UncontrolledDropdown'; -import { css } from 'aphrodite/es'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome/index.es'; -import { faFilter } from '@fortawesome/free-solid-svg-icons'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import connect from 'react-redux/es/connect/connect'; -import navStyles from '../../../Toolbar/navStyles'; -import FilterRadioButton from './FilterRadioButton'; -import DisplayEmptyCheckBox from './DisplayEmptyCheckBox'; -import * as filterStates from '../../../Common/filterStates'; - -library.add(faFilter); - -const connector = connect( - () => ({ - buttonsBarClasses: css(navStyles.buttonsBar), - filterIconName: faFilter.iconName, - dropdownButtonClasses: css(navStyles.toolbarButton), - filterDropdownClasses: css(navStyles.filterDropdown), - filter_ALL: filterStates.ALL, - filter_FAILED: filterStates.FAILED, - filter_PASSED: filterStates.PASSED, - }), -); - -export default connector(({ - toolbarStyle, buttonsBarClasses, filterIconName, dropdownButtonClasses, - filterDropdownClasses, filter_ALL, filter_PASSED, filter_FAILED -}) => ( - -
- - - -
- - - - - - - -
-)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/FilterRadioButton.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/FilterRadioButton.jsx deleted file mode 100644 index 3b63b7dcb..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/FilterRadioButton.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import DropdownItem from 'reactstrap/lib/DropdownItem'; -import Input from 'reactstrap/lib/Input'; -import Label from 'reactstrap/lib/Label'; -import { css } from 'aphrodite/es'; -import connect from 'react-redux/es/connect/connect'; -import { mkGetUIFilter } from '../state/uiSelectors'; -import { setFilter } from '../state/uiActions'; -import navStyles from '../../../Toolbar/navStyles'; - -const connector = connect( - () => { - const getFilter = mkGetUIFilter(); - return state => ({ - filter: getFilter(state), - dropdownItemClasses: css(navStyles.dropdownItem), - filterLabelClasses: css(navStyles.filterLabel), - }); - }, - { - setFilter - }, - (stateProps, dispatchProps, ownProps) => { - const { filter, dropdownItemClasses, filterLabelClasses } = stateProps; - const { setFilter } = dispatchProps; - const { value, label } = ownProps; - return { - isChecked: filter === value, - onChange: evt => setFilter(evt.currentTarget.value), - value: value || '', - label: label || '', - dropdownItemClasses, - filterLabelClasses, - }; - }, -); - -export default connector(({ - isChecked, onChange, value, label, dropdownItemClasses, filterLabelClasses, -}) => ( - - - -)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/HelpButton.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/HelpButton.jsx deleted file mode 100644 index 701e89de7..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/HelpButton.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import NavItem from 'reactstrap/lib/NavItem'; -import { css } from 'aphrodite/es'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome/index.es'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; -import connect from 'react-redux/es/connect/connect'; -import { mkGetUIIsShowHelpModal } from '../state/uiSelectors'; -import { setShowHelpModal } from '../state/uiActions'; -import navStyles from '../../../Toolbar/navStyles'; - -library.add(faQuestionCircle); - -const connector = connect( - () => { - const getIsShowHelpModal = mkGetUIIsShowHelpModal(); - return state => ({ - isShowHelpModal: getIsShowHelpModal(state), - toolbarButtonClasses: css(navStyles.toolbarButton), - toolbarIconName: faQuestionCircle.iconName, - buttonsBarClasses: css(navStyles.buttonsBar), - }); - }, - { - setShowHelpModal, - }, - (stateProps, dispatchProps) => { - const { - isShowHelpModal, - toolbarButtonClasses, - toolbarIconName, - buttonsBarClasses, - } = stateProps; - const { setShowHelpModal } = dispatchProps; - return { - toolbarButtonClasses, - buttonsBarClasses, - toolbarIconName, - onClick: evt => { - evt.stopPropagation(); - setShowHelpModal(!isShowHelpModal); - }, - }; - }, -); - -/** - * Return the button which toggles the help modal. - * @returns {React.FunctionComponentElement} - */ -export default connector(({ - toolbarButtonClasses, buttonsBarClasses, toolbarIconName, onClick -}) => ( - -
- - - -
-
-)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/InfoButton.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/InfoButton.jsx deleted file mode 100644 index 84b62ebac..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/InfoButton.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import NavItem from 'reactstrap/lib/NavItem'; -import { css } from 'aphrodite/es'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome/index.es'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faInfo } from '@fortawesome/free-solid-svg-icons'; -import connect from 'react-redux/es/connect/connect'; -import { mkGetUIIsShowInfoModal } from '../state/uiSelectors'; -import { setShowInfoModal } from '../state/uiActions'; -import navStyles from '../../../Toolbar/navStyles'; - -library.add(faInfo); - -const connector = connect( - () => { - const getIsShowInfoModal = mkGetUIIsShowInfoModal(); - return state => ({ - isShowInfoModal: getIsShowInfoModal(state), - toolbarIconName: faInfo.iconName, - toolbarIconClasses: css(navStyles.toolbarButton), - buttonsBarClasses: css(navStyles.buttonsBar), - }); - }, - { - setShowInfoModal, - }, - (stateProps, dispatchProps) => { - const { - isShowInfoModal, toolbarIconName, toolbarIconClasses, buttonsBarClasses - } = stateProps; - const { setShowInfoModal } = dispatchProps; - return { - buttonsBarClasses, - toolbarIconClasses, - toolbarIconName, - toggleInfo: evt => { - evt.stopPropagation(); - setShowInfoModal(!isShowInfoModal); - }, - }; - }, -); - -export default connector(({ - buttonsBarClasses, toolbarIconClasses, toolbarIconName, toggleInfo -}) => ( - -
- - - -
-
-)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/NavBreadcrumb.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/NavBreadcrumb.jsx deleted file mode 100644 index 3ef3e4b0e..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/NavBreadcrumb.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { withRouter } from 'react-router'; -import { css } from 'aphrodite/es'; -import connect from 'react-redux/es/connect/connect'; -import { setSelectedTestCase } from '../state/uiActions'; -import StyledNavLink from './StyledNavLink'; -import { CommonStyles, navBreadcrumbStyles } from '../style'; -import NavEntry from '../../../Nav/NavEntry'; -import { safeGetNumPassedFailedErrored } from '../utils'; - -const connector = connect( - () => ({ - linkClasses: css( - navBreadcrumbStyles.breadcrumbEntry, - CommonStyles.unselectable, - ), - }), - { - setSelectedTestCase, - }, - (stateProps, dispatchProps, ownProps) => { - const { linkClasses } = stateProps; - const { setSelectedTestCase } = dispatchProps; - // `matchedUrl` is the matched Route, not necessarily the current URL - const { - entry: { - name: entryName, - status: entryStatus, - category: entryCategory, - counter: entryCounter, - uid: entryUid, - }, - match: { - url: matchedUrl, - }, - } = ownProps; - const [ - numPassed, - numFailed - ] = safeGetNumPassedFailedErrored(entryCounter, 0); - return { - linkClasses, - entryName, - entryStatus, - entryCategory, - entryUid, - numPassed, - numFailed, - matchedUrl, - onClick: () => setSelectedTestCase(null), - }; - } -); - -export default connector(withRouter(({ - linkClasses, entryName, entryStatus, entryCategory, entryUid, - numPassed, numFailed, matchedUrl, onClick, -}) => ( - - - -))); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/NavBreadcrumbContainer.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/NavBreadcrumbContainer.jsx deleted file mode 100644 index 273150123..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/NavBreadcrumbContainer.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { css } from 'aphrodite/es'; -import connect from 'react-redux/es/connect/connect'; -import { navBreadcrumbStyles } from '../style'; - -const connector = connect( - () => ({ - navBreadcrumbClasses: css(navBreadcrumbStyles.navBreadcrumbs), - breadcrumbContainerClasses: css(navBreadcrumbStyles.breadcrumbContainer), - }), - null, - (stateProps, _, ownProps) => { - const { navBreadcrumbClasses, breadcrumbContainerClasses } = stateProps; - const { children } = ownProps; - return { - navBreadcrumbClasses, - breadcrumbContainerClasses, - children: children || null, - }; - }, -); - -export default connector(({ - children, navBreadcrumbClasses, breadcrumbContainerClasses, -}) => ( -
-
    - {children} -
-
-)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/NavBreadcrumbWithNextRoute.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/NavBreadcrumbWithNextRoute.jsx deleted file mode 100644 index 880a06d09..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/NavBreadcrumbWithNextRoute.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { Route } from 'react-router'; -import { withRouter } from 'react-router'; -import connect from 'react-redux/es/connect/connect'; -import { mkGetUIHashComponentToAlias } from '../state/uiSelectors'; -import { setHashComponentAlias } from '../state/uiActions'; -import uriComponentCodec from '../../../Common/uriComponentCodec'; -import NavBreadcrumb from './NavBreadcrumb'; - -const connector = connect( - () => { - const getHashComponentToAlias = mkGetUIHashComponentToAlias(); - return state => ({ - hashComponentToAlias: getHashComponentToAlias(state), - }); - }, - { - setHashComponentAlias, - }, - (stateProps, dispatchProps, ownProps) => { - const { hashComponentToAlias } = stateProps; - const { setHashComponentAlias } = dispatchProps; - const { entries, url, match: { params: { id: encodedID } } } = ownProps; - let tgtEntry = null; - if(Array.isArray(entries)) { - let decodedID = hashComponentToAlias[encodedID]; - if(!decodedID) { - decodedID = uriComponentCodec.decode(encodedID); - setHashComponentAlias({ [decodedID]: encodedID }); - } - tgtEntry = entries.find(e => decodedID === e.name); - } - return { - tgtEntry, - url, - }; - }, -); - -const NavBreadcrumbWithNextRoute = connector(withRouter(({ tgtEntry, url }) => { - return !tgtEntry ? null : ( - <> - - - - }/> - - ); -})); - -export default NavBreadcrumbWithNextRoute; diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/NavPanes.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/NavPanes.jsx deleted file mode 100644 index 2256ecea9..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/NavPanes.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { Route } from 'react-router'; -import connect from 'react-redux/es/connect/connect'; -import { mkGetReportIsFetching } from '../state/reportSelectors'; -import { mkGetReportLastFetchError } from '../state/reportSelectors'; -import { mkGetReportDocument } from '../state/reportSelectors'; -import EmptyListGroupItem from './EmptyListGroupItem'; -import NavBreadcrumbContainer from './NavBreadcrumbContainer'; -import NavBreadcrumbWithNextRoute from './NavBreadcrumbWithNextRoute'; -import NavSidebarWithNextRoute from './NavSidebarWithNextRoute'; -import AutoSelectRedirect from './AutoSelectRedirect'; - -const connector = connect( - () => { - const getReportIsFetching = mkGetReportIsFetching(); - const getReportLastFetchError = mkGetReportLastFetchError(); - const getReportDocument = mkGetReportDocument(); - return state => { - const document = getReportDocument(state); - return { - document, - documentEntries: [ document ], - lastFetchError: getReportLastFetchError(state), - isFetching: getReportIsFetching(state), - }; - }; - }, -); - -export default connector(({ - document, documentEntries, fetchError, isFetching -}) => (isFetching || fetchError || !document) ? : ( - <> - { - /** - * Here each path component adds a new breadcrumb to the top nav, - * and it sets up the next route that will receive the next path - * component when the user navigates further - */ - } - - ( - - )}/> - - { - /** - * Here each path component completely replaces the nav sidebar. - * This contains the links that will determine the next set of routes. - */ - } - - - }/> - - - }/> - -)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/NavSidebar.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/NavSidebar.jsx deleted file mode 100644 index 87b91d2d8..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/NavSidebar.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import ListGroup from 'reactstrap/lib/ListGroup'; -import { css } from 'aphrodite/es'; -import connect from 'react-redux/es/connect/connect'; -import { mkGetUIFilter } from '../state/uiSelectors'; -import { isFilteredOut, safeGetNumPassedFailedErrored } from '../utils'; -import BoundStyledListGroupItemLink from './BoundStyledListGroupItemLink'; -import Column from '../../../Nav/Column'; -import { COLUMN_WIDTH } from '../../../Common/defaults'; -import { navListStyles } from '../style'; -import EmptyListGroupItem from './EmptyListGroupItem'; - -const connector = connect( - () => { - const getFilter = mkGetUIFilter(); - return state => ({ - filter: getFilter(state), - buttonListClasses: css(navListStyles.buttonList), - colWidthStr: `${COLUMN_WIDTH}`, - }); - }, -); - -export default connector(({ - entries, filter, buttonListClasses, colWidthStr -}) => { - const items = entries.map((entry, idx) => { - const [ - nPass, nFail, nErr, - ] = safeGetNumPassedFailedErrored(entry.counter, 0); - return isFilteredOut(filter, [ nPass, nFail, nErr ]) ? null : ( - - ); - }).filter(Boolean); - return ( - - - {items.length ? items : } - - - ); -}); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/NavSidebarWithNextRoute.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/NavSidebarWithNextRoute.jsx deleted file mode 100644 index 806af58b6..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/NavSidebarWithNextRoute.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; -import { Redirect } from 'react-router'; -import { Route } from 'react-router'; -import { withRouter } from 'react-router'; -import connect from 'react-redux/es/connect/connect'; -import { setHashComponentAlias } from '../state/uiActions'; -import { BOTTOMMOST_ENTRY_CATEGORY } from '../../../Common/defaults'; -import { mkGetUIHashComponentToAlias } from '../state/uiSelectors'; -import uriComponentCodec from '../../../Common/uriComponentCodec'; -import NavSidebar from './NavSidebar'; - -const connector = connect( - () => { - const getHashComponentToAlias = mkGetUIHashComponentToAlias(); - return state => ({ - hashComponentToAlias: getHashComponentToAlias(state), - }); - }, - { - setHashComponentAlias - }, - (stateProps, dispatchProps, ownProps) => { - const { hashComponentToAlias } = stateProps; - const { setHashComponentAlias } = dispatchProps; - const { - previousPath, - bottommostPath, - entries, - // Assume: - // - The route that was matched === "/aaa/bbb/ccc/:id" - // - The URL that matched === "/aaa/bbb/ccc/12345" - // Then the value of the following variables are: - // * url = "/aaa/bbb/ccc/12345" - // * path = "/aaa/bbb/ccc/:id" - // * params.id = "12345" - match: { url, params: { id: encodedID } }, - } = ownProps; - let tgtEntry = null; - if(Array.isArray(entries)) { - // ths incoming `encodedID` may be URL-encoded and so it won't match - // `entry.name` in the `entries` array, so we grab whatever `id` is - // actually an alias for, and use that to find our target `entry` object. - let decodedID = hashComponentToAlias[encodedID]; - // on refresh on an aliased path, the `componentAliases` will be empty so - // we need to fill it with the aliased component - if(!decodedID) { - decodedID = uriComponentCodec.decode(encodedID); - setHashComponentAlias({ [decodedID]: encodedID }); - } - tgtEntry = entries.find(e => decodedID === e.name); - } - return { - tgtEntry, - url, - previousPath, - bottommostPath, - }; - }, -); - -const NavSidebarWithNextRoute = connector(withRouter(({ - tgtEntry, url, previousPath, bottommostPath -}) => { - if(!tgtEntry) return null; - const isBottommost = tgtEntry.category === BOTTOMMOST_ENTRY_CATEGORY; - if(typeof bottommostPath === 'undefined' && isBottommost && previousPath) { - bottommostPath = previousPath; - } - const routePath = typeof bottommostPath === 'string' ? bottommostPath : url; - return ( - <> - - - }/> - - {(() => isBottommost ? : ( - - ) - )()} - - - ); -})); - -export default NavSidebarWithNextRoute; diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/PrintButton.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/PrintButton.jsx deleted file mode 100644 index d1b9db101..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/PrintButton.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import NavItem from 'reactstrap/lib/NavItem'; -import { css } from 'aphrodite/es'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome/index.es'; -import { faPrint } from '@fortawesome/free-solid-svg-icons'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import connect from 'react-redux/es/connect/connect'; -import navStyles from '../../../Toolbar/navStyles'; - -library.add(faPrint); - -const connector = connect( - () => ({ - buttonsBarClasses: css(navStyles.buttonsBar), - toolbarButtonClasses: css(navStyles.toolbarButton), - printIconName: faPrint.iconName, - }), -); - -export default connector(({ - buttonsBarClasses, toolbarButtonClasses, printIconName -}) => ( - -
- - - -
-
-)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/StyledListGroupItemLink.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/StyledListGroupItemLink.jsx deleted file mode 100644 index c16431b83..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/StyledListGroupItemLink.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import ListGroupItem from 'reactstrap/lib/ListGroupItem'; -import { css } from 'aphrodite/es'; -import connect from 'react-redux/es/connect/connect'; -import StyledNavLink from './StyledNavLink'; -import { navUtilsStyles } from '../style'; - -const connector = connect( - () => ({ - linkClasses: css( - navUtilsStyles.navButton, - navUtilsStyles.navButtonInteract, - ), - }), -); - -export default connector(({ linkClasses, pathname, dataUid, ...props }) => ( - -)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/StyledNavLink.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/StyledNavLink.jsx deleted file mode 100644 index 32475325b..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/StyledNavLink.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import connect from 'react-redux/es/connect/connect'; -import { withRouter } from 'react-router'; -import { NavLink } from 'react-router-dom'; -import { css } from 'aphrodite/es'; -import { mkGetUISelectedTestCase } from '../state/uiSelectors'; -import { navUtilsStyles } from '../style'; - -const connector = connect( - () => { - const getSelectedTestCase = mkGetUISelectedTestCase(); - return state => ({ selectedTestCase: getSelectedTestCase(state) }); - }, - null, - (stateProps, _, ownProps) => { - const { selectedTestCase } = stateProps; - const { pathname, dataUid, style, location, ...props } = ownProps; - return { - style: style && typeof style === 'object' ? style : { - textDecoration: 'none', - color: 'currentColor', - }, - linkedLocation: { - search: location.search, - pathname: pathname.replace(/\/{2,}/g, '/'), - }, - isActive: () => ( - selectedTestCase && - (typeof selectedTestCase === 'object') && - selectedTestCase.uid === dataUid - ), - navButtonClasses: css(navUtilsStyles.navButtonInteract), - ...props, - }; - }, -); - -export default connector(withRouter(({ - style, linkedLocation, isActive, dataUid, navButtonClasses, ...props -}) => ( - -))); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/TagsButton.jsx b/testplan/web_ui/testing/src/Report/BatchReport/components/TagsButton.jsx deleted file mode 100644 index 9d53fa689..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/TagsButton.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import NavItem from 'reactstrap/lib/NavItem'; -import { css } from 'aphrodite/es'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome/index.es'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faTags } from '@fortawesome/free-solid-svg-icons'; -import connect from 'react-redux/es/connect/connect'; - -import { mkGetUIIsShowTags } from '../state/uiSelectors'; -import { setShowTags } from '../state/uiActions'; -import navStyles from '../../../Toolbar/navStyles'; - -library.add(faTags); - -const connector = connect( - () => { - const getIsShowTags = mkGetUIIsShowTags(); - return state => ({ isShowTags: getIsShowTags(state) }); - }, - { - setShowTags, - }, - (stateProps, dispatchProps) => { - const { isShowTags } = stateProps; - const { setShowTags } = dispatchProps; - return { - buttonsBarClasses: css(navStyles.buttonsBar), - toolbarButtonClasses: css(navStyles.toolbarButton), - tagsIconName: faTags.iconName, - onClick: evt => { - evt.stopPropagation(); - setShowTags(!isShowTags); - }, - }; - }, -); - -export default connector(({ - onClick, buttonsBarClasses, toolbarButtonClasses, tagsIconName -}) => ( - -
- - - -
-
-)); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/index.jsx b/testplan/web_ui/testing/src/Report/BatchReport/index.jsx deleted file mode 100644 index bfdfdad0c..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/index.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { css } from 'aphrodite/es'; -import connect from 'react-redux/es/connect/connect'; -import { mkGetIsDevel } from '../../state/appSelectors'; -import { mkGetIsTesting } from '../../state/appSelectors'; -import { mkGetSkipFetch } from '../../state/appSelectors'; -import CenterPane from './components/CenterPane'; -import Toolbar from './components/Toolbar'; -import UIRouter from './state/UIRouter'; -import NavPanes from './components/NavPanes'; -import { batchReportStyles } from './style'; -import { fetchReport } from './state/reportActions'; - -const BATCH_REPORT_CLASSES = css(batchReportStyles.batchReport); - -const connector = connect( - () => { - const getIsDevel = mkGetIsDevel(); - const getIsTesting = mkGetIsTesting(); - const getSkipFetch = mkGetSkipFetch(); - return state => ({ - isDevel: getIsDevel(state), - isTesting: getIsTesting(state), - skipFetch: getSkipFetch(state), - }); - }, - { fetchReport }, - (stateProps, dispatchProps, ownProps) => { - const { isDevel, isTesting, skipFetch } = stateProps; - const { match: { params: { id: uid } } } = ownProps; - const { fetchReport } = dispatchProps; - return { isDevel, isTesting, fetchReport, uid, skipFetch }; - } -); - -export default connector(({ isDevel, isTesting, fetchReport, uid, skipFetch }) => { - React.useEffect(() => { - if(!skipFetch) { - return fetchReport(uid).abort; - } - }, [ uid, skipFetch, fetchReport ]); - return ( - -
- - - -
-
- ); -}); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/state/UIRouter.jsx b/testplan/web_ui/testing/src/Report/BatchReport/state/UIRouter.jsx deleted file mode 100644 index cb9b64e24..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/state/UIRouter.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { createHashHistory } from 'history'; -import { Router } from 'react-router'; -import ErrorCatch from '../../../Common/ErrorCatch'; - -export const uiHistory = createHashHistory({ basename: '/' }); - -export default function UIRouter({ children }) { - return ( - - - {children} - - - ); -} diff --git a/testplan/web_ui/testing/src/Report/BatchReport/state/reportActions/fetchReport.js b/testplan/web_ui/testing/src/Report/BatchReport/state/reportActions/fetchReport.js deleted file mode 100644 index 83006d648..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/state/reportActions/fetchReport.js +++ /dev/null @@ -1,55 +0,0 @@ -import { createAsyncThunk } from '@reduxjs/toolkit/dist/redux-toolkit.esm'; -import * as reportSelectors from '../reportSelectors'; -import * as appSelectors from '../../../../state/appSelectors'; -import { PropagateIndices } from '../../../reportUtils'; - -export const REPORT_FETCH_CANCEL = 'REPORT_FETCH_CANCEL'; - -export default createAsyncThunk( - 'FETCH_REPORT', - async (reportUid, { dispatch, getState, signal, rejectWithValue }) => { - const { setDownloadProgress, setReportUID } = await import('./'); - const { default: Axios } = await import('axios/lib/core/Axios'); - try { - // setup fetch - dispatch(setReportUID(reportUid)); - const cancelSource = Axios.CancelToken.source(); - const axiosInstance = Axios.create({ - ...reportSelectors.getReportAxiosPartialConfig(getState()), - cancelToken: cancelSource.token, - }); - signal.addEventListener('abort', () => { - cancelSource.cancel('The fetch was cancelled.'); - }); - // execute fetch - const response = await axiosInstance.request({ - url: `/${reportUid}`, - method: 'GET', - onDownloadProgress: progress => dispatch(setDownloadProgress(progress)), - }); - // process response - return PropagateIndices(response.data); - } catch(err) { - if(Axios.isCancel(err)) { - return rejectWithValue(REPORT_FETCH_CANCEL); - } else { - return rejectWithValue({ - name: err.name, - message: err.message, - stack: err.stack, - }); - } - } - }, - // @ts-ignore - { - condition(reportUid, { getState }) { - const currReportUid = reportSelectors.getReportUid(getState()); - const isFetching = reportSelectors.getReportIsFetching(getState()); - if(isFetching && reportUid === currReportUid) { - return false; - } - }, - dispatchConditionRejection: false, - } -); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/state/reportSelectors.js b/testplan/web_ui/testing/src/Report/BatchReport/state/reportSelectors.js deleted file mode 100644 index 50b4acb2a..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/state/reportSelectors.js +++ /dev/null @@ -1,55 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit/dist/redux-toolkit.esm'; -import * as appSelectors from '../../../state/appSelectors'; - -export const mkGetReportUid = () => st => st.uid; -export const getReportUid = mkGetReportUid(); - -export const mkGetReportDocument = () => st => st.document; -export const getReportDocument = mkGetReportDocument(); - -export const mkGetReportIsFetching = () => st => st.report.isFetching; -export const getReportIsFetching = mkGetReportIsFetching(); - -export const mkGetReportIsFetchCancelled = () => st => { - return st.report.isFetchCancelled; -}; -export const getReportIsFetchCancelled = mkGetReportIsFetchCancelled(); - -export const mkGetReportLastFetchError = () => st => st.report.fetchError; -export const getReportLastFetchError = mkGetReportLastFetchError(); - -export const mkGetReportMaxContentLength = () => st => { - return st.report.maxContentLength; -}; -export const getReportMaxContentLength = mkGetReportMaxContentLength(); - -export const mkGetReportFetchTimeout = () => st => st.report.fetchTimeout; -export const getReportFetchTimeout = mkGetReportFetchTimeout(); - -export const mkGetReportDownloadProgress = () => st => { - return st.report.downloadProgress; -}; -export const getReportDownloadProgress = mkGetReportDownloadProgress(); - -export const mkGetReportApiBaseURL = () => createSelector( - appSelectors.mkGetApiBaseURL(), - baseURL => { - const baseURLShaven = (baseURL || '').replace(/\/$/, ''); - return new URL(`${baseURLShaven}/reports`).href; - } -); -export const getReportApiBaseURL = mkGetReportApiBaseURL(); - -export const mkGetMaxContentLength = () => st => st.report.maxContentLength; -export const getMaxContentLength = mkGetMaxContentLength(); - -export const mkGetReportAxiosPartialConfig = () => createSelector( - appSelectors.getApiBaseURL, - getReportFetchTimeout, - appSelectors.getApiHeaders, - getMaxContentLength, - (baseURL, timeout, headers, maxContentLength) => ({ - baseURL, timeout, headers, maxContentLength, - }) -); -export const getReportAxiosPartialConfig = mkGetReportAxiosPartialConfig(); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/state/uiSelectors.js b/testplan/web_ui/testing/src/Report/BatchReport/state/uiSelectors.js deleted file mode 100644 index 83df59990..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/state/uiSelectors.js +++ /dev/null @@ -1,57 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit/dist/redux-toolkit.esm'; -import _has from 'lodash/has'; -import { mkGetReportDocument } from './reportSelectors'; -import navStyles from '../../../Toolbar/navStyles'; -import { STATUS_CATEGORY } from '../../../Common/defaults'; - -export const mkGetUIHashAliasToComponent = () => st => { - return st.ui.hashAliasToComponent; -}; -export const getUIHashAliasToComponent = mkGetUIHashAliasToComponent(); - -export const mkGetUIHashComponentToAlias = () => st => { - return st.ui.hashComponentToAlias; -}; -export const getUIHashComponentToAlias = mkGetUIHashComponentToAlias(); - -export const mkGetUIIsShowHelpModal = () => st => st.ui.isShowHelpModal; -export const getUIIsShowHelpModal = mkGetUIIsShowHelpModal(); - -export const mkGetUIIsDisplayEmpty = () => st => st.ui.isDisplayEmpty; -export const getUIIsDisplayEmpty = mkGetUIIsDisplayEmpty(); - -export const mkGetUIFilter = () => st => st.ui.filter; -export const getUIFilter = mkGetUIFilter(); - -export const mkGetUIIsShowTags = () => st => st.ui.isShowTags; -export const getUIIsShowTags = mkGetUIIsShowTags(); - -export const mkGetUIIsShowInfoModal = () => st => st.ui.isShowInfoModal; -export const getUIIsShowInfoModal = mkGetUIIsShowInfoModal(); - -export const mkGetUISelectedTestCase = () => st => st.ui.selectedTestCase; -export const getUISelectedTestCase = mkGetUISelectedTestCase(); - -export const mkGetUIDoAutoSelect = () => st => st.ui.doAutoSelect; -export const getUIDoAutoSelect = mkGetUIDoAutoSelect(); - -export const mkGetUIToolbarStyle = () => createSelector( - mkGetReportDocument(), - document => { - const status = _has(document, 'status') ? document.status : null; - const category = STATUS_CATEGORY[status]; - if(category) { - const style = { - passed: navStyles.toolbarPassed, - failed: navStyles.toolbarFailed, - error: navStyles.toolbarFailed, - unstable: navStyles.toolbarUnstable, - }[category]; - if(style) { - return style; - } - } - return navStyles.toolbarUnknown; - } -); -export const getUIToolbarStyle = mkGetUIToolbarStyle(); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/state/uiSlice.js b/testplan/web_ui/testing/src/Report/BatchReport/state/uiSlice.js deleted file mode 100644 index 3cdf6241c..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/state/uiSlice.js +++ /dev/null @@ -1,89 +0,0 @@ -// @ts-nocheck -import { createSlice } from '@reduxjs/toolkit/dist/redux-toolkit.esm'; -import * as filterStates from '../../../Common/filterStates'; - -/** This state slice contains information specific to how the UI should look */ -export default createSlice({ - name: 'ui', - initialState: { - hashAliasToComponent: {}, - hashComponentToAlias: {}, - isShowHelpModal: false, - isDisplayEmpty: true, - filter: filterStates.ALL, - isShowTags: false, - isShowInfoModal: false, - selectedTestCase: null, - doAutoSelect: true, - }, - reducers: { - setHashComponentAlias: { - reducer(state, { payload: aliasToComponentMap }) { - for(const [ alias, component ] of Object.entries(aliasToComponentMap)) { - state.hashAliasToComponent[alias] = component; - state.hashComponentToAlias[component] = alias; - } - }, - prepare: aliasToComponentMap => ({ payload: aliasToComponentMap }), - }, - unsetHashComponentAliasByAlias: { - reducer(state, { payload: aliases }) { - for(const _alias of aliases) { - const component = state.hashAliasToComponent[_alias]; - delete state.hashComponentToAlias[component]; - delete state.hashAliasToComponent[_alias]; - } - }, - prepare: (aliases = []) => ({ - payload: Array.isArray(aliases) ? aliases : [ aliases ], - }), - }, - unsetHashComponentAliasByComponent: { - reducer(state, { payload: components }) { - for(const _component of components) { - const alias = state.hashComponentToAlias[_component]; - delete state.hashAliasToComponent[alias]; - delete state.hashComponentToAlias[_component]; - } - }, - prepare: (components = []) => ({ - payload: Array.isArray(components) ? components : [ components ], - }), - }, - setSelectedTestCase: { - reducer(state, { payload }) { state.selectedTestCase = payload; }, - prepare: message => ({ payload: message }), - }, - setShowTags: { - reducer(state, { payload }) { state.isShowTags = payload; }, - prepare: (showTags = false) => ({ payload: !!showTags }), - }, - setShowInfoModal: { - reducer(state, { payload }) { state.isShowInfoModal = payload; }, - prepare: (showInfoModal = false) => ({ payload: !!showInfoModal }), - }, - setDoAutoSelect: { - reducer(state, { payload }) { state.doAutoSelect = payload; }, - prepare: (doAutoSelect = true) => ({ payload: !!doAutoSelect }), - }, - setFilter: { - reducer(state, { payload }) { state.filter = payload; }, - prepare: (filter = filterStates.ALL) => { - if(!(filter in Object.values(filterStates))) { - filter = filterStates.ALL; - } - return { - payload: `${filter}` - }; - }, - }, - setDisplayEmpty: { - reducer(state, { payload }) { state.isDisplayEmpty = payload; }, - prepare: (displayEmpty = true) => ({ payload: !!displayEmpty }), - }, - setShowHelpModal: { - reducer(state, { payload }) { state.isShowHelpModal = payload; }, - prepare: (showHelpModal = false) => ({ payload: !!showHelpModal }), - }, - }, -}); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/style.js b/testplan/web_ui/testing/src/Report/BatchReport/style.js deleted file mode 100644 index 16947c11e..000000000 --- a/testplan/web_ui/testing/src/Report/BatchReport/style.js +++ /dev/null @@ -1,20 +0,0 @@ -import { StyleSheet } from 'aphrodite/es'; - -export { default as CommonStyles } from '../../Common/Styles'; -export { styles as navBreadcrumbStyles } from '../../Nav/NavBreadcrumbs'; -export { styles as navUtilsStyles } from '../../Nav/navUtils'; -export { COLUMN_WIDTH } from '../../Common/defaults'; - -export const navListStyles = StyleSheet.create({ - buttonList: { - 'overflow-y': 'auto', - 'height': '100%', - } -}); - -export const batchReportStyles = StyleSheet.create({ - batchReport: { - /** overflow will hide dropdown div */ - // overflow: 'hidden' - } -}); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/__tests__/BatchReport.test.js b/testplan/web_ui/testing/src/Report/BatchReportBeta/__tests__/BatchReport.test.js similarity index 100% rename from testplan/web_ui/testing/src/Report/BatchReport/__tests__/BatchReport.test.js rename to testplan/web_ui/testing/src/Report/BatchReportBeta/__tests__/BatchReport.test.js diff --git a/testplan/web_ui/testing/src/Report/BatchReport/__tests__/BatchReport_routing.test.js b/testplan/web_ui/testing/src/Report/BatchReportBeta/__tests__/BatchReport_routing.test.js similarity index 100% rename from testplan/web_ui/testing/src/Report/BatchReport/__tests__/BatchReport_routing.test.js rename to testplan/web_ui/testing/src/Report/BatchReportBeta/__tests__/BatchReport_routing.test.js diff --git a/testplan/web_ui/testing/src/Report/BatchReport/__tests__/__snapshots__/BatchReport.test.js.snap b/testplan/web_ui/testing/src/Report/BatchReportBeta/__tests__/__snapshots__/BatchReport.test.js.snap similarity index 100% rename from testplan/web_ui/testing/src/Report/BatchReport/__tests__/__snapshots__/BatchReport.test.js.snap rename to testplan/web_ui/testing/src/Report/BatchReportBeta/__tests__/__snapshots__/BatchReport.test.js.snap diff --git a/testplan/web_ui/testing/src/Report/BatchReportBeta/components/AutoSelectRedirect.jsx b/testplan/web_ui/testing/src/Report/BatchReportBeta/components/AutoSelectRedirect.jsx new file mode 100644 index 000000000..f20bef047 --- /dev/null +++ b/testplan/web_ui/testing/src/Report/BatchReportBeta/components/AutoSelectRedirect.jsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Redirect } from 'react-router'; +import { Route } from 'react-router'; +import connect from 'react-redux/es/connect/connect'; +import _isObjectLike from 'lodash/isObjectLike'; +import _isString from 'lodash/isString'; +import uriComponentCodec from '../../../Common/uriComponentCodec'; +import { setSelectedEntry } from '../state/uiActions'; +import { setHashComponentAlias } from '../state/uiActions'; +import { mkGetUIDoAutoSelect } from '../state/uiSelectors'; +import { mkGetUIHashAliasToComponent } from '../state/uiSelectors'; +import { BOTTOMMOST_ENTRY_CATEGORY } from '../../../Common/defaults'; + +const connector = connect( + () => { + const getDoAutoSelect = mkGetUIDoAutoSelect(); + const getAliasToComponent = mkGetUIHashAliasToComponent(); + return state => ({ + doAutoSelect: getDoAutoSelect(state), + aliasToComponent: getAliasToComponent(state), + }); + }, + { setSelectedEntry, setHashComponentAlias }, + (stateProps, dispatchProps, ownProps) => { + const { doAutoSelect, aliasToComponent } = stateProps; + const { setSelectedEntry, setHashComponentAlias } = dispatchProps; + const { location, entry } = ownProps; + return { + getRedirectToEntry: () => { + const to = { ...location, pathname: ( + `${location.pathname.replace(/\/+$/, '').trim()}/${entry.name || ''}` + )}; + let currEntry = entry, nextEntry, nextAlias; + while( + doAutoSelect + && _isObjectLike(currEntry) + && currEntry.category !== BOTTOMMOST_ENTRY_CATEGORY + && Array.isArray(currEntry.entries) + && currEntry.entries.length === 1 + && _isObjectLike(nextEntry = currEntry.entries[0] || {}) + && _isString(nextAlias = nextEntry.name) + ) { + currEntry = nextEntry; + let nextURLBasename = aliasToComponent[nextAlias]; + if(!nextURLBasename) { + nextURLBasename = uriComponentCodec.encode(nextAlias); + setHashComponentAlias(nextURLBasename, nextAlias); + } + to.pathname = ( + to.pathname.replace(/\/+$/, '').trim() + + '/' + + nextURLBasename + ); + } + setSelectedEntry(currEntry); + return to; + }, + }; + }, +); + +const AutoSelectRedirect = ({ getRedirectToEntry }) => ( + +); + +export default connector(AutoSelectRedirect); diff --git a/testplan/web_ui/testing/src/Report/BatchReportBeta/components/CenterPane/Placeholder.jsx b/testplan/web_ui/testing/src/Report/BatchReportBeta/components/CenterPane/Placeholder.jsx new file mode 100644 index 000000000..d9da46c63 --- /dev/null +++ b/testplan/web_ui/testing/src/Report/BatchReportBeta/components/CenterPane/Placeholder.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import connect from 'react-redux/es/connect/connect'; +import _isObject from 'lodash/isObject'; +import { mkGetReportIsFetching } from '../../state/reportSelectors'; +import { mkGetReportLastFetchError } from '../../state/reportSelectors'; +import { mkGetReportDocument } from '../../state/reportSelectors'; +import Message from '../../../../Common/Message'; + +const STARTING_MSG = 'Waiting to fetch Testplan report...'; +const FETCHING_MSG = 'Fetching Testplan report...'; +const FINISHED_MSG = 'Please select an entry.'; +const ERRORED_PREFIX_MSG = 'Error fetching Testplan report'; + +const connector = connect( + () => { + const getDocument = mkGetReportDocument(); + const getIsFetching = mkGetReportIsFetching(); + const getError = mkGetReportLastFetchError(); + return state => ({ + isFetching: getIsFetching(state), + error: getError(state), + document: getDocument(state), + }); + }, +); + +const CenterPanePlaceholder = ({ isFetching, error, document, left }) => { + const MessageStyled = React.useCallback(props => ( + + ), [ left ]); + if(!isFetching && error) { + const errorMessage = typeof error === 'object' + ? `${ERRORED_PREFIX_MSG}: ${error.message}` + : `${ERRORED_PREFIX_MSG}.`; + return ( + + ); + } + if(isFetching) { + return (); + } + if(!isFetching && !error && _isObject(document)) { + return (); + } + return (); +}; + +export default connector(CenterPanePlaceholder); diff --git a/testplan/web_ui/testing/src/Report/BatchReportBeta/components/CenterPane/index.jsx b/testplan/web_ui/testing/src/Report/BatchReportBeta/components/CenterPane/index.jsx new file mode 100644 index 000000000..f1c4717c2 --- /dev/null +++ b/testplan/web_ui/testing/src/Report/BatchReportBeta/components/CenterPane/index.jsx @@ -0,0 +1,67 @@ +import React from 'react'; +import connect from 'react-redux/es/connect/connect'; +import { createSelector } from '@reduxjs/toolkit/dist/redux-toolkit.esm'; +import { mkGetUIFilter } from '../../state/uiSelectors'; +import { mkGetUISelectedEntry } from '../../state/uiSelectors'; +import { mkGetReportDocument } from '../../state/reportSelectors'; +import AssertionPane from '../../../../AssertionPane/AssertionPane'; +import { COLUMN_WIDTH } from '../../../../Common/defaults'; +import { BOTTOMMOST_ENTRY_CATEGORY } from '../../../../Common/defaults'; +import { isNonemptyArray } from '../../../../Common/utils'; +import Placeholder from './Placeholder'; + +// using a static placeholder will help prevent unnecessary rerenders +const SELECTED_ENTRY_PLACEHOLDER = { + uid: '', + category: '', + logs: [], + entries: [], + description: [] +}; +const LEFT_WIDTH = `${(COLUMN_WIDTH || 0) + 1.5}`; + +const connector = connect( + () => { + const getFilter = mkGetUIFilter(); + const getSelectedEntrySafe = createSelector( + mkGetUISelectedEntry(), + entry => (entry || {}) + ); + const getReportUID = createSelector( + mkGetReportDocument(), + document => (document || {}).uid, + ); + return state => { + const selectedEntry = getSelectedEntrySafe(state); + return { + filter: getFilter(state), + selectedEntry: selectedEntry.category === BOTTOMMOST_ENTRY_CATEGORY + ? selectedEntry + : SELECTED_ENTRY_PLACEHOLDER, + reportUID: getReportUID(state), + }; + }; + }, +); + +const CenterPane = ({ selectedEntry, reportUID, filter }) => { + const { uid, category, logs, entries, description } = selectedEntry; + const descriptionEntries = React.useMemo(() => ( + isNonemptyArray(description) ? description : [ description ] + ).filter(Boolean), [ description ]); + if(category === BOTTOMMOST_ENTRY_CATEGORY) { + return ( + + ); + } + return ; +}; + +export default connector(CenterPane); diff --git a/testplan/web_ui/testing/src/Report/BatchReport/components/DisplayEmptyCheckBox.jsx b/testplan/web_ui/testing/src/Report/BatchReportBeta/components/DisplayEmptyCheckBox.jsx similarity index 62% rename from testplan/web_ui/testing/src/Report/BatchReport/components/DisplayEmptyCheckBox.jsx rename to testplan/web_ui/testing/src/Report/BatchReportBeta/components/DisplayEmptyCheckBox.jsx index 993f59b5e..c1462691d 100644 --- a/testplan/web_ui/testing/src/Report/BatchReport/components/DisplayEmptyCheckBox.jsx +++ b/testplan/web_ui/testing/src/Report/BatchReportBeta/components/DisplayEmptyCheckBox.jsx @@ -8,39 +8,30 @@ import { mkGetUIIsDisplayEmpty } from '../state/uiSelectors'; import { setDisplayEmpty } from '../state/uiActions'; import navStyles from '../../../Toolbar/navStyles'; +const DROPDOWN_ITEM_CLASSES = css(navStyles.dropdownItem); +const FILTER_LABEL_CLASSES = css(navStyles.filterLabel); + const connector = connect( () => { const getIsDisplayEmpty = mkGetUIIsDisplayEmpty(); - return state => ({ - isDisplayEmpty: getIsDisplayEmpty(state), - dropdownItemClasses: css(navStyles.dropdownItem), - filterLabelClasses: css(navStyles.filterLabel), - }); - }, - { - setDisplayEmpty, + return state => ({ isDisplayEmpty: getIsDisplayEmpty(state) }); }, + { setDisplayEmpty }, (stateProps, dispatchProps, ownProps) => { - const { - dropdownItemClasses, filterLabelClasses, isDisplayEmpty - } = stateProps; + const { isDisplayEmpty } = stateProps; const { setDisplayEmpty } = dispatchProps; const { label } = ownProps; return { label: label || '', - dropdownItemClasses, - filterLabelClasses, isDisplayEmpty, onChange: () => setDisplayEmpty(!isDisplayEmpty), }; } ); -export default connector(({ - label, isDisplayEmpty, onChange, dropdownItemClasses, filterLabelClasses, -}) => ( - -