Skip to content
This repository has been archived by the owner on Apr 29, 2023. It is now read-only.

Commit

Permalink
Improve tests (#65)
Browse files Browse the repository at this point in the history
* Use JSX in test
* Prefer native event over emulation
* Improve tests by distinguishing between integration tests and unit tests
* Improve test coverage from 97% to 100%
* Don't remove URI param with invalid character sequences
* Add some integration tests
* Fix bug around clicking an external link
  • Loading branch information
Yuku TAKAHASHI authored and jorgebucaran committed Apr 3, 2018
1 parent 795eb9b commit b19c91f
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 150 deletions.
14 changes: 13 additions & 1 deletion package.json
Expand Up @@ -27,12 +27,24 @@
"release": "npm run build && npm test && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
},
"babel": {
"presets": "env"
"presets": [
"env"
],
"plugins": [
[
"transform-react-jsx",
{
"pragma": "h"
}
]
]
},
"jest": {
"testURL": "http://localhost"
},
"devDependencies": {
"babel-jest": "^22.4.3",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-env": "^1.6.1",
"hyperapp": "^1.2.5",
"jest": "^22.4.3",
Expand Down
12 changes: 11 additions & 1 deletion src/Link.js
@@ -1,5 +1,15 @@
import { h } from "hyperapp"

function getOrigin(loc) {
return loc.protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "")
}

function isExternal(anchorElement) {
// Location.origin and HTMLAnchorElement.origin are not
// supported by IE and Safari.
return getOrigin(location) !== getOrigin(anchorElement)
}

