test: add nitro preset tests (v2 & v3) (#104)

This commit is contained in:
pooya parsa 2021-04-23 21:52:32 +02:00 committed by GitHub
parent 3f712f3d46
commit 5aa59c2ca5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 11042 additions and 12 deletions

49
.github/workflows/test-compat.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: test-compat
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test-compat:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node: [14]
steps:
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- name: checkout
uses: actions/checkout@master
- uses: actions/cache@v2
id: yarn-cache
with:
path: |
.yarn
test/fixtures/compat/.yarn
key: ${{ runner.os }}-yarn-compat-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-compat-
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: yarn --immutable && cd test/fixtures/compat && yarn --immutable
- name: Build
run: yarn build
- name: Test
run: TEST_COMPAT=1 yarn jest --ci
# - name: Coverage
# uses: codecov/codecov-action@v1

View File

@ -1,4 +1,4 @@
name: ci
name: test
on:
push:
@ -9,7 +9,7 @@ on:
- main
jobs:
ci:
test:
runs-on: ${{ matrix.os }}
strategy:
@ -43,8 +43,8 @@ jobs:
- name: Build
run: yarn build
# - name: Test
# run: yarn jest
- name: Test
run: yarn jest --ci
# - name: Coverage
# uses: codecov/codecov-action@v1

6
.gitignore vendored
View File

@ -2,9 +2,8 @@
node_modules
jspm_packages
# Only keep yarn.lock in the root
package-lock.json
*/**/yarn.lock
# */**/yarn.lock
# Logs
*.log
@ -58,3 +57,6 @@ coverage
Network Trash Folder
Temporary Items
.apdisk
.vercel_build_output
*.lock

10
jest.config.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'\\.[jt]sx?$': 'ts-jest'
},
testPathIgnorePatterns: [
'.output/.*'
]
}

View File

