From 7f49f2c6a60a68d609243f77c5c01ba1047deef2 Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Mon, 18 May 2020 13:18:06 +0200 Subject: [PATCH] feat: manager to support single instances with nested npm installations BREAKING CHANGE: add singleton-manager --- .gitignore | 2 + packages/singleton-manager/README.md | 302 ++++++++++++++++++ .../singleton-manager/demo/fail/demo-app.js | 91 ++++++ .../singleton-manager/demo/fail/index.html | 11 + .../demo/fail/node_modules/overlays/index.js | 29 ++ .../fail/node_modules/overlays/instance.js | 3 + .../fail/node_modules/overlays/package.json | 4 + .../fail/node_modules/page-a/package.json | 7 + .../demo/fail/node_modules/page-a/page-a.js | 34 ++ .../page-b/node_modules/overlays/index.js | 29 ++ .../page-b/node_modules/overlays/instance.js | 3 + .../page-b/node_modules/overlays/package.json | 4 + .../fail/node_modules/page-b/package.json | 7 + .../demo/fail/node_modules/page-b/page-b.js | 34 ++ .../singleton-manager/demo/fail/package.json | 8 + .../singleton-manager/demo/fail/server.js | 6 + .../demo/singleton-complex/demo-app.js | 93 ++++++ .../demo/singleton-complex/index.html | 11 + .../node_modules/overlays/index.js | 29 ++ .../node_modules/overlays/instance.js | 4 + .../node_modules/overlays/package.json | 4 + .../node_modules/page-a/package.json | 7 + .../node_modules/page-a/page-a.js | 35 ++ .../page-b/node_modules/overlays/index.js | 29 ++ .../page-b/node_modules/overlays/instance.js | 4 + .../page-b/node_modules/overlays/package.json | 4 + .../node_modules/page-b/package.json | 7 + .../node_modules/page-b/page-b.js | 49 +++ .../singleton-complex/overlayCompatibility.js | 64 ++++ .../demo/singleton-complex/package.json | 8 + .../demo/singleton-complex/server.js | 6 + .../demo/singleton/demo-app.js | 93 ++++++ .../demo/singleton/index.html | 11 + .../singleton/node_modules/overlays/index.js | 29 ++ .../node_modules/overlays/instance.js | 4 + .../node_modules/overlays/package.json | 4 + .../node_modules/page-a/package.json | 7 + .../singleton/node_modules/page-a/page-a.js | 34 ++ .../page-b/node_modules/overlays/index.js | 29 ++ .../page-b/node_modules/overlays/instance.js | 4 + .../page-b/node_modules/overlays/package.json | 4 + .../node_modules/page-b/package.json | 7 + .../singleton/node_modules/page-b/page-b.js | 34 ++ .../demo/singleton/overlayCompatibility.js | 24 ++ .../demo/singleton/package.json | 8 + .../demo/singleton/server.js | 6 + packages/singleton-manager/index.js | 5 + packages/singleton-manager/package.json | 38 +++ .../src/SingletonManagerClass.js | 20 ++ .../test/SingletonManagerClass.test.js | 31 ++ .../test/singleton-demo.test.js | 15 + 51 files changed, 1335 insertions(+) create mode 100644 packages/singleton-manager/README.md create mode 100644 packages/singleton-manager/demo/fail/demo-app.js create mode 100644 packages/singleton-manager/demo/fail/index.html create mode 100644 packages/singleton-manager/demo/fail/node_modules/overlays/index.js create mode 100644 packages/singleton-manager/demo/fail/node_modules/overlays/instance.js create mode 100644 packages/singleton-manager/demo/fail/node_modules/overlays/package.json create mode 100644 packages/singleton-manager/demo/fail/node_modules/page-a/package.json create mode 100644 packages/singleton-manager/demo/fail/node_modules/page-a/page-a.js create mode 100644 packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/index.js create mode 100644 packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/instance.js create mode 100644 packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/package.json create mode 100644 packages/singleton-manager/demo/fail/node_modules/page-b/package.json create mode 100644 packages/singleton-manager/demo/fail/node_modules/page-b/page-b.js create mode 100644 packages/singleton-manager/demo/fail/package.json create mode 100644 packages/singleton-manager/demo/fail/server.js create mode 100644 packages/singleton-manager/demo/singleton-complex/demo-app.js create mode 100644 packages/singleton-manager/demo/singleton-complex/index.html create mode 100644 packages/singleton-manager/demo/singleton-complex/node_modules/overlays/index.js create mode 100644 packages/singleton-manager/demo/singleton-complex/node_modules/overlays/instance.js create mode 100644 packages/singleton-manager/demo/singleton-complex/node_modules/overlays/package.json create mode 100644 packages/singleton-manager/demo/singleton-complex/node_modules/page-a/package.json create mode 100644 packages/singleton-manager/demo/singleton-complex/node_modules/page-a/page-a.js create mode 100644 packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/index.js create mode 100644 packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/instance.js create mode 100644 packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/package.json create mode 100644 packages/singleton-manager/demo/singleton-complex/node_modules/page-b/package.json create mode 100644 packages/singleton-manager/demo/singleton-complex/node_modules/page-b/page-b.js create mode 100644 packages/singleton-manager/demo/singleton-complex/overlayCompatibility.js create mode 100644 packages/singleton-manager/demo/singleton-complex/package.json create mode 100644 packages/singleton-manager/demo/singleton-complex/server.js create mode 100644 packages/singleton-manager/demo/singleton/demo-app.js create mode 100644 packages/singleton-manager/demo/singleton/index.html create mode 100644 packages/singleton-manager/demo/singleton/node_modules/overlays/index.js create mode 100644 packages/singleton-manager/demo/singleton/node_modules/overlays/instance.js create mode 100644 packages/singleton-manager/demo/singleton/node_modules/overlays/package.json create mode 100644 packages/singleton-manager/demo/singleton/node_modules/page-a/package.json create mode 100644 packages/singleton-manager/demo/singleton/node_modules/page-a/page-a.js create mode 100644 packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/index.js create mode 100644 packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/instance.js create mode 100644 packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/package.json create mode 100644 packages/singleton-manager/demo/singleton/node_modules/page-b/package.json create mode 100644 packages/singleton-manager/demo/singleton/node_modules/page-b/page-b.js create mode 100644 packages/singleton-manager/demo/singleton/overlayCompatibility.js create mode 100644 packages/singleton-manager/demo/singleton/package.json create mode 100644 packages/singleton-manager/demo/singleton/server.js create mode 100644 packages/singleton-manager/index.js create mode 100644 packages/singleton-manager/package.json create mode 100644 packages/singleton-manager/src/SingletonManagerClass.js create mode 100644 packages/singleton-manager/test/SingletonManagerClass.test.js create mode 100644 packages/singleton-manager/test/singleton-demo.test.js diff --git a/.gitignore b/.gitignore index 79e07046d..1df5c5078 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ local.log ## browserstack browserstack.err + +!packages/singleton-manager/demo/**/node_modules diff --git a/packages/singleton-manager/README.md b/packages/singleton-manager/README.md new file mode 100644 index 000000000..681132119 --- /dev/null +++ b/packages/singleton-manager/README.md @@ -0,0 +1,302 @@ +# Singleton Manager + +A singleton manager provides a way to make sure a singleton instance loaded from multiple file locations stays a singleton. +Primarily useful if two major version of a package with a singleton is used. + +## How to use + +### Installation + +```sh +npm i --save singleton-manager +``` + +⚠️ You need to make SURE that only ONE version of `singleton-manager` is installed. For how see [Non Goals](#non-goals). + +### Example Singleton Users + +Use the same singleton for both versions (as we don't use any of the breaking features) + +```js +// managed-my-singleton.js +import { singletonManager } from 'singleton-manager'; +import { mySingleton } from 'my-singleton'; // is available as 1.x and 2.x via node resolution + +singletonManager.set('my-singleton::index.js::1.x', mySingleton); +singletonManager.set('my-singleton::index.js::2.x', mySingleton); +``` + +OR create a special compatible version of the singleton + +```js +// managed-my-singleton.js +import { singletonManager } from 'singleton-manager'; +import { MySingleton } from 'my-singleton'; // is available as 1.x and 2.x via node resolution + +class CompatibleSingleton extends MySingleton { + // add forward or backward compatibility code +} +const compatibleSingleton = new CompatibleSingleton(); + +singletonManager.set('my-singleton::index.js::1.x', compatibleSingleton); +singletonManager.set('my-singleton::index.js::2.x', compatibleSingleton); +``` + +AND in you App then you need to load the above code BEFORE loading the singleton or any feature using it. + +```js +import './managed-my-singleton.js'; + +import { mySingleton } from 'my-singleton'; // will no always be what is "defined" in managed-my-singleton.js +``` + +### Warning + +Overriding version is an App level concern hence components or "features" are not allowed to use it. +If you try to call multiple times for the same key then it will throw an error. + +```js +// on app level +singletonManager.set('my-singleton/index.js::1.x', compatibleSingleton); + +// somewhere in a dependency +singletonManager.set('my-singleton/index.js::1.x', compatibleSingleton); + +// will throw an error that it is already defined +``` + +### Example Singleton Maintainers + +If you are a maintainer of a singleton be sure to check if a singleton manager version is set. +If that is the case return it instead of your default instance. + +It could look something like this: + +```js +// my-singleton.js +import { singletonManager } from 'singleton-manager'; +import { MySingleton } from './src/MySingleton.js'; + +export const overlays = + singletonManager.get('my-singleton/my-singleton.js::1.x') || new MySingleton(); +``` + +## Convention Singleton Key + +The key for a singleton needs to be "unique" for the package. +Hence the following convention helps maintaining this. + +As a key use the `::::`. + +Examples Do: + +- `overlays::overlays::1.x` - instance created in index.js +- `@scope/overlays::overlays::1.x` - with scope +- `overlays::overlays::1.x` - version 1.x.x (> 1.0.0 you do 1.x, 2.x) +- `overlays::overlays::2.x` - version 2.x.x (> 1.0.0 you do 1.x, 2.x) +- `overlays::overlays::0.10.x` - version 0.10.x (< 1.0.0 you do 0.1.x, 0.2.x) + +Examples Don't: + +- `overlays` - too generic +- `overlays::overlays` - you should include a version +- `overlays::1.x` - you should include a package name & unique var +- `./index.js::1.x` - it should start with a package name + +--- + +## Singleton Manager Rationale + +We have an app with 2 pages. + +- page-a uses overlays 1.x +- page-b uses overlays 2.x (gets installed nested) + +```txt +my-app (node_modules) +├── overlays (1.x) +├── page-a +│ └── page-a.js +└── page-b + ├── node_modules + │ └── overlays (2.x) + └── page-b.js +``` + +The tough part in this case is the OverlaysManager within the overlays package as it needs to be a singleton. + +It starts of simplified like this + +```js +export class OverlaysManager { + name = 'OverlayManager 1.x'; + blockBody = false; + constructor() { + this._setupBlocker(); + } + _setupBlocker() { + /* ... */ + } + block() { + this.blockBody = true; // ... + } + + unBlock() { + this.blockBody = false; // ... + } +} +``` + +## Example A (fail) + +See it "fail" e.g. 2 separate OverlaysManager are at work and are "fighting" over the way to block the body. + +```bash +npm run start:fail +``` + +Steps to reproduce: + +1. Page A click on block +2. Page B => "Blocked: false" (even when hitting the refresh button) + +See [the code](./demo/fail/demo-app.js). + +--- + +## Example B (singleton manager) + +The breaking change in `OverlayManager` was renaming of 2 function (which has been deprecated before). + +- `block()` => `blockingBody()` +- `unBlock()` => `unBlockingBody()` + +knowing that we can create a Manager that is compatible with both via + +```js +import { OverlaysManager } from 'overlays'; + +class CompatibleOverlaysManager extends OverlaysManager { + blockingBody() { + this.block(); + } + unBlockingBody() { + this.unBlock(); + } +} +``` + +all that is left is a to "override" the default instance of the "users" + +```js +import { singletonManager } from 'singleton-manager'; + +const compatibleOverlaysManager = new CompatibleOverlaysManager(); +singletonManager.set('overlays::overlays::1.x', compatibleOverlaysManager); +singletonManager.set('overlays::overlays::2.x', compatibleOverlaysManager); +``` + +See it in action + +```bash +npm run start:singleton +``` + +and [the code](./demo/singleton/demo-app.js). + +--- + +## Example C (singleton and complex patching on app level) + +The breaking change in `OverlayManager` was converting a property to a function and a rename of a function. + +- `blockBody` => `_blockBody` +- `block()` => `blockBody()` +- `unBlock()` => `unBlockBody()` + +e.g. what is impossible to make compatible with a single instance is to have `blockBody` act as a property for 1.x and as a function `blockBody()` for 2.x. + +So how do we solve it then? + +We will make 2 separate instances of the `OverlayManager`. + +```js +compatibleManager1 = new CompatibleManager1(); // 1.x +compatibleManager2 = new CompatibleManager2(); // 2.x +console.log(typeof compatibleManager1.blockBody); // Boolean +console.log(typeof compatibleManager2.blockBody); // Function + +// and override +singletonManager.set('overlays::overlays::1.x', compatibleManager1); +singletonManager.set('overlays::overlays::2.x', compatibleManager2); +``` + +and they are "compatible" to each other because they sync the important data to each other. +e.g. even though there are 2 instances there is only `one` dom element inserted which both can write to. +When syncing data only the initiator will update the dom. +This makes sure even though functions and data is separate it will be always consistent. + +See it in action + +```bash +npm run start:singleton-complex +``` + +and [the code](./demo/singleton-complex/demo-app.js). + +--- + +## How does it work? + +As a user you can override what the import of `overlays/instance.js` provides. +You do this via a singletonManager and a "magic" string. + +- Reason be that you can target ranges of versions + +```js +singletonManager.set('overlays::overlays::1.x', compatibleManager1); +singletonManager.set('overlays::overlays::2.x', compatibleManager2); +``` + +### Potential Improvements + +Potentially we could have "range", "exacts version" and symbol for unique filename. +So you can override with increasing specificity. +If you have a use case for that please open an issue. + +## Non Goals + +Making sure that there are only 2 major versions of a specific packages. +npm is not meant to handle it - and it never will + +```txt +my-app +├─┬ feat-a@x +│ └── foo@2.x +├─┬ feat-a@x +│ └── foo@2.x +└── foo@1.x +``` + +dedupe works by moving dependencies up the tree + +```txt +// this app +my-app +my-app/node_modules/feat-a/node_modules/foo +my-app/node_modules/foo + +// can become if versions match +my-app +my-app/node_modules/foo +``` + +in there `feat-a` will grab the version of it's "parent" because of the node resolution system. +If however the versions do not match or there is no "common" folder to move it up to then it needs to be "duplicated" by npm/yarn. + +Only by using a more controlled way like + +- [import-maps](https://github.com/WICG/import-maps) +- [yarn resolutions](https://classic.yarnpkg.com/en/docs/selective-version-resolutions/) + +you can "hard" code it to the same versions. diff --git a/packages/singleton-manager/demo/fail/demo-app.js b/packages/singleton-manager/demo/fail/demo-app.js new file mode 100644 index 000000000..f2caa2baf --- /dev/null +++ b/packages/singleton-manager/demo/fail/demo-app.js @@ -0,0 +1,91 @@ +import { LitElement, css, html } from 'lit-element'; + +import 'page-a/page-a.js'; +import 'page-b/page-b.js'; + +class DemoApp extends LitElement { + constructor() { + super(); + this.page = 'A'; + } + + static get properties() { + return { + page: { type: String }, + }; + } + + static get styles() { + return css` + :host { + display: block; + max-width: 680px; + margin: 0 auto; + } + + nav { + padding: 0 10px 10px 10px; + } + + button { + border: none; + padding: 1rem 2rem; + background: #0069ed; + color: #fff; + font-size: 1rem; + cursor: pointer; + text-align: center; + transition: background 250ms ease-in-out, transform 150ms ease; + } + + button:hover, + button:focus { + background: #0053ba; + } + + button:focus { + outline: 1px solid #fff; + outline-offset: -4px; + } + + button:active { + transform: scale(0.99); + } + + button.active { + background: #33a43f; + } + + h1 { + text-align: center; + } + `; + } + + render() { + return html` +

