Skip to content

Commit 0b98a0f

Browse files
New faceted search results component (#132)
* Bare initial code for new faceted search component * Add Cypress to this component * Copy cypress test from current faceted search result - WIP * Use fix version (1.2.1) from `random-words` module as the upgraded version (1.3.0) was erred with webpack loader error TODO: fix this later * Add CheckboxFacetGroup component * Removed unneeded test folder as we use Cypress for testing * Add CheckboxFacetGroup component * Improve CheckboxFacetGroup component * Add FilterSidebar component * Add MultiselectDropdownFacetGroup component * Add MultiselectDropdownFacetGroup to FacetGroups cypress tests * Add MultiselectDropdownFacetGroup to FilterSidebar component * Fix name and description of the facet groups Randomise species, cell types and organism parts * Add FilterList component without fetch loader but processing new response structure from backend * Add FacetedSearchContainer component - WIP (need to fix onChange propagation) * Fix some failing test in FacetedSearchContainer cypress test - 1 is still failing WIP * Fix FacetedSearchContainer - its test is passing now TODO: improve the test, clean the code * Fix CheckboxFacetGroup when we select multiply checkbox * Add missing fixtures * Fix queryParams prop type definition * Fix the checkboxes checked state in Checkbox facet group * Add html demo with real data for the new faceted search result * Fix species checkbox group behaviour WIP for the other groups * Fix multi select dropdown facet groups behaviour * Fix is marker gene behaviour * Clean the code * Fix deleting an already selected facet or all of the facets * Fix adding/deleting filter options that contains comma in their names * Fix cypress tests in CheckboxFacetGroup * Fix cypress tests in CheckboxFacetGroup and in MultiSelectDropdownFacetGroup * Fix cypress tests in FacetedSearchContainer.cy.js * Add header config to webpack configuration * Add code coverage for cypress tests * Update README.md * Optimising REST calls Not calling the endpoint of the currently modified facet group * Reorganising tests for facet groups * Apply code review findings for FacetedSearchContainer test * Apply code review findings for package.json * Apply code review findings * Apply code review findings for README * Apply code review findings for FilterList and convert it to stateless * Apply code review findings for CheckboxFacetGroup and convert it to stateless * Replace id with role * Fix the state modification in FacetedSearchController * Replace ids from the standard vocabulary for roles * Remove unneeded coveralls dependency * Update webpack version to ^5.73 * Clean and simplify map usage Co-authored-by: Alfonso Muñoz-Pomer Fuentes <[email protected]> * Fix the cleaned code in the previous commit :-) * Update Cypress to the latest version * Simplify more facet groups default properties * Generalise facet group initialization * Update the `scripts` section of package.json * Replaced double quotes to single one in the import section * Add back `webpack-cli` as a dev dependency and fix the README * Wait for response of the result list + add general interception for each component * Fix onChange property definition * Remove sorting from the demo page as we are not using it * Add ReactToolTip to properly show the tooltip on the page * Update cypress version Co-authored-by: Alfonso Muñoz-Pomer Fuentes <[email protected]> * Fix quotes Co-authored-by: Alfonso Muñoz-Pomer Fuentes <[email protected]> --------- Co-authored-by: Alfonso Muñoz-Pomer Fuentes <[email protected]>
1 parent 1ac1630 commit 0b98a0f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+35655
-7
lines changed

Diff for: package-lock.json

+1,740-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "root",
33
"private": true,
44
"devDependencies": {
5-
"lerna": "^5.1.2"
5+
"lerna": "^5.1.2",
6+
"webpack-cli": "^5.1.4"
67
}
78
}

Diff for: packages/scxa-faceted-search-results-v5/.babelrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"presets": ["@babel/preset-react", "@babel/preset-env"],
3+
"plugins": [ "transform-class-properties", "istanbul" ]
4+
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
webpack.config.js
2+
serve.config.js
3+
coverage/
4+
__mocks__/
5+
lib/
6+
dist/
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "@ebi-gene-expression-group"
3+
}

