Skip to content

Commit

Permalink
Merge pull request #453 from gemini-testing/HERMIONE-290.gui_checkbox
Browse files Browse the repository at this point in the history
feat: retry tests with checkboxes
  • Loading branch information
KuznetsovRoman committed Jul 3, 2023
2 parents 13574f4 + d3190c1 commit 665b80a
Show file tree
Hide file tree
Showing 37 changed files with 1,904 additions and 452 deletions.
6 changes: 6 additions & 0 deletions lib/common-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const {
RUNNING,
QUEUED
} = require('./constants/test-statuses');
const {UNCHECKED, INDETERMINATE, CHECKED} = require('./constants/checked-statuses');

exports.getShortMD5 = (str) => {
return crypto.createHash('md5').update(str, 'ascii').digest('hex').substr(0, 7);
Expand Down Expand Up @@ -115,3 +116,8 @@ function isRelativeUrl(url) {
return true;
}
}

exports.isCheckboxChecked = (status) => status == CHECKED; // eslint-disable-line eqeqeq
exports.isCheckboxIndeterminate = (status) => status == INDETERMINATE; // eslint-disable-line eqeqeq
exports.isCheckboxUnchecked = (status) => status == UNCHECKED; // eslint-disable-line eqeqeq
exports.getToggledCheckboxState = (status) => exports.isCheckboxChecked(status) ? UNCHECKED : CHECKED;
7 changes: 7 additions & 0 deletions lib/constants/checked-statuses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

module.exports = {
UNCHECKED: 0,
INDETERMINATE: 0.5,
CHECKED: 1
};
30 changes: 30 additions & 0 deletions lib/static/components/bullet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import classNames from 'classnames';
import {Checkbox} from 'semantic-ui-react';
import PropTypes from 'prop-types';
import {isCheckboxChecked, isCheckboxIndeterminate} from '../../common-utils';
import {CHECKED, INDETERMINATE, UNCHECKED} from '../../constants/checked-statuses';
import useLocalStorage from '../hooks/useLocalStorage';

const Bullet = ({status, onClick, className}) => {
const [isCheckbox] = useLocalStorage('showCheckboxes', false);

if (!isCheckbox) {
return <span className={classNames('bullet_type-simple', className)} />;
}

return <Checkbox
className={classNames('bullet_type-checkbox', className)}
checked={isCheckboxChecked(status)}
indeterminate={isCheckboxIndeterminate(status)}
onClick={onClick}
/>;
};

Bullet.propTypes = {
status: PropTypes.oneOf([CHECKED, UNCHECKED, INDETERMINATE]),
onClick: PropTypes.func,
bulletClassName: PropTypes.string
};

export default Bullet;
6 changes: 4 additions & 2 deletions lib/static/components/controls/common-filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import {connect} from 'react-redux';
import * as actions from '../../modules/actions';
import TestNameFilterInput from './test-name-filter-input';
import StrictMatchFilterInput from './strict-match-filter-input';
import ShowCheckboxesInput from './show-checkboxes-input';
import BrowserList from './browser-list';

