Skip to content

Commit

Permalink
test(loader): add more loader tests, fix constructor detection
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Apr 30, 2024
1 parent e67ea4a commit ea310e9
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 102 deletions.
8 changes: 7 additions & 1 deletion packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ export const symbols = {
immediate: Symbol.for('cordis.immediate') as typeof Service.immediate,
}

const GeneratorFunction = function* () {}.constructor
const AsyncGeneratorFunction = async function* () {}.constructor

export function isConstructor(func: any): func is new (...args: any) => any {
// async function or arrow function
if (!func.prototype) return false
// generator function or malformed definition
if (func.prototype.constructor !== func) return false
// we cannot use below check because `mock.fn()` is proxified
// if (func.prototype.constructor !== func) return false
if (func instanceof GeneratorFunction) return false
if (func instanceof AsyncGeneratorFunction) return false
return true
}

Expand Down
1 change: 1 addition & 0 deletions packages/loader/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { promises as fs } from 'fs'
import * as dotenv from 'dotenv'
import * as path from 'path'

export * from './entry.ts'
export * from './shared.ts'

const oldEnv = { ...process.env }
Expand Down
202 changes: 108 additions & 94 deletions packages/loader/tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,100 +5,116 @@ import { defineProperty } from 'cosmokit'
import MockLoader from './utils'

