Skip to content
This repository has been archived by the owner on Sep 4, 2020. It is now read-only.

Commit

Permalink
Merge 61913f4 into 6ce6b7b
Browse files Browse the repository at this point in the history
  • Loading branch information
lingyun1010 committed Feb 27, 2019
2 parents 6ce6b7b + 61913f4 commit 7a9a2fc
Show file tree
Hide file tree
Showing 21 changed files with 1,140 additions and 170 deletions.
90 changes: 20 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,23 @@
# Template for Gene Expression Atlas and Single Cell Expression Atlas NPM packages

## Instructions

***Be sure to be running npm@4.0.0 or later. At least Node.js 8 LTS is strongly recommended.***

### Clone this repository
```
git clone https://github.com/gxa/atlas-package my-package
cd my-package
rm -rf .git
git init
git remote add origin https://github.com/gxa/my-package.git
```
Remember to create the new repository. The recommendation is to prefix the package name with “atlas-”.

### Fill in package metadata
Fill in the fields `name`, `description` and `repository`. As a general rule the packages are prefixed with
“expression-atlas-” or “sc-atlas-”. Finally, replace or remove `README.md`.

## Scripts

### `prepack`
Runs the `build` script before `npm publish`. Only the `lib` directory is packaged, so make sure everything (including
assests such as CSS or images are there).

### `postversion`, `postpublish`
After bumping the version with e.g. `npm version minor`, the package is automatically published and pushed, with all
tags, so new versions can be published in a single step.

### `test`
`npm test` runs all phases of the test lifecycle (i.e. `pretest`, `test` and `posttest`); in case you’ve added support
for Coveralls you won’t likely want to run the `posttest` phase. If that’s the case just do `npx jest`.

