Skip to content

Commit

Permalink
tests: added some simple Jest tests (#36)
Browse files Browse the repository at this point in the history
They simply ensure that we can run dev, build and fetch assets/pages. Hopefully we can introduce more advanced tests in the future.
  • Loading branch information
DuCanhGH authored Jun 18, 2023
1 parent 9def912 commit d060048
Show file tree
Hide file tree
Showing 45 changed files with 2,376 additions and 272 deletions.
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"access": "public",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": ["@next-pwa-example/*"]
"ignore": ["@next-pwa-example/*", "tests", "docs"]
}
2 changes: 2 additions & 0 deletions .changeset/spotty-readers-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = {
"docs/tsconfig.json",
"packages/*/tsconfig.json",
"examples/*/tsconfig.json",
"__tests__/tsconfig.json",
],
ecmaVersion: "latest",
sourceType: "module",
Expand Down
17 changes: 15 additions & 2 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ env:
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_REMOTE_ONLY: true
jobs:
# TODO: add tests
build_and_test:
build_and_lint_check:
name: 🔎 Typecheck, check formatting and lint
uses: ./.github/workflows/build-reusable.yml
with:
Expand All @@ -16,3 +15,17 @@ jobs:
pnpm lint &&
pnpm docs:lint
secrets: inherit
test-dev:
name: ⚫️ Run Jest (development mode) tests
uses: ./.github/workflows/build-reusable.yml
with:
afterBuild: |
NEXT_TEST_MODE=dev pnpm jest __tests__/e2e --ci --runInBand
secrets: inherit
test-prod:
name: ⚫️ Run Jest (production mode) tests
uses: ./.github/workflows/build-reusable.yml
with:
afterBuild: |
NEXT_TEST_MODE=start pnpm jest __tests__/e2e --ci --runInBand
secrets: inherit
1 change: 1 addition & 0 deletions .github/workflows/pull-request-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
matrix:
node-version: ["18.x"]
pnpm-version: ["8.x"]
if: ${{ !contains(github.event.pull_request.title, 'Publish packages') }}
steps:
- name: Checkout repo
uses: actions/checkout@v3
Expand Down
32 changes: 32 additions & 0 deletions __tests__/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
node_modules/
.pnp/
.pnp.js/

# testing
coverage/

# next.js
.next/
out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*

# typescript
*.tsbuildinfo
next-env.d.ts
Binary file added __tests__/e2e/app-dir/app/favicon.ico
Binary file not shown.
8 changes: 8 additions & 0 deletions __tests__/e2e/app-dir/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const RootLayout = ({ children }: { children: React.ReactNode }) => (
<html lang="en">
<head />
<body>{children}</body>
</html>
);

export default RootLayout;
16 changes: 16 additions & 0 deletions __tests__/e2e/app-dir/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Image from "next/image";

const Page = () => (
<main>
<p id="welcome-text">This is a Next.js PWA!</p>
<Image
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</main>
);

export default Page;
27 changes: 27 additions & 0 deletions __tests__/e2e/app-dir/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createDescribe } from "../../utils/create-describe";

createDescribe(
"e2e app-dir",
{ sourceDir: __dirname, skipInstall: false },
({ next }) => {
it("should render", async () => {
const $ = await next.render("/");
expect($("#welcome-text").text()).toBe("This is a Next.js PWA!");
});

it("should fetch image", async () => {
const image = await next.fetch("/next.svg");
expect(image.status).toBe(200);
const favicon = await next.fetch("/favicon.ico");
expect(favicon.status).toBe(200);
});

it("should be able to fetch service worker", async () => {
const sw = await next.fetch("/sw.js");
expect(sw.status).toBe(200);
expect(
sw.headers.get("Content-Type")?.includes("application/javascript")
).toBe(true);
});
}
);
8 changes: 8 additions & 0 deletions __tests__/e2e/app-dir/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const withPWA = require("@ducanh2912/next-pwa").default({
dest: "public",
});

/** @type {import('next').NextConfig} */
const nextConfig = {};

module.exports = withPWA(nextConfig);
1 change: 1 addition & 0 deletions __tests__/e2e/app-dir/public/next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions __tests__/e2e/app-dir/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
28 changes: 28 additions & 0 deletions __tests__/e2e/pages-dir/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createDescribe } from "../../utils/create-describe";

