Skip to content

Commit

Permalink
Refactor tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Septh committed Mar 24, 2024
1 parent c15cc12 commit b82fa5d
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 123 deletions.
69 changes: 40 additions & 29 deletions test/_common.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,63 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import type { Plugin, RollupError, ObjectHook } from 'rollup'
import type { Plugin, RollupError, ObjectHook, PluginContextMeta, PluginHooks, PluginContext, NormalizedInputOptions } from 'rollup'
import { nodeExternals, type ExternalsOptions } from '../source/index.ts'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

const warnings: string[] = []
const fakePluginContext = {
meta: {
watchMode: false
},
class MockPluginContext {
private readonly externals: Plugin
readonly warnings: string[]
readonly meta: PluginContextMeta

constructor(externals: Plugin) {
this.externals = externals
this.warnings = []
this.meta = {
rollupVersion: '4.9.6',
watchMode: false
}
}

async buildStart() {
let { buildStart } = this.externals
if (typeof buildStart === 'object')
buildStart = buildStart.handler
if (typeof buildStart === 'function')
return await buildStart.call(this as any, {} as NormalizedInputOptions)
throw new Error('Ooops')
}

async resolveId(specifier: string, importer: string | undefined) {
let { resolveId } = this.externals
if (typeof resolveId === 'object')
resolveId = resolveId.handler
if (typeof resolveId === 'function')
return await resolveId.call(this as any, specifier, importer, { attributes: {}, isEntry: typeof importer === 'string' ? false : true })
throw new Error('Ooops')
}

error(err: string | RollupError): never {
const message: string = typeof err === 'string'
? err
: err.message
throw new Error(message)
},
}

warn(message: string): void {
warnings.push(message)
},
this.warnings.push(message)
}

addWatchFile(_file: string) {
// nop
}
}

// node-externals only implements these hooks
type ImplementedHooks =
| 'buildStart'
| 'resolveId'

export async function callHook(plugin: Plugin, hookName: ImplementedHooks, ...args: any[]) {
const hook = plugin[hookName] as ObjectHook<(this: typeof fakePluginContext, ...args: any) => any>
if (typeof hook === 'function')
return hook.apply(fakePluginContext, args)
if (typeof hook === 'object' && typeof hook.handler === 'function')
return hook.handler.apply(fakePluginContext, args)
throw new Error('Ooops')
}