Diff for: packages/scxa-faceted-search-results-v5/.gitignore

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# IntelliJ IDEA project files
2+
.idea/
3+
4+
# https://github.com/github/gitignore/blob/master/Node.gitignore
5+
6+
# Logs
7+
logs
8+
*.log
9+
npm-debug.log*
10+
yarn-debug.log*
11+
yarn-error.log*
12+
13+
# Runtime data
14+
pids
15+
*.pid
16+
*.seed
17+
*.pid.lock
18+
19+
# Directory for instrumented libs generated by jscoverage/JSCover
20+
lib-cov
21+
22+
# Coverage directory used by tools like istanbul
23+
coverage
24+
25+
# nyc test coverage
26+
.nyc_output
27+
28+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
29+
.grunt
30+
31+
# Bower dependency directory (https://bower.io/)
32+
bower_components
33+
34+
# node-waf configuration
35+
.lock-wscript
36+
37+
# Compiled binary addons (http://nodejs.org/api/addons.html)
38+
build/Release
39+
40+
# Dependency directories
41+
node_modules/
42+
jspm_packages/
43+
44+
# Typescript v1 declaration files
45+
typings/
46+
47+
# Optional npm cache directory
48+
.npm
49+
50+
# Optional eslint cache
51+
.eslintcache
52+
53+
# Optional REPL history
54+
.node_repl_history
55+
56+
# Output of 'npm pack'
57+
*.tgz
58+
59+
# Yarn Integrity file
60+
.yarn-integrity
61+
62+
# dotenv environment variables file
63+
.env
64+
65+
# Build files
66+
dist/
67+
lib/