createDescribe(
"e2e app-dir",
{ sourceDir: __dirname, skipInstall: false },
({ next }) => {
it("should render", async () => {
const $ = await next.render("/");
expect($("#welcome-text").text()).toBe("This is a Next.js PWA!");
expect($("#app-root-text").text()).toBe("This is placed at _app.tsx!");
});

it("should fetch image", async () => {
const image = await next.fetch("/next.svg");
expect(image.status).toBe(200);
const favicon = await next.fetch("/favicon.ico");
expect(favicon.status).toBe(200);
});

it("should be able to fetch service worker", async () => {
const sw = await next.fetch("/sw.js");
expect(sw.status).toBe(200);
expect(
sw.headers.get("Content-Type")?.includes("application/javascript")
).toBe(true);
});
}
);
8 changes: 8 additions & 0 deletions __tests__/e2e/pages-dir/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const withPWA = require("@ducanh2912/next-pwa").default({
dest: "public",
});

/** @type {import('next').NextConfig} */
const nextConfig = {};

module.exports = withPWA(nextConfig);
10 changes: 10 additions & 0 deletions __tests__/e2e/pages-dir/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { AppProps } from "next/app";

const App = ({ Component, pageProps }: AppProps) => (
<>
<p id="app-root-text">This is placed at _app.tsx!</p>
<Component {...pageProps} />
</>
);

export default App;
16 changes: 16 additions & 0 deletions __tests__/e2e/pages-dir/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Image from "next/image";

const Page = () => (
<main>
<p id="welcome-text">This is a Next.js PWA!</p>
<Image
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</main>
);

export default Page;
Binary file added __tests__/e2e/pages-dir/public/favicon.ico
Binary file not shown.
1 change: 1 addition & 0 deletions __tests__/e2e/pages-dir/public/next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions __tests__/e2e/pages-dir/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
2 changes: 2 additions & 0 deletions __tests__/jest-setup-after-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// a timeout of 5 minutes
jest.setTimeout(300 * 1000);
17 changes: 17 additions & 0 deletions __tests__/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "tests",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev"
},
"dependencies": {
"next": "latest",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/react": "18.2.11",
"@types/react-dom": "18.2.4"
}
}
9 changes: 9 additions & 0 deletions __tests__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true
},
"files": ["../node_modules/jest-chain/types/index.d.ts"],
"include": ["./**/*.ts", "./**/*.tsx"],
"exclude": []
}
72 changes: 72 additions & 0 deletions __tests__/utils/create-describe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { NextInstance, NextInstanceOpts } from "./next-instance-base";
import { NextInstanceDev } from "./next-instance-dev";
import { NextInstanceStart } from "./next-instance-start";

const validTestModes = ["dev", "start"] as const;

type NextTestMode = (typeof validTestModes)[number];

interface NextTestOpts extends NextInstanceOpts {
sourceDir: string;
}

const isValidTestMode = (mode: string | undefined): mode is NextTestMode =>
typeof mode === "string" && validTestModes.includes(mode as any);

let testMode: NextTestMode = "start";

const envTestMode = process.env.NEXT_TEST_MODE;

if (isValidTestMode(envTestMode)) {
testMode = envTestMode;
}

const createNext = async (opts: NextTestOpts) => {
let nextInstance: NextInstance | undefined = undefined;
try {
switch (testMode) {
case "dev":
nextInstance = new NextInstanceDev(opts);
break;
case "start":
nextInstance = new NextInstanceStart(opts);
break;
}
await nextInstance.setup(opts.sourceDir);
await nextInstance.spawn();
return nextInstance;
} catch (err) {
console.error(
`failed to create next instance: ${JSON.stringify(err, null, 2)}`
);
try {
await nextInstance?.destroy();
} catch {
// do nothing
}
process.exit(1);
}
};

export const createDescribe = (
name: string,
opts: NextTestOpts,
fn: (args: { next: NextInstance; testMode: NextTestMode }) => void
) => {
describe(name, () => {
let next: NextInstance;
beforeAll(async () => {
next = await createNext(opts);
});
afterAll(async () => {
await next.destroy();
});
const nextProxy = new Proxy<NextInstance>({} as NextInstance, {
get(_target, property: keyof NextInstance) {
const prop = next[property];
return typeof prop === "function" ? prop.bind(next) : prop;
},
});
fn({ next: nextProxy, testMode });
});
};
Loading

0 comments on commit d060048

Please sign in to comment.