From d458fe72e35951c951d699e62cf311652fb952a8 Mon Sep 17 00:00:00 2001 From: Niko Simonson Date: Mon, 8 Jan 2018 06:07:46 -0800 Subject: [PATCH] [gatsby-link] Add location object as valid to prop (#3407) * Add tests for location path * Add location object as valid to prop * Update tests * Update typescript type --- packages/gatsby-link/index.d.ts | 1 - packages/gatsby-link/src/__tests__/index.js | 81 ++++++++++++++++++++- packages/gatsby-link/src/index.js | 35 ++++++--- 3 files changed, 102 insertions(+), 15 deletions(-) diff --git a/packages/gatsby-link/index.d.ts b/packages/gatsby-link/index.d.ts index 039dc625bd465..c2a76e17bb95a 100644 --- a/packages/gatsby-link/index.d.ts +++ b/packages/gatsby-link/index.d.ts @@ -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 } diff --git a/packages/gatsby-link/src/__tests__/index.js b/packages/gatsby-link/src/__tests__/index.js index f79aa0645fcd8..0c926a4e20322 100644 --- a/packages/gatsby-link/src/__tests__/index.js +++ b/packages/gatsby-link/src/__tests__/index.js @@ -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 = () => { @@ -27,8 +33,9 @@ const getWithPrefix = (pathPrefix = ``) => { describe(``, () => { 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() }) @@ -39,7 +46,7 @@ describe(``, () => { to, }) - expect(instance.state.to).toEqual(to) + expect(instance.state.to.pathname).toEqual(to) }) /* @@ -60,6 +67,74 @@ describe(``, () => { }) }) + 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( + + link + , + 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( + + link + , + 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( + + link + , + 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`) diff --git a/packages/gatsby-link/src/index.js b/packages/gatsby-link/src/index.js index 0065f651d54a8..853298429fc49 100644 --- a/packages/gatsby-link/src/index.js +++ b/packages/gatsby-link/src/index.js @@ -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__) { @@ -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, @@ -45,7 +52,7 @@ const handleIntersection = (el, cb) => { } class GatsbyLink extends React.Component { - constructor(props) { + constructor(props, context) { super() // Default to no support for IntersectionObserver let IOSupported = false @@ -53,8 +60,12 @@ class GatsbyLink extends React.Component { 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) @@ -62,12 +73,14 @@ class GatsbyLink extends React.Component { 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) } } } @@ -75,7 +88,7 @@ class GatsbyLink extends React.Component { componentDidMount() { // Preserve non IO functionality if no support if (!this.state.IOSupported) { - ___loader.enqueue(this.state.to) + ___loader.enqueue(this.state.path) } } @@ -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) }) } } @@ -116,7 +129,7 @@ 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(`#`) @@ -124,7 +137,7 @@ class GatsbyLink extends React.Component { .join(``) } if (pathname === window.location.pathname) { - const hashFragment = this.state.to + const hashFragment = this.state.path .split(`#`) .slice(1) .join(`#`) @@ -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) } } @@ -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 = { @@ -167,5 +180,5 @@ GatsbyLink.contextTypes = { export default GatsbyLink export const navigateTo = pathname => { - window.___navigateTo(withPrefix(pathname)) + window.___navigateTo(pathname) }