Skip to content

Commit 665b80a

Browse files
Merge pull request #453 from gemini-testing/HERMIONE-290.gui_checkbox
feat: retry tests with checkboxes
2 parents 13574f4 + d3190c1 commit 665b80a

37 files changed

+1904
-452
lines changed

lib/common-utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
RUNNING,
1616
QUEUED
1717
} = require('./constants/test-statuses');
18+
const {UNCHECKED, INDETERMINATE, CHECKED} = require('./constants/checked-statuses');
1819

1920
exports.getShortMD5 = (str) => {
2021
return crypto.createHash('md5').update(str, 'ascii').digest('hex').substr(0, 7);
@@ -115,3 +116,8 @@ function isRelativeUrl(url) {
115116
return true;
116117
}
117118
}
119+
120+
exports.isCheckboxChecked = (status) => status == CHECKED; // eslint-disable-line eqeqeq
121+
exports.isCheckboxIndeterminate = (status) => status == INDETERMINATE; // eslint-disable-line eqeqeq
122+
exports.isCheckboxUnchecked = (status) => status == UNCHECKED; // eslint-disable-line eqeqeq
123+
exports.getToggledCheckboxState = (status) => exports.isCheckboxChecked(status) ? UNCHECKED : CHECKED;

lib/constants/checked-statuses.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
module.exports = {
4+
UNCHECKED: 0,
5+
INDETERMINATE: 0.5,
6+
CHECKED: 1
7+
};

lib/static/components/bullet.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react';
2+
import classNames from 'classnames';
3+
import {Checkbox} from 'semantic-ui-react';
4+
import PropTypes from 'prop-types';
5+
import {isCheckboxChecked, isCheckboxIndeterminate} from '../../common-utils';
6+
import {CHECKED, INDETERMINATE, UNCHECKED} from '../../constants/checked-statuses';
7+
import useLocalStorage from '../hooks/useLocalStorage';
8+
9+
const Bullet = ({status, onClick, className}) => {
10+
const [isCheckbox] = useLocalStorage('showCheckboxes', false);
11+
12+
if (!isCheckbox) {
13+
return <span className={classNames('bullet_type-simple', className)} />;
14+
}
15+
16+
return <Checkbox
17+
className={classNames('bullet_type-checkbox', className)}
18+
checked={isCheckboxChecked(status)}
19+
indeterminate={isCheckboxIndeterminate(status)}
20+
onClick={onClick}
21+
/>;
22+
};
23+
24+
Bullet.propTypes = {
25+
status: PropTypes.oneOf([CHECKED, UNCHECKED, INDETERMINATE]),
26+
onClick: PropTypes.func,
27+
bulletClassName: PropTypes.string
28+
};
29+
30+
export default Bullet;

lib/static/components/controls/common-filters.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import {connect} from 'react-redux';
66
import * as actions from '../../modules/actions';
77
import TestNameFilterInput from './test-name-filter-input';
88
import StrictMatchFilterInput from './strict-match-filter-input';
9+
import ShowCheckboxesInput from './show-checkboxes-input';
910
import BrowserList from './browser-list';
1011

