diff --git a/.circleci/config.yml b/.circleci/config.yml index f89bda0ccf410..59d0a86634dd2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -155,7 +155,13 @@ jobs: - e2e-test: test_path: e2e-tests/gatsby-image - e2e_tests_runtime: + e2e_tests_development_runtime: + <<: *e2e-executor + steps: + - e2e-test: + test_path: e2e-tests/development-runtime + + e2e_tests_production_runtime: <<: *e2e-executor steps: - e2e-test: @@ -200,7 +206,9 @@ workflows: <<: *e2e-test-workflow - e2e_tests_gatsby-image: <<: *e2e-test-workflow - - e2e_tests_runtime: + - e2e_tests_development_runtime: + <<: *e2e-test-workflow + - e2e_tests_production_runtime: <<: *e2e-test-workflow - starters_publish: filters: diff --git a/.eslintrc.json b/.eslintrc.json index 8505600fca219..56c0ebc6d8fc1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,6 +24,7 @@ "jest": true }, "globals": { + "before": true, "spyOn": true, "__PATH_PREFIX__": true }, diff --git a/e2e-tests/development-runtime/.gitignore b/e2e-tests/development-runtime/.gitignore new file mode 100644 index 0000000000000..a17a058d51437 --- /dev/null +++ b/e2e-tests/development-runtime/.gitignore @@ -0,0 +1,76 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# dotenv environment variables file +.env + +# gatsby files +.cache/ +public + +# Mac files +.DS_Store + +# Yarn +yarn-error.log +.pnp/ +.pnp.js +# Yarn Integrity file +.yarn-integrity + +# misc files +cypress/screenshots +cypress/fixtures +cypress/videos + +__history__.json diff --git a/e2e-tests/development-runtime/LICENSE b/e2e-tests/development-runtime/LICENSE new file mode 100644 index 0000000000000..5169a5e4135e9 --- /dev/null +++ b/e2e-tests/development-runtime/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 gatsbyjs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/e2e-tests/development-runtime/README.md b/e2e-tests/development-runtime/README.md new file mode 100644 index 0000000000000..01a842cc769ee --- /dev/null +++ b/e2e-tests/development-runtime/README.md @@ -0,0 +1,97 @@ +

+ + Gatsby + +

+

+ Gatsby's default starter +

