From fb77dcb12f177e1cc35fc377150261449a83c459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Feb 2024 17:28:17 +0100 Subject: [PATCH] Use method hash for controller alias if not provided --- .../controller-api/controller-alias.type.ts | 2 +- .../libs/controller-api/controller.test.ts | 8 +-- ...e-extension-initializer.controller.test.ts | 2 +- .../extension-api-initializer.test.ts | 4 +- .../extension-element-initializer.test.ts | 4 +- .../observer.controller.test.ts | 58 +++++++++++++++++++ .../observable-api/observer.controller.ts | 4 +- .../src/libs/observable-api/utils/index.ts | 1 + .../utils/simple-hash-code.function.ts | 15 +++++ .../collection-view.manager.test.ts | 4 +- .../selection.manager.test.ts | 2 +- 11 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts create mode 100644 src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/simple-hash-code.function.ts diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts index e25e734e02..6d57214862 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-alias.type.ts @@ -1 +1 @@ -export type UmbControllerAlias = string | symbol | undefined; +export type UmbControllerAlias = string | number | symbol | undefined; diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts index e00bf00316..631d169f13 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller.test.ts @@ -6,9 +6,9 @@ import type { UmbControllerHost } from './controller-host.interface.js'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; @customElement('test-my-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} -export class UmbTestControllerImplementation extends UmbControllerHostMixin(class {}) { +class UmbTestControllerImplementation extends UmbControllerHostMixin(class {}) { testIsConnected = false; testIsDestroyed = false; @@ -47,9 +47,7 @@ export class UmbTestControllerImplementation extends UmbControllerHostMixin(clas } describe('UmbController', () => { - type NewType = UmbControllerHostElement; - - let hostElement: NewType; + let hostElement: UmbControllerHostElement; beforeEach(() => { hostElement = document.createElement('test-my-controller-host') as UmbControllerHostElement; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts index a276cee811..23e639a49f 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts @@ -16,7 +16,7 @@ import { UmbSwitchCondition } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @customElement('umb-test-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} class UmbTestExtensionController extends UmbBaseExtensionInitializer { constructor( diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.test.ts index 469d237d91..96569a144f 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.test.ts @@ -9,9 +9,9 @@ import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; import { type ManifestSection, UmbSwitchCondition } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-test-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} -export class UmbTestApiController extends UmbBaseController { +class UmbTestApiController extends UmbBaseController { public i_am_test_api_controller = true; constructor(host: UmbControllerHost) { diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-initializer.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-initializer.test.ts index a76dffd077..5ba23d9f44 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-initializer.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-initializer.test.ts @@ -1,13 +1,13 @@ import { expect, fixture } from '@open-wc/testing'; import { UmbExtensionRegistry } from '../registry/extension.registry.js'; import { UmbExtensionElementInitializer } from './index.js'; -import type { UmbControllerHostElement} from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; import { type ManifestSection, UmbSwitchCondition } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-test-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} describe('UmbExtensionElementController', () => { describe('Manifest without conditions', () => { diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts new file mode 100644 index 0000000000..0e7dc299b8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts @@ -0,0 +1,58 @@ +import { expect } from '@open-wc/testing'; +import { UmbObjectState } from './states/object-state.js'; +import { UmbObserverController } from './observer.controller.js'; +import { customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; + +@customElement('test-my-observer-controller-host') +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} + +describe('UmbObserverController', () => { + describe('Observer Controllers against other Observer Controllers', () => { + let hostElement: UmbTestControllerHostElement; + + beforeEach(() => { + hostElement = document.createElement('test-my-observer-controller-host') as UmbTestControllerHostElement; + }); + + it('controller is replaced by another controller using the same string as controller-alias', () => { + const state = new UmbObjectState(undefined); + const observable = state.asObservable(); + + const callbackMethod = (state: unknown) => {}; + + const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod, 'my-test-alias'); + const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, 'my-test-alias'); + + expect(hostElement.hasController(firstCtrl)).to.be.false; + expect(hostElement.hasController(secondCtrl)).to.be.true; + }); + + it('controller is replaced by another controller using the the same symbol as controller-alias', () => { + const state = new UmbObjectState(undefined); + const observable = state.asObservable(); + + const callbackMethod = (state: unknown) => {}; + + const mySymbol = Symbol(); + const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod, mySymbol); + const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, mySymbol); + + expect(hostElement.hasController(firstCtrl)).to.be.false; + expect(hostElement.hasController(secondCtrl)).to.be.true; + }); + + it('controller is replacing another controller when using the same callback method and no controller-alias', () => { + const state = new UmbObjectState(undefined); + const observable = state.asObservable(); + + const callbackMethod = (state: unknown) => {}; + + const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod); + const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod); + + expect(hostElement.hasController(firstCtrl)).to.be.false; + expect(hostElement.hasController(secondCtrl)).to.be.true; + }); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts index 5f790c6047..f870d2fe70 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts @@ -1,4 +1,5 @@ import { type ObserverCallback, UmbObserver } from './observer.js'; +import { simpleHashCode } from './utils/simple-hash-code.function.js'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbController, UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -18,7 +19,8 @@ export class UmbObserverController extends UmbObserver implement ) { super(source, callback); this.#host = host; - this.#alias = alias; + // Fallback to use a hash of the provided method: + this.#alias = alias ?? simpleHashCode(callback.toString()); // Lets check if controller is already here: // No we don't want this, as multiple different controllers might be looking at the same source. diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts index c0c329548a..fce5474f94 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts @@ -8,3 +8,4 @@ export * from './naive-object-comparison.function.js'; export * from './observe-multiple.function.js'; export * from './partial-update-frozen-array.function.js'; export * from './push-to-unique-array.function.js'; +export * from './simple-hash-code.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/simple-hash-code.function.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/simple-hash-code.function.ts new file mode 100644 index 0000000000..2e02997326 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/simple-hash-code.function.ts @@ -0,0 +1,15 @@ +/** + * Returns a hash code from a string + * @param {String} str - The string to hash. + * @return {Number} - A 32bit integer + */ +export function simpleHashCode(str: string) { + let hash = 0, + i = 0; + const len = str.length; + while (i < len) { + hash = (hash << 5) - hash + str.charCodeAt(i++); + hash |= 0; // Convert to 32bit integer + } + return hash; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts index 57bed25f32..f3e06f7078 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.test.ts @@ -2,12 +2,12 @@ import { expect } from '@open-wc/testing'; import { UmbCollectionViewManager } from './collection-view.manager.js'; import { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; -import type { ManifestCollectionView} from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestCollectionView } from '@umbraco-cms/backoffice/extension-registry'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; @customElement('test-my-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} const VIEW_1_ALIAS = 'UmbTest.CollectionView.1'; const VIEW_2_ALIAS = 'UmbTest.CollectionView.2'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.test.ts index adf7318509..32f70c9825 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.test.ts @@ -5,7 +5,7 @@ import { customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; @customElement('test-my-controller-host') -export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} describe('UmbSelectionManager', () => { let manager: UmbSelectionManager;