describe('@cordisjs/loader', () => {
const root = new Context()
root.plugin(MockLoader)
describe('basic support', () => {
const root = new Context()
root.plugin(MockLoader)

const foo = mock.fn((ctx: Context) => {
ctx.accept()
})
const bar = mock.fn((ctx: Context) => {
ctx.accept()
})
const qux = mock.fn((ctx: Context) => {
ctx.accept()
})
root.loader.register('foo', foo)
root.loader.register('bar', bar)
root.loader.register('qux', qux)

it('basic support', async () => {
root.loader.config = [{
id: '1',
name: 'foo',
}, {
id: '2',
name: 'cordis/group',
config: [{
id: '3',
name: 'bar',
config: {
a: 1,
},
const foo = root.loader.mock('foo', (ctx: Context) => ctx.accept())
const bar = root.loader.mock('bar', (ctx: Context) => ctx.accept())
const qux = root.loader.mock('qux', (ctx: Context) => ctx.accept())

it('loader initiate', async () => {
await root.loader.restart([{
id: '1',
name: 'foo',
}, {
id: '2',
name: 'cordis/group',
config: [{
id: '3',
name: 'bar',
config: {
a: 1,
},
}, {
id: '4',
name: 'qux',
disabled: true,
}],
}])

root.loader.expectEnable(foo, {})
root.loader.expectEnable(bar, { a: 1 })
root.loader.expectDisable(qux)
expect(foo.mock.calls).to.have.length(1)
expect(bar.mock.calls).to.have.length(1)
expect(qux.mock.calls).to.have.length(0)
})

it('loader update', async () => {
foo.mock.resetCalls()
bar.mock.resetCalls()
root.loader.restart([{
id: '1',
name: 'foo',
}, {
id: '4',
disabled: true,
name: 'qux',
}],
}]

await root.start()
expect(root.registry.get(foo)).to.be.ok
expect(root.registry.get(foo)?.config).to.deep.equal({})
expect(foo.mock.calls).to.have.length(1)
expect(root.registry.get(bar)).to.be.ok
expect(root.registry.get(bar)?.config).to.deep.equal({ a: 1 })
expect(bar.mock.calls).to.have.length(1)
expect(root.registry.get(qux)).to.be.not.ok
})
}])

it('entry update', async () => {
root.loader.config = [{
id: '1',
name: 'foo',
}, {
id: '4',
name: 'qux',
}]

foo.mock.resetCalls()
bar.mock.resetCalls()
await root.loader.start()
await new Promise((resolve) => setTimeout(resolve, 0))
expect(root.registry.get(foo)).to.be.ok
expect(root.registry.get(bar)).to.be.not.ok
expect(root.registry.get(qux)).to.be.ok
expect(foo.mock.calls).to.have.length(0)
expect(bar.mock.calls).to.have.length(0)
expect(qux.mock.calls).to.have.length(1)
})
await new Promise((resolve) => setTimeout(resolve, 0))
root.loader.expectEnable(foo, {})
root.loader.expectDisable(bar)
root.loader.expectEnable(qux, {})
expect(foo.mock.calls).to.have.length(0)
expect(bar.mock.calls).to.have.length(0)
expect(qux.mock.calls).to.have.length(1)
})

it('plugin update', async () => {
const runtime = root.registry.get(foo)
runtime!.update({ a: 3 })
await new Promise((resolve) => setTimeout(resolve, 0))
expect(root.loader.config).to.deep.equal([{
id: '1',
name: 'foo',
config: { a: 3 },
}, {
id: '4',
name: 'qux',
}])
})
it('plugin self-update 1', async () => {
root.registry.get(foo)!.update({ a: 3 })
await new Promise((resolve) => setTimeout(resolve, 0))
expect(root.loader.config).to.deep.equal([{
id: '1',
name: 'foo',
config: { a: 3 },
}, {
id: '4',
name: 'qux',
}])
})

it('plugin dispose', async () => {
const runtime = root.registry.get(foo)
runtime!.dispose()
await new Promise((resolve) => setTimeout(resolve, 0))
expect(root.loader.config).to.deep.equal([{
id: '1',
name: 'foo',
disabled: true,
config: { a: 3 },
}, {
id: '4',
name: 'qux',
}])
it('plugin self-update 2', async () => {
root.registry.get(foo)!.children[0].update({ a: 5 })
await new Promise((resolve) => setTimeout(resolve, 0))
expect(root.loader.config).to.deep.equal([{
id: '1',
name: 'foo',
config: { a: 5 },
}, {
id: '4',
name: 'qux',
}])
})

it('plugin self-dispose 1', async () => {
root.registry.get(foo)!.dispose()
await new Promise((resolve) => setTimeout(resolve, 0))
expect(root.loader.config).to.deep.equal([{
id: '1',
name: 'foo',
disabled: true,
config: { a: 5 },
}, {
id: '4',
name: 'qux',
}])
})

it('plugin self-dispose 2', async () => {
root.registry.get(qux)!.children[0].dispose()
await new Promise((resolve) => setTimeout(resolve, 0))
expect(root.loader.config).to.deep.equal([{
id: '1',
name: 'foo',
disabled: true,
config: { a: 5 },
}, {
id: '4',
name: 'qux',
disabled: true,
}])
})
})

describe('service isolation', async () => {
Expand All @@ -118,12 +134,12 @@ describe('@cordisjs/loader', () => {
static [Service.provide] = 'qux'
static [Service.immediate] = true
}
root.loader.register('foo', foo)
root.loader.register('bar', Bar)
root.loader.register('qux', Qux)
root.loader.mock('foo', foo)
root.loader.mock('bar', Bar)
root.loader.mock('qux', Qux)

it('basic support', async () => {
root.loader.config = [{
await root.loader.restart([{
id: '1',
name: 'bar',
}, {
Expand All @@ -136,9 +152,7 @@ describe('@cordisjs/loader', () => {
id: '4',
name: 'foo',
}],
}]

await root.start()
}])
expect(root.registry.get(foo)).to.be.ok
expect(root.registry.get(Bar)).to.be.ok
expect(root.registry.get(Qux)).to.be.ok
Expand Down
35 changes: 28 additions & 7 deletions packages/loader/tests/utils.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,52 @@
import { Dict } from 'cosmokit'
import { Context, Plugin } from '@cordisjs/core'
import { group, Loader } from '../src/shared'
import { Entry, group, Loader } from '../src'
import { Mock, mock } from 'node:test'
import { expect } from 'chai'

declare module '../src/shared' {
interface Loader {
register(name: string, plugin: any): void
mock<F extends Function>(name: string, plugin: F): Mock<F>
restart(config: Entry.Options[]): Promise<void>
expectEnable(plugin: any, config?: any): void
expectDisable(plugin: any): void
}
}

export default class MockLoader extends Loader {
public data: Dict<Plugin.Object> = Object.create(null)
public modules: Dict<Plugin.Object> = Object.create(null)

constructor(ctx: Context) {
super(ctx, { name: 'cordis' })
this.register('cordis/group', group)
this.mock('cordis/group', group)
this.writable = true
}

register(name: string, plugin: any) {
this.data[name] = plugin
mock<F extends Function>(name: string, plugin: F) {
return this.modules[name] = mock.fn(plugin)
}

async import(name: string) {
return this.data[name]
return this.modules[name]
}

async readConfig() {
return this.config
}

async restart(config: Entry.Options[]) {
this.config = config
return this.start()
}

expectEnable(plugin: any, config?: any) {
const runtime = this.ctx.registry.get(plugin)
expect(runtime).to.be.ok
expect(runtime!.config).to.deep.equal(config)
}

expectDisable(plugin: any) {
const runtime = this.ctx.registry.get(plugin)
expect(runtime).to.be.not.ok
}
}

0 comments on commit ea310e9

Please sign in to comment.