diff --git a/packages/core/src/js/Collection.js b/packages/core/src/js/Collection.js index 3d713a6af..8d7a378d5 100644 --- a/packages/core/src/js/Collection.js +++ b/packages/core/src/js/Collection.js @@ -1,4 +1,4 @@ -import { eventEmitter, maybeRunMethod } from "./utilities"; +import { eventEmitter, getElement, maybeRunMethod } from "./utilities"; import { dispatchLifecycleHook, pluginsArray } from "./helpers"; import { CollectionEntry } from "./CollectionEntry"; @@ -66,23 +66,37 @@ export class Collection { } async register(query, config = {}) { - // Create the collection entry object and mount it. - const entry = await this.createEntry(query, config); + // Get the element to register. + const element = getElement(query); - // Check if an entry with the provided ID has already been registered. - const index = this.collection.findIndex(item => item.id === entry.id); + // Throw an error if element wasn't found. + if (element === null) { + throw new Error(`${this.module} element was not found with ID: "${query?.id || query}"`); + } + + // Check if the element with the provided ID has already been registered. + const index = this.collection.findIndex(item => item.id === element.id); if (~index) { - // Replace the existing entry in the collection. - this.collection[index] = entry; + // Get the entry from the collection. + const entry = this.collection[index]; + // Override the element property with the provided element. + entry.el = element; + // Run the entry init() method if it exists. + if (typeof entry.init === "function") { + await entry.init(config); + } + // Return the registered entry. + return entry; } else { + // Create the collection entry object. + const entry = await this.createEntry(element, config); // Add the entry to the collection. this.collection.push(entry); + // Dispatch onRegisterEntry lifecycle hooks. + await dispatchLifecycleHook("onRegisterEntry", this, entry); + // Return the registered entry. + return entry; } - - // Dispatch onRegisterEntry lifecycle hooks. - await dispatchLifecycleHook("onRegisterEntry", this, entry); - - return entry; } async deregister(id) { diff --git a/packages/core/tests/plugins/propStore.test.js b/packages/core/tests/plugins/propStore.test.js index 7341af022..c43526372 100644 --- a/packages/core/tests/plugins/propStore.test.js +++ b/packages/core/tests/plugins/propStore.test.js @@ -7,139 +7,179 @@ document.body.innerHTML = `
Three
`; -const collection = new Collection({ - selector: ".entry" -}); +let collection; -test("should register and setup the propStore plugin on collection mount", async () => { - expect(collection.plugins.length).toBe(0); - await collection.mount({ - plugins: [ - propStore() - ] +describe("propStore", () => { + beforeEach(() => { + collection = new Collection({ + selector: ".entry" + }); + }); + + afterEach(async () => { + await collection.unmount(); }); - expect(collection.plugins.length).toBe(1); - const entry = collection.get("entry-1"); - const plugin = collection.plugins.get("propStore"); - expect(entry.id).toBe("entry-1"); - expect(typeof plugin.store.get).toBe("function"); - expect(typeof plugin.store.set).toBe("function"); - expect(typeof plugin.settings.onChange).toBe("function"); - expect(typeof plugin.settings.condition).toBe("boolean"); - plugin.settings.onChange(); - expect(plugin.settings.condition).toBe(false); -}); -test("should remove the propStore plugin when the remove method is called", async () => { - expect(collection.plugins.length).toBe(1); - await collection.plugins.remove("propStore"); - expect(collection.plugins.length).toBe(0); -}); + it("should register and setup the propStore plugin on collection mount", async () => { + expect(collection.plugins.length).toBe(0); + await collection.mount({ plugins: [ propStore() ] }); + expect(collection.plugins.length).toBe(1); + const entry = collection.get("entry-1"); + const plugin = collection.plugins.get("propStore"); + expect(entry.id).toBe("entry-1"); + expect(typeof plugin.store.get).toBe("function"); + expect(typeof plugin.store.set).toBe("function"); + expect(typeof plugin.settings.onChange).toBe("function"); + expect(typeof plugin.settings.condition).toBe("boolean"); + await plugin.settings.onChange(); + expect(plugin.settings.condition).toBe(false); + }); -test("should remove propStore from an entry if it is deregistered", async () => { - await collection.mount({ - plugins: [ - propStore({ - name: "quickPropStore" - }) - ] + it("should remove the propStore plugin when the remove method is called", async () => { + expect(collection.plugins.length).toBe(0); + await collection.mount({ plugins: [propStore()] }); + expect(collection.plugins.length).toBe(1); + await collection.plugins.remove("propStore"); + expect(collection.plugins.length).toBe(0); }); - const entry = collection.get("entry-1"); - await collection.deregister("entry-1"); - expect(entry).toEqual({ "id": "entry-1" }); - await collection.register("entry-1"); - const el = document.getElementById("entry-1"); - expect(collection.get("entry-1").el).toBe(el); -}); -test("should be able to set a condition and onChange callback", async () => { - const spyFunction = vi.fn(); - await collection.mount({ - plugins: [ - propStore({ - prop: "example", - condition() { - return true; - }, - onChange: spyFunction - }) - ] + it("should remove propStore from an entry if it is deregistered", async () => { + // Initial setup for propStore. + await collection.mount({ + plugins: [ + propStore({ + prop: "example", + value: "asdf", + condition: true + }) + ] + }); + + // Get a reference to the plugin's local store object. + const store = collection.plugins.get("propStore").store; + + const entry = collection.get("entry-1"); + // Check that the property and initial value has been added to entry. + expect(entry.example).toBe("asdf"); + // Check that the value of entry has been stored in local storage. + expect(store.get("entry-1")).toBe("asdf"); + + // Deregister the entry from the collection. + await collection.deregister("entry-1"); + + // Check that the property has been removed. + expect(entry.example).toBe(undefined); + // Check that the value of entry has been removed from local storage. + expect(store.get("entry-1")).toBe(undefined); + + // Ensure that the other entries still have their values stored in local storage. + expect(store.get("entry-2")).toBe("asdf"); + expect(store.get("entry-3")).toBe("asdf"); }); - const entry = collection.get("entry-1"); - expect(spyFunction).toBeCalledTimes(0); - entry.example = "fdsa"; - expect(entry.example).toBe("fdsa"); - expect(spyFunction).toBeCalledTimes(1); -}); -test("should not fire onChange callback if setting a value that isn't different", async () => { - const entry = collection.get("entry-2"); - entry.example = "asdf"; - expect(entry.example).toBe("asdf"); - entry.example = "asdf"; -}); + it("should be able to set a condition and onChange callback", async () => { + const spyFunction = vi.fn(); + await collection.mount({ + plugins: [ + propStore({ + prop: "example", + condition() { + return true; + }, + onChange: spyFunction + }) + ] + }); + const entry = collection.get("entry-1"); + expect(spyFunction).toBeCalledTimes(0); + entry.example = "fdsa"; + expect(entry.example).toBe("fdsa"); + expect(spyFunction).toBeCalledTimes(1); + }); -test("should setup a store property for accessing the local storage value", async () => { - const entry = collection.get("entry-3"); - entry.example = "test"; - expect(entry.store).toBe("test"); - entry.store = "new"; - entry.example = "new"; - expect(entry.store).toBe("new"); -}); + it("should not fire onChange callback if setting a value that isn't different", async () => { + const spyFunction = vi.fn(); + await collection.mount({ + plugins: [ + propStore({ + prop: "example", + condition() { + return true; + }, + onChange: spyFunction + }) + ] + }); + const entry = collection.get("entry-1"); + + // Ensure onChange has not been called. + expect(spyFunction).toBeCalledTimes(0); + + // Set the value of the prop and ensure onChange is called once. + entry.example = "asdf"; + expect(spyFunction).toBeCalledTimes(1); + + // Set the value again but ensure onChange is NOT called again since the value has not changed. + entry.example = "asdf"; + expect(spyFunction).toBeCalledTimes(1); -test("should be able to set an initial value for the property", async() => { - const spyFunction = vi.fn(); - expect(collection.plugins.length).toBe(2); - await collection.mount({ - plugins: [ - propStore({ - name: "newPropStore", - prop: "hello", - value: "world", - condition: () => true, - onChange: spyFunction - }) - ] + // Ensure that setting the prop to a different value does fire onChange. + entry.example = "fdsa"; + expect(spyFunction).toBeCalledTimes(2); }); - expect(collection.plugins.length).toBe(3); - const entry = collection.get("entry-1"); - expect(entry.hello).toBe("world"); - expect(spyFunction).toBeCalledTimes(3); - entry.hello = "buddy"; - expect(spyFunction).toBeCalledTimes(4); -}); -test("should be able to set an initial value using a function definition", async() => { - expect(collection.plugins.length).toBe(3); - await collection.mount({ - plugins: [ - propStore({ - name: "anotherNewPropStore", - prop: "hello", - value: () => "my" + " " + "friend", - condition: () => true - }) - ] + it("should setup a store property for accessing the local storage value", async () => { + await collection.mount({ + plugins: [ + propStore({ + prop: "example", + condition: true + }) + ] + }); + const entry = collection.get("entry-3"); + + // Setting the property directly should update store. + entry.example = "test"; + expect(entry.store).toBe("test"); + expect(entry.example).toBe("test"); + + // Setting the store value directly should update the property. + entry.store = "new"; + expect(entry.store).toBe("new"); + expect(entry.example).toBe("new"); + }); + + it("should be able to set an initial value for the property", async() => { + const spyFunction = vi.fn(); + await collection.mount({ + plugins: [ + propStore({ + prop: "hello", + value: "world", + condition: () => true, + onChange: spyFunction + }) + ] + }); + const entry = collection.get("entry-1"); + expect(entry.hello).toBe("world"); + expect(spyFunction).toBeCalledTimes(3); + entry.hello = "buddy"; + expect(spyFunction).toBeCalledTimes(4); }); - expect(collection.plugins.length).toBe(4); - const entry = collection.get("entry-1"); - expect(entry.hello).toBe("my friend"); -}); -test("should default to the original property value if value function returns falsy", async() => { - expect(collection.plugins.length).toBe(4); - await collection.mount({ - plugins: [ - propStore({ - name: "oneMorePropStore", - prop: "hello", - value: () => undefined, - condition: () => false - }) - ] + it("should be able to set an initial value using a function definition", async() => { + await collection.mount({ + plugins: [ + propStore({ + prop: "hello", + value: () => "my" + " " + "friend", + condition: () => true + }) + ] + }); + const entry = collection.get("entry-1"); + expect(entry.hello).toBe("my friend"); }); - expect(collection.plugins.length).toBe(5); - const entry = collection.get("entry-1"); - expect(entry.hello).toBe("my friend"); }); diff --git a/packages/drawer/tests/api.test.js b/packages/drawer/tests/api.test.js index 2287077b9..f44c75389 100644 --- a/packages/drawer/tests/api.test.js +++ b/packages/drawer/tests/api.test.js @@ -155,10 +155,15 @@ describe("register() & deregister()", () => { }); it("should disable setting tabindex on drawer dialog", async () => { + // Disable tabindex before register. drawer.settings.setTabindex = false; let entry = await drawer.register("drawer-1"); expect(entry.dialog.getAttribute("tabindex")).toBe(null); + // Deregister the entry before updating the setting and re-registering. + await drawer.deregister("drawer-1"); + + // Enable tabindex before register. drawer.settings.setTabindex = true; entry = await drawer.register("drawer-1"); expect(entry.dialog.getAttribute("tabindex")).toBe("-1");