@ -13,6 +13,8 @@
"nu": "./node_modules/.bin/nu",
"play": "yarn run nu dev playground",
"lint": "eslint --ext .vue,.ts,.js .",
"test": "yarn lint && jest",
"test:compat": "TEST_COMPAT=1 jest",
"version": "yarn && git add yarn.lock"
},
"resolutions": {
@ -23,11 +25,13 @@
"@nuxtjs/eslint-config-typescript": "^6.0.0",
"@types/jest": "^26.0.22",
"@types/node": "^14.14.41",
"@types/object-hash": "^2",
"eslint": "^7.24.0",
"eslint-plugin-jsdoc": "^32.3.1",
"jest": "^26.6.3",
"jiti": "^1.9.1",
"lerna": "^4.0.0",
"object-hash": "^2.1.1",
"ts-jest": "^26.5.5",
"typescript": "^4.2.4",
"unbuild": "^0.2.2"

View File

@ -25,6 +25,7 @@
"@rollup/plugin-replace": "^2.4.2",
"@rollup/plugin-virtual": "^2.0.3",
"@rollup/pluginutils": "^4.1.0",
"@types/jsdom": "^16.2.10",
"@vercel/nft": "^0.10.1",
"@vue/server-renderer": "^3.0.11",
"archiver": "^5.3.0",
@ -61,7 +62,7 @@
"std-env": "^2.3.0",
"table": "^6.4.0",
"ufo": "^0.7.1",
"unenv": "^0.2.2",
"unenv": "^0.2.3",
"unstorage": "^0.1.5",
"upath": "^2.0.1",
"vue": "3.0.11",

View File

@ -90,6 +90,7 @@ function clientPlugins (ctx: WebpackConfigContext) {
if (!ctx.isDev && options.build.analyze) {
const statsDir = path.resolve(options.buildDir, 'stats')
// @ts-ignore
config.plugins.push(new BundleAnalyzerPlugin({
analyzerMode: 'static',
defaultSizes: 'gzip',

View File

@ -65,6 +65,7 @@ export function getWebpackConfig (ctx: WebpackConfigContext): Configuration {
const builder = {}
const loaders = []
// @ts-ignore
const { extend } = options.build
if (typeof extend === 'function') {
const extendedConfig = extend.call(

3
test/fixtures/basic/nuxt.config.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import { defineNuxtConfig } from '@nuxt/kit'
export default defineNuxtConfig({})

7
test/fixtures/basic/package.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"private": true,
"name": "fixture-compat",
"scripts": {
"build": "nuxt build"
}
}

3
test/fixtures/basic/pages/index.vue vendored Normal file
View File

@ -0,0 +1,3 @@
<template>
<div>Hello Vue</div>
</template>

View File

@ -0,0 +1 @@
export default () => 'Hello API'

10
test/fixtures/compat/nuxt.config.ts vendored Normal file
View File

@ -0,0 +1,10 @@
import { defineNuxtConfig } from '@nuxt/kit'
// @ts-ignore
global.__NUXT_PREPATHS__ = (global.__NUXT_PREPATHS__ || []).concat(__dirname)
export default defineNuxtConfig({
buildModules: [
'@nuxt/nitro/compat'
]
})

13
test/fixtures/compat/package.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"private": true,
"name": "fixture-basic",
"scripts": {
"dev": "nuxt dev",
"build": "nuxt build"
},
"devDependencies": {
"core-js": "^3",
"nuxt": "^2",
"vue": "^2"
}
}

3
test/fixtures/compat/pages/index.vue vendored Normal file
View File

@ -0,0 +1,3 @@
<template>
<div>Hello Vue</div>
</template>

View File

@ -0,0 +1 @@
export default () => 'Hello API'

10569
test/fixtures/compat/yarn.lock vendored Normal file

File diff suppressed because it is too large Load Diff

103
test/presets/_utils.ts Normal file
View File

@ -0,0 +1,103 @@
import { RequestListener } from 'http'
import { resolve } from 'upath'
import destr from 'destr'
import consola from 'consola'
import { Listener, listen } from 'listhen'
import { $fetch } from 'ohmyfetch/node'
import createRequire from 'create-require'
import type { LoadNuxtOptions } from '@nuxt/kit'
import { fixtureDir, buildFixture, loadFixture } from '../utils'
const isCompat = Boolean(process.env.TEST_COMPAT)
export interface TestContext {
rootDir: string
outDir: string
nuxt?: any
fetch: (url: string) => Promise<any>
server?: Listener
}
export interface AbstractRequest {
url: string
headers?: any
method?: string
body?: any
}
export interface AbstractResponse {
data: string
}
export type AbstractHandler = (req: AbstractRequest) => Promise<AbstractResponse>
export function setupTest (): TestContext {
const fixture = isCompat ? 'compat' : 'basic'
const rootDir = fixtureDir(fixture)
const outDir = resolve(__dirname, '.output', fixture)
const ctx: TestContext = {
rootDir,
outDir,
fetch: url => $fetch<any>(url, { baseURL: ctx.server.url })
}
beforeAll(() => {
jest.mock('jiti', () => createRequire)
consola.wrapAll()
consola.mock(() => jest.fn())
})
afterAll(async () => {
if (ctx.nuxt) {
await ctx.nuxt.close()
}
if (ctx.server) {
await ctx.server.close()
}
})
return ctx
}
export function testNitroBuild (ctx: TestContext, preset: string) {
test('nitro build', async () => {
ctx.outDir = resolve(ctx.outDir, preset)
const loadOpts: LoadNuxtOptions = { rootDir: ctx.rootDir, dev: false, version: isCompat ? 2 : 3 }
await buildFixture(loadOpts)
const nuxt = await loadFixture(loadOpts, {
nitro: {
preset,
minify: false,
serveStatic: false,
externals: preset === 'cloudflare' ? false : { trace: false },
output: { dir: ctx.outDir }
}
})
await nuxt.callHook('build:done', {})
ctx.nuxt = nuxt
}, 60000)
}
export async function startServer (ctx: TestContext, handle: RequestListener) {
ctx.server = await listen(handle)
}
export function testNitroBehavior (_ctx: TestContext, getHandler: () => Promise<AbstractHandler>) {
let handler
test('setup handler', async () => {
handler = await getHandler()
})
test('SSR Works', async () => {
const { data } = await handler({ url: '/' })
expect(data).toMatch('Hello Vue')
}, 10000)
test('API Works', async () => {
const { data } = await handler({ url: '/api/hello' })
expect(destr(data)).toEqual('Hello API')
})
}

View File

@ -0,0 +1,66 @@
import { resolve } from 'path'
import { readFile } from 'fs-extra'
import { JSDOM } from 'jsdom'
import { setupTest, testNitroBuild, testNitroBehavior } from './_utils'
// TODO: fix SyntaxError: Unexpected end of input on script executation
describe.skip('nitro:preset:cloudflare', () => {
const ctx = setupTest()
testNitroBuild(ctx, 'cloudflare')
testNitroBehavior(ctx, async () => {
const script = await readFile(resolve(ctx.outDir, 'server/index.js'), 'utf-8')
const dom = new JSDOM(
`<!DOCTYPE html>
<html>
<body>
<script>
global = window
window.Response = class Response {
constructor (body, { headers, status, statusText } = {}) {
this.body = body
this.status = status || 200
this.headers = headers || {}
this.statusText = statusText || ''
}
get ok() {
return this.status === 200
}
async text() {
return this.body
}
async json() {
return JSON.parse(this.body)
}
}
window.addEventListener = (method, handler) => {
window.handleEvent = async event => {
event.respondWith = response => {
event.response = response
}
await handler(event)
return event.response
}
}
</script>
<script>${script}</script>
</body>
</html>`,
{ runScripts: 'dangerously' }
)
return async ({ url, headers, method, body }) => {
const data = await dom.window.handleEvent({
request: {
url: 'http://localhost' + url,
headers: headers || {},
method: method || 'GET',
redirect: null,
body: body || null
}
}).then(r => r.text())
return { data }
}
})
})

View File

@ -0,0 +1,26 @@
import { resolve } from 'path'
import { testNitroBuild, setupTest, testNitroBehavior } from './_utils'
describe('nitro:preset:lambda', () => {
const ctx = setupTest()
testNitroBuild(ctx, 'lambda')
testNitroBehavior(ctx, async () => {
const { handler } = await import(resolve(ctx.outDir, 'server/index.js'))
return async ({ url: rawRelativeUrl, headers, method, body }) => {
// creating new URL object to parse query easier
const url = new URL(`https://example.com${rawRelativeUrl}`)
const queryStringParameters = Object.fromEntries(url.searchParams.entries())
const event = {
path: url.pathname,
headers: headers || {},
method: method || 'GET',
queryStringParameters,
body: body || ''
}
const res = await handler(event)
return {
data: res.body
}
}
})
})

17
test/presets/node.test.ts Normal file
View File

@ -0,0 +1,17 @@
import { resolve } from 'path'
import { testNitroBuild, startServer, setupTest, testNitroBehavior } from './_utils'
describe('nitro:preset:node', () => {
const ctx = setupTest()
testNitroBuild(ctx, 'node')
testNitroBehavior(ctx, async () => {
const { handle } = await import(resolve(ctx.outDir, 'server/index.js'))
await startServer(ctx, handle)
return async ({ url }) => {
const data = await ctx.fetch(url)
return {
data
}
}
})
})

View File

@ -0,0 +1,18 @@
import { resolve } from 'path'
import { testNitroBuild, setupTest, startServer, testNitroBehavior } from './_utils'
describe('nitro:preset:vercel', () => {
const ctx = setupTest()
testNitroBuild(ctx, 'vercel')
testNitroBehavior(ctx, async () => {
const handle = await import(resolve(ctx.outDir, 'functions/node/server/index.js'))
.then(r => r.default || r)
await startServer(ctx, handle)
return async ({ url }) => {
const data = await ctx.fetch(url)
return {
data
}
}
})
})

80
test/utils.ts Normal file
View File

@ -0,0 +1,80 @@
import { resolve, dirname } from 'path'
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync } from 'fs'
import { execSync } from 'child_process'
import defu from 'defu'
import hash from 'object-hash'
import type { LoadNuxtOptions, NuxtConfig } from '@nuxt/kit'
export function fixtureDir (name: string) {
return resolve(__dirname, 'fixtures', name)
}
export async function loadFixture (opts: LoadNuxtOptions, unhashedConfig?: NuxtConfig) {
const buildId = hash(opts)
const buildDir = resolve(opts.rootDir, '.nuxt', buildId)
const { loadNuxt } = await import('@nuxt/kit')
const nuxt = await loadNuxt(defu(opts, { config: { buildDir, ...unhashedConfig } }))
return nuxt
}
export async function buildFixture (opts: LoadNuxtOptions) {
const buildId = hash(opts)
const buildDir = resolve(opts.rootDir, '.nuxt', buildId)
const lockFile = resolve(opts.rootDir, `.build-${buildId}.lock`)
mkdirpSync(dirname(lockFile))
await waitWhile(() => isAlive(readSync(lockFile)))
writeFileSync(lockFile, process.pid + '', 'utf8')
try {
const integrity = gitHead() // TODO: Calculate hash from project source
const integrityFile = resolve(buildDir, '.integrity')
const lastBuildIntegrity = readSync(integrityFile)
if (integrity !== lastBuildIntegrity) {
const nuxt = await loadFixture(opts)
const { buildNuxt } = await import('@nuxt/kit')
await buildNuxt(nuxt)
await nuxt.close()
await writeFileSync(integrityFile, integrity)
}
} finally {
existsSync(lockFile) && rmSync(lockFile)
}
}
function mkdirpSync (dir) {
if (!existsSync(dir)) {
mkdirpSync(dirname(dir))
mkdirSync(dir)
}
}
function readSync (file) {
return existsSync(file) ? readFileSync(file, 'utf8') : null
}
function isAlive (pid) {
try {
process.kill(pid, 0)
return true
} catch (e) {
return e.code === 'EPERM'
}
}
function waitWhile (check, interval = 100, timeout = 30000) {
return new Promise((resolve, reject) => {
const t = setTimeout(() => reject(new Error('Timeout')), timeout)
const i = setInterval(() => {
if (!check()) {
clearTimeout(t)
clearInterval(i)
resolve(true)
}
}, interval)
})
}
function gitHead (): string {
return execSync('git rev-parse HEAD').toString('utf8').trim()
}

View File

@ -1662,6 +1662,7 @@ __metadata:
"@types/debounce": ^1.2.0
"@types/fs-extra": ^9.0.11
"@types/http-proxy": ^1.17.5
"@types/jsdom": ^16.2.10
"@types/node-fetch": ^2.5.10
"@types/serve-static": ^1.13.9
"@vercel/nft": ^0.10.1
@ -1701,7 +1702,7 @@ __metadata:
table: ^6.4.0
ufo: ^0.7.1
unbuild: ^0.2.2
unenv: ^0.2.2
unenv: ^0.2.3
unstorage: ^0.1.5
upath: ^2.0.1
vue: 3.0.11
@ -2281,6 +2282,17 @@ __metadata:
languageName: node
linkType: hard
"@types/jsdom@npm:^16.2.10":
version: 16.2.10
resolution: "@types/jsdom@npm:16.2.10"
dependencies:
"@types/node": "*"
"@types/parse5": "*"
"@types/tough-cookie": "*"
checksum: 9e39f1d2d00e7373222da490a78f11e66a8ea19e3b08ed9b223bcd8da0ce6eb15aabd670d36996a727291d4a917366a8340353a5adef83b4c714fed6e61e786f
languageName: node
linkType: hard
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.3, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.6":
version: 7.0.7
resolution: "@types/json-schema@npm:7.0.7"
@ -2370,6 +2382,13 @@ __metadata:
languageName: node
linkType: hard
"@types/object-hash@npm:^2":
version: 2.1.0
resolution: "@types/object-hash@npm:2.1.0"
checksum: 276948b96fff4f3ddbb37a566401fbdcd68ffac16267e464bf73b3448d3a7f8b2876be39c8a731478710d5b5a01dc4a61d927b12babfbf02e4acba6a3d8a0a42
languageName: node
linkType: hard
"@types/parse-json@npm:^4.0.0":
version: 4.0.0
resolution: "@types/parse-json@npm:4.0.0"
@ -2377,6 +2396,13 @@ __metadata:
languageName: node
linkType: hard
"@types/parse5@npm:*":
version: 6.0.0
resolution: "@types/parse5@npm:6.0.0"
checksum: 356652dc0b341dcc8f93c4b4b59e163efe9b4ccc993e15aa9d96b7b96776eef629939d6c597d79c0abb8adcee9068f590e4bb1102deb770b993d37a24fdac8b8
languageName: node
linkType: hard
"@types/parse5@npm:^5.0.0":
version: 5.0.3
resolution: "@types/parse5@npm:5.0.3"
@ -2448,6 +2474,13 @@ __metadata:
languageName: node
linkType: hard
"@types/tough-cookie@npm:*":
version: 4.0.0
resolution: "@types/tough-cookie@npm:4.0.0"
checksum: 5a0cccc6d073b84dbb48d603e692b55ac30cea1653a7fc813e97a3bcaa9edd97c703c15727378d632e416a90178e6da657151eba8175cbb201a9a98de22ac3a8
languageName: node
linkType: hard
"@types/uglify-js@npm:*":
version: 3.13.0
resolution: "@types/uglify-js@npm:3.13.0"
@ -10487,11 +10520,13 @@ __metadata:
"@nuxtjs/eslint-config-typescript": ^6.0.0
"@types/jest": ^26.0.22
"@types/node": ^14.14.41
"@types/object-hash": ^2
eslint: ^7.24.0
eslint-plugin-jsdoc: ^32.3.1
jest: ^26.6.3
jiti: ^1.9.1
lerna: ^4.0.0
object-hash: ^2.1.1
ts-jest: ^26.5.5
typescript: ^4.2.4
unbuild: ^0.2.2
@ -10563,6 +10598,13 @@ __metadata:
languageName: node
linkType: hard
"object-hash@npm:^2.1.1":
version: 2.1.1
resolution: "object-hash@npm:2.1.1"
checksum: fe49a0864cba7ac4055c604295692ae75f5f4d22ba929aaaa987469809d17ab030d1111e8f0d425a070fa4a78b8021b55260e26e98b66b59e0f7be2bd9069fb8
languageName: node
linkType: hard
"object-inspect@npm:^1.9.0":
version: 1.10.2
resolution: "object-inspect@npm:1.10.2"
@ -14322,9 +14364,9 @@ typescript@^4.2.4:
languageName: node
linkType: hard
"unenv@npm:^0.2.2":
version: 0.2.2
resolution: "unenv@npm:0.2.2"
"unenv@npm:^0.2.3":
version: 0.2.3
resolution: "unenv@npm:0.2.3"
dependencies:
buffer: ^6.0.2
defu: ^3.2.2
@ -14335,7 +14377,7 @@ typescript@^4.2.4:
process: ^0.11.10
upath: ^2.0.1
util: ^0.12.3
checksum: 64597e78d6b660223381fbd3686752a3cfae65cc0c8a89cee241c6617d26b73e734d5dd1c0b6acdbfebc79024d96cdc9177dece6ed9475865dfe9143e4299427
checksum: 75fe628ffca2694106baac8be25122bd6a159eb712f4ae89d9c53f5c6bf77ccac30fa5f2a6a2ee2bc372d55a3f97cddbd7c597396a47fa56b088fc8b7d30c96a
languageName: node
linkType: hard