Skip to content
This repository was archived by the owner on May 10, 2021. It is now read-only.

Commit 93585b4

Browse files
committed
Add tests for default config
Add a test suite for simulating a NextJS app, building that app, running next-on-netlify, and then testing the resulting directories and files.
1 parent 58d80e7 commit 93585b4

File tree

13 files changed

+12572
-21
lines changed

13 files changed

+12572
-21
lines changed

package-lock.json

Lines changed: 12077 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,19 @@
2121
"homepage": "https://github.com/FinnWoelm/next-on-netlify",
2222
"author": "Finn Woelm",
2323
"license": "MIT",
24-
"devDependencies": {},
2524
"files": [
2625
"/lib"
2726
],
27+
"scripts": {
28+
"test": "jest"
29+
},
30+
"devDependencies": {
31+
"isomorphic-unfetch": "^3.0.0",
32+
"jest": "^25.4.0",
33+
"next": "^9.3.5",
34+
"react": "^16.13.1",
35+
"react-dom": "^16.13.1"
36+
},
2837
"dependencies": {
2938
"fs-extra": "^8.1.0",
3039
"next-aws-lambda": "^2.3.4",

tests/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Ignore builds of NextJS that are created for testing purposes
2+
builds/

tests/defaults.test.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Test default next-on-netlify configuration
2+
const { parse, join } = require('path')
3+
const { copySync, emptyDirSync, existsSync,
4+
readdirSync, readFileSync, readJsonSync } = require('fs-extra')
5+
const npmRunBuild = require("./helpers/npmRunBuild")
6+
7+
// The name of this test file (without extension)
8+
const FILENAME = parse(__filename).name
9+
10+
// The directory which will be used for testing.
11+
// We simulate a NextJS app within that directory, with pages, and a
12+
// package.json file.
13+
const PROJECT_PATH = join(__dirname, "builds", FILENAME)
14+
15+
// The directory that contains the fixtures, such as NextJS pages,
16+
// NextJS config, and package.json
17+
const FIXTURE_PATH = join(__dirname, "fixtures")
18+
19+
// Capture the output of `npm run build` to verify successful build
20+
let BUILD_OUTPUT
21+
22+
beforeAll(
23+
async () => {
24+
// Clear project directory
25+
emptyDirSync(PROJECT_PATH)
26+
emptyDirSync(join(PROJECT_PATH, "pages"))
27+
28+
// Copy NextJS pages and config
29+
copySync(
30+
join(FIXTURE_PATH, "pages"),
31+
join(PROJECT_PATH, "pages")
32+
)
33+
copySync(
34+
join(FIXTURE_PATH, "next.config.js"),
35+
join(PROJECT_PATH, "next.config.js")
36+
)
37+
38+
// Copy package.json
39+
copySync(
40+
join(FIXTURE_PATH, "package.json"),
41+
join(PROJECT_PATH, "package.json")
42+
)
43+
44+
// Invoke `npm run build`: Build Next and run next-on-netlify
45+
const { stdout } = await npmRunBuild({ directory: PROJECT_PATH })
46+
BUILD_OUTPUT = stdout
47+
},
48+
// time out after 30 seconds
49+
30 * 1000
50+
)
51+
52+
describe('Next', () => {
53+
test('builds successfully', () => {
54+
expect(BUILD_OUTPUT).toMatch("Creating an optimized production build...")
55+
expect(BUILD_OUTPUT).toMatch("Automatically optimizing pages...")
56+
expect(BUILD_OUTPUT).toMatch("First Load JS shared by all")
57+
})
58+
})
59+
60+
describe('SSR Pages', () => {
61+
const router = join(PROJECT_PATH, "functions", "nextRouter")
62+
63+
test('creates nextRouter.js Netlify Function', () => {
64+
expect(existsSync(join(router, "nextRouter.js"))).toBe(true)
65+
})
66+
67+
test('lists all routes in routes.json', () => {
68+
// read routes
69+
const { routes } = readJsonSync(join(router, "routes.json"))
70+
71+
// check entries
72+
expect(routes).toContainEqual({
73+
file: "pages/index.js",
74+
regex: "^\\/(?:\\/)?$"
75+
})
76+
expect(routes).toContainEqual({
77+
file: "pages/shows/[id].js",
78+
regex: "^\\/shows\\/([^\\/]+?)(?:\\/)?$"
79+
})
80+
expect(routes).toContainEqual({
81+
file: "pages/shows/[...params].js",
82+
regex: "^\\/shows(?:\\/((?:[^\\/]+?)(?:\\/(?:[^\\/]+?))*))?(?:\\/)?$"
83+
})
84+
})
85+
86+
test('requires all pages in allPages.js', () => {
87+
// read allPages.js
88+
const contents = readFileSync(join(router, "allPages.js"))
89+
90+
// Convert contents into an array, each line being one element
91+
const requires = contents.toString().split("\n")
92+
93+
// Verify presence of require statements
94+
expect(requires).toContain('require("./pages/index.js")')
95+
expect(requires).toContain('require("./pages/shows/[id].js")')
96+
expect(requires).toContain('require("./pages/shows/[...params].js")')
97+
})
98+
99+
test('bundles all SSR-pages in /pages', () => {
100+
const pages = join(PROJECT_PATH, "public", "_next", "pages")
101+
102+
expect(existsSync(join(router, "pages", "index.js"))).toBe(true)
103+
expect(existsSync(join(router, "pages", "shows", "[id].js"))).toBe(true)
104+
expect(existsSync(join(router, "pages", "shows", "[...params].js"))).toBe(true)
105+
})
106+
})
107+
108+
describe('Static Pages', () => {
109+
test('copies static pages to public/_next/ directory', () => {
110+
const pages = join(PROJECT_PATH, "public", "_next", "pages")
111+
112+
expect(existsSync(join(pages, "static.html"))).toBe(true)
113+
expect(existsSync(join(pages, "static/[id].html"))).toBe(true)
114+
})
115+
116+
test('copies static assets to public/_next/ directory', () => {
117+
const dirs = readdirSync(join(PROJECT_PATH, "public", "_next", "static"))
118+
119+
expect(dirs.length).toBe(3)
120+
expect(dirs).toContain("chunks")
121+
expect(dirs).toContain("runtime")
122+
})
123+
})
124+
125+
describe('Routing',() => {
126+
test('creates Netlify redirects', async () => {
127+
// Read _redirects file
128+
const contents = readFileSync(join(PROJECT_PATH, "public", "_redirects"))
129+
130+
// Convert contents into an array, each line being one element
131+
const redirects = contents.toString().split("\n")
132+
133+
// Check that routes are present
134+
expect(redirects).toContain("/static /_next/pages/static.html 200")
135+
expect(redirects).toContain("/static/:id /_next/pages/static/[id].html 200")
136+
expect(redirects).toContain("/ /.netlify/functions/nextRouter?_path=/ 200")
137+
expect(redirects).toContain("/index /.netlify/functions/nextRouter?_path=/index 200")
138+
expect(redirects).toContain("/shows/:id /.netlify/functions/nextRouter?_path=/shows/:id 200")
139+
expect(redirects).toContain("/shows/* /.netlify/functions/nextRouter?_path=/shows/* 200")
140+
})
141+
})

tests/fixtures/next.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
target: 'serverless',
3+
};

