Skip to content

Commit

Permalink
refactor: move parent argument to options
Browse files Browse the repository at this point in the history
  • Loading branch information
tpluscode committed Jan 17, 2024
1 parent 2aab3d0 commit 1af85c1
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 35 deletions.
3 changes: 1 addition & 2 deletions packages/env/Environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ interface EnvironmentCtor {

new<F extends ReadonlyArray<FactoryConstructor<any>>, E extends Environment<any>>(
factories: Narrow<F>,
parent?: E,
options?: { bind: boolean },
options: { parent?: E; bind?: boolean },
): DerivedEnvironment<E, Environment<Distribute<F[number]>>>
}

Expand Down
4 changes: 2 additions & 2 deletions packages/env/Environment.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { extend } from './lib/extend.js'

export default class Environment {
constructor(factories, parent, { bind = false } = {}) {
constructor(factories, { parent, bind = false } = {}) {
this._parent = parent
this._factories = factories
this._factories = factories.slice()

const extended = parent ? extend(parent, this) : this

Expand Down
10 changes: 6 additions & 4 deletions packages/env/lib/extend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@ type CombinedEnvironment<E extends ReadonlyArray<Environment<any>>> = Environmen
export type DerivedEnvironment<Env extends Environment<unknown>, Ex extends Environment<unknown>> = CombinedEnvironment<[Env, Ex]>

export function extend<E extends Environment<any>, P extends Environment<any>>(self: E, parent: P) {
return new Proxy([self, parent], {
const envs = [self, parent]

return new Proxy({}, {
get(target, prop) {
const value = target.find(env => prop in env)
const value = envs.find(env => prop in env)
if (value) {
return value[prop as keyof E]
}
},
set(target, prop, value) {
target[0][prop as keyof E] = value
envs[0][prop as keyof E] = value

return true
},
has(target, prop) {
return target.some(env => prop in env)
return envs.some(env => prop in env)
},
}) as any as DerivedEnvironment<P, E>
}
34 changes: 34 additions & 0 deletions packages/env/test/Environment-extend.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import DataFactory from '@rdfjs/data-model/Factory.js'
import DatasetFactory from '@rdfjs/dataset/Factory.js'
import { expect } from 'chai'
import { schema } from '@tpluscode/rdf-ns-builders'
import Environment from '../Environment.js'
import { TestFactory } from './TestFactory.js'

describe('Environment with parent', () => {
it('merges all factories', () => {
// given
const dataEnv = new Environment([DataFactory])

// when
const env = new Environment([DatasetFactory], { parent: dataEnv })

// then
expect(env).to.have.property('dataset')
expect(env).to.have.property('namedNode')
expect(env.namedNode('http://schema.org/Person')).to.deep.eq(schema.Person)
})

it('merge multiple levels', () => {
// given
const dataEnv = new Environment([DataFactory])
const datasetEnv = new Environment([DatasetFactory], { parent: dataEnv })

// when
const env = new Environment([TestFactory], { parent: datasetEnv })

// then
const test = env.test()
expect(test.size).to.eq(1)
})
})
181 changes: 154 additions & 27 deletions packages/env/test/Environment.test.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,164 @@
import DataFactory from '@rdfjs/data-model/Factory.js'
import DatasetFactory from '@rdfjs/dataset/Factory.js'
import { describe } from 'mocha'
import { expect } from 'chai'
import { schema } from '@tpluscode/rdf-ns-builders'
/* eslint-disable @typescript-eslint/no-this-alias */
import { strictEqual, notStrictEqual, deepStrictEqual } from 'node:assert'
import Environment from '../Environment.js'
import { TestFactory } from './TestFactory.js'

describe('Environment', () => {
context('with parent', () => {
it('merges all factories', () => {
// given
const dataEnv = new Environment([DataFactory])

// when
const env = new Environment([DatasetFactory], dataEnv)

// then
expect(env).to.have.property('dataset')
expect(env).to.have.property('namedNode')
expect(env.namedNode('http://schema.org/Person')).to.deep.eq(schema.Person)
it('should copy the given factories array to ._factories', () => {
class FactoryA {}
class FactoryB {}
const factories = [FactoryA, FactoryB]

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const env = new Environment(factories) as any

notStrictEqual(env._factories, factories)
strictEqual(env._factories[0], FactoryA)
strictEqual(env._factories[1], FactoryB)
})

it('should call the init function of each factory', () => {
const called: unknown[] = []
class FactoryA {
init() {
called.push('a')
}
}
class FactoryB {
init() {
called.push('b')
}
}

new Environment([FactoryA, FactoryB]) // eslint-disable-line no-new

deepStrictEqual(called, ['a', 'b'])
})

it('should use the environment as this context for the init call', () => {
let context = null
class Factory {
init() {
context = this
}
}

const env = new Environment([Factory])

strictEqual(context, env)
})

it('should use the environment as this context for all methods if bind is true', () => {
let context = null
class Factory {
a() {
context = this
}

static get exports() {
return ['a']
}
}

const env = new Environment([Factory], { bind: true })
const { a } = env

a()

strictEqual(context, env)
})

it('should attach all methods defined in exports', () => {
class Factory {
a() {}
b() {}

static get exports() {
return ['a', 'b']
}
}

const env = new Environment([Factory])

strictEqual(env.a, Factory.prototype.a)
strictEqual(env.b, Factory.prototype.b)
})

it('should not attach methods not contained in exports', () => {
class Factory {
a() {}
b() {}

static get exports() {
return ['b']
}
}

const env = new Environment([Factory])

strictEqual(typeof env.a, 'undefined')
})

describe('.clone', () => {
it('should be a method', () => {
const env = new Environment([])

strictEqual(typeof env.clone, 'function')
})

it('should return a new Environment instance', () => {
const env = new Environment([])

const result = env.clone()

strictEqual(result instanceof Environment, true)
notStrictEqual(result, env)
})

it('should call the clone function of each factory', () => {
const called: unknown[] = []
class FactoryA {
clone() {
called.push('a')
}
}
class FactoryB {
clone() {
called.push('b')
}
}
const env = new Environment([FactoryA, FactoryB])

env.clone()

deepStrictEqual(called, ['a', 'b'])
})

it('should use the environment as this context for the clone call', () => {
let context = null
class Factory {
clone() {
context = this
}
}
const env = new Environment([Factory])

const clone = env.clone()

strictEqual(context, clone)
})

it('merge multiple levels', () => {
// given
const dataEnv = new Environment([DataFactory])
const datasetEnv = new Environment([DatasetFactory], dataEnv)
it('should use the environment as this context for the clone call', () => {
let other = null
class Factory {
clone(original: unknown) {
other = original
}
}
const env = new Environment([Factory])

// when
const env = new Environment([TestFactory], datasetEnv)
env.clone()

// then
const test = env.test()
expect(test.size).to.eq(1)
strictEqual(other, env)
})
})
})

0 comments on commit 1af85c1

Please sign in to comment.