From d6eaafe7c962aa5b470e5b8680b2ae7f75d7655f Mon Sep 17 00:00:00 2001 From: gvjacob Date: Thu, 23 Jan 2025 16:54:21 -0500 Subject: [PATCH 1/4] x-accordion: toggle item --- src/lib/__tests__/x-accordion.test.js | 20 ++++++++++++++++++++ src/lib/toolbelt/x-accordion.js | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/src/lib/__tests__/x-accordion.test.js b/src/lib/__tests__/x-accordion.test.js index f25a224..7b73e70 100644 --- a/src/lib/__tests__/x-accordion.test.js +++ b/src/lib/__tests__/x-accordion.test.js @@ -194,6 +194,26 @@ describe("x-accordion", () => { }); }); }); + + describe("methods", () => { + test("should open and close an item when .toggle is called", async () => { + const item = screen.getByTestId("item-1"); + const trigger = screen.getByTestId("trigger-1"); + const content = screen.getByTestId("content-1"); + + item.toolbelt.toggle(); + + await waitFor(() => { + expectItemToBeOpen({ item, trigger, content }, true); + }); + + item.toolbelt.toggle(); + + await waitFor(() => { + expectItemToBeOpen({ item, trigger, content }, false); + }); + }); + }); }); describe("(x-accordion.single) single item only configuration", () => { diff --git a/src/lib/toolbelt/x-accordion.js b/src/lib/toolbelt/x-accordion.js index 214b56e..03e38dc 100644 --- a/src/lib/toolbelt/x-accordion.js +++ b/src/lib/toolbelt/x-accordion.js @@ -101,6 +101,12 @@ function handleItem(el, Alpine, config) { if (config.open) { this.openItems.add(el); } + + el.toolbelt = { + toggle: () => { + this.toggleItem(this.__item); + }, + }; }, "x-id"() { From 408b17ab64096b507ae4a18632933823b6c0bcc6 Mon Sep 17 00:00:00 2001 From: gvjacob Date: Thu, 23 Jan 2025 23:11:40 -0500 Subject: [PATCH 2/4] x-accordion: provide override option for toggle --- src/lib/__tests__/x-accordion.test.js | 22 +++++++++++++++++++--- src/lib/toolbelt/x-accordion.js | 16 +++++++++------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/lib/__tests__/x-accordion.test.js b/src/lib/__tests__/x-accordion.test.js index 7b73e70..4b4b130 100644 --- a/src/lib/__tests__/x-accordion.test.js +++ b/src/lib/__tests__/x-accordion.test.js @@ -195,8 +195,8 @@ describe("x-accordion", () => { }); }); - describe("methods", () => { - test("should open and close an item when .toggle is called", async () => { + describe("javascript methods", () => { + test("(item.toolbelt.toggle) should open and close an item", async () => { const item = screen.getByTestId("item-1"); const trigger = screen.getByTestId("trigger-1"); const content = screen.getByTestId("content-1"); @@ -205,9 +205,25 @@ describe("x-accordion", () => { await waitFor(() => { expectItemToBeOpen({ item, trigger, content }, true); + item.toolbelt.toggle(); }); - item.toolbelt.toggle(); + await waitFor(() => { + expectItemToBeOpen({ item, trigger, content }, false); + }); + }); + + test("(item.toolbelt.toggle) should open and close an item when override is given", async () => { + const item = screen.getByTestId("item-1"); + const trigger = screen.getByTestId("trigger-1"); + const content = screen.getByTestId("content-1"); + + item.toolbelt.toggle(true); + + await waitFor(() => { + expectItemToBeOpen({ item, trigger, content }, true); + item.toolbelt.toggle(false); + }); await waitFor(() => { expectItemToBeOpen({ item, trigger, content }, false); diff --git a/src/lib/toolbelt/x-accordion.js b/src/lib/toolbelt/x-accordion.js index 03e38dc..0c90923 100644 --- a/src/lib/toolbelt/x-accordion.js +++ b/src/lib/toolbelt/x-accordion.js @@ -43,15 +43,17 @@ function handleRoot(el, Alpine, config) { triggers: [], openItems: new Set(), - toggleItem(el) { - if (config.type === "single" && this.openItems.has(el)) { + toggleItem(el, open = null) { + const shouldOpen = open === null ? !this.openItems.has(el) : open; + + if (config.type === "single" && !shouldOpen) { this.openItems.delete(el); - } else if (config.type === "single" && !this.openItems.has(el)) { + } else if (config.type === "single" && shouldOpen) { this.openItems.clear(); this.openItems.add(el); - } else if (config.type === "multiple" && this.openItems.has(el)) { + } else if (config.type === "multiple" && !shouldOpen) { this.openItems.delete(el); - } else if (config.type === "multiple" && !this.openItems.has(el)) { + } else if (config.type === "multiple" && shouldOpen) { this.openItems.add(el); } @@ -103,8 +105,8 @@ function handleItem(el, Alpine, config) { } el.toolbelt = { - toggle: () => { - this.toggleItem(this.__item); + toggle: (open = null) => { + this.toggleItem(this.__item, open); }, }; }, From 5dbc026aa50b2f096e9808046f3148844066aba3 Mon Sep 17 00:00:00 2001 From: gvjacob Date: Fri, 24 Jan 2025 09:58:18 -0500 Subject: [PATCH 3/4] Use constants for test suite scopes --- src/lib/__tests__/utils.js | 6 ++++++ src/lib/__tests__/x-accordion.test.js | 19 ++++++++++++------- src/lib/__tests__/x-checkbox.test.js | 9 +++++++-- src/lib/__tests__/x-dialog.test.js | 9 +++++++-- src/lib/__tests__/x-flyout.test.js | 9 +++++++-- src/lib/__tests__/x-tabs.test.js | 17 +++++++++++------ 6 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/lib/__tests__/utils.js b/src/lib/__tests__/utils.js index f0750f9..116c650 100644 --- a/src/lib/__tests__/utils.js +++ b/src/lib/__tests__/utils.js @@ -35,3 +35,9 @@ export function initializeAlpine() { export function createMockCustomEventListener() { return vi.fn((e) => [e.target, e.detail]); } + +export const scopes = { + CUSTOM_EVENTS: "custom events", + KEYBOARD_NAVIGATION: "keyboard navigation", + JAVASCRIPT_METHODS: "javascript methods", +}; diff --git a/src/lib/__tests__/x-accordion.test.js b/src/lib/__tests__/x-accordion.test.js index 4b4b130..07c5bfe 100644 --- a/src/lib/__tests__/x-accordion.test.js +++ b/src/lib/__tests__/x-accordion.test.js @@ -1,7 +1,12 @@ import { expect, describe, beforeAll, beforeEach, test } from "vitest"; import { fireEvent, screen, waitFor } from "@testing-library/dom"; -import { createMockCustomEventListener, html, initializeAlpine } from "./utils"; +import { + createMockCustomEventListener, + html, + initializeAlpine, + scopes, +} from "./utils"; describe("x-accordion", () => { beforeAll(initializeAlpine); @@ -85,7 +90,7 @@ describe("x-accordion", () => { }); }); - describe("custom events", () => { + describe(scopes.CUSTOM_EVENTS, () => { test("should indicate item is open", async () => { const item = screen.getByTestId("item-1"); const trigger = screen.getByTestId("trigger-1"); @@ -132,7 +137,7 @@ describe("x-accordion", () => { }); }); - describe("keyboard navigation", () => { + describe(scopes.KEYBOARD_NAVIGATION, () => { let trigger1, trigger2; beforeEach(() => { @@ -195,7 +200,7 @@ describe("x-accordion", () => { }); }); - describe("javascript methods", () => { + describe(scopes.JAVASCRIPT_METHODS, () => { test("(item.toolbelt.toggle) should open and close an item", async () => { const item = screen.getByTestId("item-1"); const trigger = screen.getByTestId("trigger-1"); @@ -289,7 +294,7 @@ describe("x-accordion", () => { }); }); - describe("custom events", () => { + describe(scopes.CUSTOM_EVENTS, () => { test("should trigger separate events for opened and closed items.", async () => { const item1 = screen.getByTestId("item-1"); const trigger1 = screen.getByTestId("trigger-1"); @@ -347,7 +352,7 @@ describe("x-accordion", () => { `; }); - describe("keyboard navigation", () => { + describe(scopes.KEYBOARD_NAVIGATION, () => { let trigger1, trigger2; beforeEach(() => { @@ -392,7 +397,7 @@ describe("x-accordion", () => { `; }); - describe("keyboard navigation", () => { + describe(scopes.KEYBOARD_NAVIGATION, () => { let trigger1, trigger2; beforeEach(() => { diff --git a/src/lib/__tests__/x-checkbox.test.js b/src/lib/__tests__/x-checkbox.test.js index 01a258b..45ffae1 100644 --- a/src/lib/__tests__/x-checkbox.test.js +++ b/src/lib/__tests__/x-checkbox.test.js @@ -1,7 +1,12 @@ import { expect, describe, beforeAll, beforeEach, test } from "vitest"; import { fireEvent, screen, waitFor } from "@testing-library/dom"; -import { createMockCustomEventListener, html, initializeAlpine } from "./utils"; +import { + createMockCustomEventListener, + html, + initializeAlpine, + scopes, +} from "./utils"; describe("x-checkbox", () => { beforeAll(initializeAlpine); @@ -97,7 +102,7 @@ describe("x-checkbox", () => { }); }); - describe("custom events", () => { + describe(scopes.CUSTOM_EVENTS, () => { test("should indicate checkbox is open", async () => { const root = screen.getByTestId("root"); const indicator = screen.getByTestId("indicator"); diff --git a/src/lib/__tests__/x-dialog.test.js b/src/lib/__tests__/x-dialog.test.js index e25031a..c93a6c7 100644 --- a/src/lib/__tests__/x-dialog.test.js +++ b/src/lib/__tests__/x-dialog.test.js @@ -1,7 +1,12 @@ import { expect, describe, beforeAll, beforeEach, test } from "vitest"; import { fireEvent, screen, waitFor } from "@testing-library/dom"; -import { createMockCustomEventListener, html, initializeAlpine } from "./utils"; +import { + createMockCustomEventListener, + html, + initializeAlpine, + scopes, +} from "./utils"; describe("x-dialog", () => { beforeAll(initializeAlpine); @@ -117,7 +122,7 @@ describe("x-dialog", () => { }); }); - describe("custom events", () => { + describe(scopes.CUSTOM_EVENTS, () => { test("should indicate dialog is open", async () => { const listener = createMockCustomEventListener(); diff --git a/src/lib/__tests__/x-flyout.test.js b/src/lib/__tests__/x-flyout.test.js index 4812eab..7accfc2 100644 --- a/src/lib/__tests__/x-flyout.test.js +++ b/src/lib/__tests__/x-flyout.test.js @@ -1,7 +1,12 @@ import { expect, describe, beforeAll, beforeEach, test } from "vitest"; import { fireEvent, screen, waitFor } from "@testing-library/dom"; -import { createMockCustomEventListener, html, initializeAlpine } from "./utils"; +import { + createMockCustomEventListener, + html, + initializeAlpine, + scopes, +} from "./utils"; describe("x-flyout", () => { beforeAll(initializeAlpine); @@ -90,7 +95,7 @@ describe("x-flyout", () => { }); }); - describe("custom events", () => { + describe(scopes.CUSTOM_EVENTS, () => { test("should indicate flyout is open", async () => { const listener = createMockCustomEventListener(); diff --git a/src/lib/__tests__/x-tabs.test.js b/src/lib/__tests__/x-tabs.test.js index c644fa8..3d4d172 100644 --- a/src/lib/__tests__/x-tabs.test.js +++ b/src/lib/__tests__/x-tabs.test.js @@ -1,7 +1,12 @@ import { expect, describe, beforeAll, beforeEach, test } from "vitest"; import { fireEvent, screen, waitFor } from "@testing-library/dom"; -import { createMockCustomEventListener, html, initializeAlpine } from "./utils"; +import { + createMockCustomEventListener, + html, + initializeAlpine, + scopes, +} from "./utils"; describe("x-tabs", () => { beforeAll(initializeAlpine); @@ -78,7 +83,7 @@ describe("x-tabs", () => { }); }); - describe("custom events", () => { + describe(scopes.CUSTOM_EVENTS, () => { test("should indicate tab is open", async () => { const listener = createMockCustomEventListener(); @@ -108,7 +113,7 @@ describe("x-tabs", () => { }); }); - describe("keyboard navigation", () => { + describe(scopes.KEYBOARD_NAVIGATION, () => { test("pressing right arrow should move focus to the next tab", async () => { fireEvent.keyDown(tab1, { key: "ArrowRight" }); @@ -179,7 +184,7 @@ describe("x-tabs", () => { tab2 = screen.getByTestId("tab2"); }); - describe("keyboard navigation", () => { + describe(scopes.KEYBOARD_NAVIGATION, () => { test("pressing right arrow on the last tab should loop", async () => { fireEvent.keyDown(tab2, { key: "ArrowRight" }); @@ -220,7 +225,7 @@ describe("x-tabs", () => { panel2 = screen.getByTestId("panel2"); }); - describe("keyboard navigation", () => { + describe(scopes.KEYBOARD_NAVIGATION, () => { test("pressing right arrow should automatically open the next tab", async () => { fireEvent.keyDown(tab1, { key: "ArrowRight" }); @@ -281,7 +286,7 @@ describe("x-tabs", () => { panel2 = screen.getByTestId("panel2"); }); - describe("keyboard navigation", () => { + describe(scopes.KEYBOARD_NAVIGATION, () => { test("pressing down arrow should move focus to the next tab", async () => { fireEvent.keyDown(tab1, { key: "ArrowDown" }); From 12f45875ca50dba0d60b1b16fbc76210837bef0c Mon Sep 17 00:00:00 2001 From: gvjacob Date: Fri, 24 Jan 2025 10:14:55 -0500 Subject: [PATCH 4/4] Check for undefined instead of null --- src/lib/toolbelt/x-accordion.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/toolbelt/x-accordion.js b/src/lib/toolbelt/x-accordion.js index 0c90923..045d1af 100644 --- a/src/lib/toolbelt/x-accordion.js +++ b/src/lib/toolbelt/x-accordion.js @@ -43,8 +43,9 @@ function handleRoot(el, Alpine, config) { triggers: [], openItems: new Set(), - toggleItem(el, open = null) { - const shouldOpen = open === null ? !this.openItems.has(el) : open; + toggleItem(el, open) { + const shouldOpen = + open === undefined ? !this.openItems.has(el) : open; if (config.type === "single" && !shouldOpen) { this.openItems.delete(el); @@ -105,7 +106,7 @@ function handleItem(el, Alpine, config) { } el.toolbelt = { - toggle: (open = null) => { + toggle: (open) => { this.toggleItem(this.__item, open); }, };