tests/fixtures/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "next-on-netlify-test",
3+
"scripts": {
4+
"build": "../../../node_modules/.bin/next build",
5+
"postbuild": "node ../../../next-on-netlify"
6+
}
7+
}

tests/fixtures/pages/index.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import fetch from 'isomorphic-unfetch'
2+
import Link from 'next/link'
3+
4+
const Index = ({ shows }) => (
5+
<div>
6+
<img
7+
src="/next-on-netlify.png" alt="NextJS on Netlify Banner"
8+
style={{ maxWidth: '100%' }}/>
9+
10+
<h1>NextJS on Netlify</h1>
11+
<p>
12+
This is a demo of a NextJS application with Server-Side Rendering (SSR).
13+
<br/>
14+
It is hosted on Netlify.
15+
<br/>
16+
Server-side rendering is handled by Netlify Functions.
17+
<br/>
18+
Minimal configuration is required.<br/>
19+
Everything is handled by the
20+
{' '}
21+
<a href="https://www.npmjs.com/package/next-on-netlify">
22+
next-on-netlify
23+
</a>
24+
{' '}
25+
npm package.
26+
</p>
27+
28+
<h1>1. Server-Side Rendering Made Easy</h1>
29+
<p>
30+
This page is server-side rendered.
31+
<br/>
32+
It fetches a random list of five TV shows
33+
from the TVmaze REST API.
34+
<br/>
35+
Refresh this page to see it change.
36+
</p>
37+
38+
<ul>
39+
{shows.map(({ id, name }) => (
40+
<li key={id}>
41+
<Link href="/shows/[id]" as={`/shows/${id}`}>
42+
<a>#{id}: {name}</a>
43+
</Link>
44+
</li>
45+
))}
46+
</ul>
47+
48+
<h1>2. Full Support for Dynamic Pages</h1>
49+
<p>
50+
Dynamic pages, introduced in NextJS 9.2, are fully supported.
51+
<br/>
52+
Click on a show to check out a server-side rendered page with dynamic
53+
routing (/shows/:id).
54+
</p>
55+
56+
<ul>
57+
{shows.slice(0, 3).map(({ id, name }) => (
58+
<li key={id}>
59+
<Link href="/shows/[id]" as={`/shows/${id}`}>
60+
<a>#{id}: {name}</a>
61+
</Link>
62+
</li>
63+
))}
64+
</ul>
65+
66+
<h1>3. Catch-All Routes? Included ✔</h1>
67+
<p>You can even take advantage of
68+
{' '}
69+
<a href="https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes">
70+
NextJS' catch-all routes feature
71+
</a>.
72+
<br/>
73+
Here are three examples:
74+
</p>
75+
<ul>
76+
<li>
77+
<Link href="/shows/[...params]" as={`/shows/73/whatever/path/you/want`}>
78+
<a>/shows/73/whatever/path/you/want</a>
79+
</Link>
80+
</li>
81+
<li>
82+
<Link href="/shows/[...params]" as={`/shows/94/whatever/path/you`}>
83+
<a>/shows/94/whatever/path/you</a>
84+
</Link>
85+
</li>
86+
<li>
87+
<Link href="/shows/[...params]" as={`/shows/106/whatever/path`}>
88+
<a>/shows/106/whatever/path</a>
89+
</Link>
90+
</li>
91+
</ul>
92+
93+
<h1>4. Static Pages Stay Static</h1>
94+
<p>
95+
next-on-netlify automatically determines which pages are dynamic and
96+
which ones are static.
97+
<br/>
98+
Only dynamic pages are server-side rendered.
99+
<br/>
100+
Static pages are pre-rendered and served directly by Netlify's CDN.
101+
</p>
102+
103+
<ul>
104+
<li>
105+
<Link href="/static">
106+
<a>Static NextJS page: /static</a>
107+
</Link>
108+
</li>
109+
<li>
110+
<Link href="/static/[id]" as="/static/123456789">
111+
<a>Static NextJS page with dynamic routing: /static/:id</a>
112+
</Link>
113+
</li>
114+
</ul>
115+
116+
<h1>Want to Learn More?</h1>
117+
<p>
118+
Check out the
119+
{' '}
120+
<a href="https://github.com/FinnWoelm/next-on-netlify-demo">
121+
source code on GitHub
122+
</a>.
123+
</p>
124+
</div>
125+
)
126+
127+
Index.getInitialProps = async function() {
128+
// Set a random page between 1 and 100
129+
const randomPage = Math.floor(Math.random()*100) + 1
130+
131+
// Get the data
132+
const res = await fetch(`https://api.tvmaze.com/shows?page=${randomPage}`);
133+
const data = await res.json();
134+
135+
return { shows: data.slice(0, 5) }
136+
}
137+
138+
export default Index
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import fetch from 'isomorphic-unfetch'
2+
import Error from 'next/error'
3+
import Link from 'next/link'
4+
5+
const CatchAll = ({ errorCode, show, params }) => {
6+
7+
// If show item was not found, render 404 page
8+
if (errorCode) {
9+
return <Error statusCode={errorCode} />
10+
}
11+
12+
// Otherwise, render show
13+
return (
14+
<div>
15+
<p>
16+
This is a server-side rendered catch-all page. It catches all requests
17+
made to /shows/:id/any/path/can/go/here... and makes those parameters
18+
available in getInitialProps():
19+
<br/>
20+
{params.map((param, index) => (
21+
<span key={index}>
22+
[{index}]: {param}<br/>
23+
</span>
24+
))}
25+
<br/>
26+
Refresh the page to see server-side rendering in action.
27+
<br/>
28+
You can also try changing the URL to something random,
29+
such as /shows/{show.id}/whatever/path/you/want
30+
</p>
31+
32+
<hr/>
33+
34+
<h1>Show #{show.id}</h1>
35+
<p>
36+
{show.name}
37+
</p>
38+
39+
<hr/>
40+
41+
<Link href="/">
42+
<a>Go back home</a>
43+
</Link>
44+
</div>
45+
)
46+
}
47+
48+
CatchAll.getInitialProps = async ({ res: req, query }) => {
49+
// Get the params to render
50+
const { params } = query
51+
52+
// Get the ID to render
53+
const id = params[0]
54+
55+
// Get the data
56+
const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
57+
const data = await res.json();
58+
59+
// Set error code if show item could not be found
60+
const errorCode = res.status > 200 ? res.status : false
61+
62+
return { errorCode, show: data, params }
63+
}
64+
65+
export default CatchAll

0 commit comments

Comments
 (0)