Diff for: packages/scxa-faceted-search-results-v5/.npmignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Ignore everything
2+
*
3+
.*
4+
5+
# But the build files...
6+
!lib/**

Diff for: packages/scxa-faceted-search-results-v5/README.md

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Faceted Search Results for Single Cell Expression Atlas
2+
3+
[![Build Status](https://travis-ci.org/ebi-gene-expression-group/scxa-faceted-search-results.svg?branch=master)](https://travis-ci.org/ebi-gene-expression-group/scxa-faceted-search-results) [![Coverage Status](https://coveralls.io/repos/github/ebi-gene-expression-group/scxa-faceted-search-results/badge.svg?branch=master)](https://coveralls.io/github/ebi-gene-expression-group/scxa-faceted-search-results?branch=master)
4+
5+
A lightweight and extensible component to list and filter lists of search results. It receives a URL (as a combination
6+
of `host` and `resource`) to async-fetch the results, and a React component class to visualise each element (e.g. a
7+
card).
8+
9+
The facets are used to render a sidebar on the left as a set of filtering controls.
10+
Filter groups can be displayed either as searchable, multiselect dropdown lists or as checkboxes.
11+
12+
Two more optional props, namely `noResultsMessageFormatter` and `resultsMessageFormatter`, return a string which is
13+
displayed when there are no results, and as a header on top of the search results, respectively. In many occasions it
14+
is useful to display such messages using information returned by the server; that’s the reason why both functions take
15+
the JSON payload as an argument, allowing for some customisation in that regard. For example, if in the case of no
16+
results the server returns a field `reason` you could have something like this:
17+
```
18+
<ReactFacetedSearch
19+
...
20+
noResultsMessageFormatter={(data) => `Your search yielded no results: ${data.reason}`}
21+
```
22+
23+
# Requirements
24+
25+
You should have at least Node 16 LTS with NPM 8 or later.
26+
You can check your current installed version executing this command.
27+
```shell
28+
npm --version
29+
8.5.6
30+
```
31+
32+
## How to execute the unit tests with Cypress
33+
34+
We are using the [Cypress framework](https://docs.cypress.io/guides/overview/why-cypress) for unit testing this package.
35+
You can execute the existing tests in the following way:
36+
1. Type `npx cypress open` in the terminal in the root folder of this package.
37+
2. In the appearing browser window you have to click on `Component Testing`,
38+
select a browser and click on `Start Component Testing in <browser name>`.
39+
3. In the appearing list just click on the test you would like to run and check the results on the screen.
40+
41+
## Try out the component with a given example
42+
Just run [webpack-dev-server](https://github.com/webpack/webpack-dev-server):
43+
```
44+
npx webpack serve --mode=development
45+
```
46+
47+
## How to run test with code coverage in the console
48+
49+
Run tests with:
50+
51+
```shell
52+
npx cypress run --component
53+
```
54+
55+
If you want to omit video recording:
56+
57+
```shell
58+
npx cypress run --component --record false
59+
```
60+
61+
Find coverage reports at: `./coverage/lcov-report/index.html`
62+
63+
64+
## Add code coverage (in a nutshell)
65+
66+
This is an abridged version of https://docs.cypress.io/guides/tooling/code-coverage.
67+
68+
Add the following three packages:
69+
```bash
70+
npm install -D babel-plugin-transform-class-properties babel-plugin-istanbul @cypress/code-coverage
71+
```
72+
73+
Add the two plugins to your `.babelrc` file:
74+
```json
75+
{
76+
"presets": ["@babel/preset-react", "@babel/preset-env"],
77+
"plugins": [ "transform-class-properties", "istanbul" ]
78+
}
79+
```
80+
81+
Add `@cypress/code-coverage` in your `cypress.config.js` file so it looks like this:
82+
```js
83+
const { defineConfig } = require(`cypress`)
84+
85+
module.exports = defineConfig({
86+
component: {
87+
setupNodeEvents(on, config) {
88+
require(`@cypress/code-coverage/task`)(on, config)
89+
// include any other plugin code...
90+
91+
// It's IMPORTANT to return the config object
92+
// with any changed environment variables
93+
return config
94+
},
95+
devServer: {
96+
framework: `react`,
97+
bundler: `webpack`
98+
}
99+
}
100+
})
101+
```
102+
103+
Lastly, add the following import to `cypress/support/component.js`:
104+
```js
105+
import '@cypress/code-coverage/support'
106+
```
107+
108+
# Combining with the EBI Visual Framework
109+
React-Select allows you to style the `div` that encloses the `input` element, but not the `input` element itself.
110+
Therefore it’s convenient to add the snippet below to override the styling set by the
111+
[EBI Visual Framework](https://github.com/ebiwd/EBI-Framework) for `input` elements.
112+
```
113+
.input-clear input, .input-clear input:focus {
114+
height: unset;
115+
box-shadow: none;
116+
margin: 0;
117+
}
118+
```
119+
At the time of writing this applies to the most recent version (v1.3). Wrapping these styles within the appropriate
120+
`div` selector is sufficient. See
121+
https://github.com/ebi-gene-expression-group/scxa-faceted-search-results/blob/master/html/filter-list.html for a
122+
working example.
123+
124+
# A note about building with Webpack
125+
The component uses `async`/`await` to fetch the JSON payload from the server. This requires to prefix the entry with
126+
`@babel/polyfill`. If you are already using an equivalent polyfill or
127+
[Runtime transform](http://babeljs.io/docs/plugins/transform-runtime/) you may skip this.
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const { defineConfig } = require(`cypress`)
2+
3+
module.exports = defineConfig({
4+
component: {
5+
setupNodeEvents(on, config) {
6+
require(`@cypress/code-coverage/task`)(on, config)
7+
// include any other plugin code...
8+
9+
// It's IMPORTANT to return the config object
10+
// with any changed environment variables
11+
return config
12+
},
13+
devServer: {
14+
framework: `react`,
15+
bundler: `webpack`
16+
}
17+
}
18+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import React from 'react'
2+
3+
import CheckboxFacetGroup from '../../src/facetgroups/CheckboxFacetGroup'
4+
5+
import { getFacets, getPropsForCheckBoxGroupWithTooltip, getPropsWithoutTooltip, getRandomInt } from './TestUtils'
6+
import vindicators from '../fixtures/vindicatorsResponse.json'
7+
8+
const props = {
9+
name: `Vindicators`,
10+
description: `Show the vindicators`,
11+
facets: [],
12+
queryParams: []
13+
}
14+
15+
describe(`CheckboxFacetGroup`, () => {
16+
beforeEach(() => {
17+
while (props.facets.length === 0) {
18+
props.facets = vindicators.filter(() => Math.random() > 0.5)
19+
}
20+
props.facets = props.facets.map(e => ({ ...e, disabled: false }))
21+
})
22+
23+
it(`displays the expected tooltip if it exists`, () => {
24+
const props = {
25+
...getPropsForCheckBoxGroupWithTooltip(),
26+
facets: getFacets()
27+
}
28+
29+
cy.mount(<CheckboxFacetGroup {...props }/>)
30+
cy.get(`div.padding-bottom-xlarge h4 sup span`)
31+
.should(`have.class`, `icon icon-generic`)
32+
.should(`have.attr`, `data-tip`).then((dataTip) => {
33+
expect(dataTip.toString()
34+
.replace(`<span>`, ``)
35+
.replace(`</span>`, ``)
36+
.replace(`<br>`, ` `))
37+
.to.equal(props.description)
38+
})
39+
})
40+
41+
it(`doesn't display tooltip if not present`, () => {
42+
const propsWithoutTooltip = {
43+
...getPropsWithoutTooltip(),
44+
facets: getFacets()
45+
}
46+
cy.mount(<CheckboxFacetGroup {...propsWithoutTooltip}/>)
47+
cy.get(`div.padding-bottom-xlarge h4`)
48+
.children()
49+
.should(`have.length`, 0)
50+
})
51+
52+
it(`renders the right number of checkboxes and the facet name`, () => {
53+
cy.mount(<CheckboxFacetGroup {...props} />)
54+
55+
cy.get(`input`).should((inputs) => {
56+
expect(inputs.length).to.equal(props.facets.length)
57+
})
58+
cy.get(`input[type="checkbox"]`).should((checkboxes) => {
59+
expect(checkboxes.length).to.equal(props.facets.length)
60+
})
61+
cy.get(`h4`).should(`contains.text`, props.name)
62+
cy.get(`input`).should(`not.have.attr`, `disabled`)
63+
cy.get(`label`).should(`not.have.attr`, `color`)
64+
})
65+
66+
it(`displays disabled check boxes greyed out`, () => {
67+
props.facets = props.facets.map(e => ({ ...e, disabled: true }))
68+
cy.mount(<CheckboxFacetGroup {...props} />)
69+
70+
cy.get(`input`).should(`have.attr`, `disabled`)
71+
cy.get(`label`)
72+
.should(`have.attr`, `style`)
73+
.should(`eq`, `color: lightgrey;`)
74+
})
75+
76+
it(`callback is called when a checkbox is checked/unchecked with the right arguments`, () => {
77+
const randomCheckboxIndex = getRandomInt(0, props.facets.length)
78+
const mockCallbackWrapper = {
79+
onChange: function (facetGroup, checkedFacets) {
80+
}
81+
}
82+
cy.spy(mockCallbackWrapper, `onChange`).as(`onChange`)
83+
84+
cy.mount(<CheckboxFacetGroup {...props} onChange={mockCallbackWrapper.onChange} />)
85+
86+
cy.get(`input[type="checkbox"]`).eq(randomCheckboxIndex).click()
87+
cy.get(`@onChange`)
88+
.should(`have.been.calledOnceWithExactly`,
89+
props.name,
90+
[props.facets[randomCheckboxIndex]],
91+
props.facets
92+
)
93+
94+
cy.get(`input[type="checkbox"]`).eq(randomCheckboxIndex).click()
95+
cy.get(`@onChange`)
96+
.should(`have.been.callCount`, 2)
97+
.should(`have.been.calledWith`,
98+
props.name, [props.facets[randomCheckboxIndex]]
99+
)
100+
.should(`have.been.calledWith`,
101+
props.name, [], props.facets
102+
)
103+
})
104+
})

0 commit comments

Comments
 (0)