diff --git a/package.json b/package.json index 19ba902..30339de 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,10 @@ ], "author": "Vinoth", "license": "ISC", -"repository": { - "type": "git", - "url": "https://github.com/vinothdevelop/HTMLReport4Jest" - }, + "repository": { + "type": "git", + "url": "https://github.com/vinothdevelop/HTMLReport4Jest" + }, "bugs": { "url": "https://github.com/vinothdevelop/HTMLReport4Jest/issues" }, diff --git a/src/App.js b/src/App.js index 9d49747..6b7e0d8 100644 --- a/src/App.js +++ b/src/App.js @@ -6,6 +6,7 @@ import Header from './Components/Header/Header'; import Sidebar from './Components/Sidebar/Sidebar'; import Main from './Components/Main/Main'; let data; +const statusList = []; class App extends Component { constructor(props) { data = window.resultData; @@ -13,14 +14,19 @@ class App extends Component { this.state = { menuState: 'close', testResults: data, - treeViewData: this.formatTreeViewData(data), + treeViewData: this.formatTreeViewData(data, []), information: this.getInformation(data), }; this.onTreeNodeClick = this.onTreeNodeClick.bind(this); this.state.gridData = this.state.treeViewData; this.menuStateChange = this.menuStateChange.bind(this); + this.onStatusChecked = this.onStatusChecked.bind(this); } - formatTreeViewData(testResults) { + getStatusList() { + return statusList; + } + + formatTreeViewData(testResults, statusFilter) { const testResultData = {}; Object.assign(testResultData, testResults); let treeViewData = []; @@ -40,13 +46,14 @@ class App extends Component { testResultData.testResults, [], id, + statusFilter, ); } treeViewData = rootElement; return treeViewData; } - parseTreeData(testResults, parentArray, id) { + parseTreeData(testResults, parentArray, id, statusFilter) { let subArray = []; testResults.forEach(element => { if (element.testFilePath || element.fullName) { @@ -70,15 +77,30 @@ class App extends Component { element.testResults, [], id, + statusFilter, ); - parentArray.push(nodeValue); + if ( + statusFilter.length === 0 || + nodeValue.children.length > 0 + ) { + parentArray.push(nodeValue); + } } else if (element.ancestorTitles) { - [subArray, id] = this.parseAncestor( - element.ancestorTitles, - element, - subArray, - id, - ); + if (statusList.indexOf(element.status) < 0) { + statusList.push(element.status); + } + if ( + statusFilter.length === 0 || + statusFilter.indexOf(element.status) >= 0 + ) { + [subArray, id] = this.parseAncestor( + element.ancestorTitles, + element, + subArray, + id, + statusFilter, + ); + } } } }); @@ -88,7 +110,7 @@ class App extends Component { return [parentArray, id]; } - parseAncestor(ancestors, testCase, parentArray, id) { + parseAncestor(ancestors, testCase, parentArray, id, statusFilter) { const ancestorCopy = [...ancestors]; if (ancestors.length > 0) { const itemTitle = ancestors[0]; @@ -113,6 +135,7 @@ class App extends Component { testCase, [], id, + statusFilter, ); parentArray.push(nodeValue); } else { @@ -139,6 +162,7 @@ class App extends Component { testCase, parentArray[elementIndex].children, id, + statusFilter, ); } } else { @@ -197,6 +221,15 @@ class App extends Component { return information; } + onStatusChecked = checkedStatuses => { + this.setState({ + gridData: this.formatTreeViewData( + this.state.testResults, + checkedStatuses, + ), + }); + }; + render() { return (
@@ -219,6 +252,8 @@ class App extends Component { this.state.testResults?.reporterOptions?.expandResults } information={this.state.information} + statusList={this.getStatusList()} + onStatusChecked={this.onStatusChecked} />
); diff --git a/src/App.test.js b/src/App.test.js index 4740815..5af7ac5 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -73,3 +73,20 @@ describe('Tree click', () => { expect(container.querySelector('.main')).toMatchSnapshot(); }); }); + +describe('Status filter', () => { + test('Should have checkboxes', () => { + window.resultData = sampleData; + const { container } = render(); + expect(container.querySelectorAll('.checkboxLabel').length).toEqual(4); + }); + test('Should filter on checkbox click', () => { + window.resultData = sampleData; + const { container } = render(); + const statusCheckboxes = container.querySelectorAll('.checkboxLabel'); + fireEvent.click(statusCheckboxes[3]); + expect(container.querySelectorAll('.tab-content').length).toEqual(2); + fireEvent.click(statusCheckboxes[3]); + expect(container.querySelectorAll('.tab-content').length).toEqual(40); + }); +}); diff --git a/src/Components/FilterToggler/CheckBox.css b/src/Components/FilterToggler/CheckBox.css new file mode 100644 index 0000000..20b6931 --- /dev/null +++ b/src/Components/FilterToggler/CheckBox.css @@ -0,0 +1,51 @@ +.checkboxLabel { + text-transform: capitalize; + position: relative; + padding-left: 35px; + margin-bottom: 5px; + cursor: pointer; + display: 'inline'; +} + +/* Hide the default checkbox */ +.checkboxLabel input[type='checkbox'] { + visibility: hidden; +} + +/* creating a custom checkbox based + on demand */ +.checkboxSpan { + position: absolute; + top: 0; + left: 0; + height: 1em; + width: 1em; + background-color: white; + border: solid black; + border-width: 1px 1px 1px 1px; +} + +/* specify the background color to be +shown when checkbox is checked */ +.checkboxLabel input:checked ~ .checkboxSpan { + background-color: #333334; +} + +/* checkmark to be shown in checkbox */ +/* It is not be shown when not checked */ +.checkboxSpan:after { + position: absolute; + display: none; +} + +/* styling the checkmark using webkit */ +/* creating a square to be the sign of + checkmark */ +.checkboxLabel .checkboxSpan:after { + left: 6px; + bottom: 5px; + width: 6px; + height: 6px; + border: solid black; + border-width: 4px 4px 4px 4px; +} diff --git a/src/Components/FilterToggler/CheckBox.js b/src/Components/FilterToggler/CheckBox.js new file mode 100644 index 0000000..4bfe208 --- /dev/null +++ b/src/Components/FilterToggler/CheckBox.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import './CheckBox.css'; +export const CheckBox = props => { + return ( + + ); +}; +CheckBox.propTypes = { + handleCheck: PropTypes.func.isRequired, + isChecked: PropTypes.bool, + value: PropTypes.string.isRequired, +}; +export default CheckBox; diff --git a/src/Components/FilterToggler/CheckBox.test.js b/src/Components/FilterToggler/CheckBox.test.js new file mode 100644 index 0000000..8182f15 --- /dev/null +++ b/src/Components/FilterToggler/CheckBox.test.js @@ -0,0 +1,50 @@ +import React from 'react'; +import CheckBox from './CheckBox'; +import { render, fireEvent } from '@testing-library/react'; +test('Should contain value', () => { + const { container } = render( + , + ); + expect(container).toHaveTextContent('Test'); +}); + +test('Should not be checked', () => { + const { container } = render( + , + ); + expect(container.firstChild.lastChild.previousSibling).not.toBeChecked(); +}); + +test('Should be checked', () => { + const { container } = render( + , + ); + expect(container.firstChild.lastChild.previousSibling).toBeChecked(); +}); + +test('Should call function on change', () => { + const mockCallback = jest.fn(); + const { container } = render( + , + ); + fireEvent.click(container.firstChild); + expect(mockCallback.mock.calls.length).toBe(1); + fireEvent.click(container.firstChild); + expect(mockCallback.mock.calls.length).toBe(2); +}); diff --git a/src/Components/FilterToggler/FilterToggler.js b/src/Components/FilterToggler/FilterToggler.js new file mode 100644 index 0000000..b100787 --- /dev/null +++ b/src/Components/FilterToggler/FilterToggler.js @@ -0,0 +1,61 @@ +import React from 'react'; +import CheckBox from './CheckBox'; +import PropTypes from 'prop-types'; +export default class FilterToggler extends React.Component { + constructor(props) { + super(props); + this.state = { + statusList: this.init(this.props.statusList), + }; + } + + init(statusList) { + if (statusList && statusList.length > 0) { + return statusList.map(status => { + return { value: status, isChecked: false }; + }); + } else { + return []; + } + } + + handleChecked = event => { + const checkStatuses = []; + this.state.statusList.forEach(status => { + if (status.value === event.target.value) { + status.isChecked = event.target.checked; + } + if (status.isChecked) { + checkStatuses.push(status.value); + } + }); + this.setState(this.state.statusList); + this.props.onStatusChecked(checkStatuses); + }; + render() { + if (this.props.statusList && this.props.statusList.length > 0) { + return ( +
+

+ Filter:{' '} + {this.state.statusList.map(status => { + return ( + + ); + })} +

+
+ ); + } else { + return null; + } + } +} +FilterToggler.propTypes = { + statusList: PropTypes.array, + onStatusChecked: PropTypes.func.isRequired, +}; diff --git a/src/Components/FilterToggler/FilterToggler.test.js b/src/Components/FilterToggler/FilterToggler.test.js new file mode 100644 index 0000000..9090d48 --- /dev/null +++ b/src/Components/FilterToggler/FilterToggler.test.js @@ -0,0 +1,39 @@ +import React from 'react'; +import FilterToggler from './FilterToggler'; +import { render, fireEvent } from '@testing-library/react'; +test('Should contain one checkbox', () => { + const { container } = render( + , + ); + expect(container.getElementsByTagName('input').length).toEqual(1); +}); + +test('Should contain two checkbox', () => { + const { container } = render( + , + ); + expect(container.getElementsByTagName('input').length).toEqual(2); +}); + +test('Should call function on change', () => { + const mockCallback = jest.fn(); + const { container } = render( + , + ); + fireEvent.click(container.firstChild.getElementsByTagName('input')[0]); + expect(mockCallback.mock.calls.length).toBe(1); + expect(mockCallback.mock.calls[0][0].length).toBe(1); + expect(mockCallback.mock.calls[0][0][0]).toBe('passed'); + fireEvent.click(container.firstChild.getElementsByTagName('input')[0]); + expect(mockCallback.mock.calls.length).toBe(2); + expect(mockCallback.mock.calls[1][0].length).toBe(0); +}); diff --git a/src/Components/Grid/GridTabView.css b/src/Components/Grid/GridTabView.css index a24ad9b..937c7d3 100644 --- a/src/Components/Grid/GridTabView.css +++ b/src/Components/Grid/GridTabView.css @@ -1,17 +1,16 @@ - - /* Accordion styles */ - .tabs { +/* Accordion styles */ +.tabs { border-radius: 8px; box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.5); padding: 1vh 0vw; - } +} - .tab { +.tab { width: 100%; color: white; overflow: hidden; - } - .tab-label { +} +.tab-label { display: -webkit-box; display: flex; -webkit-box-pack: justify; @@ -21,21 +20,21 @@ font-weight: bold; cursor: pointer; color: #ffffff; - font-size: .75rem; + font-size: 0.75rem; white-space: nowrap; - } - .tab-label:hover { +} +.tab-label:hover { background: #1a252f; - } - .tab-label::after { - content: "\276F"; +} +.tab-label::after { + content: '\276F'; width: 1em; height: 1em; text-align: center; -webkit-transition: all 0.35s; transition: all 0.35s; - } - .tab-content { +} +.tab-content { padding: 3px; color: #2c3e50; background: white; @@ -43,8 +42,8 @@ transition: all 0.35s; font-size: 1rem; display: none; - } - .tab-close { +} +.tab-close { display: -webkit-box; display: flex; -webkit-box-pack: end; @@ -53,26 +52,25 @@ font-size: 0.75em; background: #2c3e50; cursor: pointer; - } - .tab-close:hover { +} +.tab-close:hover { background: #1a252f; - } +} - input:checked + .tab-label { +input.togglerCheckBox:checked + .tab-label { background: #1a252f; - } - input:checked + .tab-label::after { +} +input.togglerCheckBox:checked + .tab-label::after { -webkit-transform: rotate(90deg); transform: rotate(90deg); - } - input:checked ~ .tab-content { +} +input.togglerCheckBox:checked ~ .tab-content { max-height: fit-content; display: block; - } +} -input { +input.togglerCheckBox { position: absolute; opacity: 0; z-index: -1; - } - +} diff --git a/src/Components/Grid/Status.js b/src/Components/Grid/Status.js index 9a0957d..5ebe13f 100644 --- a/src/Components/Grid/Status.js +++ b/src/Components/Grid/Status.js @@ -101,6 +101,6 @@ class Status extends Component { } } Status.propTypes = { - status: PropTypes.string.isRequired, + status: PropTypes.string, }; export default Status; diff --git a/src/Components/Grid/TabHeading.js b/src/Components/Grid/TabHeading.js index 6632d12..27a5e66 100644 --- a/src/Components/Grid/TabHeading.js +++ b/src/Components/Grid/TabHeading.js @@ -29,6 +29,7 @@ class TabHeading extends Component { id={`elem_${this.props.item.id}`} checked={this.state.isChecked} onChange={this.toggleChange} + className="togglerCheckBox" />