Skip to content

Commit

Permalink
feat($rename): rename to redux-first-router + add scroll-restoration
Browse files Browse the repository at this point in the history
  • Loading branch information
faceyspacey committed May 2, 2017
1 parent cf64937 commit fc49239
Show file tree
Hide file tree
Showing 26 changed files with 306 additions and 145 deletions.
Binary file added .DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ module.exports = {
'import/no-named-default': 1,
'no-unused-vars': 1,
'import/no-unresolved': 1,
'flowtype/no-weak-types': 1,
semi: [2, 'never'],
'no-console': [2, { allow: ['warn', 'error'] }],
'flowtype/semi': [2, 'never'],
'jsx-quotes': [2, 'prefer-single'],
'react/jsx-filename-extension': [2, { extensions: ['.jsx', '.js'] }],
Expand Down
9 changes: 4 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
dist/
coverage/
node_modules/
.DS_Store
npm-debug.log
dist
coverage
node_modules
*.log
7 changes: 7 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
coverage
__test-helpers__
__tests__
docs
flow-typed
src
*.log
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ node_js:
- stable
cache: yarn
script:
- node_modules/.bin/travis-github-status lint flow jest snyk codeclimate # git.repos.createSatus(error count 4 tool)
- node_modules/.bin/travis-github-status lint flow jest snyk codeclimate
notifications:
email: false
webhooks:
urls:
- https://webhooks.gitter.im/e/5c214c2a052a937c9922
- https://webhooks.gitter.im/e/d3ef59f99b0a3955be68
on_success: always # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always
Expand All @@ -17,4 +17,3 @@ after_success:
branches:
except:
- /^v\d+\.\d+\.\d+$/

54 changes: 27 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
# Pure Redux Router [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square)](https://gitter.im/pure-redux-router/Lobby)
# Pure Redux Router [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square)](https://gitter.im/redux-first-router/Lobby)

<p align="center">
<a href="https://www.npmjs.com/package/pure-redux-router">
<img src="https://img.shields.io/npm/v/pure-redux-router.svg" alt="Version" />
<a href="https://www.npmjs.com/package/redux-first-router">
<img src="https://img.shields.io/npm/v/redux-first-router.svg" alt="Version" />
</a>

<a href="https://travis-ci.org/faceyspacey/pure-redux-router">
<img src="https://travis-ci.org/faceyspacey/pure-redux-router.svg?branch=master" alt="Build Status" />
<a href="https://travis-ci.org/faceyspacey/redux-first-router">
<img src="https://travis-ci.org/faceyspacey/redux-first-router.svg?branch=master" alt="Build Status" />
</a>

<a href="https://lima.codeclimate.com/github/faceyspacey/pure-redux-router/coverage">
<img src="https://lima.codeclimate.com/github/faceyspacey/pure-redux-router/badges/coverage.svg" alt="Coverage Status"/>
<a href="https://lima.codeclimate.com/github/faceyspacey/redux-first-router/coverage">
<img src="https://lima.codeclimate.com/github/faceyspacey/redux-first-router/badges/coverage.svg" alt="Coverage Status"/>
</a>

<a href="https://greenkeeper.io">
<img src="https://badges.greenkeeper.io/faceyspacey/pure-redux-router.svg" alt="Green Keeper" />
<img src="https://badges.greenkeeper.io/faceyspacey/redux-first-router.svg" alt="Green Keeper" />
</a>

<a href="https://lima.codeclimate.com/github/faceyspacey/pure-redux-router">
<img src="https://lima.codeclimate.com/github/faceyspacey/pure-redux-router/badges/gpa.svg" alt="GPA" />
<a href="https://lima.codeclimate.com/github/faceyspacey/redux-first-router">
<img src="https://lima.codeclimate.com/github/faceyspacey/redux-first-router/badges/gpa.svg" alt="GPA" />
</a>

<a href="https://www.npmjs.com/package/pure-redux-router">
<img src="https://img.shields.io/npm/dt/pure-redux-router.svg" alt="Downloads" />
<a href="https://www.npmjs.com/package/redux-first-router">
<img src="https://img.shields.io/npm/dt/redux-first-router.svg" alt="Downloads" />
</a>

<a href="https://snyk.io/test/github/faceyspacey/pure-redux-router">
<img src="https://snyk.io/test/github/faceyspacey/pure-redux-router/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/faceyspacey/pure-redux-router">
<a href="https://snyk.io/test/github/faceyspacey/redux-first-router">
<img src="https://snyk.io/test/github/faceyspacey/redux-first-router/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/faceyspacey/redux-first-router">
</a>

<a href="https://www.npmjs.com/package/pure-redux-router">
<img src="https://img.shields.io/npm/l/pure-redux-router.svg" alt="License" />
<a href="https://www.npmjs.com/package/redux-first-router">
<img src="https://img.shields.io/npm/l/redux-first-router.svg" alt="License" />
</a>
</p>


![pure-redux-router flow chart](https://raw.githubusercontent.com/faceyspacey/pure-redux-router/master/docs/pure-redux-router-flow.png)
![redux-first-router flow chart](https://raw.githubusercontent.com/faceyspacey/redux-first-router/master/docs/redux-first-router-flow-chart.png)

At face value, the goal of **Pure Redux Router** is to think of your app in *states*--which, thanks to tools like Redux and React itself,
so many of us have found effective--NOT *routes*; and of course while keeping the address bar in sync.
Expand All @@ -51,10 +51,10 @@ part is that once you set it up there's virtually nothing left to do. It's truly

## Installation

Install `pure-redux-router` and its peer dependency `history` plus our small `<Link />` package:
Install `redux-first-router` and its peer dependency `history` plus our small `<Link />` package:

```bash
yarn add history pure-redux-router pure-redux-router-link
yarn add history redux-first-router redux-first-router-link
```

## Motivation - What Routing in Redux is Meant To Be
Expand Down Expand Up @@ -87,7 +87,7 @@ get in the way of optimizing animations.
## The Gist
It's *set-and-forget-it*, so here's the most work you'll ever do! :+1:
```javascript
import { connectRoutes } from 'pure-redux-router'
import { connectRoutes } from 'redux-first-router'
import { createStore, applyMiddleware, compose } from 'redux'
import createHistory from 'history/createBrowserHistory'
import userIdReducer from './reducers/userIdReducer'
Expand All @@ -109,7 +109,7 @@ const store = createStore(rootReducer, compose(enhancer, middlewares))
```

```javascript
import { NOT_FOUND } from 'pure-redux-router'
import { NOT_FOUND } from 'redux-first-router'

export const userIdReducer = (state = null, action = {}) => {
switch(action.type) {
Expand All @@ -129,7 +129,7 @@ And here's how you'd embed SEO/Redux-friendly links in your app, while making us
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider, connect } from 'react-redux'
import Link from 'pure-redux-router-link'
import Link from 'redux-first-router-link'
import store from './configureStore'

const App = ({ userId, onClick }) =>
Expand Down Expand Up @@ -179,12 +179,12 @@ as keys in the payload object:

*note: if you have more keys in your payload that is fine--so long as you have the minimum required keys to populate the path*

Lastly, we haven't mentioned `pure-redux-router-link`yet--**Pure Redux Router** is purposely built in
Lastly, we haven't mentioned `redux-first-router-link`yet--**Pure Redux Router** is purposely built in
a very modular way, which is why the `<Link />` component is in a separate package. It's extremely simple
and you're free to make your own. Basically it passes the `href` on to **Pure Redux Router** and calls
`event.preventDefault()` to stop page reloads. It also can take an action object as a prop, which it will transform
into a URL for you! The package is obvious enough once you get the hang of what's going on here--check it
out when you're ready: [pure-redux-router-link](http://github.com/faceyspacey/pure-redux-router-link). And if
out when you're ready: [redux-first-router-link](http://github.com/faceyspacey/redux-first-router-link). And if
you're wondering, we don't offer route matching components like *React Router*--that's what state is for!
See our FAQ below.

Expand Down Expand Up @@ -250,7 +250,7 @@ That's all folks! :+1:

What about if the URL is not found?
> If the path is not found, or if actions fail to be converted to paths, our `NOT_FOUND` action type will be dispatched. You can apply it
as a case in your reducer's switch statements. Here's where you get it: `import { NOT_FOUND } from 'pure-redux-router'`. We have strong
as a case in your reducer's switch statements. Here's where you get it: `import { NOT_FOUND } from 'redux-first-router'`. We have strong
idiomatic way to deal with it in server side rendering--check it out: [server side rendering](./docs/server-rendering.md).

What about query strings and hashes?
Expand Down Expand Up @@ -292,8 +292,8 @@ That said, you absolutely don't need to have a URL for every action. In our apps
to the biggest visual changes in the page that we want search engines to pick up.

And what about actually getting links on the page for search engines to see?
> Use [pure-redux-router-link](http://github.com/faceyspacey/pure-redux-router-link). This package has been built in a modular way,
which is why that's not in here. *pure-redux-router-link's* `<Link />` component is simple. Review its code. Perhaps you want to make your own.
> Use [redux-first-router-link](http://github.com/faceyspacey/redux-first-router-link). This package has been built in a modular way,
which is why that's not in here. *redux-first-router-link's* `<Link />` component is simple. Review its code. Perhaps you want to make your own.
All it does is take an `href`, pass that along to **Pure Redux Router** and call `event.preventDefault()` to prevent the browser
from reloading the page as it visits the new URL. The net result is you have `<a>` tags on your page for *Google* to pick up.

Expand Down
4 changes: 2 additions & 2 deletions __tests__/__snapshots__/action-creators.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Object {
"payload": Object {
"someKey": "foo",
},
"type": "@@pure-redux-router/NOT_FOUND",
"type": "@@redux-first-router/NOT_FOUND",
},
"history": Object {
"entries": Array [
Expand All @@ -67,7 +67,7 @@ Object {
"payload": Object {
"someKey": "foo",
},
"type": "@@pure-redux-router/NOT_FOUND",
"type": "@@redux-first-router/NOT_FOUND",
}
`;

Expand Down
39 changes: 37 additions & 2 deletions __tests__/__snapshots__/connectRoutes.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`enhancer -> _historyAttemptDispatchAction() calls onBackNext handler with action + location arguments on path change 1`] = `
Object {
"meta": Object {
"location": Object {
"backNext": true,
"current": Object {
"pathname": "/second/foo",
"payload": Object {
"param": "foo",
},
"type": "SECOND",
},
"history": Object {
"entries": Array [
"/",
],
"index": 0,
"length": 1,
},
"load": undefined,
"prev": Object {
"pathname": "",
"payload": Object {},
"type": "",
},
"redirect": undefined,
},
},
"payload": Object {
"param": "foo",
},
"type": "SECOND",
}
`;

exports[`enhancer -> _historyAttemptDispatchAction() dispatches action matching pathname when history location changes 1`] = `
Object {
"meta": Object {
Expand Down Expand Up @@ -180,7 +215,7 @@ Object {
"current": Object {
"pathname": "/",
"payload": Object {},
"type": "@@pure-redux-router/NOT_FOUND",
"type": "@@redux-first-router/NOT_FOUND",
},
"history": Object {
"entries": Array [
Expand All @@ -199,6 +234,6 @@ Object {
},
},
"payload": Object {},
"type": "@@pure-redux-router/NOT_FOUND",
"type": "@@redux-first-router/NOT_FOUND",
}
`;
56 changes: 40 additions & 16 deletions __tests__/connectRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,13 @@ describe('middleware', () => {
store.getState() /*? $.location */

expect(action).toMatchObject({
type: '@@pure-redux-router/NOT_FOUND',
type: '@@redux-first-router/NOT_FOUND',
payload: {},
meta: {
location: {
current: {
pathname: '/',
type: '@@pure-redux-router/NOT_FOUND',
type: '@@redux-first-router/NOT_FOUND',
payload: {}
},
prev: { pathname: '', type: '', payload: {} },
Expand Down Expand Up @@ -154,15 +154,15 @@ describe('middleware', () => {
const action = store.dispatch(receivedAction) /*? */
const warnArg = console.warn.mock.calls[0][0] /*? */
expect(warnArg).toEqual(
'pure-redux-router: location update did not dispatch as your action has an error.'
'redux-first-router: location update did not dispatch as your action has an error.'
)

expect(action).toEqual(receivedAction)
})

it('calls onChange handler on route change', () => {
const onChange = jest.fn()
const { middleware, reducer } = setup('/first', { onChange })
it('calls beforeChange handler on route change', () => {
const beforeChange = jest.fn()
const { middleware, reducer } = setup('/first', { beforeChange })
const middlewares = applyMiddleware(middleware)

const rootReducer = (state = {}, action = {}) => ({
Expand All @@ -173,7 +173,26 @@ describe('middleware', () => {
const store = createStore(rootReducer, middlewares)
store.dispatch({ type: 'SECOND', payload: { param: 'bar' } })

expect(onChange).toHaveBeenCalled()
expect(beforeChange).toHaveBeenCalled()
})

it('calls afterChange handler on route change', () => {
const afterChange = jest.fn()
const { middleware, reducer } = setup('/first', { afterChange })
const middlewares = applyMiddleware(middleware)

const rootReducer = (state = {}, action = {}) => ({
location: reducer(state.location, action),
title: action.type
})

const store = createStore(rootReducer, middlewares)

jest.useFakeTimers()
store.dispatch({ type: 'SECOND', payload: { param: 'bar' } })
jest.runAllTimers()

expect(afterChange).toHaveBeenCalled()
})

it('scrolls to top on route change when options.scrollTop === true', () => {
Expand All @@ -188,7 +207,10 @@ describe('middleware', () => {
})

const store = createStore(rootReducer, middlewares)

jest.useFakeTimers()
store.dispatch({ type: 'SECOND', payload: { param: 'bar' } })
jest.runAllTimers()

expect(scrollTo).toHaveBeenCalled()
})
Expand Down Expand Up @@ -355,10 +377,11 @@ describe('enhancer', () => {
describe('enhancer -> _historyAttemptDispatchAction()', () => {
it('dispatches action matching pathname when history location changes', () => {
const dispatch = jest.fn()
const store = { dispatch }
const historyLocation = { pathname: '/second/foo' }
const { _historyAttemptDispatchAction } = setup()

_historyAttemptDispatchAction(dispatch, historyLocation)
_historyAttemptDispatchAction(store, historyLocation)

const action = dispatch.mock.calls[0][0] /*? */

Expand All @@ -371,31 +394,32 @@ describe('enhancer -> _historyAttemptDispatchAction()', () => {

it('does not dispatch if pathname is the same (i.e. was handled by middleware already)', () => {
const dispatch = jest.fn()
const store = { dispatch }
const historyLocation = { pathname: '/second/foo' }
const { _historyAttemptDispatchAction } = setup()

_historyAttemptDispatchAction(jest.fn(), historyLocation)
_historyAttemptDispatchAction(dispatch, historyLocation)
_historyAttemptDispatchAction({ dispatch: jest.fn() }, historyLocation)
_historyAttemptDispatchAction(store, historyLocation)

// insure multiple dispatches are prevented for the same action/pathname
// so that middleware and history listener don't double dispatch
expect(dispatch.mock.calls).toEqual([])
})

it('calls onBackNext handler with action + location arguments on path change', () => {
const dispatch = jest.fn()
const dispatch = jest.fn(action => expect(action).toMatchSnapshot())
const getState = jest.fn()
const store = { dispatch, getState }
const historyLocation = { pathname: '/second/foo' }
const onBackNext = jest.fn()
const { _historyAttemptDispatchAction } = setup('/', { onBackNext })

_historyAttemptDispatchAction(dispatch, historyLocation)
_historyAttemptDispatchAction(store, historyLocation)

const args = onBackNext.mock.calls[0]
const action = args[0] /*? */
const histLocation = args[1] /*? */

expect(action.meta.location.current.pathname).toEqual('/second/foo')
expect(histLocation.pathname).toEqual('/second/foo')
expect(args[0]).toEqual(dispatch)
expect(args[1]).toEqual(getState)
})
})

Expand Down
2 changes: 1 addition & 1 deletion __tests__/pure-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ describe('pathToAction(path, routesMap)', () => {
expect(action.payload.param).toEqual(69)
})

it('parsed path not found and return NOT_FOUND action.type: "@@pure-redux-router/NOT_FOUND"', () => {
it('parsed path not found and return NOT_FOUND action.type: "@@redux-first-router/NOT_FOUND"', () => {
const path = '/info/foo/bar'
const routesMap = {
INFO_PARAM: { path: '/info/:param/' }
Expand Down
Loading

0 comments on commit fc49239

Please sign in to comment.