diff --git a/src/lib/behaviors/active.svelte.test.ts b/src/lib/behaviors/active.svelte.test.ts index 24325ae..2a9932d 100644 --- a/src/lib/behaviors/active.svelte.test.ts +++ b/src/lib/behaviors/active.svelte.test.ts @@ -5,6 +5,8 @@ import type { ActiveState, RouteStatus } from "$lib/types.js"; import { render } from "@testing-library/svelte"; import TestActiveBehavior from "../../testing/TestActiveBehavior.svelte"; import { flushSync } from "svelte"; +import type { ClassValue } from "svelte/elements"; +import { clsx } from "clsx"; describe("activeBehavior", () => { let mockElement: HTMLElement; @@ -46,6 +48,44 @@ describe("activeBehavior", () => { expect(mockElement.setAttribute).toHaveBeenCalledWith('style', 'background: white; color: red;'); expect(mockElement.classList.add).toHaveBeenCalledWith('active-class'); }); + test.each<{ + text: string; + classValue: ClassValue + }>([ + { + text: "string", + classValue: "single-class", + }, + { + text: "object", + classValue: { active: true, }, + }, + { + text: "array", + classValue: ["class1", "class2"], + }, + ])("Should apply classes provided in $text form when route is active.", ({ classValue }) => { + // Arrange. + const routeKey = "test-route"; + const routeStatus: Record = { + [routeKey]: { + match: true, + routeParams: undefined + } + }; + const activeState: ActiveState & { key: string } = { + key: routeKey, + class: classValue, + }; + + // Act. + const attachment = activeBehavior(routeStatus, activeState); + attachment(mockElement); + + // Assert. + const expectedClass = clsx(classValue).split(' '); + expect(mockElement.classList.add).toHaveBeenCalledWith(...expectedClass); + }); test("Should return a cleanup function when route is active.", () => { // Arrange const routeKey = "test-route"; @@ -287,8 +327,8 @@ describe("activeBehavior", () => { attachment(mockElement); // Assert - clsx should process the class value and add it to classList if non-empty - const processedClass = expect.any(String); - expect(mockElement.classList.add).toHaveBeenCalledWith(processedClass); + const processedClass = clsx(classValue).split(' ').filter(c => c.trim().length > 0); + expect(mockElement.classList.add).toHaveBeenCalledWith(...processedClass); }); }); diff --git a/src/lib/behaviors/active.svelte.ts b/src/lib/behaviors/active.svelte.ts index 0eca645..84c4bfa 100644 --- a/src/lib/behaviors/active.svelte.ts +++ b/src/lib/behaviors/active.svelte.ts @@ -46,9 +46,9 @@ export function activeBehavior( return function (el: HTMLElement) { if (isRouteActive(rsOrRouter, activeState.key)) { el.setAttribute('style', joinStyles(baseStyle, activeState.style) ?? ''); - const activeClass = clsx(activeState.class); - if (activeClass) { - el.classList.add(activeClass); + const activeClass = clsx(activeState.class).split(' ').filter(c => c.trim().length > 0); + if (activeClass.length) { + el.classList.add(...activeClass); } if (activeState.aria) { for (let [attr, value] of Object.entries(activeState.aria)) { @@ -57,8 +57,8 @@ export function activeBehavior( } return () => { el.setAttribute('style', baseStyle ?? ''); - if (activeClass) { - el.classList.remove(activeClass); + if (activeClass.length) { + el.classList.remove(...activeClass); } if (activeState.aria) { for (let attr of Object.keys(activeState.aria)) {