Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gatsby-link] Add location object as valid to prop #3407

Merged
merged 6 commits into from
Jan 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/gatsby-link/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as React from "react";
import { NavLinkProps } from "react-router-dom";

export interface GatsbyLinkProps extends NavLinkProps {
to: string;
onClick?: (event: any) => void
className?: string
}
Expand Down
81 changes: 78 additions & 3 deletions packages/gatsby-link/src/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import React from "react"
import ReactDOM from "react-dom"
import { MemoryRouter } from "react-router-dom"

const getInstance = (props, pathPrefix = ``) => {
Object.assign(global.window, {
__PREFIX_PATHS__: pathPrefix ? true : false,
__PATH_PREFIX__: pathPrefix,
})

const context = { router: { history: {} } }

const Link = require(`../`).default
return new Link(props)
return new Link(props, context)
}

const getNavigateTo = () => {
Expand All @@ -27,8 +33,9 @@ const getWithPrefix = (pathPrefix = ``) => {
describe(`<Link />`, () => {
it(`does not fail to initialize when __PREFIX_PATHS__ is not defined`, () => {
expect(() => {
const context = { router: { history: {} } }
const Link = require(`../`).default
const link = new Link({}) //eslint-disable-line no-unused-vars
const link = new Link({}, context) //eslint-disable-line no-unused-vars
}).not.toThrow()
})

Expand All @@ -39,7 +46,7 @@ describe(`<Link />`, () => {
to,
})

expect(instance.state.to).toEqual(to)
expect(instance.state.to.pathname).toEqual(to)
})

/*
Expand All @@ -60,6 +67,74 @@ describe(`<Link />`, () => {
})
})

describe(`the location to link to`, () => {
global.___loader = {
enqueue: jest.fn(),
}

it(`accepts to as a string`, () => {
const location = `/courses?sort=name`

const node = document.createElement(`div`)
const Link = require(`../`).default

ReactDOM.render(
<MemoryRouter>
<Link to={location}>link</Link>
</MemoryRouter>,
node
)

const href = node.querySelector(`a`).getAttribute(`href`)

expect(href).toEqual(location)
})

it(`accepts a location "to" prop`, () => {
const location = {
pathname: `/courses`,
search: `?sort=name`,
hash: `#the-hash`,
state: { fromDashboard: true },
}

const node = document.createElement(`div`)
const Link = require(`../`).default

ReactDOM.render(
<MemoryRouter>
<Link to={location}>link</Link>
</MemoryRouter>,
node
)

const href = node.querySelector(`a`).getAttribute(`href`)

expect(href).toEqual(`/courses?sort=name#the-hash`)
})

it(`resolves to with no pathname using current location`, () => {
const location = {
search: `?sort=name`,
hash: `#the-hash`,
}

const node = document.createElement(`div`)
const Link = require(`../`).default

ReactDOM.render(
<MemoryRouter initialEntries={[`/somewhere`]}>
<Link to={location}>link</Link>
</MemoryRouter>,
node
)

const href = node.querySelector(`a`).getAttribute(`href`)

expect(href).toEqual(`/somewhere?sort=name#the-hash`)
})
})

it(`navigateTo is called with correct args`, () => {
getNavigateTo()(`/some-path`)

Expand Down
35 changes: 24 additions & 11 deletions packages/gatsby-link/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React from "react"
import { Link, NavLink } from "react-router-dom"
import PropTypes from "prop-types"
import { createLocation as cL, createPath } from "history"

let pathPrefix = `/`
if (typeof __PREFIX_PATHS__ !== `undefined` && __PREFIX_PATHS__) {
Expand All @@ -16,6 +17,12 @@ function normalizePath(path) {
return path.replace(/^\/\//g, `/`)
}

function createLocation(path, history) {
const location = cL(path, null, null, history.location)
location.pathname = withPrefix(location.pathname)
return location
}

const NavLinkPropTypes = {
activeClassName: PropTypes.string,
activeStyle: PropTypes.object,
Expand Down Expand Up @@ -45,37 +52,43 @@ const handleIntersection = (el, cb) => {
}

class GatsbyLink extends React.Component {
constructor(props) {
constructor(props, context) {
super()
// Default to no support for IntersectionObserver
let IOSupported = false
if (typeof window !== `undefined` && window.IntersectionObserver) {
IOSupported = true
}

const { history } = context.router
const to = createLocation(props.to, history)

this.state = {
to: withPrefix(props.to),
path: createPath(to),
to,
IOSupported,
}
this.handleRef = this.handleRef.bind(this)
}

componentWillReceiveProps(nextProps) {
if (this.props.to !== nextProps.to) {
const to = createLocation(nextProps.to, history)
this.setState({
to: withPrefix(nextProps.to),
path: createPath(to),
to,
})
// Preserve non IO functionality if no support
if (!this.state.IOSupported) {
___loader.enqueue(this.state.to)
___loader.enqueue(this.state.path)
}
}
}

componentDidMount() {
// Preserve non IO functionality if no support
if (!this.state.IOSupported) {
___loader.enqueue(this.state.to)
___loader.enqueue(this.state.path)
}
}

Expand All @@ -85,7 +98,7 @@ class GatsbyLink extends React.Component {
if (this.state.IOSupported && ref) {
// If IO supported and element reference found, setup Observer functionality
handleIntersection(ref, () => {
___loader.enqueue(this.state.to)
___loader.enqueue(this.state.path)
})
}
}
Expand Down Expand Up @@ -116,15 +129,15 @@ class GatsbyLink extends React.Component {
) {
// Is this link pointing to a hash on the same page? If so,
// just scroll there.
let pathname = this.state.to
let pathname = this.state.path
if (pathname.split(`#`).length > 1) {
pathname = pathname
.split(`#`)
.slice(0, -1)
.join(``)
}
if (pathname === window.location.pathname) {
const hashFragment = this.state.to
const hashFragment = this.state.path
.split(`#`)
.slice(1)
.join(`#`)
Expand All @@ -139,7 +152,7 @@ class GatsbyLink extends React.Component {
// loaded before continuing.
if (process.env.NODE_ENV === `production`) {
e.preventDefault()
window.___navigateTo(this.state.to)
window.___navigateTo(this.state.path)
}
}

Expand All @@ -157,7 +170,7 @@ GatsbyLink.propTypes = {
...NavLinkPropTypes,
innerRef: PropTypes.func,
onClick: PropTypes.func,
to: PropTypes.string.isRequired,
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
}

GatsbyLink.contextTypes = {
Expand All @@ -167,5 +180,5 @@ GatsbyLink.contextTypes = {
export default GatsbyLink

export const navigateTo = pathname => {
window.___navigateTo(withPrefix(pathname))
window.___navigateTo(pathname)
}