export function Link(props, children) {
return function(state, actions) {
var to = props.to
Expand All @@ -21,7 +31,7 @@ export function Link(props, children) {
e.ctrlKey ||
e.shiftKey ||
props.target === "_blank" ||
(location.origin && e.currentTarget.origin !== location.origin)
isExternal(e.currentTarget)
) {
} else {
e.preventDefault()
Expand Down
14 changes: 9 additions & 5 deletions src/parseRoute.js
Expand Up @@ -12,6 +12,14 @@ function trimTrailingSlash(url) {
return url.slice(0, len + 1)
}

function decodeParam(val) {
try {
return decodeURIComponent(val)
} catch (e) {
return val
}
}

export function parseRoute(path, url, options) {
if (path === url || !path) {
return createMatch(path === url, path, url)
Expand All @@ -27,11 +35,7 @@ export function parseRoute(path, url, options) {

for (var i = 0, params = {}, len = paths.length, url = ""; i < len; i++) {
if (":" === paths[i][0]) {
try {
params[paths[i].slice(1)] = urls[i] = decodeURI(urls[i])
} catch (_) {
continue
}
params[paths[i].slice(1)] = urls[i] = decodeParam(urls[i])
} else if (paths[i] !== urls[i]) {
return
}
Expand Down
142 changes: 114 additions & 28 deletions test/index.test.js
@@ -1,38 +1,124 @@
import { h, app } from "hyperapp"
import { Route, location } from "../src"
import { Route, Link, Redirect, location } from "../src"

test("Router", done => {
const state = {
location: location.state
}
const wait = async ms => new Promise(resolve => setTimeout(resolve, ms))
const click = e => e.dispatchEvent(new MouseEvent("click", { button: 0 }))

let state, actions, unsubscribe

const actions = {
location: location.actions
beforeEach(() => {
state = { location: location.state }
actions = {
location: location.actions,
getLocation: () => state => state.location
}
unsubscribe = null
})

afterEach(() => {
unsubscribe && unsubscribe()
})

const main = app(
state,
actions,
(state, actions) =>
h(Route, {
path: "/test",
render: () =>
h(
"h1",
{
oncreate() {
expect(document.body.innerHTML).toBe(`<h1>Hello</h1>`)
unsubscribe()
done()
}
},
"Hello"
)
}),
document.body
test("Transition by location.go()", async done => {
const spy = jest.fn()
const view = () => <Route path="/test" render={spy} />
const main = app(state, actions, view, document.body)
unsubscribe = location.subscribe(main.location)
await wait(0)
expect(spy).not.toBeCalled()
main.location.go("/test")
await wait(0)
expect(spy).toBeCalled()
done()
})

test("Transition by clicking Link", async done => {
const spy = jest.fn()
const view = () => (
<div>
<Link to="/test" />
<Route path="/test" render={spy} />
</div>
)
const main = app(state, actions, view, document.body)
unsubscribe = location.subscribe(main.location)
await wait(0)
expect(spy).not.toBeCalled()
click(document.body.getElementsByTagName("a")[0])
await wait(0)
expect(spy).toBeCalled()
// Clicking the same link again doesn't cause transition.
const count = spy.mock.calls.length
click(document.body.getElementsByTagName("a")[0])
await wait(0)
expect(spy).toHaveBeenCalledTimes(count)
done()
})

const unsubscribe = location.subscribe(main.location)
test('Click Link with target="_blank"', async done => {
const spy = jest.fn()
const view = () => (
<div>
<Link to="/test" target="_blank" />
<Route path="/test" render={spy} />
</div>
)
const main = app(state, actions, view, document.body)
unsubscribe = location.subscribe(main.location)
await wait(0)
click(document.body.getElementsByTagName("a")[0])
await wait(0)
expect(spy).not.toBeCalled()
done()
})

test("Click external Link", async done => {
const spy = jest.fn()
const view = () => <Link to="http://example.com/" />
const main = app(state, actions, view, document.body)
unsubscribe = location.subscribe(main.location)
await wait(0)
expect(main.getLocation().pathname).toEqual("/")
click(document.body.getElementsByTagName("a")[0])
await wait(0)
expect(main.getLocation().pathname).toEqual("/")
done()
})

test("Transition by clicking Link including non alphanumeric characters", async done => {
const spy = jest.fn()
const view = () => (
<div>
<Link to="/test/café" />
<Route path="/test/:id" render={spy} />
</div>
)
const main = app(state, actions, view, document.body)
unsubscribe = location.subscribe(main.location)
await wait(0)
expect(spy).not.toBeCalled()
click(document.body.getElementsByTagName("a")[0])
await wait(0)
expect(spy).toBeCalled()
expect(spy.mock.calls[0][0].match.params).toEqual({ id: "café" })
expect(main.getLocation().pathname).toEqual("/test/caf%C3%A9")
done()
})

test("Transition by rendering Redirect", async done => {
const spy = jest.fn()
const view = () => (
<div>
<Route path="/test" render={() => <Redirect to="/somewhere" />} />
<Route path="/somewhere" render={spy} />
</div>
)
const main = app(state, actions, view, document.body)
unsubscribe = location.subscribe(main.location)
await wait(0)
expect(spy).not.toBeCalled()
main.location.go("/test")
await wait(0)
expect(spy).toBeCalled()
done()
})
74 changes: 9 additions & 65 deletions test/link.test.js
@@ -1,74 +1,18 @@
import { h, app } from "hyperapp"
import { Route, Link, location } from "../src"
import { Link } from "../src"

const fakeEvent = {
button: 0,
currentTarget: { origin: window.location.origin },
preventDefault: () => {}
}

test("Link", done => {
const state = {
location: location.state
}

const actions = {
location: location.actions
}

const main = app(
state,
actions,
(state, actions) =>
h("div", {}, [
h(Route, {
path: "/test",
render: () => {
h(Link, {
to: "/done"
})(state, actions).attributes.onclick(fakeEvent)
}
}),
h(Route, {
path: "/done",
render: () =>
h(
"h1",
{
oncreate() {
expect(document.body.innerHTML).toBe(
`<div><h1>Hello</h1></div>`
)
unsubscribe()
done()
}
},
"Hello"
)
})
]),
document.body
)

const unsubscribe = location.subscribe(main.location)

main.location.go("/test")
})

test("pass through attributes", () => {
const vnode = h(Link, { to: "/path", pass: "through", location })({}, {})
test('Link passes given attributes except "to" and "location" to underlying element', () => {
const vnode = Link({ to: "/path", pass: "through", location })({}, {})
expect(vnode.attributes.to).toBeUndefined()
expect(vnode.attributes.location).toBeUndefined()
expect(vnode.attributes.href).toEqual("/path")
expect(vnode.attributes.pass).toEqual("through")
})

test("custom onclick handler", () => {
const event = { defaultPrevented: false }
const onclick = e => {
e.defaultPrevented = true
}
const vnode = h(Link, { to: "/path", onclick })({}, {})
test("Calling onclick of VNode transparently calls Link's onclick prop", () => {
const onclickProp = jest.fn()
const vnode = Link({ onclick: onclickProp })({}, {})
const event = new Object()
expect(vnode.attributes.onclick).not.toBe(onclickProp)
vnode.attributes.onclick(event)
expect(event.defaultPrevented).toBe(true)
expect(onclickProp).toBeCalledWith(event)
})
49 changes: 0 additions & 49 deletions test/redirect.test.js

This file was deleted.

0 comments on commit b19c91f

Please sign in to comment.