+ +Kick off your project with this default boilerplate ([live demo](https://gatsby-starter-default-demo.netlify.com/)). This barebones starter ships with the main Gatsby configuration files you might need. + +_Have another more specific idea? You may want to check out our vibrant collection of [official and community-created starters](https://www.gatsbyjs.org/docs/gatsby-starters/)._ + +## πŸš€ Quick start + +1. **Create a Gatsby site.** + + Use the Gatsby CLI to create a new site, specifying the default starter. + + ```sh + # create a new Gatsby site using the default starter + npx gatsby new my-default-starter + ``` + +1. **Start developing.** + + Navigate into your new site’s directory and start it up. + + ```sh + cd my-default-starter/ + gatsby develop + ``` + +1. **Open the source code and start editing!** + + Your site is now running at `http://localhost:8000`! + + \_Note: You'll also see a second link: `http://localhost:8000/___graphql`. This is a tool you can use to experiment with querying your data. Learn more about using this tool in the [Gatsby tutorial](https://www.gatsbyjs.org/tutorial/part-five/#introducing-graphiql).\_ + + Open the `my-default-starter` directory in your code editor of choice and edit `src/pages/index.js`. Save your changes and the browser will update in real time! + +## 🧐 What's inside? + +A quick look at the top-level files and directories you'll see in a Gatsby project. + + . + β”œβ”€β”€ node_modules + β”œβ”€β”€ src + β”œβ”€β”€ .gitignore + β”œβ”€β”€ .prettierrc + β”œβ”€β”€ gatsby-browser.js + β”œβ”€β”€ gatsby-config.js + β”œβ”€β”€ gatsby-node.js + β”œβ”€β”€ gatsby-ssr.js + β”œβ”€β”€ LICENSE + β”œβ”€β”€ package-lock.json + β”œβ”€β”€ package.json + β”œβ”€β”€ README.md + └── yarn.lock + +1. **`/node_modules`**: This directory contains all of the modules of code that your project depends on (npm packages) are automatically installed. + +2. **`/src`**: This directory will contain all of the code related to what you will see on the front-end of your site (what you see in the browser) such as your site header or a page template. `src` is a convention for β€œsource code”. + +3. **`.gitignore`**: This file tells git which files it should not track / not maintain a version history for. + +4. **`.prettierrc`**: This is a configuration file for [Prettier](https://prettier.io/). Prettier is a tool to help keep the formatting of your code consistent. + +5. **`gatsby-browser.js`**: This file is where Gatsby expects to find any usage of the [Gatsby browser APIs](https://www.gatsbyjs.org/docs/browser-apis/) (if any). These allow customization/extension of default Gatsby settings affecting the browser. + +6. **`gatsby-config.js`**: This is the main configuration file for a Gatsby site. This is where you can specify information about your site (metadata) like the site title and description, which Gatsby plugins you’d like to include, etc. (Check out the [config docs](https://www.gatsbyjs.org/docs/gatsby-config/) for more detail). + +7. **`gatsby-node.js`**: This file is where Gatsby expects to find any usage of the [Gatsby Node APIs](https://www.gatsbyjs.org/docs/node-apis/) (if any). These allow customization/extension of default Gatsby settings affecting pieces of the site build process. + +8. **`gatsby-ssr.js`**: This file is where Gatsby expects to find any usage of the [Gatsby server-side rendering APIs](https://www.gatsbyjs.org/docs/ssr-apis/) (if any). These allow customization of default Gatsby settings affecting server-side rendering. + +9. **`LICENSE`**: Gatsby is licensed under the MIT license. + +10. **`package-lock.json`** (See `package.json` below, first). This is an automatically generated file based on the exact versions of your npm dependencies that were installed for your project. **(You won’t change this file directly).** + +11. **`package.json`**: A manifest file for Node.js projects, which includes things like metadata (the project’s name, author, etc). This manifest is how npm knows which packages to install for your project. + +12. **`README.md`**: A text file containing useful reference information about your project. + +13. **`yarn.lock`**: [Yarn](https://yarnpkg.com/) is a package manager alternative to npm. You can use either yarn or npm, though all of the Gatsby docs reference npm. This file serves essentially the same purpose as `package-lock.json`, just for a different package management system. + +## πŸŽ“ Learning Gatsby + +Looking for more guidance? Full documentation for Gatsby lives [on the website](https://www.gatsbyjs.org/). Here are some places to start: + +- **For most developers, we recommend starting with our [in-depth tutorial for creating a site with Gatsby](https://www.gatsbyjs.org/tutorial/).** It starts with zero assumptions about your level of ability and walks through every step of the process. + +- **To dive straight into code samples, head [to our documentation](https://www.gatsbyjs.org/docs/).** In particular, check out the _Guides_, _API Reference_, and _Advanced Tutorials_ sections in the sidebar. + +## πŸ’« Deploy + +[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/gatsbyjs/gatsby-starter-default) diff --git a/e2e-tests/development-runtime/content/2018-12-14-hello-world.md b/e2e-tests/development-runtime/content/2018-12-14-hello-world.md new file mode 100644 index 0000000000000..7598fd3256790 --- /dev/null +++ b/e2e-tests/development-runtime/content/2018-12-14-hello-world.md @@ -0,0 +1,8 @@ +--- +title: Hello World +date: 2018-12-14 +--- + +This is a truly meaningful blog post + +

%SUB_TITLE%

diff --git a/e2e-tests/development-runtime/cypress.json b/e2e-tests/development-runtime/cypress.json new file mode 100644 index 0000000000000..cd20b72e75761 --- /dev/null +++ b/e2e-tests/development-runtime/cypress.json @@ -0,0 +1,4 @@ +{ + "baseUrl": "http://localhost:8000", + "failOnStatusCode": false +} diff --git a/e2e-tests/development-runtime/cypress/integration/functionality/data-update.js b/e2e-tests/development-runtime/cypress/integration/functionality/data-update.js new file mode 100644 index 0000000000000..a6f88b021473b --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/functionality/data-update.js @@ -0,0 +1,31 @@ +const FILE_CONTENT = ` +--- +title: A new post +date: ${new Date().toJSON()} +--- + +A brand new post + +`.trim() + +describe(`on new file`, () => { + beforeEach(() => { + cy.visit(`/`).waitForAPI(`onRouteUpdate`) + }) + + /* + * TODO: This seems to cause a page re-load + */ + it.skip(`re-runs GraphQL queries with new file contents`, () => { + cy.exec( + `npm run update -- --file content/sample.md --file-content '${JSON.stringify( + FILE_CONTENT + )}'` + ) + + cy.get(`ul`) + .find(`li`) + .its(`length`) + .should(`be.gt`, 1) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/functionality/hooks.js b/e2e-tests/development-runtime/cypress/integration/functionality/hooks.js new file mode 100644 index 0000000000000..5fb5b2074dd45 --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/functionality/hooks.js @@ -0,0 +1,27 @@ +const COUNT_ID = `count` + +describe(`hooks`, () => { + beforeEach(() => { + cy.visit(`/hooks`).waitForAPI(`onRouteUpdate`) + }) + + it(`displays initial state`, () => { + cy.getTestElement(COUNT_ID) + .invoke(`text`) + .should(`eq`, `0`) + }) + + it(`can update local state`, () => { + cy.getTestElement(`increment`).click() + + cy.getTestElement(COUNT_ID) + .invoke(`text`) + .should(`eq`, `1`) + + cy.getTestElement(`decrement`).click() + + cy.getTestElement(COUNT_ID) + .invoke(`text`) + .should(`eq`, `0`) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/functionality/page-arrow-functions.js b/e2e-tests/development-runtime/cypress/integration/functionality/page-arrow-functions.js new file mode 100644 index 0000000000000..2704e57642cbf --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/functionality/page-arrow-functions.js @@ -0,0 +1,21 @@ +describe(`anonymous arrow function pages`, () => { + beforeEach(() => { + cy.visit(`/anonymous-arrow`).waitForAPI(`onRouteUpdate`) + }) + + it(`displays arrow function component correctly`, () => { + cy.getTestElement(`title`) + .invoke(`text`) + .should(`contain`, `Anonymous Arrow Function`) + }) + + it(`updates page on navigation`, () => { + cy.getTestElement(`link`) + .click() + .waitForAPI(`onRouteUpdate`) + + cy.getTestElement(`title`) + .invoke(`text`) + .should(`contain`, `Two`) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/functionality/route-update.js b/e2e-tests/development-runtime/cypress/integration/functionality/route-update.js new file mode 100644 index 0000000000000..8da582a9f5f0c --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/functionality/route-update.js @@ -0,0 +1,17 @@ +describe(`hooks`, () => { + beforeEach(() => { + cy.visit(`/`).waitForAPI(`onRouteUpdate`) + }) + + it(`registers one route update on initial route`, () => { + cy.lifecycleCallCount(`onRouteUpdate`).should(`eq`, 1) + }) + + it(`registers new route update on page navigation`, () => { + cy.getTestElement(`page-two`) + .click() + .waitForAPI(`onRouteUpdate`) + + cy.lifecycleCallCount(`onRouteUpdate`).should(`eq`, 2) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/hot-reloading/arrow-functions.js b/e2e-tests/development-runtime/cypress/integration/hot-reloading/arrow-functions.js new file mode 100644 index 0000000000000..1742e8a2a34a4 --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/hot-reloading/arrow-functions.js @@ -0,0 +1,30 @@ +const IDS = { + title: `title`, + subTitle: `sub-title`, +} + +describe(`hot-reloading anonymous arrow functions`, () => { + beforeEach(() => { + cy.visit(`/arrows`).waitForAPI(`onRouteUpdate`) + }) + it(`displays placeholders on launch`, () => { + cy.getTestElement(IDS.title) + .invoke(`text`) + .should(`contain`, `%TITLE%`) + + cy.getTestElement(IDS.subTitle) + .invoke(`text`) + .should(`contain`, `%SUB_TITLE%`) + }) + + it(`upates on change`, () => { + const text = `The title` + cy.exec( + `npm run update -- --file src/components/title.js --replacements "TITLE:${text}"` + ) + + cy.getTestElement(IDS.title) + .invoke(`text`) + .should(`eq`, text) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/hot-reloading/class-component.js b/e2e-tests/development-runtime/cypress/integration/hot-reloading/class-component.js new file mode 100644 index 0000000000000..7c50c01a1ea31 --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/hot-reloading/class-component.js @@ -0,0 +1,34 @@ +const TEST_ID = `class-component` + +describe(`reloading class component`, () => { + beforeEach(() => { + cy.visit(`/`).waitForAPI(`onRouteUpdate`) + }) + it(`displays placeholder on launch`, () => { + cy.getTestElement(TEST_ID) + .invoke(`text`) + .should(`contain`, `%CLASS_COMPONENT%`) + }) + + it(`updates placeholder and hot reloads`, () => { + const text = `class component` + cy.exec( + `npm run update -- --file src/components/class-component.js --replacements "CLASS_COMPONENT:${text}"` + ) + + cy.getTestElement(TEST_ID) + .invoke(`text`) + .should(`contain`, text) + }) + + it(`updates state and hot reloads`, () => { + const value = `custom` + cy.exec( + `npm run update -- --file src/components/class-component.js --replacements "CUSTOM_STATE:${value}"` + ) + + cy.getTestElement(`stateful-${TEST_ID}`) + .invoke(`text`) + .should(`eq`, `Custom Message`) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/hot-reloading/hooks.js b/e2e-tests/development-runtime/cypress/integration/hot-reloading/hooks.js new file mode 100644 index 0000000000000..b471394446704 --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/hot-reloading/hooks.js @@ -0,0 +1,20 @@ +const COUNT_ID = `count` + +describe(`hot-reloading hooks`, () => { + beforeEach(() => { + cy.visit(`/hooks`).waitForAPI(`onRouteUpdate`) + }) + + it.skip(`can update component`, () => { + const amount = 100 + cy.exec( + `npm run update -- --file src/pages/hooks.js --replacements "count + 1:count + ${amount}" --exact` + ) + + cy.getTestElement(`increment`).click() + + cy.getTestElement(COUNT_ID) + .invoke(`text`) + .should(`eq`, `${amount}`) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/hot-reloading/new-file.js b/e2e-tests/development-runtime/cypress/integration/hot-reloading/new-file.js new file mode 100644 index 0000000000000..6a06ef4a68311 --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/hot-reloading/new-file.js @@ -0,0 +1,30 @@ +describe(`hot reloading new page component`, () => { + before(() => { + cy.exec(`npm run update -- --file src/pages/sample.js`) + }) + + beforeEach(() => { + cy.visit(`/`).waitForAPI(`onRouteUpdate`) + }) + + it(`can navigate to new page`, () => { + cy.visit(`/sample`).waitForAPI(`onRouteUpdate`) + + cy.getTestElement(`message`) + .invoke(`text`) + .should(`contain`, `Hello`) + }) + + it(`can hot reload a new page file`, () => { + const text = `World` + cy.exec( + `npm run update -- --file src/pages/sample.js --replacements "REPLACEMENT:${text}"` + ) + + cy.visit(`/sample`).waitForAPI(`onRouteUpdate`) + + cy.getTestElement(`message`) + .invoke(`text`) + .should(`eq`, `Hello ${text}`) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/hot-reloading/non-js-file.js b/e2e-tests/development-runtime/cypress/integration/hot-reloading/non-js-file.js new file mode 100644 index 0000000000000..e8abc8d5b56b0 --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/hot-reloading/non-js-file.js @@ -0,0 +1,25 @@ +const TEMPLATE = `SUB_TITLE` +const TEST_ID = `sub-title` + +describe(`hot reloading non-js file`, () => { + beforeEach(() => { + cy.visit(`/2018-12-14-hello-world/`).waitForAPI(`onRouteUpdate`) + }) + + it(`displays placeholder content on launch`, () => { + cy.getTestElement(TEST_ID) + .invoke(`text`) + .should(`contain`, TEMPLATE) + }) + + it.skip(`hot reloads with new content`, () => { + const message = `This is a sub-title` + cy.exec( + `npm run update -- --file content/2018-12-14-hello-world.md --replacements "${TEMPLATE}:${message}"` + ) + + cy.getTestElement(TEST_ID) + .invoke(`text`) + .should(`eq`, message) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/hot-reloading/page-component.js b/e2e-tests/development-runtime/cypress/integration/hot-reloading/page-component.js new file mode 100644 index 0000000000000..d827815dd438b --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/hot-reloading/page-component.js @@ -0,0 +1,23 @@ +const TEST_ID = `page-component` + +describe(`hot reloading page component`, () => { + beforeEach(() => { + cy.visit(`/`).waitForAPI(`onRouteUpdate`) + }) + it(`displays placeholder content on launch`, () => { + cy.getTestElement(TEST_ID) + .invoke(`text`) + .should(`contain`, `%GATSBY_SITE%`) + }) + + it(`hot reloads with new content`, () => { + const text = `Gatsby site` + cy.exec( + `npm run update -- --file src/pages/index.js --replacements "GATSBY_SITE:${text}"` + ) + + cy.getTestElement(TEST_ID) + .invoke(`text`) + .should(`contain`, text) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/hot-reloading/template-component.js b/e2e-tests/development-runtime/cypress/integration/hot-reloading/template-component.js new file mode 100644 index 0000000000000..1d23d188dad03 --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/hot-reloading/template-component.js @@ -0,0 +1,24 @@ +const TEST_ID = `message` +const TEMPLATE = `MESSAGE` + +describe(`hot reloading template component`, () => { + beforeEach(() => { + cy.visit(`/2018-12-14-hello-world/`).waitForAPI(`onRouteUpdate`) + }) + it(`displays placeholder content on launch`, () => { + cy.getTestElement(TEST_ID) + .invoke(`text`) + .should(`contain`, TEMPLATE) + }) + + it(`hot reloads with new content`, () => { + const message = `World` + cy.exec( + `npm run update -- --file src/templates/blog-post.js --replacements "${TEMPLATE}:${message}"` + ) + + cy.getTestElement(TEST_ID) + .invoke(`text`) + .should(`eq`, `Hello ${message}`) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/navigation/linking.js b/e2e-tests/development-runtime/cypress/integration/navigation/linking.js new file mode 100644 index 0000000000000..7fcddc06c9b89 --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/navigation/linking.js @@ -0,0 +1,40 @@ +describe(`navigation`, () => { + beforeEach(() => { + cy.visit(`/`).waitForAPI(`onRouteUpdate`) + }) + + it(`displays content from other pages`, () => { + cy.visit(`/page-2`).waitForAPI(`onRouteUpdate`) + + cy.getTestElement(`page-2-message`) + .invoke(`text`) + .should(`equal`, `Hi from the second page`) + }) + + it(`re-routes on link click`, () => { + cy.getTestElement(`page-two`).click() + + cy.location(`pathname`).should(`equal`, `/page-2/`) + }) + + it(`can navigate to and from pages`, () => { + cy.getTestElement(`page-two`).click() + + cy.getTestElement(`back-button`).click() + + cy.location(`pathname`).should(`equal`, `/`) + }) + + /* + * Browser API onRouteUpdate is not triggered on a 404 + */ + it.skip(`displays 404 page on broken link`, () => { + cy.getTestElement(`broken-link`) + .click() + .waitForAPI(`onRouteUpdate`) + + cy.get(`h1`) + .invoke(`text`) + .should(`eq`, `Gatsby.js development 404 page`) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/integration/page-not-found/404.js b/e2e-tests/development-runtime/cypress/integration/page-not-found/404.js new file mode 100644 index 0000000000000..02d017088360a --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/page-not-found/404.js @@ -0,0 +1,23 @@ +describe(`page not found`, () => { + beforeEach(() => { + cy.visit(`/__404__`) + }) + it(`should display message `, () => { + cy.get(`h1`) + .invoke(`text`) + .should(`eq`, `Gatsby.js development 404 page`) + }) + it.skip(`can preview 404 page`, () => { + cy.get(`button`).click() + + cy.get(`h1`) + .invoke(`text`) + .should(`eq`, `NOT FOUND`) + }) + it(`shows page listing`, () => { + cy.get(`ul`) + .find(`li`) + .its(`length`) + .should(`be.gte`, 4) + }) +}) diff --git a/e2e-tests/development-runtime/cypress/plugins/index.js b/e2e-tests/development-runtime/cypress/plugins/index.js new file mode 100644 index 0000000000000..fd170fba6912b --- /dev/null +++ b/e2e-tests/development-runtime/cypress/plugins/index.js @@ -0,0 +1,17 @@ +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/e2e-tests/development-runtime/cypress/support/commands.js b/e2e-tests/development-runtime/cypress/support/commands.js new file mode 100644 index 0000000000000..62bedced4b6be --- /dev/null +++ b/e2e-tests/development-runtime/cypress/support/commands.js @@ -0,0 +1,10 @@ +Cypress.Commands.add(`lifecycleCallCount`, action => + cy + .window() + .then( + win => + win.___PageComponentLifecycleCallsLog.filter( + entry => entry.action === action + ).length + ) +) diff --git a/e2e-tests/development-runtime/cypress/support/index.js b/e2e-tests/development-runtime/cypress/support/index.js new file mode 100644 index 0000000000000..13970dd45babb --- /dev/null +++ b/e2e-tests/development-runtime/cypress/support/index.js @@ -0,0 +1,2 @@ +import "../../../../packages/cypress-gatsby" +import "./commands" diff --git a/e2e-tests/development-runtime/gatsby-browser.js b/e2e-tests/development-runtime/gatsby-browser.js new file mode 100644 index 0000000000000..8fc6d2c5b2850 --- /dev/null +++ b/e2e-tests/development-runtime/gatsby-browser.js @@ -0,0 +1,22 @@ +if (typeof window !== `undefined`) { + window.___PageComponentLifecycleCallsLog = [] +} + +const addLogEntry = (action, location) => { + window.___PageComponentLifecycleCallsLog.push({ + action, + pathname: location.pathname, + }) +} + +exports.onPreRouteUpdate = ({ location }) => { + addLogEntry(`onPreRouteUpdate`, location) +} + +exports.onRouteUpdate = ({ location }) => { + addLogEntry(`onRouteUpdate`, location) +} + +exports.onPrefetchPathname = ({ pathname }) => { + addLogEntry(`onPrefetchPathname`, pathname) +} diff --git a/e2e-tests/development-runtime/gatsby-config.js b/e2e-tests/development-runtime/gatsby-config.js new file mode 100644 index 0000000000000..e229721485cea --- /dev/null +++ b/e2e-tests/development-runtime/gatsby-config.js @@ -0,0 +1,47 @@ +module.exports = { + siteMetadata: { + title: `Gatsby Default Starter`, + description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`, + author: `@gatsbyjs`, + }, + plugins: [ + `gatsby-plugin-react-helmet`, + { + resolve: `gatsby-source-filesystem`, + options: { + name: `images`, + path: `${__dirname}/src/images`, + }, + }, + { + resolve: `gatsby-source-filesystem`, + options: { + name: `blog`, + path: `${__dirname}/content`, + }, + }, + `gatsby-transformer-sharp`, + { + resolve: `gatsby-transformer-remark`, + options: { + plugins: [], + }, + }, + `gatsby-plugin-sharp`, + { + resolve: `gatsby-plugin-manifest`, + options: { + name: `gatsby-starter-default`, + short_name: `starter`, + start_url: `/`, + background_color: `#663399`, + theme_color: `#663399`, + display: `minimal-ui`, + icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site. + }, + }, + // this (optional) plugin enables Progressive Web App + Offline functionality + // To learn more, visit: https://gatsby.app/offline + // 'gatsby-plugin-offline', + ], +} diff --git a/e2e-tests/development-runtime/gatsby-node.js b/e2e-tests/development-runtime/gatsby-node.js new file mode 100644 index 0000000000000..63f0550f84d0f --- /dev/null +++ b/e2e-tests/development-runtime/gatsby-node.js @@ -0,0 +1,60 @@ +const path = require(`path`) +const { createFilePath } = require(`gatsby-source-filesystem`) + +exports.onCreateNode = function onCreateNode({ + actions: { createNodeField }, + node, + getNode, +}) { + switch (node.internal.type) { + case `MarkdownRemark`: { + const slug = createFilePath({ + node, + getNode, + }) + + createNodeField({ + name: `slug`, + value: slug, + node, + }) + break + } + + default: { + break + } + } +} + +exports.createPages = async function createPages({ + actions: { createPage }, + graphql, +}) { + const { data } = await graphql(` + { + posts: allMarkdownRemark { + edges { + node { + fields { + slug + } + } + } + } + } + `) + + const blogPostTemplate = path.resolve(`src/templates/blog-post.js`) + + data.posts.edges.forEach(({ node }) => { + const { slug } = node.fields + createPage({ + path: slug, + component: blogPostTemplate, + context: { + slug, + }, + }) + }) +} diff --git a/e2e-tests/development-runtime/gatsby-ssr.js b/e2e-tests/development-runtime/gatsby-ssr.js new file mode 100644 index 0000000000000..b17b8fc19d659 --- /dev/null +++ b/e2e-tests/development-runtime/gatsby-ssr.js @@ -0,0 +1,7 @@ +/** + * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. + * + * See: https://www.gatsbyjs.org/docs/ssr-apis/ + */ + +// You can delete this file if you're not using it diff --git a/e2e-tests/development-runtime/package.json b/e2e-tests/development-runtime/package.json new file mode 100644 index 0000000000000..f3dc3285df795 --- /dev/null +++ b/e2e-tests/development-runtime/package.json @@ -0,0 +1,51 @@ +{ + "name": "e2e-development-runtime", + "description": "An e2e-test validating development runtime functionality", + "version": "1.0.0", + "author": "Dustin Schau ", + "dependencies": { + "gatsby": "^2.0.71", + "gatsby-image": "^2.0.20", + "gatsby-plugin-manifest": "^2.0.9", + "gatsby-plugin-offline": "^2.0.20", + "gatsby-plugin-react-helmet": "^3.0.2", + "gatsby-plugin-sharp": "^2.0.14", + "gatsby-source-filesystem": "^2.0.12", + "gatsby-transformer-remark": "^2.1.17", + "gatsby-transformer-sharp": "^2.1.8", + "prop-types": "^15.6.2", + "react": "16.7.0-alpha.2", + "react-dom": "16.7.0-alpha.2", + "react-helmet": "^5.2.0" + }, + "keywords": [ + "gatsby", + "gatsby-e2e" + ], + "license": "MIT", + "scripts": { + "build": "gatsby build", + "develop": "gatsby develop", + "serve": "gatsby serve", + "start": "npm run develop", + "format": "prettier --write \"src/**/*.js\"", + "test": "npm run start-server-and-test || npm run reset", + "posttest": "npm run reset", + "reset": "node scripts/reset.js", + "update": "node scripts/update.js", + "start-server-and-test": "start-server-and-test develop http://localhost:8000 cy:run", + "cy:open": "cypress open", + "cy:run": "cypress run --browser chrome" + }, + "devDependencies": { + "cypress": "^3.1.3", + "fs-extra": "^7.0.1", + "prettier": "^1.15.2", + "start-server-and-test": "^1.7.11", + "yargs": "^12.0.5" + }, + "repository": { + "type": "git", + "url": "https://github.com/gatsbyjs/gatsby" + } +} diff --git a/e2e-tests/development-runtime/scripts/history.js b/e2e-tests/development-runtime/scripts/history.js new file mode 100644 index 0000000000000..2b6996deffe35 --- /dev/null +++ b/e2e-tests/development-runtime/scripts/history.js @@ -0,0 +1,25 @@ +const fs = require(`fs-extra`) + +const HISTORY_FILE = `__history__.json` + +exports.__HISTORY_FILE__ = HISTORY_FILE + +exports.getHistory = async (file = HISTORY_FILE) => { + try { + const contents = await fs + .readFile(file, `utf8`) + .then(contents => JSON.parse(contents)) + + return new Map(contents) + } catch (e) { + return new Map() + } +} + +exports.writeHistory = async (contents, file = HISTORY_FILE) => { + try { + await fs.writeFile(file, JSON.stringify([...contents]), `utf8`) + } catch (e) { + console.error(e) + } +} diff --git a/e2e-tests/development-runtime/scripts/reset.js b/e2e-tests/development-runtime/scripts/reset.js new file mode 100644 index 0000000000000..9aabb5ed7bb82 --- /dev/null +++ b/e2e-tests/development-runtime/scripts/reset.js @@ -0,0 +1,21 @@ +const fs = require(`fs-extra`) +const path = require(`path`) + +const { __HISTORY_FILE__, getHistory } = require(`./history`) + +async function reset() { + const history = await getHistory() + + await Promise.all( + Array.from(history).map(([filePath, value]) => { + if (typeof value === `string`) { + return fs.writeFile(path.resolve(filePath), value, `utf8`) + } + return fs.remove(path.resolve(filePath)) + }) + ) + + await fs.remove(__HISTORY_FILE__) +} + +reset() diff --git a/e2e-tests/development-runtime/scripts/update.js b/e2e-tests/development-runtime/scripts/update.js new file mode 100644 index 0000000000000..589b9f98020bc --- /dev/null +++ b/e2e-tests/development-runtime/scripts/update.js @@ -0,0 +1,72 @@ +const fs = require(`fs-extra`) +const path = require(`path`) +const yargs = require(`yargs`) + +const { getHistory, writeHistory } = require(`./history`) + +const args = yargs + .option(`file`, { + demand: true, + type: `string`, + }) + .option(`replacements`, { + default: [], + type: `array`, + }) + .option(`exact`, { + default: false, + type: `boolean`, + }) + .option(`fileContent`, { + default: JSON.stringify( + ` + import React from 'react'; + + import Layout from '../components/layout'; + + export default function SomeComponent() { + return ( + +

Hello %REPLACEMENT%

+
+ ) + } + ` + ).trim(), + type: `string`, + }).argv + +async function update() { + const history = await getHistory() + + const { file: fileArg, replacements } = args + const filePath = path.resolve(fileArg) + let exists = true + if (!fs.existsSync(filePath)) { + exists = false + await fs.writeFile( + filePath, + JSON.parse(args.fileContent).replace(/\+n/g, `\n`), + `utf8` + ) + } + let file = await fs.readFile(filePath, `utf8`) + + if (!history.has(filePath)) { + history.set(filePath, exists ? file : false) + } + + const contents = replacements.reduce((replaced, pair) => { + const [key, value] = pair.split(`:`) + return replaced.replace( + args.exact ? key : new RegExp(`%${key}%`, `g`), + value + ) + }, file) + + await fs.writeFile(filePath, contents, `utf8`) + + await writeHistory(history) +} + +update() diff --git a/e2e-tests/development-runtime/src/components/class-component.js b/e2e-tests/development-runtime/src/components/class-component.js new file mode 100644 index 0000000000000..0c3553b15237b --- /dev/null +++ b/e2e-tests/development-runtime/src/components/class-component.js @@ -0,0 +1,17 @@ +import React, { Component } from "react" + +class ClassComponent extends Component { + state = { + custom: true, + } + + render() { + const custom = this.state[`%CUSTOM_STATE%`] + if (custom) { + return

Custom Message

+ } + return

I am a %CLASS_COMPONENT%

+ } +} + +export default ClassComponent diff --git a/e2e-tests/development-runtime/src/components/header.js b/e2e-tests/development-runtime/src/components/header.js new file mode 100644 index 0000000000000..22f4e99c50e26 --- /dev/null +++ b/e2e-tests/development-runtime/src/components/header.js @@ -0,0 +1,42 @@ +import { Link } from "gatsby" +import PropTypes from "prop-types" +import React from "react" + +const Header = ({ siteTitle }) => ( +
+
+

+ + {siteTitle} + +

+
+
+) + +Header.propTypes = { + siteTitle: PropTypes.string, +} + +Header.defaultProps = { + siteTitle: ``, +} + +export default Header diff --git a/e2e-tests/development-runtime/src/components/image.js b/e2e-tests/development-runtime/src/components/image.js new file mode 100644 index 0000000000000..2d068b89a89c9 --- /dev/null +++ b/e2e-tests/development-runtime/src/components/image.js @@ -0,0 +1,32 @@ +import React from "react" +import { StaticQuery, graphql } from "gatsby" +import Img from "gatsby-image" + +/* + * This component is built using `gatsby-image` to automatically serve optimized + * images with lazy loading and reduced file sizes. The image is loaded using a + * `StaticQuery`, which allows us to load the image from directly within this + * component, rather than having to pass the image data down from pages. + * + * For more information, see the docs: + * - `gatsby-image`: https://gatsby.app/gatsby-image + * - `StaticQuery`: https://gatsby.app/staticquery + */ + +const Image = () => ( + } + /> +) +export default Image diff --git a/e2e-tests/development-runtime/src/components/layout.css b/e2e-tests/development-runtime/src/components/layout.css new file mode 100644 index 0000000000000..9e53afc65d0a4 --- /dev/null +++ b/e2e-tests/development-runtime/src/components/layout.css @@ -0,0 +1,624 @@ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; +} +audio:not([controls]) { + display: none; + height: 0; +} +progress { + vertical-align: baseline; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; + -webkit-text-decoration-skip: objects; +} +a:active, +a:hover { + outline-width: 0; +} +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted; +} +b, +strong { + font-weight: inherit; + font-weight: bolder; +} +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background-color: #ff0; + color: #000; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +img { + border-style: none; +} +svg:not(:root) { + overflow: hidden; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +figure { + margin: 1em 40px; +} +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} +button, +input, +optgroup, +select, +textarea { + font: inherit; + margin: 0; +} +optgroup { + font-weight: 700; +} +button, +input { + overflow: visible; +} +button, +select { + text-transform: none; +} +[type="reset"], +[type="submit"], +button, +html [type="button"] { + -webkit-appearance: button; +} +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner, +button::-moz-focus-inner { + border-style: none; + padding: 0; +} +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring, +button:-moz-focusring { + outline: 1px dotted ButtonText; +} +fieldset { + border: 1px solid silver; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal; +} +textarea { + overflow: auto; +} +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; + padding: 0; +} +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} +[type="search"] { + -webkit-appearance: textfield; + outline-offset: -2px; +} +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-input-placeholder { + color: inherit; + opacity: 0.54; +} +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; +} +html { + font: 112.5%/1.45em georgia, serif; + box-sizing: border-box; + overflow-y: scroll; +} +* { + box-sizing: inherit; +} +*:before { + box-sizing: inherit; +} +*:after { + box-sizing: inherit; +} +body { + color: hsla(0, 0%, 0%, 0.8); + font-family: georgia, serif; + font-weight: normal; + word-wrap: break-word; + font-kerning: normal; + -moz-font-feature-settings: "kern", "liga", "clig", "calt"; + -ms-font-feature-settings: "kern", "liga", "clig", "calt"; + -webkit-font-feature-settings: "kern", "liga", "clig", "calt"; + font-feature-settings: "kern", "liga", "clig", "calt"; +} +img { + max-width: 100%; + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +h1 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 2.25rem; + line-height: 1.1; +} +h2 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 1.62671rem; + line-height: 1.1; +} +h3 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 1.38316rem; + line-height: 1.1; +} +h4 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 1rem; + line-height: 1.1; +} +h5 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 0.85028rem; + line-height: 1.1; +} +h6 { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + color: inherit; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-weight: bold; + text-rendering: optimizeLegibility; + font-size: 0.78405rem; + line-height: 1.1; +} +hgroup { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +ul { + margin-left: 1.45rem; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + list-style-position: outside; + list-style-image: none; +} +ol { + margin-left: 1.45rem; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + list-style-position: outside; + list-style-image: none; +} +dl { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +dd { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +p { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +figure { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +pre { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + font-size: 0.85rem; + line-height: 1.42; + background: hsla(0, 0%, 0%, 0.04); + border-radius: 3px; + overflow: auto; + word-wrap: normal; + padding: 1.45rem; +} +table { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; + font-size: 1rem; + line-height: 1.45rem; + border-collapse: collapse; + width: 100%; +} +fieldset { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +blockquote { + margin-left: 1.45rem; + margin-right: 1.45rem; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +form { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +noscript { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +iframe { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +hr { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: calc(1.45rem - 1px); + background: hsla(0, 0%, 0%, 0.2); + border: none; + height: 1px; +} +address { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + margin-bottom: 1.45rem; +} +b { + font-weight: bold; +} +strong { + font-weight: bold; +} +dt { + font-weight: bold; +} +th { + font-weight: bold; +} +li { + margin-bottom: calc(1.45rem / 2); +} +ol li { + padding-left: 0; +} +ul li { + padding-left: 0; +} +li > ol { + margin-left: 1.45rem; + margin-bottom: calc(1.45rem / 2); + margin-top: calc(1.45rem / 2); +} +li > ul { + margin-left: 1.45rem; + margin-bottom: calc(1.45rem / 2); + margin-top: calc(1.45rem / 2); +} +blockquote *:last-child { + margin-bottom: 0; +} +li *:last-child { + margin-bottom: 0; +} +p *:last-child { + margin-bottom: 0; +} +li > p { + margin-bottom: calc(1.45rem / 2); +} +code { + font-size: 0.85rem; + line-height: 1.45rem; +} +kbd { + font-size: 0.85rem; + line-height: 1.45rem; +} +samp { + font-size: 0.85rem; + line-height: 1.45rem; +} +abbr { + border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); + cursor: help; +} +acronym { + border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); + cursor: help; +} +abbr[title] { + border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); + cursor: help; + text-decoration: none; +} +thead { + text-align: left; +} +td, +th { + text-align: left; + border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); + font-feature-settings: "tnum"; + -moz-font-feature-settings: "tnum"; + -ms-font-feature-settings: "tnum"; + -webkit-font-feature-settings: "tnum"; + padding-left: 0.96667rem; + padding-right: 0.96667rem; + padding-top: 0.725rem; + padding-bottom: calc(0.725rem - 1px); +} +th:first-child, +td:first-child { + padding-left: 0; +} +th:last-child, +td:last-child { + padding-right: 0; +} +tt, +code { + background-color: hsla(0, 0%, 0%, 0.04); + border-radius: 3px; + font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono", + "Liberation Mono", Menlo, Courier, monospace; + padding: 0; + padding-top: 0.2em; + padding-bottom: 0.2em; +} +pre code { + background: none; + line-height: 1.42; +} +code:before, +code:after, +tt:before, +tt:after { + letter-spacing: -0.2em; + content: " "; +} +pre code:before, +pre code:after, +pre tt:before, +pre tt:after { + content: ""; +} +@media only screen and (max-width: 480px) { + html { + font-size: 100%; + } +} diff --git a/e2e-tests/development-runtime/src/components/layout.js b/e2e-tests/development-runtime/src/components/layout.js new file mode 100644 index 0000000000000..ce4f8f7938738 --- /dev/null +++ b/e2e-tests/development-runtime/src/components/layout.js @@ -0,0 +1,41 @@ +import React from "react" +import PropTypes from "prop-types" +import { StaticQuery, graphql } from "gatsby" + +import Header from "./header" +import "./layout.css" + +const Layout = ({ children }) => ( + ( + <> +
+
+ {children} +
+ + )} + /> +) + +Layout.propTypes = { + children: PropTypes.node.isRequired, +} + +export default Layout diff --git a/e2e-tests/development-runtime/src/components/seo.js b/e2e-tests/development-runtime/src/components/seo.js new file mode 100644 index 0000000000000..284672d55824e --- /dev/null +++ b/e2e-tests/development-runtime/src/components/seo.js @@ -0,0 +1,96 @@ +import React from "react" +import PropTypes from "prop-types" +import Helmet from "react-helmet" +import { StaticQuery, graphql } from "gatsby" + +function SEO({ description, lang, meta, keywords, title }) { + return ( + { + const metaDescription = + description || data.site.siteMetadata.description + return ( + 0 + ? { + name: `keywords`, + content: keywords.join(`, `), + } + : [] + ) + .concat(meta)} + /> + ) + }} + /> + ) +} + +SEO.defaultProps = { + lang: `en`, + meta: [], + keywords: [], +} + +SEO.propTypes = { + description: PropTypes.string, + lang: PropTypes.string, + meta: PropTypes.array, + keywords: PropTypes.arrayOf(PropTypes.string), + title: PropTypes.string.isRequired, +} + +export default SEO + +const detailsQuery = graphql` + query DefaultSEOQuery { + site { + siteMetadata { + title + description + author + } + } + } +` diff --git a/e2e-tests/development-runtime/src/components/sub-title.js b/e2e-tests/development-runtime/src/components/sub-title.js new file mode 100644 index 0000000000000..2645ac9cebe8b --- /dev/null +++ b/e2e-tests/development-runtime/src/components/sub-title.js @@ -0,0 +1,3 @@ +import React from "react" + +export default () =>

{`%SUB_TITLE%`}

diff --git a/e2e-tests/development-runtime/src/components/title.js b/e2e-tests/development-runtime/src/components/title.js new file mode 100644 index 0000000000000..3733199c9f939 --- /dev/null +++ b/e2e-tests/development-runtime/src/components/title.js @@ -0,0 +1,3 @@ +import React from "react" + +export default () =>

{`%TITLE%`}

diff --git a/e2e-tests/development-runtime/src/images/gatsby-astronaut.png b/e2e-tests/development-runtime/src/images/gatsby-astronaut.png new file mode 100644 index 0000000000000..da58ece0a8c5b Binary files /dev/null and b/e2e-tests/development-runtime/src/images/gatsby-astronaut.png differ diff --git a/e2e-tests/development-runtime/src/images/gatsby-icon.png b/e2e-tests/development-runtime/src/images/gatsby-icon.png new file mode 100644 index 0000000000000..908bc78a7f559 Binary files /dev/null and b/e2e-tests/development-runtime/src/images/gatsby-icon.png differ diff --git a/e2e-tests/development-runtime/src/pages/404.js b/e2e-tests/development-runtime/src/pages/404.js new file mode 100644 index 0000000000000..26a69be3ae54a --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/404.js @@ -0,0 +1,14 @@ +import React from "react" + +import Layout from "../components/layout" +import SEO from "../components/seo" + +const NotFoundPage = () => ( + + +

NOT FOUND

+

You just hit a route that does not exist... the sadness.

+
+) + +export default NotFoundPage diff --git a/e2e-tests/development-runtime/src/pages/anonymous-arrow-two.js b/e2e-tests/development-runtime/src/pages/anonymous-arrow-two.js new file mode 100644 index 0000000000000..b43fe900012e0 --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/anonymous-arrow-two.js @@ -0,0 +1,13 @@ +import React from "react" +import { Link } from "gatsby" + +import Layout from "../components/layout" + +export default () => ( + +

Anonymous Arrow Function Two

+ + First page + +
+) diff --git a/e2e-tests/development-runtime/src/pages/anonymous-arrow.js b/e2e-tests/development-runtime/src/pages/anonymous-arrow.js new file mode 100644 index 0000000000000..2022679c07f14 --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/anonymous-arrow.js @@ -0,0 +1,13 @@ +import React from "react" +import { Link } from "gatsby" + +import Layout from "../components/layout" + +export default () => ( + +

Anonymous Arrow Function

+ + Second page + +
+) diff --git a/e2e-tests/development-runtime/src/pages/arrows.js b/e2e-tests/development-runtime/src/pages/arrows.js new file mode 100644 index 0000000000000..67ff798779f73 --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/arrows.js @@ -0,0 +1,16 @@ +import React from "react" + +import Layout from "../components/layout" +import Title from "../components/title" +import Subtitle from "../components/sub-title" + +function Arrows() { + return ( + + + <Subtitle /> + </Layout> + ) +} + +export default Arrows diff --git a/e2e-tests/development-runtime/src/pages/hooks.js b/e2e-tests/development-runtime/src/pages/hooks.js new file mode 100644 index 0000000000000..b26780701fa48 --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/hooks.js @@ -0,0 +1,22 @@ +import React, { useState } from "react" + +import Layout from "../components/layout" + +function Hooks() { + const [count, setCount] = useState(0) + const increment = () => setCount(count + 1) + const decrement = () => setCount(count - 1) + return ( + <Layout> + <h1 data-testid="count">{count}</h1> + <button data-testid="decrement" onClick={decrement}> + - + </button> + <button data-testid="increment" onClick={increment}> + + + </button> + </Layout> + ) +} + +export default Hooks diff --git a/e2e-tests/development-runtime/src/pages/index.js b/e2e-tests/development-runtime/src/pages/index.js new file mode 100644 index 0000000000000..e78378d640377 --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/index.js @@ -0,0 +1,57 @@ +import React from "react" +import { Link, graphql } from "gatsby" + +import ClassComponent from "../components/class-component" +import Layout from "../components/layout" +import Image from "../components/image" +import SEO from "../components/seo" +import InstrumentPage from "../utils/instrument-page" + +const IndexPage = ({ data }) => ( + <Layout> + <SEO title="Home" keywords={[`gatsby`, `application`, `react`]} /> + <h1>Hi people</h1> + <p data-testid="page-component">Welcome to your new %GATSBY_SITE%</p> + <p>Now go build something great.</p> + <ClassComponent /> + <div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}> + <Image /> + </div> + <Link to="/page-2/" data-testid="page-two"> + Go to page 2 + </Link> + <Link to="/__non_existant_page__/" data-testid="broken-link"> + Go to a broken link + </Link> + <h2>Blog posts</h2> + <ul> + {data.posts.edges.map(({ node }) => ( + <li key={node.id}> + <Link to={node.fields.slug}>{node.frontmatter.title}</Link> + </li> + ))} + </ul> + </Layout> +) + +export default InstrumentPage(IndexPage) + +export const indexQuery = graphql` + { + posts: allMarkdownRemark( + sort: { fields: [frontmatter___date], order: DESC } + ) { + edges { + node { + id + fields { + slug + } + frontmatter { + title + } + } + } + } + } +` diff --git a/e2e-tests/development-runtime/src/pages/page-2.js b/e2e-tests/development-runtime/src/pages/page-2.js new file mode 100644 index 0000000000000..c6c81c46c047e --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/page-2.js @@ -0,0 +1,18 @@ +import React from "react" +import { Link } from "gatsby" + +import Layout from "../components/layout" +import SEO from "../components/seo" + +const SecondPage = () => ( + <Layout> + <SEO title="Page two" /> + <h1 data-testid="page-2-message">Hi from the second page</h1> + <p>Welcome to page 2</p> + <Link to="/" data-testid="back-button"> + Go back to the homepage + </Link> + </Layout> +) + +export default SecondPage diff --git a/e2e-tests/development-runtime/src/templates/blog-post.js b/e2e-tests/development-runtime/src/templates/blog-post.js new file mode 100644 index 0000000000000..d978055ad7d48 --- /dev/null +++ b/e2e-tests/development-runtime/src/templates/blog-post.js @@ -0,0 +1,31 @@ +import React from "react" +import { graphql, Link } from "gatsby" + +import Layout from "../components/layout" +import SEO from "../components/seo" + +function BlogPost({ data: { post } }) { + return ( + <Layout> + <SEO title={post.frontmatter.title} description={post.excerpt} /> + <h1>{post.frontmatter.title}</h1> + <h2 data-testid="message">Hello %MESSAGE%</h2> + <div dangerouslySetInnerHTML={{ __html: post.html }} /> + <Link to="/">Back to home</Link> + </Layout> + ) +} + +export default BlogPost + +export const blogPostQuery = graphql` + query GetBlogPostBySlug($slug: String!) { + post: markdownRemark(fields: { slug: { eq: $slug } }) { + html + excerpt(pruneLength: 160) + frontmatter { + title + } + } + } +` diff --git a/e2e-tests/development-runtime/src/utils/instrument-page.js b/e2e-tests/development-runtime/src/utils/instrument-page.js new file mode 100644 index 0000000000000..8ff2cf776584b --- /dev/null +++ b/e2e-tests/development-runtime/src/utils/instrument-page.js @@ -0,0 +1,33 @@ +import React from "react" + +export default Page => + class extends React.Component { + addLogEntry(action) { + if (typeof window !== `undefined`) { + window.___PageComponentLifecycleCallsLog.push({ + action, + pageComponent: this.props.pageResources.page.componentChunkName, + locationPath: this.props.location.pathname, + pagePath: this.props.pageResources.page.path, + }) + } + } + + constructor(props) { + super(props) + this.addLogEntry(`constructor`) + } + + componentDidMount() { + this.addLogEntry(`componentDidMount`) + } + + componentWillUnmount() { + this.addLogEntry(`componentWillUnmount`) + } + + render() { + this.addLogEntry(`render`) + return <Page {...this.props} /> + } + }