## Testing
Basic test boilerplate is included with [Jest](https://facebook.github.io/jest/) and
[Enzyme](http://airbnb.io/enzyme/). Jest is a test runner, an assertion library and a snapshot tester, whereas Enzyme
allows DOM testing. See the examples included in `__test__` to get an idea.

### Continuous integration
If you want CI and nice passing/failing badges, enable the repository in [Travis CI](https://travis-ci.org/). Now, with each push, Travis CI will run your tests and generate a report. You can display a test status badge going to
Travis CI, clicking on the badge and pasting the Markdown embed snippet on your `README.md`.

Enabling code coverage is very similar. You need to enable your repository in [Coveralls](https://coveralls.io/).
Every time that Travis is run, it will generate coverage information and send it to Coveralls for a coverage report.
If you go to Coveralls, you can also get a snippet to embed the coverage report shield on your readme file.

## What’s included?
- [React 16 and PropTypes](https://facebook.github.io/react/)
- [URI.js](https://medialize.github.io/URI.js/) for URL manipulation (the rich version of `query-string`)
- [Babel](https://babeljs.io/) with presets `env` and `react` (see `.babelrc`)
- [Webpack 4 with Webpack-CLI and Webpack-Dev-Server](https://webpack.js.org/)
- [Jest](https://facebook.github.io/jest/) and [Enzyme](http://airbnb.io/enzyme/) for testing

## Polyfills
No polyfills are included by default, but you might want one or both of these:
- [Fetch polyfill](https://github.com/github/fetch)
- [Babel polyfill](https://babeljs.io/docs/usage/polyfill/)

### NPM
```
npm install --save-dev whatwg-fetch @babel/polyfill
```

Tweak your `webpack.config.js` to include them in your entry points:
```
entry: {
myComponent: [`@babel/polyfill`, `whatwg-fetch`, `./html/render.js`]
...
}
# Atlas experiment table
We implement a sortable table header and check box table cell for downloading atlas experiments' files. [Evergreen Table](https://evergreen.segment.com/) component is used in this repository.

## Table header/content props structure

Table information is passed by an array of objects, named as `tableHeader`, including mandatory entries `type`, `title`, `width` and `dataParam`.
If the table cell links to another page, please indicate `link`, `resource`, `endpoint`, which will be transformed as a href to `host/resource/data[link]/endpoint`

***For example:***
```
[
{type: `plain`, title: `index`, width: 60, dataParam: null, link: null}
{type: `sort`, title: `Loaded date`, width: 140, dataParam: `lastUpdate`, link: null},
{type: `search`, title: `species`, width: 200, dataParam: `species`, link: null},
{type: `search`, title: `experiment description`, width: 360, dataParam: `experimentDescription`,
link: `experimentAccession`, resource: `experiments`, endpoint: `Results`},
{type: `search`, title: `experiment factors`, width: 260, dataParam: `experimentalFactors`, link: null},
{type: `sort`, title: `Number of assays`, width: 160, dataParam: `numberOfAssays`,
link: `experimentAccession`, resource: `experiments`, endpoint: `Experiment Design`}
]
```

## Run it on your browser
Expand Down
31 changes: 31 additions & 0 deletions __test__/CalloutAlert.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react'
import Enzyme from 'enzyme'
import renderer from 'react-test-renderer'
import { shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

import CalloutAlert from '../src/CalloutAlert'

Enzyme.configure({ adapter: new Adapter() })

describe(`CalloutAlert`, () => {
const props = {
error: {
description: `A human-readable description of the error, hopefully useful to the user`,
name: `Error name`,
message: `Error message`
}
}

it(`prints all the relevant error information`, () => {
const wrapper = shallow(<CalloutAlert {...props} />)
expect(wrapper.text()).toMatch(props.error.description)
expect(wrapper.text()).toMatch(props.error.name)
expect(wrapper.text()).toMatch(props.error.message)
})

it(`matches snapshot`, () => {
const tree = renderer.create(<CalloutAlert {...props} />).toJSON()
expect(tree).toMatchSnapshot()
})
})
121 changes: 121 additions & 0 deletions __test__/ExperimentTable.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React from 'react'
import Enzyme from 'enzyme'
import {shallow, mount} from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import {getRandomInt, TableCellDiv, data, tableHeader} from './TestUtils'
import ExperimentTable from '../src/ExperimentTable'
import TableFooter from '../src/TableFooter'
import TableHeaderCells from '../src/TableHeaderCells'
import { Table } from 'evergreen-ui'
import _ from "lodash"

Enzyme.configure({ adapter: new Adapter() })

describe(`ExperimentTable`, () => {
const props = {
data: data,
tableHeader: tableHeader,
host: `fool`,
resource: `bool`,
enableDownload: true,
enableIndex: true,
TableCellDiv: TableCellDiv
}

test(`should render three search general boxes and a table with head and body and two bottom info boxes`, () => {
const wrapper = shallow(<ExperimentTable {...props}/>)
expect(wrapper.find(`.small-8.columns`)).toHaveLength(3)

expect(wrapper.find(Table)).toHaveLength(1)
expect(wrapper.find(Table.Head)).toHaveLength(1)
expect(wrapper.find(Table.Body)).toHaveLength(1)

expect(wrapper.find(TableFooter)).toHaveLength(1)
expect(wrapper.find(TableHeaderCells)).toHaveLength(1)
})


test(`should sort table content and change header text icon`, () => {
const randomColumn = getRandomInt(1, tableHeader.length)
props.tableHeader[randomColumn].type=`sort`
const wrapper = mount(<ExperimentTable {...props}/>)

expect(wrapper.find(`.icon.icon-common.icon-sort-up`)).toHaveLength(1)
expect(wrapper.find(`.icon.icon-common.icon-sort-down`)).toHaveLength(0)

const sortedHeader = wrapper.find(`.header${randomColumn}`).at(0)
sortedHeader.simulate('click')
wrapper.update()
expect(wrapper.find(`.icon.icon-common.icon-sort-up`)).toHaveLength(0)
sortedHeader.simulate('click')
wrapper.update()
expect(wrapper.find(`.icon.icon-common.icon-sort-up`)).toHaveLength(1)
})

test(`should filter based on kingdom selection`, () => {
const event = {target: {name: `pollName`, value: `animals`}}
const wrapper = mount(<ExperimentTable {...props}/>)
const kingdomSearch = wrapper.find(`.kingdom`).at(0)
kingdomSearch.simulate(`change`, event)

expect(wrapper.state(`selectedKingdom`)).toEqual(`animals`)
expect(wrapper.find(Table.Row).length).toBeLessThanOrEqual(data.length)
})

test(`should filter based on table header search`, () => {
const randomValue = `si`
const randomColumn = getRandomInt(1, tableHeader.length)
props.tableHeader[randomColumn].type=`search`

const wrapper = mount(<ExperimentTable {...props}/>)
expect(wrapper.find(`.searchheader${randomColumn}`).exists()).toBe(true)
wrapper.setState({searchQuery: randomValue})
wrapper.update()
expect(wrapper.find(Table.Row).length).toBeLessThanOrEqual(data.length)
})

test(`should change page by clicking buttons`, () => {
const wrapper = mount(<ExperimentTable {...props}/>)
const currentPage = wrapper.state().currentPage
wrapper.setState({selectedNumber: 1, currentPage: 1})
wrapper.update()

const nextButton = wrapper.find('a.next')
nextButton.simulate('click')
wrapper.update()
expect(wrapper.state().currentPage).toEqual(currentPage+1)

const prevButton = wrapper.find('a.previous')
prevButton.simulate('click')
expect(wrapper.state().currentPage).toEqual(currentPage)

const pageNumberButton = wrapper.find(`.paginate_button.number a`)
const currentNumberButton = wrapper.find(`.paginate_button.number.current`)
expect(pageNumberButton).toHaveLength(data.length-1)
expect(currentNumberButton).toHaveLength(1)
})

test(`should show/hide download based on props`, () => {
const wrapper = shallow(<ExperimentTable {...props} enableDownload={true}/>)
expect(wrapper.find(`.downloadHeader`)).toHaveLength(1)

const wrapperNoDownload= shallow(<ExperimentTable {...props} enableDownload={false}/>)
expect(wrapperNoDownload.find(`.downloadHeader`)).toHaveLength(0)
})

test(`should save experiment accession by check download box`, () => {
const randomRow = getRandomInt(0, data.length)

const wrapper = mount(<ExperimentTable {...props} enableDownload={true}/>)
const propKey = tableHeader[wrapper.state(`orderedColumn`)].dataParam
const filteredElements = _.sortBy(data, propKey)

expect(wrapper.state(`checkedArray`)).toEqual([])
const checkbox = wrapper.find(`.checkbox`).at(randomRow)
checkbox.simulate(`change`)
expect(wrapper.state(`checkedArray`)).toEqual([filteredElements[randomRow].experimentAccession])
checkbox.simulate(`change`)
expect(wrapper.state(`checkedArray`)).toEqual([])
})

})
104 changes: 104 additions & 0 deletions __test__/FetchLoader.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react'
import Enzyme from 'enzyme'
import { shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import fetchMock from 'fetch-mock'

import { getRandomInt } from './TestUtils'

import FetchLoader from '../src/FetchLoader'
import CalloutAlert from '../src/CalloutAlert'

Enzyme.configure({ adapter: new Adapter() })

const DummyComponentClass = () => <div></div>

describe(`FetchLoader`, () => {
beforeEach(() => {
fetchMock.restore()
})

const props = {
host: `glip/`,
resource: `glops`,
noResultsMessageFormatter: DummyComponentClass
}

const getRandomHttpErrorCode = () => getRandomInt(400, 600)

test(`until the fetch promise is not resolved a loading message is displayed, then goes away`, async () => {
fetchMock.get(`*`, `{"results":[]}`)
const wrapper = shallow(<FetchLoader {...props} />)

expect(wrapper.find(`#loader`)).toHaveLength(1)
expect(wrapper.find(CalloutAlert)).toHaveLength(0)

await wrapper.instance().componentDidMount()
wrapper.update()

expect(wrapper.find(`#loader`)).toHaveLength(0)
expect(wrapper.find(CalloutAlert)).toHaveLength(0)
})

test(`renders an error message if request to the server returns 4xx or 5xx`, async () => {
fetchMock.get(`*`, getRandomHttpErrorCode)
const wrapper = shallow(<FetchLoader {...props} />)

await wrapper.instance().componentDidMount()
wrapper.update()
expect(wrapper.find(CalloutAlert)).toHaveLength(1)
})

test(`renders an error message if the component does not receive JSON`, async () => {
fetchMock.get(`*`, `Break the cycle, Morty. Rise above. Focus on the science`)
const wrapper = shallow(<FetchLoader {...props} />)

await wrapper.instance().componentDidMount()
wrapper.update()
expect(wrapper.find(CalloutAlert)).toHaveLength(1)
})

test(`renders an error message if the child receives invalid JSON (and the error boundary kicks in)`, async () => {
fetchMock.get(`*`, `{}`)
const wrapper = shallow(<FetchLoader {...props} />)

const e = new Error(`They’re inside you building a monument to compromise!`)
wrapper.instance().componentDidCatch(e, `Ruben’s seen some rough years, Morty.`)
wrapper.update()
expect(wrapper.find(CalloutAlert)).toHaveLength(1)
})

test(`re-fetches on props change and recovers from error if new fetch succeeds`, async () => {
fetchMock.get(`/glip/glops`, getRandomHttpErrorCode)
const wrapper = shallow(<FetchLoader {...props} />)

await wrapper.instance().componentDidMount()
wrapper.update()
expect(wrapper.find(CalloutAlert)).toHaveLength(1)

fetchMock.get(`/glops/glip`, `{"results":[]}`)
wrapper.setProps({
host: `glops/`,
resource: `glip`
})

await wrapper.instance().componentDidUpdate()
wrapper.update()
expect(wrapper.find(CalloutAlert)).toHaveLength(0)
})

test(`passes JSON payload to prop noResultsMessageFormatter`, async () => {
fetchMock.get(`*`, `{"results":[], "reason": "Rubber baby bubby bunkers!"}`)
const wrapper =
shallow(
<FetchLoader
{...props}
noResultsMessageFormatter={(data) => `No results: ${data.reason}`} />)

await wrapper.instance().componentDidMount()
wrapper.update()

expect(wrapper.find(`h5`).text()).toBe(`No results: Rubber baby bubby bunkers!`)
})

})
32 changes: 0 additions & 32 deletions __test__/MyComponent.test.js

This file was deleted.

Loading

0 comments on commit 7a9a2fc

Please sign in to comment.