From c3bc7cdf55904ac891196aa408583bf8309714d9 Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Thu, 4 Nov 2021 11:12:58 -0400 Subject: [PATCH 01/13] chore: move cypress out of demo --- {demo/cypress => cypress}/config/all.json | 0 {demo/cypress => cypress}/fixtures/example.json | 0 {demo/cypress => cypress}/integration/test.spec.ts | 0 {demo/cypress => cypress}/plugins/index.js | 0 {demo/cypress => cypress}/support/commands.js | 0 {demo/cypress => cypress}/tsconfig.json | 4 ++-- 6 files changed, 2 insertions(+), 2 deletions(-) rename {demo/cypress => cypress}/config/all.json (100%) rename {demo/cypress => cypress}/fixtures/example.json (100%) rename {demo/cypress => cypress}/integration/test.spec.ts (100%) rename {demo/cypress => cypress}/plugins/index.js (100%) rename {demo/cypress => cypress}/support/commands.js (100%) rename {demo/cypress => cypress}/tsconfig.json (76%) diff --git a/demo/cypress/config/all.json b/cypress/config/all.json similarity index 100% rename from demo/cypress/config/all.json rename to cypress/config/all.json diff --git a/demo/cypress/fixtures/example.json b/cypress/fixtures/example.json similarity index 100% rename from demo/cypress/fixtures/example.json rename to cypress/fixtures/example.json diff --git a/demo/cypress/integration/test.spec.ts b/cypress/integration/test.spec.ts similarity index 100% rename from demo/cypress/integration/test.spec.ts rename to cypress/integration/test.spec.ts diff --git a/demo/cypress/plugins/index.js b/cypress/plugins/index.js similarity index 100% rename from demo/cypress/plugins/index.js rename to cypress/plugins/index.js diff --git a/demo/cypress/support/commands.js b/cypress/support/commands.js similarity index 100% rename from demo/cypress/support/commands.js rename to cypress/support/commands.js diff --git a/demo/cypress/tsconfig.json b/cypress/tsconfig.json similarity index 76% rename from demo/cypress/tsconfig.json rename to cypress/tsconfig.json index 8e1ac5581f..5d0c679750 100644 --- a/demo/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../tsconfig.json", "compilerOptions": { "noEmit": true, // be explicit about types included @@ -7,7 +7,7 @@ "types": ["cypress", "mocha", "@testing-library/cypress"] }, "include": [ - "../../node_modules/cypress", + "../node_modules/cypress", "./**/*.ts" ] } \ No newline at end of file From 8ca0d6773865316f74e635e516acfb4660a41428 Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Thu, 4 Nov 2021 11:50:57 -0400 Subject: [PATCH 02/13] Update package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 85df6a88e0..15f1f27863 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ ], "scripts": { "build:demo": "next build demo", - "cy:open": "cypress open --config-file cypress/config/all.json", - "cy:run": "cypress run --config-file cypress/config/all.json", + "cy:open": "cypress open --config-file ../cypress/config/all.json", + "cy:run": "cypress run --config-file ../cypress/config/all.json", "dev:demo": "next dev demo", "format": "run-s format:check-fix:*", "format:ci": "run-s format:check:*", From f88846214e19848d419e6f2a618d6cbbc5cb2b0a Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Thu, 4 Nov 2021 12:03:12 -0400 Subject: [PATCH 03/13] chore: add ci specific config --- cypress/config/all.json | 3 ++- cypress/config/ci.json | 8 ++++++++ package.json | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 cypress/config/ci.json diff --git a/cypress/config/all.json b/cypress/config/all.json index 1af3269d61..e3093e146e 100644 --- a/cypress/config/all.json +++ b/cypress/config/all.json @@ -1,3 +1,4 @@ { - "video": false + "video": false, + "baseUrl": "http://localhost:3000" } \ No newline at end of file diff --git a/cypress/config/ci.json b/cypress/config/ci.json new file mode 100644 index 0000000000..0fbb0d7e9a --- /dev/null +++ b/cypress/config/ci.json @@ -0,0 +1,8 @@ +{ + "baseUrl": "http://localhost:3000", + "integrationFolder": "../cypress/integration", + "pluginsFile": "../cypress/plugins", + "screenshotsFolder": "../cypress/screenshots", + "supportFile": "../cypress/support/index.js", + "videoFolder": "../cypress/videos" +} \ No newline at end of file diff --git a/package.json b/package.json index 15f1f27863..204b68e478 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "scripts": { "build:demo": "next build demo", "cy:open": "cypress open --config-file ../cypress/config/all.json", - "cy:run": "cypress run --config-file ../cypress/config/all.json", + "cy:run": "cypress run --config-file ../cypress/config/ci.json", "dev:demo": "next dev demo", "format": "run-s format:check-fix:*", "format:ci": "run-s format:check:*", From f537dc4cf9da43000a29e150461c87d37af36935 Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Thu, 4 Nov 2021 12:08:37 -0400 Subject: [PATCH 04/13] chore: fix testing library support --- cypress/integration/test.spec.ts | 2 +- cypress/support/index.js | 2 ++ package.json | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 cypress/support/index.js diff --git a/cypress/integration/test.spec.ts b/cypress/integration/test.spec.ts index 796cb62ece..341034cb44 100644 --- a/cypress/integration/test.spec.ts +++ b/cypress/integration/test.spec.ts @@ -4,6 +4,6 @@ describe('TypeScript spec', () => { }) it('loads home page', () => { - cy.get('h1') + cy.findByText('Next Demo!') }) }) \ No newline at end of file diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 0000000000..7a9a126f17 --- /dev/null +++ b/cypress/support/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/no-unassigned-import +import './commands'; diff --git a/package.json b/package.json index 204b68e478..d485c179d7 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ ], "scripts": { "build:demo": "next build demo", - "cy:open": "cypress open --config-file ../cypress/config/all.json", + "cy:open": "cypress open --config-file cypress/config/all.json", "cy:run": "cypress run --config-file ../cypress/config/ci.json", "dev:demo": "next dev demo", "format": "run-s format:check-fix:*", From fed6aa235ad79cc0acb4e56277008f50a4f88d2d Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Thu, 4 Nov 2021 12:12:20 -0400 Subject: [PATCH 05/13] chore: fix config file location --- demo/netlify.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/netlify.toml b/demo/netlify.toml index 509fbf8412..40cbfe7866 100644 --- a/demo/netlify.toml +++ b/demo/netlify.toml @@ -21,4 +21,4 @@ package = "@netlify/plugin-local-install-core" package = "netlify-plugin-cypress" [context.deploy-preview.plugins.inputs] -configFile = "cypress/config/all.json" +configFile = "../cypress/config/ci.json" From 0136ae84ccc9c8a2141d0500e3d3d21c1c912bf3 Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Thu, 4 Nov 2021 12:22:22 -0400 Subject: [PATCH 06/13] chore: add data-testid for the lists --- demo/pages/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demo/pages/index.js b/demo/pages/index.js index d1225acb43..b08bab7082 100644 --- a/demo/pages/index.js +++ b/demo/pages/index.js @@ -31,7 +31,7 @@ const Index = ({ shows }) => ( Refresh this page to see it change.