Demo App

+ + ${this.page === 'A' ? html` ` : html` `} + `; + } +} + +customElements.define('demo-app', DemoApp); diff --git a/packages/singleton-manager/demo/fail/index.html b/packages/singleton-manager/demo/fail/index.html new file mode 100644 index 000000000..8f3b86abb --- /dev/null +++ b/packages/singleton-manager/demo/fail/index.html @@ -0,0 +1,11 @@ + + + + + + + Loading App... + + + + diff --git a/packages/singleton-manager/demo/fail/node_modules/overlays/index.js b/packages/singleton-manager/demo/fail/node_modules/overlays/index.js new file mode 100644 index 000000000..6e7a29fdd --- /dev/null +++ b/packages/singleton-manager/demo/fail/node_modules/overlays/index.js @@ -0,0 +1,29 @@ +export class OverlaysManager { + name = 'OverlayManager 1.x'; + + blockBody = false; + + constructor() { + this._setupBlocker(); + } + + _setupBlocker() { + const blocker = document.createElement('div'); + blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;'); + blocker.innerText = `Blocker for ${this.name}`; + + document.body.appendChild(blocker); + + this.blocker = blocker; + } + + block() { + this.blockBody = true; + this.blocker.style.backgroundColor = '#ff6161'; + } + + unBlock() { + this.blockBody = false; + this.blocker.style.backgroundColor = 'transparent'; + } +} diff --git a/packages/singleton-manager/demo/fail/node_modules/overlays/instance.js b/packages/singleton-manager/demo/fail/node_modules/overlays/instance.js new file mode 100644 index 000000000..f73ca3b47 --- /dev/null +++ b/packages/singleton-manager/demo/fail/node_modules/overlays/instance.js @@ -0,0 +1,3 @@ +import { OverlaysManager } from './index.js'; + +export const overlays = new OverlaysManager(); diff --git a/packages/singleton-manager/demo/fail/node_modules/overlays/package.json b/packages/singleton-manager/demo/fail/node_modules/overlays/package.json new file mode 100644 index 000000000..0cbd136ec --- /dev/null +++ b/packages/singleton-manager/demo/fail/node_modules/overlays/package.json @@ -0,0 +1,4 @@ +{ + "name": "overlays", + "version": "1.0.0" +} diff --git a/packages/singleton-manager/demo/fail/node_modules/page-a/package.json b/packages/singleton-manager/demo/fail/node_modules/page-a/package.json new file mode 100644 index 000000000..2c77c4dee --- /dev/null +++ b/packages/singleton-manager/demo/fail/node_modules/page-a/package.json @@ -0,0 +1,7 @@ +{ + "name": "page-a", + "version": "1.0.0", + "dependencies": { + "overlays": "^1.0.0" + } +} diff --git a/packages/singleton-manager/demo/fail/node_modules/page-a/page-a.js b/packages/singleton-manager/demo/fail/node_modules/page-a/page-a.js new file mode 100644 index 000000000..21fd3dde5 --- /dev/null +++ b/packages/singleton-manager/demo/fail/node_modules/page-a/page-a.js @@ -0,0 +1,34 @@ +import { LitElement, html, css } from 'lit-element'; +import { overlays } from 'overlays/instance.js'; + +export class PageA extends LitElement { + static get styles() { + return css` + :host { + display: block; + padding: 10px; + border: 2px solid #ccc; + } + `; + } + + render() { + return html` +

I am page A

+

Overlays Status:

+

Name: ${overlays.name}

+

Blocked: ${overlays.blockBody}

+ + + + `; + } +} + +customElements.define('page-a', PageA); diff --git a/packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/index.js b/packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/index.js new file mode 100644 index 000000000..5d97b1522 --- /dev/null +++ b/packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/index.js @@ -0,0 +1,29 @@ +export class OverlaysManager { + name = 'OverlayManager 2.x'; + + _blockBody = false; + + constructor() { + this._setupBlocker(); + } + + _setupBlocker() { + const blocker = document.createElement('div'); + blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;'); + blocker.innerText = `Blocker for ${this.name}`; + + document.body.appendChild(blocker); + + this.blocker = blocker; + } + + blockBody() { + this._blockBody = true; + this.blocker.style.backgroundColor = '#ff6161'; + } + + unBlockBody() { + this._blockBody = false; + this.blocker.style.backgroundColor = 'transparent'; + } +} diff --git a/packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/instance.js b/packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/instance.js new file mode 100644 index 000000000..f73ca3b47 --- /dev/null +++ b/packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/instance.js @@ -0,0 +1,3 @@ +import { OverlaysManager } from './index.js'; + +export const overlays = new OverlaysManager(); diff --git a/packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/package.json b/packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/package.json new file mode 100644 index 000000000..5e83291f8 --- /dev/null +++ b/packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/package.json @@ -0,0 +1,4 @@ +{ + "name": "overlays", + "version": "2.0.0" +} diff --git a/packages/singleton-manager/demo/fail/node_modules/page-b/package.json b/packages/singleton-manager/demo/fail/node_modules/page-b/package.json new file mode 100644 index 000000000..cd0fdbef4 --- /dev/null +++ b/packages/singleton-manager/demo/fail/node_modules/page-b/package.json @@ -0,0 +1,7 @@ +{ + "name": "page-b", + "version": "1.0.0", + "dependencies": { + "overlays": "^2.0.0" + } +} diff --git a/packages/singleton-manager/demo/fail/node_modules/page-b/page-b.js b/packages/singleton-manager/demo/fail/node_modules/page-b/page-b.js new file mode 100644 index 000000000..94a90c0c0 --- /dev/null +++ b/packages/singleton-manager/demo/fail/node_modules/page-b/page-b.js @@ -0,0 +1,34 @@ +import { LitElement, html, css } from 'lit-element'; +import { overlays } from 'overlays/instance.js'; + +export class PageB extends LitElement { + static get styles() { + return css` + :host { + display: block; + padding: 10px; + border: 2px solid #ccc; + } + `; + } + + render() { + return html` +

I am page B

+

Overlays Status:

+

Name: ${overlays.name}

+

Blocked: ${overlays._blockBody}

+ + + + `; + } +} + +customElements.define('page-b', PageB); diff --git a/packages/singleton-manager/demo/fail/package.json b/packages/singleton-manager/demo/fail/package.json new file mode 100644 index 000000000..a973830e0 --- /dev/null +++ b/packages/singleton-manager/demo/fail/package.json @@ -0,0 +1,8 @@ +{ + "name": "fail-demo-app", + "version": "1.0.0", + "dependencies": { + "page-a": "^1.0.0", + "page-b": "^1.0.0" + } +} diff --git a/packages/singleton-manager/demo/fail/server.js b/packages/singleton-manager/demo/fail/server.js new file mode 100644 index 000000000..18b5bf47a --- /dev/null +++ b/packages/singleton-manager/demo/fail/server.js @@ -0,0 +1,6 @@ +module.exports = { + rootDir: '../../', + appIndex: 'packages/singleton-manager/demo/fail/index.html', + nodeResolve: true, + open: true, +}; diff --git a/packages/singleton-manager/demo/singleton-complex/demo-app.js b/packages/singleton-manager/demo/singleton-complex/demo-app.js new file mode 100644 index 000000000..abe524fb3 --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/demo-app.js @@ -0,0 +1,93 @@ +import { LitElement, css, html } from 'lit-element'; + +import './overlayCompatibility.js'; + +import 'page-a/page-a.js'; +import 'page-b/page-b.js'; + +class DemoApp extends LitElement { + constructor() { + super(); + this.page = 'A'; + } + + static get properties() { + return { + page: { type: String }, + }; + } + + static get styles() { + return css` + :host { + display: block; + max-width: 680px; + margin: 0 auto; + } + + nav { + padding: 0 10px 10px 10px; + } + + button { + border: none; + padding: 1rem 2rem; + background: #0069ed; + color: #fff; + font-size: 1rem; + cursor: pointer; + text-align: center; + transition: background 250ms ease-in-out, transform 150ms ease; + } + + button:hover, + button:focus { + background: #0053ba; + } + + button:focus { + outline: 1px solid #fff; + outline-offset: -4px; + } + + button:active { + transform: scale(0.99); + } + + button.active { + background: #33a43f; + } + + h1 { + text-align: center; + } + `; + } + + render() { + return html` +

Demo App

+ + ${this.page === 'A' ? html` ` : html` `} + `; + } +} + +customElements.define('demo-app', DemoApp); diff --git a/packages/singleton-manager/demo/singleton-complex/index.html b/packages/singleton-manager/demo/singleton-complex/index.html new file mode 100644 index 000000000..8f3b86abb --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/index.html @@ -0,0 +1,11 @@ + + + + + + + Loading App... + + + + diff --git a/packages/singleton-manager/demo/singleton-complex/node_modules/overlays/index.js b/packages/singleton-manager/demo/singleton-complex/node_modules/overlays/index.js new file mode 100644 index 000000000..6e7a29fdd --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/node_modules/overlays/index.js @@ -0,0 +1,29 @@ +export class OverlaysManager { + name = 'OverlayManager 1.x'; + + blockBody = false; + + constructor() { + this._setupBlocker(); + } + + _setupBlocker() { + const blocker = document.createElement('div'); + blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;'); + blocker.innerText = `Blocker for ${this.name}`; + + document.body.appendChild(blocker); + + this.blocker = blocker; + } + + block() { + this.blockBody = true; + this.blocker.style.backgroundColor = '#ff6161'; + } + + unBlock() { + this.blockBody = false; + this.blocker.style.backgroundColor = 'transparent'; + } +} diff --git a/packages/singleton-manager/demo/singleton-complex/node_modules/overlays/instance.js b/packages/singleton-manager/demo/singleton-complex/node_modules/overlays/instance.js new file mode 100644 index 000000000..7d3333a5f --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/node_modules/overlays/instance.js @@ -0,0 +1,4 @@ +import { OverlaysManager } from './index.js'; +import { singletonManager } from '../../../../index.js'; + +export const overlays = singletonManager.get('overlays::overlays::1.x') || new OverlaysManager(); diff --git a/packages/singleton-manager/demo/singleton-complex/node_modules/overlays/package.json b/packages/singleton-manager/demo/singleton-complex/node_modules/overlays/package.json new file mode 100644 index 000000000..0cbd136ec --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/node_modules/overlays/package.json @@ -0,0 +1,4 @@ +{ + "name": "overlays", + "version": "1.0.0" +} diff --git a/packages/singleton-manager/demo/singleton-complex/node_modules/page-a/package.json b/packages/singleton-manager/demo/singleton-complex/node_modules/page-a/package.json new file mode 100644 index 000000000..2c77c4dee --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/node_modules/page-a/package.json @@ -0,0 +1,7 @@ +{ + "name": "page-a", + "version": "1.0.0", + "dependencies": { + "overlays": "^1.0.0" + } +} diff --git a/packages/singleton-manager/demo/singleton-complex/node_modules/page-a/page-a.js b/packages/singleton-manager/demo/singleton-complex/node_modules/page-a/page-a.js new file mode 100644 index 000000000..714486f76 --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/node_modules/page-a/page-a.js @@ -0,0 +1,35 @@ +import { LitElement, html, css } from 'lit-element'; +import { overlays } from 'overlays/instance.js'; + +export class PageA extends LitElement { + static get styles() { + return css` + :host { + display: block; + padding: 10px; + border: 2px solid #ccc; + } + `; + } + + render() { + return html` +

I am page A

+

Overlays Status:

+

Name: ${overlays.name}

+

Blocked: ${overlays.blockBody}

+ + + + `; + } +} + + +customElements.define('page-a', PageA); diff --git a/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/index.js b/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/index.js new file mode 100644 index 000000000..5d97b1522 --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/index.js @@ -0,0 +1,29 @@ +export class OverlaysManager { + name = 'OverlayManager 2.x'; + + _blockBody = false; + + constructor() { + this._setupBlocker(); + } + + _setupBlocker() { + const blocker = document.createElement('div'); + blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;'); + blocker.innerText = `Blocker for ${this.name}`; + + document.body.appendChild(blocker); + + this.blocker = blocker; + } + + blockBody() { + this._blockBody = true; + this.blocker.style.backgroundColor = '#ff6161'; + } + + unBlockBody() { + this._blockBody = false; + this.blocker.style.backgroundColor = 'transparent'; + } +} diff --git a/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/instance.js b/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/instance.js new file mode 100644 index 000000000..50b46d1a1 --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/instance.js @@ -0,0 +1,4 @@ +import { OverlaysManager } from './index.js'; +import { singletonManager } from '../../../../../../index.js'; + +export const overlays = singletonManager.get('overlays::overlays::2.x') || new OverlaysManager(); diff --git a/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/package.json b/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/package.json new file mode 100644 index 000000000..5e83291f8 --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/package.json @@ -0,0 +1,4 @@ +{ + "name": "overlays", + "version": "2.0.0" +} diff --git a/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/package.json b/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/package.json new file mode 100644 index 000000000..cd0fdbef4 --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/package.json @@ -0,0 +1,7 @@ +{ + "name": "page-b", + "version": "1.0.0", + "dependencies": { + "overlays": "^2.0.0" + } +} diff --git a/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/page-b.js b/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/page-b.js new file mode 100644 index 000000000..bc65fde08 --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/node_modules/page-b/page-b.js @@ -0,0 +1,49 @@ +import { LitElement, html, css } from 'lit-element'; +import { overlays } from 'overlays/instance.js'; + +export class PageB extends LitElement { + getInstance(sym, fallback) { + const ev = new CustomEvent('request-instance', { + detail: { key: sym }, + bubbles: true, + cancelable: true, + composed: true, + }); + this.dispatchEvent(ev); + return ev.detail.instance || fallback(); + } + + connectedCallback() { + super.connectedCallback(); + } + + static get styles() { + return css` + :host { + display: block; + padding: 10px; + border: 2px solid #ccc; + } + `; + } + + render() { + return html` +

I am page B

+

Overlays Status:

+

Name: ${overlays.name}

+

Blocked: ${overlays._blockBody}

+ + + + `; + } +} + +customElements.define('page-b', PageB); diff --git a/packages/singleton-manager/demo/singleton-complex/overlayCompatibility.js b/packages/singleton-manager/demo/singleton-complex/overlayCompatibility.js new file mode 100644 index 000000000..62e7e69d6 --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/overlayCompatibility.js @@ -0,0 +1,64 @@ +import { OverlaysManager } from 'overlays'; +import { singletonManager } from '../../index.js'; +import { OverlaysManager as OverlaysManager2 } from './node_modules/page-b/node_modules/overlays/index.js'; + +let compatibleManager1; +let compatibleManager2; + +const blocker = document.createElement('div'); +blocker.setAttribute( + 'style', + 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;', +); +blocker.innerText = `Shared Blocker for App`; +document.body.appendChild(blocker); + +class CompatibleManager1 extends OverlaysManager { + name = 'Compatible1 from App'; + + block(sync = true) { + super.block(); + if (sync) { + compatibleManager2.blockBody(false); + } + } + + unBlock(sync = true) { + super.unBlock(); + if (sync) { + compatibleManager2.unBlockBody(false); + } + } + + _setupBlocker() { + this.blocker = blocker; + } +} + +class CompatibleManager2 extends OverlaysManager2 { + name = 'Compatible2 from App'; + + blockBody(sync = true) { + super.blockBody(); + if (sync) { + compatibleManager1.block(); + } + } + + unBlockBody(sync = true) { + super.unBlockBody(); + if (sync) { + compatibleManager1.unBlock(); + } + } + + _setupBlocker() { + this.blocker = blocker; + } +} + +compatibleManager1 = new CompatibleManager1(); +compatibleManager2 = new CompatibleManager2(); + +singletonManager.set('overlays::overlays::1.x', compatibleManager1); +singletonManager.set('overlays::overlays::2.x', compatibleManager2); diff --git a/packages/singleton-manager/demo/singleton-complex/package.json b/packages/singleton-manager/demo/singleton-complex/package.json new file mode 100644 index 000000000..a973830e0 --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/package.json @@ -0,0 +1,8 @@ +{ + "name": "fail-demo-app", + "version": "1.0.0", + "dependencies": { + "page-a": "^1.0.0", + "page-b": "^1.0.0" + } +} diff --git a/packages/singleton-manager/demo/singleton-complex/server.js b/packages/singleton-manager/demo/singleton-complex/server.js new file mode 100644 index 000000000..788b87ad1 --- /dev/null +++ b/packages/singleton-manager/demo/singleton-complex/server.js @@ -0,0 +1,6 @@ +module.exports = { + rootDir: '../../', + appIndex: 'packages/singleton-manager/demo/singleton-complex/index.html', + nodeResolve: true, + open: true, +}; diff --git a/packages/singleton-manager/demo/singleton/demo-app.js b/packages/singleton-manager/demo/singleton/demo-app.js new file mode 100644 index 000000000..abe524fb3 --- /dev/null +++ b/packages/singleton-manager/demo/singleton/demo-app.js @@ -0,0 +1,93 @@ +import { LitElement, css, html } from 'lit-element'; + +import './overlayCompatibility.js'; + +import 'page-a/page-a.js'; +import 'page-b/page-b.js'; + +class DemoApp extends LitElement { + constructor() { + super(); + this.page = 'A'; + } + + static get properties() { + return { + page: { type: String }, + }; + } + + static get styles() { + return css` + :host { + display: block; + max-width: 680px; + margin: 0 auto; + } + + nav { + padding: 0 10px 10px 10px; + } + + button { + border: none; + padding: 1rem 2rem; + background: #0069ed; + color: #fff; + font-size: 1rem; + cursor: pointer; + text-align: center; + transition: background 250ms ease-in-out, transform 150ms ease; + } + + button:hover, + button:focus { + background: #0053ba; + } + + button:focus { + outline: 1px solid #fff; + outline-offset: -4px; + } + + button:active { + transform: scale(0.99); + } + + button.active { + background: #33a43f; + } + + h1 { + text-align: center; + } + `; + } + + render() { + return html` +

Demo App

+ + ${this.page === 'A' ? html` ` : html` `} + `; + } +} + +customElements.define('demo-app', DemoApp); diff --git a/packages/singleton-manager/demo/singleton/index.html b/packages/singleton-manager/demo/singleton/index.html new file mode 100644 index 000000000..8f3b86abb --- /dev/null +++ b/packages/singleton-manager/demo/singleton/index.html @@ -0,0 +1,11 @@ + + + + + + + Loading App... + + + + diff --git a/packages/singleton-manager/demo/singleton/node_modules/overlays/index.js b/packages/singleton-manager/demo/singleton/node_modules/overlays/index.js new file mode 100644 index 000000000..6e7a29fdd --- /dev/null +++ b/packages/singleton-manager/demo/singleton/node_modules/overlays/index.js @@ -0,0 +1,29 @@ +export class OverlaysManager { + name = 'OverlayManager 1.x'; + + blockBody = false; + + constructor() { + this._setupBlocker(); + } + + _setupBlocker() { + const blocker = document.createElement('div'); + blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;'); + blocker.innerText = `Blocker for ${this.name}`; + + document.body.appendChild(blocker); + + this.blocker = blocker; + } + + block() { + this.blockBody = true; + this.blocker.style.backgroundColor = '#ff6161'; + } + + unBlock() { + this.blockBody = false; + this.blocker.style.backgroundColor = 'transparent'; + } +} diff --git a/packages/singleton-manager/demo/singleton/node_modules/overlays/instance.js b/packages/singleton-manager/demo/singleton/node_modules/overlays/instance.js new file mode 100644 index 000000000..7d3333a5f --- /dev/null +++ b/packages/singleton-manager/demo/singleton/node_modules/overlays/instance.js @@ -0,0 +1,4 @@ +import { OverlaysManager } from './index.js'; +import { singletonManager } from '../../../../index.js'; + +export const overlays = singletonManager.get('overlays::overlays::1.x') || new OverlaysManager(); diff --git a/packages/singleton-manager/demo/singleton/node_modules/overlays/package.json b/packages/singleton-manager/demo/singleton/node_modules/overlays/package.json new file mode 100644 index 000000000..0cbd136ec --- /dev/null +++ b/packages/singleton-manager/demo/singleton/node_modules/overlays/package.json @@ -0,0 +1,4 @@ +{ + "name": "overlays", + "version": "1.0.0" +} diff --git a/packages/singleton-manager/demo/singleton/node_modules/page-a/package.json b/packages/singleton-manager/demo/singleton/node_modules/page-a/package.json new file mode 100644 index 000000000..2c77c4dee --- /dev/null +++ b/packages/singleton-manager/demo/singleton/node_modules/page-a/package.json @@ -0,0 +1,7 @@ +{ + "name": "page-a", + "version": "1.0.0", + "dependencies": { + "overlays": "^1.0.0" + } +} diff --git a/packages/singleton-manager/demo/singleton/node_modules/page-a/page-a.js b/packages/singleton-manager/demo/singleton/node_modules/page-a/page-a.js new file mode 100644 index 000000000..21fd3dde5 --- /dev/null +++ b/packages/singleton-manager/demo/singleton/node_modules/page-a/page-a.js @@ -0,0 +1,34 @@ +import { LitElement, html, css } from 'lit-element'; +import { overlays } from 'overlays/instance.js'; + +export class PageA extends LitElement { + static get styles() { + return css` + :host { + display: block; + padding: 10px; + border: 2px solid #ccc; + } + `; + } + + render() { + return html` +

I am page A

+

Overlays Status:

+

Name: ${overlays.name}

+

Blocked: ${overlays.blockBody}

+ + + + `; + } +} + +customElements.define('page-a', PageA); diff --git a/packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/index.js b/packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/index.js new file mode 100644 index 000000000..5df4fe7d3 --- /dev/null +++ b/packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/index.js @@ -0,0 +1,29 @@ +export class OverlaysManager { + name = 'OverlayManager 2.x'; + + blockBody = false; + + constructor() { + this._setupBlocker(); + } + + _setupBlocker() { + const blocker = document.createElement('div'); + blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;'); + blocker.innerText = `Blocker for ${this.name}`; + + document.body.appendChild(blocker); + + this.blocker = blocker; + } + + blockingBody() { + this.blockBody = true; + this.blocker.style.backgroundColor = '#ff6161'; + } + + unBlockingBody() { + this.blockBody = false; + this.blocker.style.backgroundColor = 'transparent'; + } +} diff --git a/packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/instance.js b/packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/instance.js new file mode 100644 index 000000000..50b46d1a1 --- /dev/null +++ b/packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/instance.js @@ -0,0 +1,4 @@ +import { OverlaysManager } from './index.js'; +import { singletonManager } from '../../../../../../index.js'; + +export const overlays = singletonManager.get('overlays::overlays::2.x') || new OverlaysManager(); diff --git a/packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/package.json b/packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/package.json new file mode 100644 index 000000000..5e83291f8 --- /dev/null +++ b/packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/package.json @@ -0,0 +1,4 @@ +{ + "name": "overlays", + "version": "2.0.0" +} diff --git a/packages/singleton-manager/demo/singleton/node_modules/page-b/package.json b/packages/singleton-manager/demo/singleton/node_modules/page-b/package.json new file mode 100644 index 000000000..cd0fdbef4 --- /dev/null +++ b/packages/singleton-manager/demo/singleton/node_modules/page-b/package.json @@ -0,0 +1,7 @@ +{ + "name": "page-b", + "version": "1.0.0", + "dependencies": { + "overlays": "^2.0.0" + } +} diff --git a/packages/singleton-manager/demo/singleton/node_modules/page-b/page-b.js b/packages/singleton-manager/demo/singleton/node_modules/page-b/page-b.js new file mode 100644 index 000000000..41fa08ad4 --- /dev/null +++ b/packages/singleton-manager/demo/singleton/node_modules/page-b/page-b.js @@ -0,0 +1,34 @@ +import { LitElement, html, css } from 'lit-element'; +import { overlays } from 'overlays/instance.js'; + +export class PageB extends LitElement { + static get styles() { + return css` + :host { + display: block; + padding: 10px; + border: 2px solid #ccc; + } + `; + } + + render() { + return html` +

I am page B

+

Overlays Status:

+

Name: ${overlays.name}

+

Blocked: ${overlays.blockBody}

+ + + + `; + } +} + +customElements.define('page-b', PageB); diff --git a/packages/singleton-manager/demo/singleton/overlayCompatibility.js b/packages/singleton-manager/demo/singleton/overlayCompatibility.js new file mode 100644 index 000000000..377398ae8 --- /dev/null +++ b/packages/singleton-manager/demo/singleton/overlayCompatibility.js @@ -0,0 +1,24 @@ +import { OverlaysManager } from 'overlays'; +import { singletonManager } from '../../index.js'; + +class CompatibleManager extends OverlaysManager { + name = 'Compatible from App'; + + constructor() { + super(); + this.blocker.innerText = `Blocker for ${this.name}`; + } + + blockingBody() { + this.block(); + } + + unBlockingBody() { + this.unBlock(); + } +} + +const compatibleManager = new CompatibleManager(); + +singletonManager.set('overlays::overlays::1.x', compatibleManager); +singletonManager.set('overlays::overlays::2.x', compatibleManager); diff --git a/packages/singleton-manager/demo/singleton/package.json b/packages/singleton-manager/demo/singleton/package.json new file mode 100644 index 000000000..a973830e0 --- /dev/null +++ b/packages/singleton-manager/demo/singleton/package.json @@ -0,0 +1,8 @@ +{ + "name": "fail-demo-app", + "version": "1.0.0", + "dependencies": { + "page-a": "^1.0.0", + "page-b": "^1.0.0" + } +} diff --git a/packages/singleton-manager/demo/singleton/server.js b/packages/singleton-manager/demo/singleton/server.js new file mode 100644 index 000000000..abf7e629c --- /dev/null +++ b/packages/singleton-manager/demo/singleton/server.js @@ -0,0 +1,6 @@ +module.exports = { + rootDir: '../../', + appIndex: 'packages/singleton-manager/demo/singleton/index.html', + nodeResolve: true, + open: true, +}; diff --git a/packages/singleton-manager/index.js b/packages/singleton-manager/index.js new file mode 100644 index 000000000..9dca108e3 --- /dev/null +++ b/packages/singleton-manager/index.js @@ -0,0 +1,5 @@ +import { SingletonManagerClass } from './src/SingletonManagerClass.js'; + +export { SingletonManagerClass } from './src/SingletonManagerClass.js'; + +export const singletonManager = new SingletonManagerClass(); diff --git a/packages/singleton-manager/package.json b/packages/singleton-manager/package.json new file mode 100644 index 000000000..2b6d4f922 --- /dev/null +++ b/packages/singleton-manager/package.json @@ -0,0 +1,38 @@ +{ + "name": "singleton-manager", + "version": "0.0.0", + "description": "Manage singletons across multiple major versions so they converge to a single instance", + "author": "ing-bank", + "homepage": "https://github.com/ing-bank/lion/", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/ing-bank/lion.git", + "directory": "packages/singleton-manager" + }, + "scripts": { + "prepublishOnly": "../../scripts/npm-prepublish.js", + "start:fail": "es-dev-server -c demo/fail/server.js", + "start:singleton": "es-dev-server -c demo/singleton/server.js", + "start:singleton-complex": "es-dev-server -c demo/singleton-complex/server.js", + "test": "cd ../../ && yarn test:browser --grep \"packages/singleton-manager/test/**/*.test.js\"", + "test:watch": "cd ../../ && yarn test:browser:watch --grep \"packages/singleton-manager/test/**/*.test.js\"" + }, + "keywords": [ + "lion", + "singleton-manager" + ], + "main": "index.js", + "module": "index.js", + "sideEffects": false, + "files": [ + "docs", + "src", + "test", + "test-helpers", + "*.js" + ] +} diff --git a/packages/singleton-manager/src/SingletonManagerClass.js b/packages/singleton-manager/src/SingletonManagerClass.js new file mode 100644 index 000000000..c35c6c225 --- /dev/null +++ b/packages/singleton-manager/src/SingletonManagerClass.js @@ -0,0 +1,20 @@ +export class SingletonManagerClass { + constructor() { + this._map = new Map(); + } + + set(key, value) { + if (this.has(key)) { + throw new Error(`The key "${key}" is already defined and can not be overridden.`); + } + this._map.set(key, value); + } + + get(key) { + return this._map.get(key); + } + + has(key) { + return this._map.has(key); + } +} diff --git a/packages/singleton-manager/test/SingletonManagerClass.test.js b/packages/singleton-manager/test/SingletonManagerClass.test.js new file mode 100644 index 000000000..bd2f0e77b --- /dev/null +++ b/packages/singleton-manager/test/SingletonManagerClass.test.js @@ -0,0 +1,31 @@ +import { expect } from '@open-wc/testing'; + +import { SingletonManagerClass } from '../src/SingletonManagerClass.js'; + +describe('SingletonManagerClass', () => { + it('returns undefined and has false if not set', async () => { + const mngr = new SingletonManagerClass(); + expect(mngr.get('overlays/overlays.js::0.13.x')).to.be.undefined; + expect(mngr.has('overlays/overlays.js::0.13.x')).to.be.false; + }); + + it('return value and has true if set', () => { + const mngr = new SingletonManagerClass(); + mngr.set('overlays/overlays.js::0.13.x', 'is-set'); + expect(mngr.get('overlays/overlays.js::0.13.x')).to.equal('is-set'); + expect(mngr.has('overlays/overlays.js::0.13.x')).to.be.true; + // make sure non set values are still correct + expect(mngr.get('overlays/overlays.js::0.14.x')).to.be.undefined; + expect(mngr.has('overlays/overlays.js::0.14.x')).to.be.false; + }); + + it('throws if an app tries to set an existing key again', () => { + const mngr = new SingletonManagerClass(); + mngr.set('overlays/overlays.js::0.13.x', 'is-set'); + expect(() => { + mngr.set('overlays/overlays.js::0.13.x', 'new-set'); + }).to.throw( + 'The key "overlays/overlays.js::0.13.x" is already defined and can not be overridden.', + ); + }); +}); diff --git a/packages/singleton-manager/test/singleton-demo.test.js b/packages/singleton-manager/test/singleton-demo.test.js new file mode 100644 index 000000000..d7dbac2da --- /dev/null +++ b/packages/singleton-manager/test/singleton-demo.test.js @@ -0,0 +1,15 @@ +import { expect } from '@open-wc/testing'; + +import '../demo/singleton/overlayCompatibility.js'; + +import { overlays } from '../demo/singleton/node_modules/overlays/instance.js'; +import { overlays as overlays2 } from '../demo/singleton/node_modules/page-b/node_modules/overlays/instance.js'; + +describe('singleton-demo', () => { + it('uses the compatibility overrides', async () => { + // Note: we can not test how it would work without applying the compatibility layer + // as it is a global side effect and there is only one karma instance running. + expect(overlays.name).to.equal('Compatible from App'); + expect(overlays2.name).to.equal('Compatible from App'); + }); +});