export async function initPlugin(options: ExternalsOptions = {}): Promise<{ plugin: Plugin, warnings: string[] }> {
warnings.splice(0, Infinity)

const plugin = nodeExternals(options)
await callHook(plugin, 'buildStart')
return { plugin, warnings }
export async function initPlugin(options: ExternalsOptions = {}) {
const plugin = await nodeExternals(options)
const context = new MockPluginContext(plugin)
await context.buildStart()
return context
}

export function fixture(...parts: string[]) {
Expand Down
38 changes: 19 additions & 19 deletions test/builtins.test.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,78 @@
import test from 'ava'
import { initPlugin, callHook } from './_common.ts'
import { initPlugin } from './_common.ts'

test("Marks Node builtins external by default", async t => {
const { plugin } = await initPlugin()
const context = await initPlugin()
for (const builtin of [ 'path', 'node:fs' ]) {
t.like(await callHook(plugin, 'resolveId', builtin, 'index.js'), {
t.like(await context.resolveId(builtin, 'index.js'), {
external: true
})
}
})

test("Does NOT mark Node builtins external when builtins=false", async t => {
const { plugin } = await initPlugin({ builtins: false })
const context = await initPlugin({ builtins: false })
for (const builtin of [ 'path', 'node:fs' ]) {
t.like(await callHook(plugin, 'resolveId', builtin, 'index.js'), {
t.like(await context.resolveId(builtin, 'index.js'), {
external: false
})
}
})

test("Does NOT mark Node builtins external when implicitely excluded", async t => {
const { plugin } = await initPlugin({ exclude: [ 'path', 'node:fs' ]})
const context = await initPlugin({ exclude: [ 'path', 'node:fs' ]})
for (const builtin of [ 'path', 'node:fs' ]) {
t.like(await callHook(plugin, 'resolveId', builtin, 'index.js'), {
t.like(await context.resolveId(builtin, 'index.js'), {
external: false
})
}
})

test("Marks Node builtins external when builtins=false and implicitly included", async t => {
const { plugin } = await initPlugin({ builtins: false, include: [ 'path', 'node:fs' ] })
const context = await initPlugin({ builtins: false, include: [ 'path', 'node:fs' ] })
for (const builtin of [ 'path', 'node:fs' ]) {
t.like(await callHook(plugin, 'resolveId', builtin, 'index.js'), {
t.like(await context.resolveId(builtin, 'index.js'), {
external: true
})
}
})

test("Adds 'node:' prefix to builtins by default", async t => {
const { plugin } = await initPlugin()
const context = await initPlugin()
for (const builtin of [ 'node:path', 'path' ]) {
t.like(await callHook(plugin, 'resolveId', builtin, 'index.js'), {
t.like(await context.resolveId(builtin, 'index.js'), {
id: 'node:path'
})
}
})

test("Removes 'node:' prefix when using builtinsPrefix='strip'", async t => {
const { plugin } = await initPlugin({ builtinsPrefix: 'strip' })
const context = await initPlugin({ builtinsPrefix: 'strip' })
for (const builtin of [ 'node:path', 'path' ]) {
t.like(await callHook(plugin, 'resolveId', builtin, 'index.js'), {
t.like(await context.resolveId(builtin, 'index.js'), {
id: 'path'
})
}
})

test("Does NOT remove 'node:test' prefix even with builtinsPrefix='add'", async t => {
const { plugin } = await initPlugin({ builtinsPrefix: 'strip' })
const context = await initPlugin({ builtinsPrefix: 'strip' })
for (const builtin of [ 'node:test' ]) {
t.like(await callHook(plugin, 'resolveId', builtin, 'index.js'), {
t.like(await context.resolveId(builtin, 'index.js'), {
id: builtin
})
}
})

test("Does not recognize 'test' as a Node builtin", async t => {
const { plugin } = await initPlugin()
t.is(await callHook(plugin, 'resolveId', 'node', 'index.js'), null)
const context = await initPlugin()
t.is(await context.resolveId('node', 'index.js'), null)
})

test("Ignores 'node:' prefix when using builtinsPrefix='ignore'", async t => {
const { plugin } = await initPlugin({ builtinsPrefix: 'ignore' })
const context = await initPlugin({ builtinsPrefix: 'ignore' })
for (const builtin of [ 'node:path', 'path' ]) {
t.like(await callHook(plugin, 'resolveId', builtin, 'index.js'), {
t.like(await context.resolveId(builtin, 'index.js'), {
id: builtin
})
}
Expand Down
14 changes: 7 additions & 7 deletions test/monorepo.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import fs from 'node:fs/promises'
import test from 'ava'
import { initPlugin, callHook, fixture } from './_common.ts'
import { initPlugin, fixture } from './_common.ts'

// These two tests need to be run in sequence
test.serial('git monorepo usage', async t => {
await fs.mkdir(fixture('01_monorepo/.git'), { recursive: true })
process.chdir(fixture('01_monorepo/one'))

// Should gather dependencies up to ./test/fixtures/01_monorepo
const { plugin } = await initPlugin()
const context = await initPlugin()

// Should be external
for (const dependency of [
'moment', // dependency in ./test/fixtures/01_monorepo/one/package.json (picked)
'chalk' // dependency in ./test/fixtures/01_monorepo/package.json (picked)
]) {
t.false(await callHook(plugin, 'resolveId', dependency, 'index.js'))
t.false(await context.resolveId(dependency, 'index.js'))
}

// Should be ignored
for (const dependency of [
'react', // dependency in ./test/fixtures/01_monorepo/two/package.json (not picked)
'test-dep' // dependency in ./test/fixtures/package.json (not picked)
]) {
t.is(await callHook(plugin, 'resolveId', dependency, 'index.js'), null)
t.is(await context.resolveId(dependency, 'index.js'), null)
}
})

Expand All @@ -32,7 +32,7 @@ test.serial('non-git monorepo usage', async t => {
process.chdir(fixture('01_monorepo/one'))

// Should gather dependencies up to . !
const { plugin } = await initPlugin()
const context = await initPlugin()

// Should be external
for (const dependency of [
Expand All @@ -41,13 +41,13 @@ test.serial('non-git monorepo usage', async t => {
'test-dep', // dependency in ./test/fixtures/package.json (picked)
'rollup', // peer dependency in ./package.json (picked !)
]) {
t.false(await callHook(plugin, 'resolveId', dependency, 'index.js'))
t.false(await context.resolveId(dependency, 'index.js'))
}

// Should be ignored
for (const dependency of [
'react' // dependency in ./test/fixtures/01_monorepo/two/package.json (not picked)
]) {
t.is(await callHook(plugin, 'resolveId', dependency, 'index.js'), null)
t.is(await context.resolveId(dependency, 'index.js'), null)
}
})
23 changes: 12 additions & 11 deletions test/options.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import test from 'ava'
import { testProp, fc } from '@fast-check/ava'
import type { Arbitrary } from 'fast-check'
import { initPlugin, callHook, fixture } from './_common.ts'
import { initPlugin, fixture } from './_common.ts'
import { type ExternalsOptions } from '../source/index.ts'

// Ensures tests use local package.json
Expand Down Expand Up @@ -35,28 +35,29 @@ testProp(
}
)

// Must be serial because it uses the 'warnings' global in _common.ts.
test.serial("Warns when given invalid include or exclude entry", async t => {
test("Warns when given invalid include or exclude entry", async t => {
const okay = 'some_dep' // string is ok
const notOkay = 1 // number is not (unless 0, which is falsy)

const { warnings } = await initPlugin({
include: [ okay, notOkay as any ]
const context = await initPlugin({
include: [ okay, notOkay as any ],
exclude: [ okay, notOkay as any ],
})

t.is(warnings.length, 1)
t.is(warnings[0], `Ignoring wrong entry type #1 in 'include' option: ${JSON.stringify(notOkay)}`)
t.is(context.warnings.length, 2)
t.is(context.warnings[0], `Ignoring wrong entry type #1 in 'include' option: ${JSON.stringify(notOkay)}`)
t.is(context.warnings[1], `Ignoring wrong entry type #1 in 'exclude' option: ${JSON.stringify(notOkay)}`)
})

test("Obeys 'packagePath' option (single file name)", async t => {
const { plugin } = await initPlugin({
const context = await initPlugin({
packagePath: '00_simple/package.json'
})
t.false(await callHook(plugin, 'resolveId', 'simple-dep', 'index.js'))
t.false(await context.resolveId('simple-dep', 'index.js'))
})

test("Obeys 'packagePath' option (multiple file names)", async t => {
const { plugin } = await initPlugin({
const context = await initPlugin({
packagePath: [
'00_simple/package.json',
'01_monorepo/package.json'
Expand All @@ -68,6 +69,6 @@ test("Obeys 'packagePath' option (multiple file names)", async t => {
'simple-dep', // 00_simple/package.json
'chalk', // 01_monorepo/package.json
]) {
t.false(await callHook(plugin, 'resolveId', dependency, 'index.js'))
t.false(await context.resolveId(dependency, 'index.js'))
}
})
Loading

0 comments on commit b82fa5d

Please sign in to comment.