-
    +
      {shows.map(({ id, name }) => (
    • @@ -50,7 +50,7 @@ const Index = ({ shows }) => ( Click on a show to check out a server-side rendered page with dynamic routing (/shows/:id).

      -
        +
          {shows.slice(0, 3).map(({ id, name }) => (
        • @@ -70,7 +70,7 @@ const Index = ({ shows }) => (
          Here are three examples:

          -
            +
            • /shows/73/whatever/path/you/want @@ -97,7 +97,7 @@ const Index = ({ shows }) => ( Static pages are pre-rendered and served directly by Netlify's CDN.

              -
                +
                • Static NextJS page: /static From c04b61886a916f49c00bffbe3608cb9851a88807 Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Fri, 5 Nov 2021 11:06:05 -0400 Subject: [PATCH 07/13] chore: change site to stub api request on dev --- cypress/integration/default.spec.ts | 25 ++ cypress/integration/dynamic-routes.spec.ts | 8 + cypress/integration/test.spec.ts | 9 - demo/pages/index.js | 5 +- demo/public/shows1.json | 272 +++++++++++++++++++++ 5 files changed, 309 insertions(+), 10 deletions(-) create mode 100644 cypress/integration/default.spec.ts create mode 100644 cypress/integration/dynamic-routes.spec.ts delete mode 100644 cypress/integration/test.spec.ts create mode 100644 demo/public/shows1.json diff --git a/cypress/integration/default.spec.ts b/cypress/integration/default.spec.ts new file mode 100644 index 0000000000..054e33a7d1 --- /dev/null +++ b/cypress/integration/default.spec.ts @@ -0,0 +1,25 @@ +/* eslint-disable eslint-comments/disable-enable-pair, max-nested-callbacks */ +describe('Default site', () => { + beforeEach(() => { + cy.visit('/') + }) + + it('loads home page', () => { + cy.findByText('Next Demo!') + cy.findByTestId("list-server-side").within(() => { + cy.findAllByRole("link").should('have.length', 5) + }) + + cy.findByTestId("list-dynamic-pages").within(() => { + cy.findAllByRole("link").should('have.length', 3) + }) + + cy.findByTestId("list-catch-all").within(() => { + cy.findAllByRole("link").should('have.length', 3) + }) + + cy.findByTestId("list-static").within(() => { + cy.findAllByRole("link").should('have.length', 2) + }) + }) +}) \ No newline at end of file diff --git a/cypress/integration/dynamic-routes.spec.ts b/cypress/integration/dynamic-routes.spec.ts new file mode 100644 index 0000000000..5153fe54c8 --- /dev/null +++ b/cypress/integration/dynamic-routes.spec.ts @@ -0,0 +1,8 @@ +/* eslint-disable eslint-comments/disable-enable-pair, max-nested-callbacks */ +describe('Dynamic Routing', () => { + it('loads page', () => { + cy.visit('/shows/250') + cy.findByRole('heading').should('contain', '250') + cy.findByText('Kirby Buckets') + }) +}) \ No newline at end of file diff --git a/cypress/integration/test.spec.ts b/cypress/integration/test.spec.ts deleted file mode 100644 index 341034cb44..0000000000 --- a/cypress/integration/test.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -describe('TypeScript spec', () => { - beforeEach(() => { - cy.visit('/') - }) - - it('loads home page', () => { - cy.findByText('Next Demo!') - }) -}) \ No newline at end of file diff --git a/demo/pages/index.js b/demo/pages/index.js index b08bab7082..a7b5a98831 100644 --- a/demo/pages/index.js +++ b/demo/pages/index.js @@ -118,11 +118,14 @@ const Index = ({ shows }) => ( ) Index.getInitialProps = async function () { + const dev = process.env.CONTEXT !== 'production'; + // Set a random page between 1 and 100 const randomPage = Math.floor(Math.random() * 100) + 1 + const server = dev ? 'http://localhost:3000/shows1.json' : `https://api.tvmaze.com/shows?page=${randomPage}`; // Get the data - const res = await fetch(`https://api.tvmaze.com/shows?page=${randomPage}`) + const res = await fetch(server); const data = await res.json() return { shows: data.slice(0, 5) } diff --git a/demo/public/shows1.json b/demo/public/shows1.json new file mode 100644 index 0000000000..4755d47621 --- /dev/null +++ b/demo/public/shows1.json @@ -0,0 +1,272 @@ +[{ + "id": 250, + "url": "https://www.tvmaze.com/shows/250/kirby-buckets", + "name": "Kirby Buckets", + "type": "Scripted", + "language": "English", + "genres": ["Comedy"], + "status": "Ended", + "runtime": 30, + "averageRuntime": 30, + "premiered": "2014-10-20", + "ended": "2017-02-02", + "officialSite": "http://disneyxd.disney.com/kirby-buckets", + "schedule": { + "time": "07:00", + "days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] + }, + "rating": { + "average": null + }, + "weight": 69, + "network": { + "id": 25, + "name": "Disney XD", + "country": { + "name": "United States", + "code": "US", + "timezone": "America/New_York" + } + }, + "webChannel": { + "id": 83, + "name": "DisneyNOW", + "country": { + "name": "United States", + "code": "US", + "timezone": "America/New_York" + } + }, + "dvdCountry": null, + "externals": { + "tvrage": 37394, + "thetvdb": 278449, + "imdb": "tt3544772" + }, + "image": { + "medium": "https://static.tvmaze.com/uploads/images/medium_portrait/1/4600.jpg", + "original": "https://static.tvmaze.com/uploads/images/original_untouched/1/4600.jpg" + }, + "summary": "

                  The single-camera series that mixes live-action and animation stars Jacob Bertrand as the title character. Kirby Buckets introduces viewers to the vivid imagination of charismatic 13-year-old Kirby Buckets, who dreams of becoming a famous animator like his idol, Mac MacCallister. With his two best friends, Fish and Eli, by his side, Kirby navigates his eccentric town of Forest Hills where the trio usually find themselves trying to get out of a predicament before Kirby's sister, Dawn, and her best friend, Belinda, catch them. Along the way, Kirby is joined by his animated characters, each with their own vibrant personality that only he and viewers can see.

                  ", + "updated": 1617744408, + "_links": { + "self": { + "href": "https://api.tvmaze.com/shows/250" + }, + "previousepisode": { + "href": "https://api.tvmaze.com/episodes/1051658" + } + } +}, { + "id": 251, + "url": "https://www.tvmaze.com/shows/251/downton-abbey", + "name": "Downton Abbey", + "type": "Scripted", + "language": "English", + "genres": ["Drama", "Family", "Romance"], + "status": "Ended", + "runtime": 60, + "averageRuntime": 71, + "premiered": "2010-09-26", + "ended": "2015-12-25", + "officialSite": "http://www.itv.com/downtonabbey", + "schedule": { + "time": "21:00", + "days": ["Sunday"] + }, + "rating": { + "average": 9.1 + }, + "weight": 96, + "network": { + "id": 35, + "name": "ITV", + "country": { + "name": "United Kingdom", + "code": "GB", + "timezone": "Europe/London" + } + }, + "webChannel": null, + "dvdCountry": null, + "externals": { + "tvrage": 26615, + "thetvdb": 193131, + "imdb": "tt1606375" + }, + "image": { + "medium": "https://static.tvmaze.com/uploads/images/medium_portrait/1/4601.jpg", + "original": "https://static.tvmaze.com/uploads/images/original_untouched/1/4601.jpg" + }, + "summary": "

                  The Downton Abbey estate stands a splendid example of confidence and mettle, its family enduring for generations and its staff a well-oiled machine of propriety. But change is afoot at Downton--change far surpassing the new electric lights and telephone. A crisis of inheritance threatens to displace the resident Crawley family, in spite of the best efforts of the noble and compassionate Earl, Robert Crawley; his American heiress wife, Cora his comically implacable, opinionated mother, Violet and his beautiful, eldest daughter, Mary, intent on charting her own course. Reluctantly, the family is forced to welcome its heir apparent, the self-made and proudly modern Matthew Crawley himself none too happy about the new arrangements. As Matthew's bristly relationship with Mary begins to crackle with electricity, hope for the future of Downton's dynasty takes shape. But when petty jealousies and ambitions grow among the family and the staff, scheming and secrets--both delicious and dangerous--threaten to derail the scramble to preserve Downton Abbey. Downton Abbey offers a spot-on portrait of a vanishing way of life.

                  ", + "updated": 1627415536, + "_links": { + "self": { + "href": "https://api.tvmaze.com/shows/251" + }, + "previousepisode": { + "href": "https://api.tvmaze.com/episodes/623237" + } + } +}, { + "id": 252, + "url": "https://www.tvmaze.com/shows/252/girl-meets-world", + "name": "Girl Meets World", + "type": "Scripted", + "language": "English", + "genres": ["Drama", "Comedy", "Family"], + "status": "Ended", + "runtime": 30, + "averageRuntime": 30, + "premiered": "2014-06-27", + "ended": "2017-01-20", + "officialSite": "http://disneychannel.disney.com/girl-meets-world", + "schedule": { + "time": "18:00", + "days": ["Friday"] + }, + "rating": { + "average": 7.7 + }, + "weight": 72, + "network": { + "id": 78, + "name": "Disney Channel", + "country": { + "name": "United States", + "code": "US", + "timezone": "America/New_York" + } + }, + "webChannel": { + "id": 83, + "name": "DisneyNOW", + "country": { + "name": "United States", + "code": "US", + "timezone": "America/New_York" + } + }, + "dvdCountry": null, + "externals": { + "tvrage": 33436, + "thetvdb": 267777, + "imdb": "tt2543796" + }, + "image": { + "medium": "https://static.tvmaze.com/uploads/images/medium_portrait/316/792450.jpg", + "original": "https://static.tvmaze.com/uploads/images/original_untouched/316/792450.jpg" + }, + "summary": "

                  Girl Meets World is based on ABC's hugely popular sitcom, Boy Meets World (1993). Set in New York City, the show tells the wonderfully funny heartfelt stories that Boy Meets World is renowned for - only this time from a tween girl's perspective - as the curious and bright 7th grader Riley Matthews and her quick-witted friend Maya Fox embark on an unforgettable middle school experience. But their plans for a carefree year will be adjusted slightly under the watchful eyes of Riley's parents - dad Cory, who's also a faculty member (and their new History teacher), and mom Topanga, who owns a trendy after school hangout that specializes in pudding.

                  ", + "updated": 1621082326, + "_links": { + "self": { + "href": "https://api.tvmaze.com/shows/252" + }, + "previousepisode": { + "href": "https://api.tvmaze.com/episodes/1011244" + } + } +}, { + "id": 253, + "url": "https://www.tvmaze.com/shows/253/hells-kitchen", + "name": "Hell's Kitchen", + "type": "Reality", + "language": "English", + "genres": ["Food"], + "status": "Running", + "runtime": 60, + "averageRuntime": 60, + "premiered": "2005-05-30", + "ended": null, + "officialSite": "https://www.fox.com/hells-kitchen", + "schedule": { + "time": "20:00", + "days": ["Monday"] + }, + "rating": { + "average": 7.1 + }, + "weight": 93, + "network": { + "id": 4, + "name": "FOX", + "country": { + "name": "United States", + "code": "US", + "timezone": "America/New_York" + } + }, + "webChannel": null, + "dvdCountry": null, + "externals": { + "tvrage": 3828, + "thetvdb": 74897, + "imdb": "tt0437005" + }, + "image": { + "medium": "https://static.tvmaze.com/uploads/images/medium_portrait/324/811405.jpg", + "original": "https://static.tvmaze.com/uploads/images/original_untouched/324/811405.jpg" + }, + "summary": "

                  In Hell's Kitchen, aspiring chefs are put through an intense culinary academy to prove they possess the right combination of ingredients to win a life-changing grand prize.

                  ", + "updated": 1631607953, + "_links": { + "self": { + "href": "https://api.tvmaze.com/shows/253" + }, + "previousepisode": { + "href": "https://api.tvmaze.com/episodes/2118484" + } + } +}, { + "id": 254, + "url": "https://www.tvmaze.com/shows/254/world-series-of-poker", + "name": "World Series of Poker", + "type": "Sports", + "language": "English", + "genres": [], + "status": "Running", + "runtime": 60, + "averageRuntime": 65, + "premiered": "2006-08-22", + "ended": null, + "officialSite": null, + "schedule": { + "time": "21:00", + "days": ["Monday", "Tuesday", "Sunday"] + }, + "rating": { + "average": 9 + }, + "weight": 77, + "network": { + "id": 180, + "name": "ESPN2", + "country": { + "name": "United States", + "code": "US", + "timezone": "America/New_York" + } + }, + "webChannel": null, + "dvdCountry": null, + "externals": { + "tvrage": 16764, + "thetvdb": 79028, + "imdb": "tt2733512" + }, + "image": { + "medium": "https://static.tvmaze.com/uploads/images/medium_portrait/1/4656.jpg", + "original": "https://static.tvmaze.com/uploads/images/original_untouched/1/4656.jpg" + }, + "summary": "

                  The World Series of Poker is where the world's best poker players battle for the title.

                  ", + "updated": 1574298803, + "_links": { + "self": { + "href": "https://api.tvmaze.com/shows/254" + }, + "previousepisode": { + "href": "https://api.tvmaze.com/episodes/1684225" + } + } +}] From 275be492dc676d77f2b8816dfce128e2f2a903f6 Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Tue, 9 Nov 2021 00:55:21 -0500 Subject: [PATCH 08/13] chore: add e2e tests --- .eslintrc.js | 13 +- cypress/integration/custom-errors.spec.ts | 14 + cypress/integration/default.spec.ts | 1 - cypress/integration/dynamic-routes.spec.ts | 1 - cypress/integration/headers.spec.ts | 9 + cypress/integration/i18n.spec.ts | 16 ++ cypress/integration/images.spec.ts | 20 ++ cypress/integration/preview.spec.ts | 21 ++ .../integration/rewrites-redirects.spec.ts | 21 ++ cypress/integration/trailing-slash.spec.ts | 11 + demo/next.config.js | 28 +- demo/pages/index.js | 244 ++++++++++-------- package.json | 1 + 13 files changed, 285 insertions(+), 115 deletions(-) create mode 100644 cypress/integration/custom-errors.spec.ts create mode 100644 cypress/integration/headers.spec.ts create mode 100644 cypress/integration/i18n.spec.ts create mode 100644 cypress/integration/images.spec.ts create mode 100644 cypress/integration/preview.spec.ts create mode 100644 cypress/integration/rewrites-redirects.spec.ts create mode 100644 cypress/integration/trailing-slash.spec.ts diff --git a/.eslintrc.js b/.eslintrc.js index 9f4e641bee..416c44a27f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -34,5 +34,16 @@ module.exports = { env: { jest: true, }, - overrides: [...overrides], + overrides: [ + ...overrides, + { + files: ['cypress/**/*.spec.ts'], + rules: { + 'max-nested-callbacks': 0, + 'promise/prefer-await-to-then': 0, + 'promise/always-return': 0, + 'promise/catch-or-return': 0 + }, + } + ], } diff --git a/cypress/integration/custom-errors.spec.ts b/cypress/integration/custom-errors.spec.ts new file mode 100644 index 0000000000..03478774de --- /dev/null +++ b/cypress/integration/custom-errors.spec.ts @@ -0,0 +1,14 @@ +/** + * @see {@link https://nextjs.org/docs/advanced-features/custom-error-page} + */ + describe('Custom error pages', () => { + it('should show custom 404 page on /404', () => { + cy.visit('/404', {failOnStatusCode: false}) + cy.findByText('Custom 404 - Page Not Found') + }) + + it('should show custom 500 page on /500', () => { + cy.visit('/500', {failOnStatusCode: false}) + cy.findByText('Custom 500 - Server-side error occurred') + }) +}) \ No newline at end of file diff --git a/cypress/integration/default.spec.ts b/cypress/integration/default.spec.ts index 054e33a7d1..28c1c1cdd0 100644 --- a/cypress/integration/default.spec.ts +++ b/cypress/integration/default.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable eslint-comments/disable-enable-pair, max-nested-callbacks */ describe('Default site', () => { beforeEach(() => { cy.visit('/') diff --git a/cypress/integration/dynamic-routes.spec.ts b/cypress/integration/dynamic-routes.spec.ts index 5153fe54c8..a0cf095942 100644 --- a/cypress/integration/dynamic-routes.spec.ts +++ b/cypress/integration/dynamic-routes.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable eslint-comments/disable-enable-pair, max-nested-callbacks */ describe('Dynamic Routing', () => { it('loads page', () => { cy.visit('/shows/250') diff --git a/cypress/integration/headers.spec.ts b/cypress/integration/headers.spec.ts new file mode 100644 index 0000000000..2b5551ecae --- /dev/null +++ b/cypress/integration/headers.spec.ts @@ -0,0 +1,9 @@ +describe('Headers', () => { + it('should set headers from the next.config.js', () => { + cy.request({ + url: '/', + }).then((response) => { + expect(response.headers).to.have.property('x-custom-header', 'my custom header value') + }) + }) +}) \ No newline at end of file diff --git a/cypress/integration/i18n.spec.ts b/cypress/integration/i18n.spec.ts new file mode 100644 index 0000000000..7dd8d2bd32 --- /dev/null +++ b/cypress/integration/i18n.spec.ts @@ -0,0 +1,16 @@ +describe('Localization', () => { + it('should use sub routing to determine current locale', () => { + cy.visit('/'); + + cy.findByText('The current locale is en') + + cy.visit('/fr') + cy.findByText('The current locale is fr') + }) + + it('should use the NEXT_LOCALE cookie to determine the default locale', () => { + cy.setCookie('NEXT_LOCALE', 'fr') + cy.visit('/'); + cy.findByText('The current locale is fr') + }) +}) \ No newline at end of file diff --git a/cypress/integration/images.spec.ts b/cypress/integration/images.spec.ts new file mode 100644 index 0000000000..0d912d33a8 --- /dev/null +++ b/cypress/integration/images.spec.ts @@ -0,0 +1,20 @@ +/** + * @see {@link https://nextjs.org/docs/api-reference/next/image#required-props} + */ +describe('next/images', () => { + it('should show static image from /public', () => { + cy.visit('/') + cy.findByRole('img').should('be.visible').and(($img) => { + // "naturalWidth" and "naturalHeight" are set when the image loads + expect( + $img[0].naturalWidth, + 'image has natural width' + ).to.be.greaterThan(0) + }) + }) + + it('should show image using next/image', () => { + cy.visit('/image') + cy.findByRole('img', { name: /shiba inu dog looks through a window/i }) + }) +}) \ No newline at end of file diff --git a/cypress/integration/preview.spec.ts b/cypress/integration/preview.spec.ts new file mode 100644 index 0000000000..39d1b66574 --- /dev/null +++ b/cypress/integration/preview.spec.ts @@ -0,0 +1,21 @@ +describe('Preview Mode', () => { + it('enters and exits preview mode', () => { + // preview mode is off by default + cy.visit('/previewTest') + cy.findByText('Number: 4') + + // enter preview mode + cy.request('/api/enterPreview').then( + (response) => { + expect(response.body).to.have.property('name', 'preview mode') + } +) + cy.visit('/previewTest') + cy.findByText('Number: 3') + + // exit preview mode + cy.request('/api/exitPreview') + cy.visit('/previewTest') + cy.findByText('Number: 4') + }) +}) \ No newline at end of file diff --git a/cypress/integration/rewrites-redirects.spec.ts b/cypress/integration/rewrites-redirects.spec.ts new file mode 100644 index 0000000000..43d94d3ab6 --- /dev/null +++ b/cypress/integration/rewrites-redirects.spec.ts @@ -0,0 +1,21 @@ +describe('Rewrites and Redirects', () => { + it('rewrites: points /old to /', () => { + // preview mode is off by default + cy.visit('/old') + cy.findByText('Next Demo!') + cy.url().should('eq', `${Cypress.config().baseUrl}/old`) + + // ensure headers are still set + cy.request('/api/enterPreview').then( + (response) => { + expect(response.body).to.have.property('name', 'preview mode') + } +) + }) + + it('redirects: redirects /redirectme to /', () => { + cy.visit('/redirectme') + cy.url().should('eq', `${Cypress.config().baseUrl}/`) + } + ) +}) \ No newline at end of file diff --git a/cypress/integration/trailing-slash.spec.ts b/cypress/integration/trailing-slash.spec.ts new file mode 100644 index 0000000000..e06e7dd982 --- /dev/null +++ b/cypress/integration/trailing-slash.spec.ts @@ -0,0 +1,11 @@ +describe('Trailing slash enabled', () => { + it('should keep the trailing slash, i.e. points /old/ to /old/', () => { + cy.visit('/old/') + cy.url().should('eq', `${Cypress.config().baseUrl}/old/`) + }) + + it('should put a trailing slash when there is none, i.e. points /old to /old/', () => { + cy.visit('/old') + cy.url().should('eq', `${Cypress.config().baseUrl}/old/`) + }) +}) \ No newline at end of file diff --git a/demo/next.config.js b/demo/next.config.js index 3b53233b90..93c2cbab23 100644 --- a/demo/next.config.js +++ b/demo/next.config.js @@ -6,9 +6,23 @@ module.exports = { defaultLocale: 'en', locales: ['en', 'es', 'fr'] }, - // trailingSlash: true, + async headers() { + return [ + { + source: '/', + headers: [ + { + key: 'x-custom-header', + value: 'my custom header value', + } + ], + }, + ] + }, + trailingSlash: true, // Configurable site features _to_ support: // basePath: '/docs', + // Rewrites allow you to map an incoming request path to a different destination path. async rewrites() { return { beforeFiles: [ @@ -18,5 +32,15 @@ module.exports = { } ] } - } + }, + // Redirects allow you to redirect an incoming request path to a different destination path. + async redirects() { + return [ + { + source: '/redirectme', + destination: '/', + permanent: true, + }, + ] + }, } diff --git a/demo/pages/index.js b/demo/pages/index.js index a7b5a98831..158358e19b 100644 --- a/demo/pages/index.js +++ b/demo/pages/index.js @@ -1,121 +1,145 @@ import Link from 'next/link' import dynamic from 'next/dynamic' const Header = dynamic(() => import(/* webpackChunkName: 'header' */ '../components/Header'), { ssr: true }) +import { useRouter } from 'next/router' -const Index = ({ shows }) => ( -
                  - NextJS on Netlify Banner - -
                  - -

                  NextJS on Netlify

                  -

                  - This is a demo of a NextJS application with Server-Side Rendering (SSR). -
                  - It is hosted on Netlify. -
                  - Server-side rendering is handled by Netlify Functions. -
                  - Minimal configuration is required. -
                  - Everything is handled by the next-on-netlify npm - package. -

                  - -

                  1. Server-Side Rendering Made Easy

                  -

                  - This page is server-side rendered. -
                  - It fetches a random list of five TV shows from the TVmaze REST API. -
                  - Refresh this page to see it change. -

                  - -
                    - {shows.map(({ id, name }) => ( -
                  • - - - #{id}: {name} - +const Index = ({ shows }) => { + const { locale } = useRouter(); + + return ( +
                    + NextJS on Netlify Banner + +
                    + +

                    NextJS on Netlify

                    +

                    + This is a demo of a NextJS application with Server-Side Rendering (SSR). +
                    + It is hosted on Netlify. +
                    + Server-side rendering is handled by Netlify Functions. +
                    + Minimal configuration is required. +
                    + Everything is handled by the next-on-netlify npm + package. +

                    + +

                    1. Server-Side Rendering Made Easy

                    +

                    + This page is server-side rendered. +
                    + It fetches a random list of five TV shows from the TVmaze REST API. +
                    + Refresh this page to see it change. +

                    + + + +

                    2. Full Support for Dynamic Pages

                    +

                    + Dynamic pages, introduced in NextJS 9.2, are fully supported. +
                    + Click on a show to check out a server-side rendered page with dynamic routing (/shows/:id). +

                    + + + +

                    3. Catch-All Routes? Included ✔

                    +

                    + You can even take advantage of{' '} + NextJS catch-all routes feature + . +
                    + Here are three examples: +

                    + - -

                    2. Full Support for Dynamic Pages

                    -

                    - Dynamic pages, introduced in NextJS 9.2, are fully supported. -
                    - Click on a show to check out a server-side rendered page with dynamic routing (/shows/:id). -

                    - - - -

                    3. Catch-All Routes? Included ✔

                    -

                    - You can even take advantage of{' '} - NextJS catch-all routes feature - . -
                    - Here are three examples: -

                    - - -

                    4. Static Pages Stay Static

                    -

                    - next-on-netlify automatically determines which pages are dynamic and which ones are static. -
                    - Only dynamic pages are server-side rendered. -
                    - Static pages are pre-rendered and served directly by Netlify's CDN. -

                    - - - -

                    Want to Learn More?

                    -

                    - Check out the source code on GitHub. -

                    -
                    -) +
                  + +

                  4. Static Pages Stay Static

                  +

                  + next-on-netlify automatically determines which pages are dynamic and which ones are static. +
                  + Only dynamic pages are server-side rendered. +
                  + Static pages are pre-rendered and served directly by Netlify's CDN. +

                  + + + +

                  5. Localization As Expected

                  +

                  + Localization (i18n) is supported! This demo uses fr with en as the default locale (at /). +

                  + The current locale is {locale} +

                  Click on the links below to see the above text change

                  + + +

                  Want to Learn More?

                  +

                  + Check out the source code on GitHub. +

                  +
                  + ) +} Index.getInitialProps = async function () { const dev = process.env.CONTEXT !== 'production'; diff --git a/package.json b/package.json index d485c179d7..a89866f64f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "cy:open": "cypress open --config-file cypress/config/all.json", "cy:run": "cypress run --config-file ../cypress/config/ci.json", "dev:demo": "next dev demo", + "dev:demo2": "next dev demo2", "format": "run-s format:check-fix:*", "format:ci": "run-s format:check:*", "format:check-fix:lint": "run-e format:check:lint format:fix:lint", From c4062ab0e06cb2188e36dfb525bf60cef8858643 Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Fri, 12 Nov 2021 10:10:19 -0500 Subject: [PATCH 09/13] chore: add locale test --- .eslintrc.js | 4 ++-- cypress/integration/i18n.spec.ts | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 416c44a27f..d9075ea9a1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -42,8 +42,8 @@ module.exports = { 'max-nested-callbacks': 0, 'promise/prefer-await-to-then': 0, 'promise/always-return': 0, - 'promise/catch-or-return': 0 + 'promise/catch-or-return': 0, }, - } + }, ], } diff --git a/cypress/integration/i18n.spec.ts b/cypress/integration/i18n.spec.ts index 7dd8d2bd32..ffe5291943 100644 --- a/cypress/integration/i18n.spec.ts +++ b/cypress/integration/i18n.spec.ts @@ -11,6 +11,28 @@ describe('Localization', () => { it('should use the NEXT_LOCALE cookie to determine the default locale', () => { cy.setCookie('NEXT_LOCALE', 'fr') cy.visit('/'); + + cy.findByText('The current locale is fr') + }) + + it('should use the NEXT_LOCALE cookie over Accept-Language header to determine the default locale', () => { + // cy.setCookie('NEXT_LOCALE', 'en') + cy.visit({ + url: '/', + headers: { + 'Accept-Language': 'fr;q=0.9' + } + }); cy.findByText('The current locale is fr') + + cy.setCookie('NEXT_LOCALE', 'en') + cy.visit({ + url: '/', + headers: { + 'Accept-Language': 'fr;q=0.9' + } + }); + + cy.findByText('The current locale is en') }) }) \ No newline at end of file From 10cce052fd0fd57ac0a8c84598320ee1a3577716 Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Fri, 12 Nov 2021 10:14:53 -0500 Subject: [PATCH 10/13] chore: get back actual call to shows --- demo/pages/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/pages/index.js b/demo/pages/index.js index 158358e19b..d7e4b6ac2c 100644 --- a/demo/pages/index.js +++ b/demo/pages/index.js @@ -146,7 +146,8 @@ Index.getInitialProps = async function () { // Set a random page between 1 and 100 const randomPage = Math.floor(Math.random() * 100) + 1 - const server = dev ? 'http://localhost:3000/shows1.json' : `https://api.tvmaze.com/shows?page=${randomPage}`; + // FIXME: stub out in dev + const server = dev ? `https://api.tvmaze.com/shows?page=${randomPage}` : `https://api.tvmaze.com/shows?page=${randomPage}`; // Get the data const res = await fetch(server); From 8c9c9a9b2d7841c75fd68d7d32e7a8fd530f5e32 Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Fri, 12 Nov 2021 10:57:11 -0500 Subject: [PATCH 11/13] chore: fix test --- cypress/integration/rewrites-redirects.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/rewrites-redirects.spec.ts b/cypress/integration/rewrites-redirects.spec.ts index 43d94d3ab6..bf62ef27b3 100644 --- a/cypress/integration/rewrites-redirects.spec.ts +++ b/cypress/integration/rewrites-redirects.spec.ts @@ -3,7 +3,7 @@ describe('Rewrites and Redirects', () => { // preview mode is off by default cy.visit('/old') cy.findByText('Next Demo!') - cy.url().should('eq', `${Cypress.config().baseUrl}/old`) + cy.url().should('eq', `${Cypress.config().baseUrl}/old/`) // ensure headers are still set cy.request('/api/enterPreview').then( From 0c0df1a94a513becf3efa8504c7ee8f80be50b2a Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Fri, 12 Nov 2021 11:09:09 -0500 Subject: [PATCH 12/13] chore: update snapidoos --- test/__snapshots__/index.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/__snapshots__/index.js.snap b/test/__snapshots__/index.js.snap index c61322a691..3e6d72fba3 100644 --- a/test/__snapshots__/index.js.snap +++ b/test/__snapshots__/index.js.snap @@ -71,7 +71,7 @@ exports.resolvePages = () => { exports[`onBuild() writes correct redirects to netlifyConfig 1`] = ` Array [ Object { - "from": "/_next/image*", + "from": "/_next/image/*", "query": Object { "q": ":quality", "url": ":url", From a9f9b86df8da6c503a996884f020eb0eb90c104c Mon Sep 17 00:00:00 2001 From: Tiffany Le-Nguyen Date: Fri, 12 Nov 2021 11:44:03 -0500 Subject: [PATCH 13/13] chore: remove unused command Co-authored-by: Matt Kane --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index a89866f64f..d485c179d7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "cy:open": "cypress open --config-file cypress/config/all.json", "cy:run": "cypress run --config-file ../cypress/config/ci.json", "dev:demo": "next dev demo", - "dev:demo2": "next dev demo2", "format": "run-s format:check-fix:*", "format:ci": "run-s format:check:*", "format:check-fix:lint": "run-e format:check:lint format:fix:lint",