Skip to content

Commit b3f026b

Browse files
author
Bryce Kalow
authored
chore: run tests in isolation (#332)
1 parent ae6c486 commit b3f026b

File tree

23 files changed

+459
-275
lines changed

23 files changed

+459
-275
lines changed

.github/workflows/test.yml

-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,5 @@ jobs:
2121
- name: Install and build
2222
run: |
2323
npm ci
24-
npm run build
2524
- name: Run Jest
2625
run: npm run test

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ __tests__/fixtures/*/out
55
examples/*/package-lock.json
66
examples/*/.next
77
dist/
8+
*.tgz

.jest/utils.tsx

+169-3
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,194 @@
11
import ReactDOMServer from 'react-dom/server'
22
import React from 'react'
3+
import { VFileCompatible } from 'vfile'
4+
import fs from 'fs'
5+
import os from 'os'
6+
import path from 'path'
7+
import http from 'http'
8+
import spawn from 'cross-spawn'
9+
import { ChildProcess } from 'child_process'
10+
import treeKill from 'tree-kill'
11+
import puppeteer, { Browser } from 'puppeteer'
12+
import { Server } from 'http'
13+
import handler from 'serve-handler'
314

415
import { MDXRemote, MDXRemoteProps } from '../src/index'
516
import { serialize } from '../src/serialize'
617
import { SerializeOptions } from '../src/types'
7-
import { VFileCompatible } from 'vfile'
818

919
export async function renderStatic(
1020
mdx: VFileCompatible,
1121
{
1222
components,
1323
scope,
1424
mdxOptions,
15-
minifyOptions,
1625
parseFrontmatter,
1726
}: SerializeOptions & Pick<MDXRemoteProps, 'components'> = {}
1827
): Promise<string> {
1928
const mdxSource = await serialize(mdx, {
2029
mdxOptions,
21-
minifyOptions,
2230
parseFrontmatter,
2331
})
2432

2533
return ReactDOMServer.renderToStaticMarkup(
2634
<MDXRemote {...mdxSource} components={components} scope={scope} />
2735
)
2836
}
37+
38+
export async function getPathToPackedPackage() {
39+
const packageJson = JSON.parse(
40+
await fs.promises.readFile(
41+
path.join(__dirname, '..', 'package.json'),
42+
'utf-8'
43+
)
44+
)
45+
46+
const filename = `${packageJson.name}-${packageJson.version}.tgz`
47+
48+
return path.join(__dirname, '..', 'dist', filename)
49+
}
50+
51+
// Create a temporary directory from one of our fixtures to run isolated tests in
52+
// Handles installing the locally-packed next-mdx-remote
53+
export async function createTmpTestDir(fixture) {
54+
const tmpDir = await fs.promises.mkdtemp(
55+
path.join(os.tmpdir(), `next-mdx-remote-${fixture}-`)
56+
)
57+
58+
// copy over the fixture
59+
const pathToFixture = path.join(
60+
process.cwd(),
61+
'__tests__',
62+
'fixtures',
63+
fixture
64+
)
65+
66+
await fs.promises.cp(pathToFixture, tmpDir, { recursive: true })
67+
68+
// install locally packed package
69+
const pathToPackedPackage = await getPathToPackedPackage()
70+
71+
console.log('installing dependencies in test directory')
72+
73+
spawn.sync('npm', ['install', pathToPackedPackage], {
74+
cwd: tmpDir,
75+
})
76+
77+
return tmpDir
78+
}
79+
80+
async function cleanupTmpTestDir(tmpDir: string) {
81+
await fs.promises.rm(tmpDir, { recursive: true, force: true })
82+
}
83+
84+
// Handles creating an isolated test dir from one of the fixtures in __tests__/fixtures/
85+
export function createDescribe(
86+
name: string,
87+
options: { fixture: string },
88+
fn: ({ dir }: { dir: () => string; browser: () => Browser }) => void
89+
): void {
90+
describe(name, () => {
91+
let tmpDir
92+
let browser
93+
94+
beforeAll(async () => {
95+
tmpDir = await createTmpTestDir(options.fixture)
96+
browser = await puppeteer.launch()
97+
})
98+
99+
fn({
100+
dir() {
101+
return tmpDir
102+
},
103+
browser() {
104+
return browser
105+
},
106+
})
107+
108+
afterAll(async () => {
109+
await browser.close()
110+
await cleanupTmpTestDir(tmpDir)
111+
})
112+
})
113+
}
114+
115+
// Starts a next dev server from the given directory on port 12333
116+
export async function startDevServer(dir: string) {
117+
const childProcess = spawn('npx', ['next', 'dev', '-p', '12333'], {
118+
stdio: ['ignore', 'pipe', 'pipe'],
119+
cwd: dir,
120+
env: { ...process.env, NODE_ENV: 'development', __NEXT_TEST_MODE: 'true' },
121+
})
122+
123+
childProcess.stderr?.on('data', (chunk) => {
124+
process.stdout.write(chunk)
125+
})
126+
127+
async function waitForStarted() {
128+
return new Promise<undefined>((resolve) => {
129+
childProcess.stdout?.on('data', (chunk) => {
130+
const msg = chunk.toString()
131+
process.stdout.write(chunk)
132+
133+
if (msg.includes('started server on') && msg.includes('url:')) {
134+
resolve(undefined)
135+
}
136+
})
137+
})
138+
}
139+
140+
await waitForStarted()
141+
142+
return childProcess
143+
}
144+
145+
// Stops running dev server using its ChildProcess instance
146+
export async function stopDevServer(childProcess: ChildProcess) {
147+
console.log('stopping development server...')
148+
const promise = new Promise((resolve) => {
149+
childProcess.on('close', () => {
150+
console.log('development server stopped')
151+
resolve(undefined)
152+
})
153+
})
154+
155+
await new Promise((resolve) => {
156+
treeKill(childProcess.pid!, 'SIGKILL', () => resolve(undefined))
157+
})
158+
159+
childProcess.kill('SIGKILL')
160+
161+
await promise
162+
}
163+
164+
// Runs next build and next export in the provided directory
165+
export function buildFixture(dir: string) {
166+
spawn.sync('npx', ['next', 'build'], {
167+
stdio: 'inherit',
168+
cwd: dir,
169+
env: { ...process.env, NODE_ENV: 'production', __NEXT_TEST_MODE: 'true' },
170+
})
171+
spawn.sync('npx', ['next', 'export'], {
172+
stdio: 'inherit',
173+
cwd: dir,
174+
env: { ...process.env, NODE_ENV: 'production', __NEXT_TEST_MODE: 'true' },
175+
})
176+
}
177+
178+
// Helper to read an html file in the out directory relative to the provided dir
179+
export function readOutputFile(dir: string, name: string) {
180+
return fs.readFileSync(path.join(dir, 'out', `${name}.html`), 'utf8')
181+
}
182+
183+
// Serves the out directory relative to the provided dir on port 1235
184+
// TODO: we should just use next start
185+
export function serveStatic(dir): Promise<Server> {
186+
return new Promise((resolve) => {
187+
const server = http.createServer((req, res) =>
188+
handler(req, res, {
189+
public: path.join(dir, 'out'),
190+
})
191+
)
192+
server.listen(1235, () => resolve(server))
193+
})
194+
}

.npmignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ tsconfig.json
55
babel.config.js
66
src
77
.circleci
8+
*.tgz

__tests__/fixtures/basic/app/app-dir-mdx/provider.js

-35
This file was deleted.

__tests__/fixtures/basic/package.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
{
2-
"private": true
2+
"private": true,
3+
"dependencies": {
4+
"@hashicorp/remark-plugins": "^3.2.1",
5+
"next": "^13.1.2",
6+
"react": "^18.2.0",
7+
"react-dom": "^18.2.0"
8+
}
39
}

__tests__/fixtures/basic/pages/index.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import fs from 'fs'
22
import path from 'path'
33
import { createContext, useEffect, useState } from 'react'
44
import dynamic from 'next/dynamic'
5-
import { serialize } from '../../../../serialize'
6-
import { MDXRemote } from '../../../../'
5+
import { serialize } from 'next-mdx-remote/serialize'
6+
import { MDXRemote } from 'next-mdx-remote'
77
import Test from '../components/test'
88
import { paragraphCustomAlerts } from '@hashicorp/remark-plugins'
99

__tests__/fixtures/basic/app/app-dir-mdx/compile-mdx/page.js renamed to __tests__/fixtures/rsc/app/app-dir-mdx/compile-mdx/page.js

+2-10
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@ import fs from 'fs'
22
import path from 'path'
33
import dynamic from 'next/dynamic'
44
import Test from '../../../components/test'
5-
import { paragraphCustomAlerts } from '@hashicorp/remark-plugins'
6-
import { Provider, Consumer } from '../provider'
75
import { compileMDX } from 'next-mdx-remote/rsc'
86

97
const MDX_COMPONENTS = {
108
Test,
11-
ContextConsumer: Consumer,
129
strong: (props) => <strong className="custom-strong" {...props} />,
1310
Dynamic: dynamic(() => import('../../../components/dynamic')),
1411
}
@@ -21,15 +18,10 @@ export default async function Page() {
2118
source,
2219
components: MDX_COMPONENTS,
2320
options: {
24-
mdxOptions: { remarkPlugins: [paragraphCustomAlerts] },
21+
mdxOptions: { remarkPlugins: [] },
2522
parseFrontmatter: true,
2623
},
2724
})
2825

29-
return (
30-
<>
31-
<h1>{frontmatter.title}</h1>
32-
<Provider>{content}</Provider>
33-
</>
34-
)
26+
return <>{content}</>
3527
}

__tests__/fixtures/basic/app/app-dir-mdx/mdxremote/page.js renamed to __tests__/fixtures/rsc/app/app-dir-mdx/mdxremote/page.js

+8-12
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@ import fs from 'fs'
22
import path from 'path'
33
import dynamic from 'next/dynamic'
44
import Test from '../../../components/test'
5-
import { paragraphCustomAlerts } from '@hashicorp/remark-plugins'
6-
import { Provider, Consumer } from '../provider'
75
import { MDXRemote } from 'next-mdx-remote/rsc'
86

97
const MDX_COMPONENTS = {
108
Test,
11-
ContextConsumer: Consumer,
129
strong: (props) => <strong className="custom-strong" {...props} />,
1310
Dynamic: dynamic(() => import('../../../components/dynamic')),
1411
}
@@ -19,15 +16,14 @@ export default async function Page() {
1916

2017
return (
2118
<>
22-
<Provider>
23-
<MDXRemote
24-
source={source}
25-
components={MDX_COMPONENTS}
26-
options={{
27-
mdxOptions: { remarkPlugins: [paragraphCustomAlerts] },
28-
}}
29-
/>
30-
</Provider>
19+
<MDXRemote
20+
source={source}
21+
components={MDX_COMPONENTS}
22+
options={{
23+
mdxOptions: { remarkPlugins: [] },
24+
parseFrontmatter: true,
25+
}}
26+
/>
3127
</>
3228
)
3329
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Dynamic() {
2+
return <div>I am a dynamic component.</div>
3+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
5+
export default function Test({ name }) {
6+
const [count, setCount] = useState(0)
7+
return (
8+
<>
9+
<p>hello {name}</p>
10+
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
11+
</>
12+
)
13+
}

__tests__/fixtures/rsc/mdx/test.mdx

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
title: 'foo'
3+
name: jeff
4+
---
5+
6+
import Bar from 'bar'
7+
8+
# {frontmatter.title}
9+
10+
## Headline
11+
12+
<Test name={frontmatter.name} />
13+
14+
Some **markdown** content
15+
16+
<Dynamic />
17+
18+
```shell-session
19+
curl localhost
20+
```
21+
22+
This is more text.
23+
24+
"Authorize \<GITHUB_USER\>"
25+
26+
(support for version \<230)
27+
28+
### Some version \<= 1.3.x
29+
30+
#### metric.name.\<operation>.\<mount>
31+
32+
< 8ms

0 commit comments

Comments
 (0)