This repository has been archived by the owner on Oct 31, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ceb-inversion-builder): inject entries in Custom Elements
- Loading branch information
Showing
12 changed files
with
432 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# @tmorin/ceb-inversion-builder | ||
|
||
[![npm version](https://badge.fury.io/js/%40tmorin%2Fceb-inversion-builder.svg)](https://badge.fury.io/js/%40tmorin%2Fceb-inversion-builder) | ||
[![api](https://img.shields.io/badge/-api-informational.svg)](https://tmorin.github.io/ceb/api/modules/_tmorin_ceb_inversion_builder.html) | ||
|
||
> The package is part of the `<ceb/>` library. | ||
> It provides a builder to inject entries in Custom Elements. | ||
## Install | ||
|
||
The NPM package is compliant [CommonJs](https://flaviocopes.com/commonjs) and [ES Module](https://flaviocopes.com/es-modules). | ||
|
||
```bash | ||
npm install @tmorin/ceb-inversion-builder | ||
``` | ||
|
||
## License | ||
|
||
Released under the [MIT license]. | ||
|
||
[Custom Elements (v1)]: https://html.spec.whatwg.org/multipage/custom-elements.html | ||
[MIT license]: http://opensource.org/licenses/MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('../../karma.conf') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
{ | ||
"name": "@tmorin/ceb-inversion-builder", | ||
"version": "4.0.3-alpha.12", | ||
"license": "MIT", | ||
"description": "The package is part of the `<ceb/>` library. It provides a builder to inject entries in Custom Elements.", | ||
"keywords": [ | ||
"custom-element-builder", | ||
"custom-elements-v1", | ||
"custom-elements", | ||
"custom-element", | ||
"typescript", | ||
"typescript-library", | ||
"javascript-library", | ||
"inversion-of-control", | ||
"ioc", | ||
"injection" | ||
], | ||
"homepage": "https://tmorin.github.io/ceb", | ||
"bugs": { | ||
"url": "https://github.com/tmorin/ceb/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git@github.com:tmorin/ceb.git" | ||
}, | ||
"author": { | ||
"name": "Thibault Morin", | ||
"url": "https://tmorin.github.io" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"exports": { | ||
".": { | ||
"import": "./dist/mjs/index.js", | ||
"require": "./dist/cjs/index.js" | ||
} | ||
}, | ||
"types": "dist/types/index.d.ts", | ||
"main": "dist/cjs/index.js", | ||
"module": "dist/mjs/index.js", | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"build": "npm run build:lib && npm run build:module && npm run build:fix", | ||
"build:fix": "../../scripts/fix-dist.js", | ||
"build:lib": "tsc -p tsconfig.build.json --module CommonJS --outDir dist/cjs --declaration --declarationDir dist/types", | ||
"build:module": "tsc -p tsconfig.build.json --module ESNext --outDir dist/mjs", | ||
"test": "karma start --single-run --no-auto-watch --browsers FirefoxHeadless,ChromeHeadless", | ||
"test:watch": "karma start --no-single-run --auto-watch --browsers Firefox" | ||
}, | ||
"dependencies": { | ||
"@tmorin/ceb-core": "4.0.3-alpha.12", | ||
"@tmorin/ceb-inversion": "4.0.3-alpha.12" | ||
} | ||
} |
75 changes: 75 additions & 0 deletions
75
packages/ceb-inversion-builder/src/builder.decorator.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import {assert} from "chai" | ||
import {Container, ContainerBuilder, OnlyConfigureModule} from "@tmorin/ceb-inversion"; | ||
import {ElementBuilder} from "@tmorin/ceb-core"; | ||
import {InversionBuilder} from "./builder"; | ||
import {InversionBuilderModule} from "./module"; | ||
|
||
class ServiceA { | ||
methodA() { | ||
return "resultA" | ||
} | ||
} | ||
|
||
class ServiceB { | ||
constructor( | ||
private readonly serviceA: ServiceA | ||
) { | ||
} | ||
|
||
methodA() { | ||
return this.serviceA.methodA() | ||
} | ||
} | ||
|
||
describe("inversion/builder/decorator", function () { | ||
let sandbox: HTMLDivElement | ||
let container: Container | ||
let testElement: TestElement | ||
const tagName = "inversion-builder-decorator" | ||
|
||
@ElementBuilder.get(TestElement).name(tagName).decorate() | ||
class TestElement extends HTMLElement { | ||
@InversionBuilder.get().decorate() | ||
serviceA?: ServiceA | ||
@InversionBuilder.get().decorate() | ||
serviceB?: ServiceB | ||
@InversionBuilder.get().key("serviceB").decorate() | ||
serviceBbis?: ServiceB | ||
@InversionBuilder.get().key(Symbol.for("serviceBWithSymbol")).decorate() | ||
serviceBWithSymbol?: ServiceB | ||
} | ||
|
||
before(async () => { | ||
container = await ContainerBuilder.get() | ||
.module(new InversionBuilderModule()) | ||
.module(OnlyConfigureModule.create(async function () { | ||
this.registry.registerFactory<ServiceA>("serviceA", () => new ServiceA()) | ||
this.registry.registerFactory<ServiceB>("serviceB", (registry) => new ServiceB(registry.resolve<ServiceA>("serviceA"))) | ||
this.registry.registerFactory<ServiceB>(Symbol.for("serviceBWithSymbol"), (registry) => registry.resolve<ServiceB>("serviceB")) | ||
})) | ||
.build() | ||
.initialize() | ||
sandbox = document.body.appendChild(document.createElement('div')) | ||
testElement = sandbox.appendChild(document.createElement(tagName) as TestElement) | ||
|
||
}) | ||
after(async () => { | ||
// @ts-ignore | ||
InversionBuilder.setDefaultContainer(undefined) | ||
container?.dispose() | ||
}) | ||
it("should inject serviceA", function () { | ||
assert.property(testElement, "serviceA") | ||
assert.strictEqual(testElement.serviceA?.methodA(), "resultA") | ||
}) | ||
it("should inject serviceB", function () { | ||
assert.property(testElement, "serviceB") | ||
assert.strictEqual(testElement.serviceB?.methodA(), "resultA") | ||
assert.property(testElement, "serviceBbis") | ||
assert.strictEqual(testElement.serviceBbis?.methodA(), "resultA") | ||
}) | ||
it("should inject serviceBWithSymbol", function () { | ||
assert.property(testElement, "serviceBWithSymbol") | ||
assert.strictEqual(testElement.serviceBWithSymbol?.methodA(), "resultA") | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import {assert} from "chai" | ||
import {Container, ContainerBuilder, OnlyConfigureModule} from "@tmorin/ceb-inversion"; | ||
import {ElementBuilder} from "@tmorin/ceb-core"; | ||
import {InversionBuilder} from "./builder"; | ||
import {InversionBuilderModule} from "./module"; | ||
|
||
class ServiceA { | ||
methodA() { | ||
return "resultA" | ||
} | ||
} | ||
|
||
class ServiceB { | ||
constructor( | ||
private readonly serviceA: ServiceA | ||
) { | ||
} | ||
|
||
methodA() { | ||
return this.serviceA.methodA() | ||
} | ||
} | ||
|
||
describe("inversion/builder", function () { | ||
let sandbox: HTMLDivElement | ||
let container: Container | ||
|
||
describe("with InversionBuilderModule", function () { | ||
let testElement: TestElement | ||
const tagName = "inversion-builder-inversion-builder-module" | ||
|
||
class TestElement extends HTMLElement { | ||
serviceA?: ServiceA | ||
serviceB?: ServiceB | ||
} | ||
|
||
before(async () => { | ||
container = await ContainerBuilder.get() | ||
.module(new InversionBuilderModule()) | ||
.module(OnlyConfigureModule.create(async function () { | ||
this.registry.registerFactory<ServiceA>("ServiceA", () => new ServiceA()) | ||
this.registry.registerFactory<ServiceB>("ServiceB", (registry) => new ServiceB(registry.resolve<ServiceA>("ServiceA"))) | ||
})) | ||
.build() | ||
.initialize() | ||
sandbox = document.body.appendChild(document.createElement('div')) | ||
ElementBuilder.get(TestElement).name(tagName).builder( | ||
InversionBuilder.get("serviceA").key("ServiceA"), | ||
InversionBuilder.get("serviceB").key("ServiceB"), | ||
).register() | ||
testElement = sandbox.appendChild(document.createElement(tagName) as TestElement) | ||
|
||
}) | ||
after(async () => { | ||
// @ts-ignore | ||
InversionBuilder.setDefaultContainer(undefined) | ||
container?.dispose() | ||
}) | ||
it("should inject ServiceA", function () { | ||
assert.property(testElement, "serviceA") | ||
assert.strictEqual(testElement.serviceA?.methodA(), "resultA") | ||
}) | ||
it("should inject ServiceB", function () { | ||
assert.property(testElement, "serviceB") | ||
assert.strictEqual(testElement.serviceB?.methodA(), "resultA") | ||
}) | ||
}) | ||
|
||
describe("with a container provider", function () { | ||
let testElement: TestElement | ||
const tagName = "inversion-builder-container-provider" | ||
|
||
class TestElement extends HTMLElement { | ||
serviceA?: ServiceA | ||
serviceB?: ServiceB | ||
} | ||
|
||
before(async () => { | ||
container = await ContainerBuilder.get() | ||
.module(OnlyConfigureModule.create(async function () { | ||
this.registry.registerFactory<ServiceA>("ServiceA", () => new ServiceA()) | ||
this.registry.registerFactory<ServiceB>("ServiceB", (registry) => new ServiceB(registry.resolve<ServiceA>("ServiceA"))) | ||
})) | ||
.build() | ||
.initialize() | ||
sandbox = document.body.appendChild(document.createElement('div')) | ||
ElementBuilder.get(TestElement).name(tagName).builder( | ||
InversionBuilder.get("serviceA").key("ServiceA").provider(() => container), | ||
InversionBuilder.get("serviceB").key("ServiceB").provider(() => container), | ||
).register() | ||
testElement = sandbox.appendChild(document.createElement(tagName) as TestElement) | ||
|
||
}) | ||
after(async () => { | ||
// @ts-ignore | ||
InversionBuilder.setDefaultContainer(undefined) | ||
container?.dispose() | ||
}) | ||
it("should inject ServiceA", function () { | ||
assert.property(testElement, "serviceA") | ||
assert.strictEqual(testElement.serviceA?.methodA(), "resultA") | ||
}) | ||
it("should inject ServiceB", function () { | ||
assert.property(testElement, "serviceB") | ||
assert.strictEqual(testElement.serviceB?.methodA(), "resultA") | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import {Builder, CustomElementConstructor, ElementBuilder, HooksRegistration} from "@tmorin/ceb-core"; | ||
import {Container, RegistryKey} from "@tmorin/ceb-inversion"; | ||
|
||
/** | ||
* Factory of a bus. | ||
*/ | ||
export interface ContainerProvider { | ||
/** | ||
* @return the bus | ||
*/ | ||
(): Container | ||
} | ||
|
||
/** | ||
* The builder injects an entry from a container into a Custom Element. | ||
*/ | ||
export class InversionBuilder<E extends HTMLElement> implements Builder<E> { | ||
|
||
private static DEFAULT_CONTAINER: Container | ||
|
||
/** | ||
* Set the default {@link Container}. | ||
* @param container the container | ||
* @internal | ||
*/ | ||
static setDefaultContainer(container: Container) { | ||
InversionBuilder.DEFAULT_CONTAINER = container | ||
} | ||
|
||
private constructor( | ||
private _propName?: string, | ||
private _key?: RegistryKey, | ||
private _provider?: ContainerProvider, | ||
) { | ||
} | ||
|
||
/** | ||
* Provides a fresh builder. | ||
* @param propName the property name | ||
* @template E the type of the Custom Element | ||
*/ | ||
static get<E extends HTMLElement>(propName?: string) { | ||
return new InversionBuilder<E>(propName) | ||
} | ||
|
||
/** | ||
* Set the registry key. | ||
* @param key the registry key | ||
*/ | ||
key(key: RegistryKey): InversionBuilder<E> { | ||
this._key = key | ||
return this | ||
} | ||
|
||
/** | ||
* Set the Container provider. | ||
* @param provider the provider | ||
*/ | ||
provider(provider: ContainerProvider): InversionBuilder<E> { | ||
this._provider = provider | ||
return this | ||
} | ||
|
||
/** | ||
* Decorates the property of the bus. | ||
*/ | ||
decorate(): PropertyDecorator { | ||
return (target, propName) => { | ||
this._propName = propName.toString() | ||
if (!this._key) { | ||
this._key = this._propName | ||
} | ||
const id = `bus-field-inversion-${this._propName}` | ||
ElementBuilder.getOrSet(target, this, id) | ||
} | ||
} | ||
|
||
build(Constructor: CustomElementConstructor<E>, hooks: HooksRegistration<E>): void { | ||
if (!this._propName) { | ||
throw new TypeError("InversionBuilder - the property name is missing") | ||
} | ||
if (!this._key) { | ||
throw new TypeError("InversionBuilder - the registry key is missing") | ||
} | ||
const _propName = this._propName | ||
const _key = this._key | ||
const _provider = this._provider | ||
hooks.before('constructorCallback', el => { | ||
let _container: Container | ||
if (_provider) { | ||
_container = _provider() | ||
} else if (InversionBuilder.DEFAULT_CONTAINER) { | ||
_container = InversionBuilder.DEFAULT_CONTAINER | ||
} else { | ||
throw new TypeError("InversionBuilder - unable to resolve a Container") | ||
} | ||
Object.defineProperty(el, _propName, { | ||
value: _container.registry.resolve(_key), | ||
configurable: false, | ||
writable: false, | ||
enumerable: true | ||
}) | ||
}) | ||
} | ||
|
||
} |
Empty file.
Oops, something went wrong.