1112
class CommonFilters extends Component {
1213
render() {
13-
const {filteredBrowsers, browsers, actions} = this.props;
14+
const {filteredBrowsers, browsers, gui, actions} = this.props;
1415

1516
return (
1617
<div className="control-container control-filters">
@@ -21,12 +22,13 @@ class CommonFilters extends Component {
2122
/>
2223
<TestNameFilterInput/>
2324
<StrictMatchFilterInput/>
25+
{gui && <ShowCheckboxesInput/>}
2426
</div>
2527
);
2628
}
2729
}
2830

2931
export default connect(
30-
({view, browsers}) => ({filteredBrowsers: view.filteredBrowsers, browsers}),
32+
({view, browsers, gui}) => ({filteredBrowsers: view.filteredBrowsers, browsers, gui}),
3133
(dispatch) => ({actions: bindActionCreators(actions, dispatch)})
3234
)(CommonFilters);

lib/static/components/controls/gui-controls.js

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,23 @@ import ControlButton from './control-button';
99
import RunButton from './run-button';
1010
import AcceptOpenedButton from './accept-opened-button';
1111
import CommonFilters from './common-filters';
12-
import {getFailedTests} from '../../modules/selectors/tree';
1312

1413
import './controls.less';
1514

1615
class GuiControls extends Component {
1716
static propTypes = {
1817
// from store
1918
running: PropTypes.bool.isRequired,
20-
processing: PropTypes.bool.isRequired,
21-
stopping: PropTypes.bool.isRequired,
22-
autoRun: PropTypes.bool.isRequired,
23-
allRootSuiteIds: PropTypes.arrayOf(PropTypes.string).isRequired,
24-
failedRootSuiteIds: PropTypes.arrayOf(PropTypes.string).isRequired,
25-
failedTests: PropTypes.arrayOf(PropTypes.shape({
26-
testName: PropTypes.string,
27-
browserName: PropTypes.string
28-
})).isRequired
29-
}
30-
31-
_runFailedTests = () => {
32-
const {actions, failedTests} = this.props;
33-
34-
return actions.runFailedTests(failedTests);
19+
stopping: PropTypes.bool.isRequired
3520
}
3621

3722
render() {
38-
const {actions, allRootSuiteIds, failedRootSuiteIds, running, autoRun, processing, stopping} = this.props;
23+
const {actions, running, stopping} = this.props;
3924
return (
4025
<div className="main-menu container">
4126
<CustomGuiControls />
4227
<div className="control-container control-buttons">
43-
<RunButton
44-
autoRun={autoRun}
45-
isDisabled={!allRootSuiteIds.length || processing}
46-
handler={actions.runAllTests}
47-
isRunning={running}
48-
/>
49-
<ControlButton
50-
label="Retry failed tests"
51-
isDisabled={!failedRootSuiteIds.length || processing}
52-
handler={this._runFailedTests}
53-
/>
28+
<RunButton />
5429
<ControlButton
5530
label="Stop tests"
5631
isDisabled={!running || stopping}
@@ -66,16 +41,6 @@ class GuiControls extends Component {
6641
}
6742

6843
export default connect(
69-
(state) => {
70-
return {
71-
running: state.running,
72-
processing: state.processing,
73-
stopping: state.stopping,
74-
autoRun: state.autoRun,
75-
allRootSuiteIds: state.tree.suites.allRootIds,
76-
failedRootSuiteIds: state.tree.suites.failedRootIds,
77-
failedTests: getFailedTests(state)
78-
};
79-
},
44+
({running, stopping}) => ({running, stopping}),
8045
(dispatch) => ({actions: bindActionCreators(actions, dispatch)})
8146
)(GuiControls);

lib/static/components/controls/run-button.js

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
'use strict';
2+
3+
import React, {useEffect, useState} from 'react';
4+
import {bindActionCreators} from 'redux';
5+
import {isEmpty} from 'lodash';
6+
import {connect} from 'react-redux';
7+
import PropTypes from 'prop-types';
8+
import classNames from 'classnames';
9+
import * as actions from '../../../modules/actions';
10+
import Popup from '../../popup';
11+
import {getFailedTests, getCheckedTests} from '../../../modules/selectors/tree';
12+
import useLocalStorage from '../../../hooks/useLocalStorage';
13+
14+
import './index.styl';
15+
16+
const RunMode = Object.freeze({
17+
ALL: 'All',
18+
FAILED: 'Failed',
19+
CHECKED: 'Checked'
20+
});
21+
22+
const RunButton = ({actions, autoRun, isDisabled, isRunning, failedTests, checkedTests}) => {
23+
const [mode, setMode] = useState(RunMode.ALL);
24+
const [showCheckboxes] = useLocalStorage('showCheckboxes', false);
25+
26+
const btnClassName = classNames('btn', {'button_blink': isRunning});
27+
28+
const shouldDisableFailed = isEmpty(failedTests);
29+
const shouldDisableChecked = !showCheckboxes || isEmpty(checkedTests);
30+
31+
const selectAllTests = () => setMode(RunMode.ALL);
32+
const selectFailedTests = () => !shouldDisableFailed && setMode(RunMode.FAILED);
33+
const selectCheckedTests = () => !shouldDisableChecked && setMode(RunMode.CHECKED);
34+
35+
const runAllTests = () => actions.runAllTests();
36+
const runFailedTests = () => actions.runFailedTests(failedTests);
37+
const runCheckedTests = () => actions.retrySuite(checkedTests);
38+
39+
const handleRunClick = () => {
40+
const action = {
41+
[RunMode.ALL]: runAllTests,
42+
[RunMode.FAILED]: runFailedTests,
43+
[RunMode.CHECKED]: runCheckedTests
44+
}[mode];
45+
46+
action();
47+
};
48+
49+
useEffect(() => {
50+
if (autoRun) {
51+
runAllTests();
52+
}
53+
}, []);
54+
55+
useEffect(() => {
56+
selectCheckedTests();
57+
}, [shouldDisableChecked]);
58+
59+
useEffect(() => {
60+
const shouldResetFailedMode = mode === RunMode.FAILED && shouldDisableFailed;
61+
const shouldResetCheckedMode = mode === RunMode.CHECKED && shouldDisableChecked;
62+
63+
if (shouldResetFailedMode || shouldResetCheckedMode) {
64+
setMode(RunMode.ALL);
65+
}
66+
}, [shouldDisableFailed, shouldDisableChecked]);
67+
68+
return (
69+
<div className='run-button'>
70+
<button disabled={isDisabled} onClick={handleRunClick} className={btnClassName}>
71+
{isRunning ? 'Running' : `Run ${mode.toLowerCase()} tests`}
72+
</button>
73+
{!isDisabled && <Popup
74+
action='hover'
75+
hideOnClick={true}
76+
target={<div className='run-button__dropdown' />}
77+
>
78+
<ul className='run-mode'>
79+
<li
80+
className='run-mode__item'
81+
onClick={selectAllTests}
82+
>
83+
{RunMode.ALL}
84+
</li>
85+
<li
86+
className={classNames('run-mode__item', {'run-mode__item_disabled': shouldDisableFailed})}
87+
onClick={selectFailedTests}>{RunMode.FAILED}
88+
</li>
89+
<li
90+
className={classNames('run-mode__item', {'run-mode__item_disabled': shouldDisableChecked})}
91+
onClick={selectCheckedTests}
92+
>
93+
{RunMode.CHECKED}
94+
</li>
95+
</ul>
96+
</Popup>}
97+
</div>
98+
);
99+
};
100+
101+
RunButton.propTypes = {
102+
// from store
103+
autoRun: PropTypes.bool.isRequired,
104+
isDisabled: PropTypes.bool,
105+
isRunning: PropTypes.bool,
106+
failedTests: PropTypes.arrayOf(PropTypes.shape({
107+
testName: PropTypes.string,
108+
browserName: PropTypes.string
109+
})).isRequired,
110+
checkedTests: PropTypes.arrayOf(PropTypes.shape({
111+
testName: PropTypes.string,
112+
browserName: PropTypes.string
113+
})).isRequired
114+
};
115+
116+
export default connect(
117+
(state) => {
118+
const autoRun = state.autoRun;
119+
const allRootSuiteIds = state.tree.suites.allRootIds;
120+
const processing = state.processing;
121+
const isDisabled = !allRootSuiteIds.length || processing;
122+
const isRunning = state.running;
123+
const failedTests = getFailedTests(state);
124+
const checkedTests = getCheckedTests(state);
125+
126+
return {autoRun, isDisabled, isRunning, failedTests, checkedTests};
127+
},
128+
(dispatch) => ({actions: bindActionCreators(actions, dispatch)})
129+
)(RunButton);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
.run-button {
2+
display: inline-flex;
3+
align-items: center;
4+
cursor: pointer;
5+
width: 143px;
6+
font-size: 11px;
7+
line-height: 11px;
8+
border: 1px solid #ccc;
9+
border-radius: 2px;
10+
background-color: #ffeba0;
11+
12+
.btn {
13+
flex-grow: 1;
14+
height: 100%;
15+
background-color: #ffeba0;
16+
cursor: pointer;
17+
border: none;
18+
}
19+
20+
.run-button__dropdown {
21+
font-family: Dropdown;
22+
23+
&::before {
24+
content: '\f0d7';
25+
padding: 5px;
26+
border-left: 1px solid #ccc;
27+
}
28+
}
29+
30+
.popup__content {
31+
padding: 0;
32+
}
33+
34+
.run-mode {
35+
padding: 0;
36+
37+
.run-mode__item {
38+
font-size: 13px;
39+
padding: 5px 10px;
40+
list-style: none;
41+
user-select: none;
42+
43+
&.run-mode__item_disabled {
44+
color: #939393;
45+
cursor: auto;
46+
}
47+
}
48+
}
49+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
import React from 'react';
4+
import {Checkbox} from 'semantic-ui-react';
5+
import useLocalStorage from '../../hooks/useLocalStorage';
6+
7+
const ShowCheckboxesInput = () => {
8+
const [showCheckboxes, setShowCheckboxes] = useLocalStorage('showCheckboxes', false);
9+
10+
const onChange = () => setShowCheckboxes(!showCheckboxes);
11+
12+
return (
13+
<div className="toggle-control">
14+
<Checkbox
15+
toggle
16+
label="Checkboxes"
17+
onChange={onChange}
18+
checked={showCheckboxes}
19+
/>
20+
</div>
21+
);
22+
};
23+
24+
export default ShowCheckboxesInput;

lib/static/components/controls/strict-match-filter-input.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const StrictMatchFilterInput = ({strictMatchFilter, actions}) => {
1717
};
1818

1919
return (
20-
<div className="strict-match-filter">
20+
<div className="toggle-control">
2121
<Checkbox
2222
toggle
2323
label="Strict match"

0 commit comments

Comments
 (0)