class CommonFilters extends Component {
render() {
const {filteredBrowsers, browsers, actions} = this.props;
const {filteredBrowsers, browsers, gui, actions} = this.props;

return (
<div className="control-container control-filters">
Expand All @@ -21,12 +22,13 @@ class CommonFilters extends Component {
/>
<TestNameFilterInput/>
<StrictMatchFilterInput/>
{gui && <ShowCheckboxesInput/>}
</div>
);
}
}

export default connect(
({view, browsers}) => ({filteredBrowsers: view.filteredBrowsers, browsers}),
({view, browsers, gui}) => ({filteredBrowsers: view.filteredBrowsers, browsers, gui}),
(dispatch) => ({actions: bindActionCreators(actions, dispatch)})
)(CommonFilters);
43 changes: 4 additions & 39 deletions lib/static/components/controls/gui-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,23 @@ import ControlButton from './control-button';
import RunButton from './run-button';
import AcceptOpenedButton from './accept-opened-button';
import CommonFilters from './common-filters';
import {getFailedTests} from '../../modules/selectors/tree';

import './controls.less';

class GuiControls extends Component {
static propTypes = {
// from store
running: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired,
stopping: PropTypes.bool.isRequired,
autoRun: PropTypes.bool.isRequired,
allRootSuiteIds: PropTypes.arrayOf(PropTypes.string).isRequired,
failedRootSuiteIds: PropTypes.arrayOf(PropTypes.string).isRequired,
failedTests: PropTypes.arrayOf(PropTypes.shape({
testName: PropTypes.string,
browserName: PropTypes.string
})).isRequired
}

_runFailedTests = () => {
const {actions, failedTests} = this.props;

return actions.runFailedTests(failedTests);
stopping: PropTypes.bool.isRequired
}

render() {
const {actions, allRootSuiteIds, failedRootSuiteIds, running, autoRun, processing, stopping} = this.props;
const {actions, running, stopping} = this.props;
return (
<div className="main-menu container">
<CustomGuiControls />
<div className="control-container control-buttons">
<RunButton
autoRun={autoRun}
isDisabled={!allRootSuiteIds.length || processing}
handler={actions.runAllTests}
isRunning={running}
/>
<ControlButton
label="Retry failed tests"
isDisabled={!failedRootSuiteIds.length || processing}
handler={this._runFailedTests}
/>
<RunButton />
<ControlButton
label="Stop tests"
isDisabled={!running || stopping}
Expand All @@ -66,16 +41,6 @@ class GuiControls extends Component {
}

export default connect(
(state) => {
return {
running: state.running,
processing: state.processing,
stopping: state.stopping,
autoRun: state.autoRun,
allRootSuiteIds: state.tree.suites.allRootIds,
failedRootSuiteIds: state.tree.suites.failedRootIds,
failedTests: getFailedTests(state)
};
},
({running, stopping}) => ({running, stopping}),
(dispatch) => ({actions: bindActionCreators(actions, dispatch)})
)(GuiControls);
27 changes: 0 additions & 27 deletions lib/static/components/controls/run-button.js

This file was deleted.

129 changes: 129 additions & 0 deletions lib/static/components/controls/run-button/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
'use strict';

import React, {useEffect, useState} from 'react';
import {bindActionCreators} from 'redux';
import {isEmpty} from 'lodash';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import * as actions from '../../../modules/actions';
import Popup from '../../popup';
import {getFailedTests, getCheckedTests} from '../../../modules/selectors/tree';
import useLocalStorage from '../../../hooks/useLocalStorage';

import './index.styl';

const RunMode = Object.freeze({
ALL: 'All',
FAILED: 'Failed',
CHECKED: 'Checked'
});

const RunButton = ({actions, autoRun, isDisabled, isRunning, failedTests, checkedTests}) => {
const [mode, setMode] = useState(RunMode.ALL);
const [showCheckboxes] = useLocalStorage('showCheckboxes', false);

const btnClassName = classNames('btn', {'button_blink': isRunning});

const shouldDisableFailed = isEmpty(failedTests);
const shouldDisableChecked = !showCheckboxes || isEmpty(checkedTests);

const selectAllTests = () => setMode(RunMode.ALL);
const selectFailedTests = () => !shouldDisableFailed && setMode(RunMode.FAILED);
const selectCheckedTests = () => !shouldDisableChecked && setMode(RunMode.CHECKED);

const runAllTests = () => actions.runAllTests();
const runFailedTests = () => actions.runFailedTests(failedTests);
const runCheckedTests = () => actions.retrySuite(checkedTests);

const handleRunClick = () => {
const action = {
[RunMode.ALL]: runAllTests,
[RunMode.FAILED]: runFailedTests,
[RunMode.CHECKED]: runCheckedTests
}[mode];

action();
};

useEffect(() => {
if (autoRun) {
runAllTests();
}
}, []);

useEffect(() => {
selectCheckedTests();
}, [shouldDisableChecked]);

useEffect(() => {
const shouldResetFailedMode = mode === RunMode.FAILED && shouldDisableFailed;
const shouldResetCheckedMode = mode === RunMode.CHECKED && shouldDisableChecked;

if (shouldResetFailedMode || shouldResetCheckedMode) {
setMode(RunMode.ALL);
}
}, [shouldDisableFailed, shouldDisableChecked]);

return (
<div className='run-button'>
<button disabled={isDisabled} onClick={handleRunClick} className={btnClassName}>
{isRunning ? 'Running' : `Run ${mode.toLowerCase()} tests`}
</button>
{!isDisabled && <Popup
action='hover'
hideOnClick={true}
target={<div className='run-button__dropdown' />}
>
<ul className='run-mode'>
<li
className='run-mode__item'
onClick={selectAllTests}
>
{RunMode.ALL}
</li>
<li
className={classNames('run-mode__item', {'run-mode__item_disabled': shouldDisableFailed})}
onClick={selectFailedTests}>{RunMode.FAILED}
</li>
<li
className={classNames('run-mode__item', {'run-mode__item_disabled': shouldDisableChecked})}
onClick={selectCheckedTests}
>
{RunMode.CHECKED}
</li>
</ul>
</Popup>}
</div>
);
};

RunButton.propTypes = {
// from store
autoRun: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool,
isRunning: PropTypes.bool,
failedTests: PropTypes.arrayOf(PropTypes.shape({
testName: PropTypes.string,
browserName: PropTypes.string
})).isRequired,
checkedTests: PropTypes.arrayOf(PropTypes.shape({
testName: PropTypes.string,
browserName: PropTypes.string
})).isRequired
};

export default connect(
(state) => {
const autoRun = state.autoRun;
const allRootSuiteIds = state.tree.suites.allRootIds;
const processing = state.processing;
const isDisabled = !allRootSuiteIds.length || processing;
const isRunning = state.running;
const failedTests = getFailedTests(state);
const checkedTests = getCheckedTests(state);

return {autoRun, isDisabled, isRunning, failedTests, checkedTests};
},
(dispatch) => ({actions: bindActionCreators(actions, dispatch)})
)(RunButton);
49 changes: 49 additions & 0 deletions lib/static/components/controls/run-button/index.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.run-button {
display: inline-flex;
align-items: center;
cursor: pointer;
width: 143px;
font-size: 11px;
line-height: 11px;
border: 1px solid #ccc;
border-radius: 2px;
background-color: #ffeba0;

.btn {
flex-grow: 1;
height: 100%;
background-color: #ffeba0;
cursor: pointer;
border: none;
}

.run-button__dropdown {
font-family: Dropdown;

&::before {
content: '\f0d7';
padding: 5px;
border-left: 1px solid #ccc;
}
}

.popup__content {
padding: 0;
}

.run-mode {
padding: 0;

.run-mode__item {
font-size: 13px;
padding: 5px 10px;
list-style: none;
user-select: none;

&.run-mode__item_disabled {
color: #939393;
cursor: auto;
}
}
}
}
24 changes: 24 additions & 0 deletions lib/static/components/controls/show-checkboxes-input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

import React from 'react';
import {Checkbox} from 'semantic-ui-react';
import useLocalStorage from '../../hooks/useLocalStorage';

const ShowCheckboxesInput = () => {
const [showCheckboxes, setShowCheckboxes] = useLocalStorage('showCheckboxes', false);

const onChange = () => setShowCheckboxes(!showCheckboxes);

return (
<div className="toggle-control">
<Checkbox
toggle
label="Checkboxes"
onChange={onChange}
checked={showCheckboxes}
/>
</div>
);
};

export default ShowCheckboxesInput;
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const StrictMatchFilterInput = ({strictMatchFilter, actions}) => {
};

return (
<div className="strict-match-filter">
<div className="toggle-control">
<Checkbox
toggle
label="Strict match"
Expand Down
Loading

0 comments on commit 665b80a

Please sign in to comment.