From e4ef20946a3952ec8577728c65f376dce58181ff Mon Sep 17 00:00:00 2001 From: binrysearch Date: Sun, 16 Jun 2024 18:16:34 +0100 Subject: [PATCH] className and onKeyDown --- src/core/addOverlayLayer.ts | 40 ---- src/core/removeShowElement.ts | 16 -- src/intro.ts | 2 +- src/packages/tour/addOverlayLayer.ts | 38 ++++ src/packages/tour/classNames.ts | 1 + src/packages/tour/exitIntro.ts | 4 +- src/{core => packages/tour}/onKeyDown.ts | 48 +++-- src/packages/tour/removeShowElement.ts | 16 ++ .../tour/{introForElement.ts => render.ts} | 27 +-- src/packages/tour/setHelperLayerPosition.ts | 3 +- src/packages/tour/showElement.ts | 2 +- src/packages/tour/steps.test.ts | 34 +-- src/packages/tour/tests/mock.ts | 2 + src/packages/tour/tour.ts | 65 +++++- src/util/addClass.test.ts | 84 -------- src/util/className.ts | 24 +++ src/util/clssName.test.ts | 194 ++++++++++++++++++ src/util/removeClass.ts | 22 -- tests/jest/core/introForElement.test.ts | 4 +- 19 files changed, 387 insertions(+), 239 deletions(-) delete mode 100644 src/core/addOverlayLayer.ts delete mode 100644 src/core/removeShowElement.ts create mode 100644 src/packages/tour/addOverlayLayer.ts rename src/{core => packages/tour}/onKeyDown.ts (59%) create mode 100644 src/packages/tour/removeShowElement.ts rename src/packages/tour/{introForElement.ts => render.ts} (55%) delete mode 100644 src/util/addClass.test.ts create mode 100644 src/util/clssName.test.ts diff --git a/src/core/addOverlayLayer.ts b/src/core/addOverlayLayer.ts deleted file mode 100644 index bbac07a32..000000000 --- a/src/core/addOverlayLayer.ts +++ /dev/null @@ -1,40 +0,0 @@ -import exitIntro from "../packages/tour/exitIntro"; -import createElement from "../util/createElement"; -import setStyle from "../util/setStyle"; -import { IntroJs } from "../intro"; - -/** - * Add overlay layer to the page - * - * @api private - */ -export default function addOverlayLayer( - intro: IntroJs, - targetElm: HTMLElement -) { - const overlayLayer = createElement("div", { - className: "introjs-overlay", - }); - - setStyle(overlayLayer, { - top: 0, - bottom: 0, - left: 0, - right: 0, - position: "fixed", - }); - - targetElm.appendChild(overlayLayer); - - if (intro._options.exitOnOverlayClick === true) { - setStyle(overlayLayer, { - cursor: "pointer", - }); - - overlayLayer.onclick = async () => { - await exitIntro(intro, targetElm); - }; - } - - return true; -} diff --git a/src/core/removeShowElement.ts b/src/core/removeShowElement.ts deleted file mode 100644 index 1c3c57487..000000000 --- a/src/core/removeShowElement.ts +++ /dev/null @@ -1,16 +0,0 @@ -import removeClass from "../util/removeClass"; - -/** - * To remove all show element(s) - * - * @api private - */ -export default function removeShowElement() { - const elms = Array.from( - document.querySelectorAll(".introjs-showElement") - ); - - for (const elm of elms) { - removeClass(elm, /introjs-[a-zA-Z]+/g); - } -} diff --git a/src/intro.ts b/src/intro.ts index 8f7e08ac7..dfb0a439f 100644 --- a/src/intro.ts +++ b/src/intro.ts @@ -10,7 +10,7 @@ import { showHintDialog, showHints, } from "./core/hint"; -import introForElement from "./packages/tour/introForElement"; +import introForElement from "./packages/tour/render"; import refresh from "./packages/tour/refresh"; import { HintStep, diff --git a/src/packages/tour/addOverlayLayer.ts b/src/packages/tour/addOverlayLayer.ts new file mode 100644 index 000000000..824d46c4b --- /dev/null +++ b/src/packages/tour/addOverlayLayer.ts @@ -0,0 +1,38 @@ +import exitIntro from "./exitIntro"; +import createElement from "../../util/createElement"; +import setStyle from "../../util/setStyle"; +import { Tour } from "./tour"; +import { overlayClassName } from "./classNames"; + +/** + * Add overlay layer to the page + * + * @api private + */ +export default function addOverlayLayer(tour: Tour) { + const overlayLayer = createElement("div", { + className: overlayClassName, + }); + + setStyle(overlayLayer, { + top: 0, + bottom: 0, + left: 0, + right: 0, + position: "fixed", + }); + + tour.getTargetElement().appendChild(overlayLayer); + + if (tour.getOption("exitOnOverlayClick") === true) { + setStyle(overlayLayer, { + cursor: "pointer", + }); + + overlayLayer.onclick = async () => { + await exitIntro(tour); + }; + } + + return true; +} diff --git a/src/packages/tour/classNames.ts b/src/packages/tour/classNames.ts index 82743c5d8..96c3d58a6 100644 --- a/src/packages/tour/classNames.ts +++ b/src/packages/tour/classNames.ts @@ -23,3 +23,4 @@ export const fullButtonClassName = "introjs-fullbutton"; export const activeClassName = "active"; export const fixedTooltipClassName = "introjs-fixedTooltip"; export const floatingElementClassName = "introjsFloatingElement"; +export const showElementClassName = "introjs-showElement"; diff --git a/src/packages/tour/exitIntro.ts b/src/packages/tour/exitIntro.ts index 07e096ef2..e3a2ddf1a 100644 --- a/src/packages/tour/exitIntro.ts +++ b/src/packages/tour/exitIntro.ts @@ -1,7 +1,7 @@ import DOMEvent from "../../util/DOMEvent"; -import onKeyDown from "../../core/onKeyDown"; +import onKeyDown from "./onKeyDown"; import onResize from "./onResize"; -import removeShowElement from "../../core/removeShowElement"; +import removeShowElement from "./removeShowElement"; import removeChild from "../../util/removeChild"; import { Tour } from "./tour"; import { diff --git a/src/core/onKeyDown.ts b/src/packages/tour/onKeyDown.ts similarity index 59% rename from src/core/onKeyDown.ts rename to src/packages/tour/onKeyDown.ts index 28506de2b..1fa80bc93 100644 --- a/src/core/onKeyDown.ts +++ b/src/packages/tour/onKeyDown.ts @@ -1,8 +1,8 @@ -import { nextStep, previousStep } from "../packages/tour/steps"; -import exitIntro from "../packages/tour/exitIntro"; -import { IntroJs } from "../intro"; -import isFunction from "../util/isFunction"; -import { Tour } from "src/packages/tour/tour"; +import { nextStep, previousStep } from "./steps"; +import exitIntro from "./exitIntro"; +import { Tour } from "./tour"; +import { previousButtonClassName, skipButtonClassName } from "./classNames"; +import { dataStepNumberAttribute } from "./dataAttributes"; /** * on keyCode: @@ -27,42 +27,40 @@ export default async function onKeyDown(tour: Tour, e: KeyboardEvent) { code = e.charCode === null ? e.keyCode : e.charCode; } - if ((code === "Escape" || code === 27) && tour.getOption('exitOnEsc') === true) { + if ( + (code === "Escape" || code === 27) && + tour.getOption("exitOnEsc") === true + ) { //escape key pressed, exit the intro //check if exit callback is defined - await exitIntro(tour, tour.getTargetElement()); + await exitIntro(tour); } else if (code === "ArrowLeft" || code === 37) { //left arrow - await previousStep(intro); + await previousStep(tour); } else if (code === "ArrowRight" || code === 39) { //right arrow - await nextStep(intro); + await nextStep(tour); } else if (code === "Enter" || code === "NumpadEnter" || code === 13) { //srcElement === ie const target = (e.target || e.srcElement) as HTMLElement; - if (target && target.className.match("introjs-prevbutton")) { + if (target && target.className.match(previousButtonClassName)) { //user hit enter while focusing on previous button - await previousStep(intro); - } else if (target && target.className.match("introjs-skipbutton")) { - //user hit enter while focusing on skip button - if ( - intro._introItems.length - 1 === intro._currentStep && - isFunction(intro._introCompleteCallback) - ) { - await intro._introCompleteCallback.call( - intro, - intro._currentStep, - "skip" - ); + await previousStep(tour); + } else if (target && target.className.match(skipButtonClassName)) { + // user hit enter while focusing on skip button + if (tour.isEnd()) { + await tour + .callback("complete") + ?.call(tour, tour.getCurrentStep(), "skip"); } - await exitIntro(intro, intro._targetElement); - } else if (target && target.getAttribute("data-step-number")) { + await exitIntro(tour); + } else if (target && target.getAttribute(dataStepNumberAttribute)) { // user hit enter while focusing on step bullet target.click(); } else { //default behavior for responding to enter - await nextStep(intro); + await nextStep(tour); } //prevent default behaviour on hitting Enter, to prevent steps being skipped in some browsers diff --git a/src/packages/tour/removeShowElement.ts b/src/packages/tour/removeShowElement.ts new file mode 100644 index 000000000..17c60fd36 --- /dev/null +++ b/src/packages/tour/removeShowElement.ts @@ -0,0 +1,16 @@ +import { queryElementsByClassName } from "src/util/queryElement"; +import removeClass from "../../util/removeClass"; +import { showElementClassName } from "./classNames"; + +/** + * To remove all show element(s) + * + * @api private + */ +export default function removeShowElement() { + const elms = Array.from(queryElementsByClassName(showElementClassName)); + + for (const elm of elms) { + removeClass(elm, /introjs-[a-zA-Z]+/g); + } +} diff --git a/src/packages/tour/introForElement.ts b/src/packages/tour/render.ts similarity index 55% rename from src/packages/tour/introForElement.ts rename to src/packages/tour/render.ts index f7ab03920..1fe3c7f0c 100644 --- a/src/packages/tour/introForElement.ts +++ b/src/packages/tour/render.ts @@ -1,10 +1,9 @@ -import addOverlayLayer from "../../core/addOverlayLayer"; +import addOverlayLayer from "./addOverlayLayer"; import DOMEvent from "../../util/DOMEvent"; import { nextStep } from "./steps"; -import onKeyDown from "../../core/onKeyDown"; +import onKeyDown from "./onKeyDown"; import onResize from "./onResize"; -import fetchIntroSteps from "./fetchSteps"; -import isFunction from "../../util/isFunction"; +import { fetchSteps } from "./steps"; import { Tour } from "./tour"; /** @@ -12,19 +11,16 @@ import { Tour } from "./tour"; * * @api private */ -export default async function introForElement( - tour: Tour, - targetElm: HTMLElement -): Promise { +export const render = async (tour: Tour): Promise => { // don't start the tour if the instance is not active - if (!tour.isActive()) return false; - - if (isFunction(tour._introStartCallback)) { - await tour._introStartCallback.call(tour, targetElm); + if (!tour.isActive()) { + return false; } + await tour.callback("start")?.call(tour, tour.getTargetElement()); + //set it to the introJs object - const steps = fetchIntroSteps(tour, targetElm); + const steps = fetchSteps(tour); if (steps.length === 0) { return false; @@ -33,11 +29,10 @@ export default async function introForElement( tour.setSteps(steps); //add overlay layer to the page - if (addOverlayLayer(tour, targetElm)) { + if (addOverlayLayer(tour)) { //then, start the show await nextStep(tour); - targetElm.addEventListener; if (tour.getOption("keyboardNavigation")) { DOMEvent.on(window, "keydown", onKeyDown, tour, true); } @@ -47,4 +42,4 @@ export default async function introForElement( } return false; -} +}; diff --git a/src/packages/tour/setHelperLayerPosition.ts b/src/packages/tour/setHelperLayerPosition.ts index 9ca39e2bf..27779ea18 100644 --- a/src/packages/tour/setHelperLayerPosition.ts +++ b/src/packages/tour/setHelperLayerPosition.ts @@ -1,7 +1,6 @@ import getOffset from "../../util/getOffset"; import isFixed from "../../util/isFixed"; -import addClass from "../../util/className"; -import removeClass from "../../util/removeClass"; +import { addClass, removeClass } from "../../util/className"; import setStyle from "../../util/setStyle"; import { TourStep } from "./steps"; import { Tour } from "./tour"; diff --git a/src/packages/tour/showElement.ts b/src/packages/tour/showElement.ts index 142ad79c0..a47953514 100644 --- a/src/packages/tour/showElement.ts +++ b/src/packages/tour/showElement.ts @@ -7,7 +7,7 @@ import setAnchorAsButton from "../../util/setAnchorAsButton"; import { TourStep, nextStep, previousStep } from "./steps"; import setHelperLayerPosition from "./setHelperLayerPosition"; import placeTooltip from "../../core/placeTooltip"; -import removeShowElement from "../../core/removeShowElement"; +import removeShowElement from "./removeShowElement"; import createElement from "../../util/createElement"; import setStyle from "../../util/setStyle"; import appendChild from "../../util/appendChild"; diff --git a/src/packages/tour/steps.test.ts b/src/packages/tour/steps.test.ts index e587c4853..2b75b2012 100644 --- a/src/packages/tour/steps.test.ts +++ b/src/packages/tour/steps.test.ts @@ -1,7 +1,7 @@ import { fetchSteps, nextStep, previousStep } from "./steps"; import _showElement from "./showElement"; import { appendMockSteps, getMockSteps, getMockTour } from "./tests/mock"; -import _createElement from "src/util/createElement"; +import createElement from "../../util/createElement"; jest.mock("./showElement"); jest.mock("./exitIntro"); @@ -143,7 +143,7 @@ describe("steps", () => { // Arrange const mockTour = getMockTour(); mockTour.addStep({ - element: document.createElement("div"), + element: createElement("div"), intro: "test step", }); @@ -164,7 +164,7 @@ describe("steps", () => { intro: "first step", }, { - element: document.createElement("div"), + element: createElement("div"), intro: "second step", }, ]); @@ -203,13 +203,7 @@ describe("steps", () => { test("should find and add elements from options.steps to the list", () => { // Arrange const mockTour = getMockTour(); - - const stepOne = document.createElement("div"); - stepOne.setAttribute("id", "first"); - const stepTwo = document.createElement("div"); - stepTwo.setAttribute("id", "second"); - document.body.appendChild(stepOne); - document.body.appendChild(stepTwo); + const [mockStepOneElement, mockStepTwoElement, _, __] = appendMockSteps(); // Act const steps = fetchSteps(mockTour); @@ -217,12 +211,12 @@ describe("steps", () => { // Assert expect(steps.length).toBe(7); - expect(steps[0].element).toBe(stepOne); + expect(steps[0].element).toBe(mockStepOneElement); expect(steps[0].position).toBe("bottom"); expect(steps[0].intro).toBe("first"); expect(steps[0].step).toBe(1); - expect(steps[1].element).toBe(stepTwo); + expect(steps[1].element).toBe(mockStepTwoElement); expect(steps[1].position).toBe("top"); expect(steps[1].intro).toBe("second"); expect(steps[1].step).toBe(2); @@ -234,7 +228,7 @@ describe("steps", () => { test("should find the data-* elements from the DOM", () => { // Arrange - const targetElement = document.createElement("div"); + const targetElement = createElement("div"); appendMockSteps(targetElement); const mockTour = getMockTour(targetElement); @@ -255,19 +249,9 @@ describe("steps", () => { test("should respect the custom step attribute (DOM)", () => { // Arrange - const targetElement = document.createElement("div"); - - const stepOne = document.createElement("div"); - stepOne.setAttribute("data-intro", "second"); - stepOne.setAttribute("data-step", "5"); - - const stepTwo = document.createElement("div"); - stepTwo.setAttribute("data-intro", "first"); - - targetElement.appendChild(stepOne); - targetElement.appendChild(stepTwo); + appendMockSteps(); - const mockTour = getMockTour(targetElement); + const mockTour = getMockTour(); // Act const steps = fetchSteps(mockTour); diff --git a/src/packages/tour/tests/mock.ts b/src/packages/tour/tests/mock.ts index 32341db99..0806be1cd 100644 --- a/src/packages/tour/tests/mock.ts +++ b/src/packages/tour/tests/mock.ts @@ -27,6 +27,8 @@ export const appendMockSteps = (targetElement: HTMLElement = document.body) => { targetElement.appendChild(mockElementTwo); targetElement.appendChild(mockElementThree); targetElement.appendChild(mockElementFour); + + return [mockElementOne, mockElementTwo, mockElementThree, mockElementFour]; }; export const getMockSteps = (): Partial[] => { diff --git a/src/packages/tour/tour.ts b/src/packages/tour/tour.ts index 7562ba592..ca1353d99 100644 --- a/src/packages/tour/tour.ts +++ b/src/packages/tour/tour.ts @@ -12,7 +12,7 @@ import { } from "./callback"; import { getDefaultTourOptions, TourOptions } from "./option"; import { setOptions, setOption } from "../../option"; -import introForElement from "./introForElement"; +import { render } from "./render"; import exitIntro from "./exitIntro"; import isFunction from "../../util/isFunction"; import { getDontShowAgain, setDontShowAgain } from "./dontShowAgain"; @@ -202,7 +202,7 @@ export class Tour implements Package { } async render(): Promise { - await introForElement(this, this._targetElement); + await render(this); return this; } @@ -211,7 +211,7 @@ export class Tour implements Package { * @returns */ async start() { - await introForElement(this, this._targetElement); + await this.render(); return this; } @@ -220,7 +220,15 @@ export class Tour implements Package { return this; } + /** + * @deprecated onbeforechange is deprecated, please use onBeforeChange instead. + * @returns + */ onbeforechange(callback: introBeforeChangeCallback) { + return this.onBeforeChange(callback); + } + + onBeforeChange(callback: introBeforeChangeCallback) { if (isFunction(callback)) { this.callbacks.beforeChange = callback; } else { @@ -230,8 +238,16 @@ export class Tour implements Package { } return this; } + + /** + * @deprecated onchange is deprecated, please use onChange instead. + */ onchange(callback: introChangeCallback) { + this.onChange(callback); + } + + onChange(callback: introChangeCallback) { if (isFunction(callback)) { this.callbacks.change = callback; } else { @@ -240,7 +256,14 @@ export class Tour implements Package { return this; } + /** + * @deprecated onafterchange is deprecated, please use onAfterChange instead. + */ onafterchange(callback: introAfterChangeCallback) { + this.onAfterChange(callback); + } + + onAfterChange(callback: introAfterChangeCallback) { if (isFunction(callback)) { this.callbacks.afterChange = callback; } else { @@ -249,7 +272,14 @@ export class Tour implements Package { return this; } + /** + * @deprecated oncomplete is deprecated, please use onComplete instead. + */ oncomplete(callback: introCompleteCallback) { + return this.onComplete(callback); + } + + onComplete(callback: introCompleteCallback) { if (isFunction(callback)) { this.callbacks.complete = callback; } else { @@ -258,7 +288,15 @@ export class Tour implements Package { return this; } + + /** + * @deprecated onstart is deprecated, please use onStart instead. + */ onstart(callback: introStartCallback) { + return this.onStart(callback); + } + + onStart(callback: introStartCallback) { if (isFunction(callback)) { this.callbacks.start = callback; } else { @@ -268,7 +306,14 @@ export class Tour implements Package { return this; } + /** + * @deprecated onexit is deprecated, please use onExit instead. + */ onexit(callback: introExitCallback) { + return this.onExit(callback); + } + + onExit(callback: introExitCallback) { if (isFunction(callback)) { this.callbacks.exit = callback; } else { @@ -278,7 +323,14 @@ export class Tour implements Package { return this; } + /** + * @deprecated onskip is deprecated, please use onSkip instead. + */ onskip(callback: introSkipCallback) { + return this.onSkip(callback); + } + + onSkip(callback: introSkipCallback) { if (isFunction(callback)) { this.callbacks.skip = callback; } else { @@ -288,7 +340,14 @@ export class Tour implements Package { return this; } + /** + * @deprecated onbeforeexit is deprecated, please use onBeforeExit instead. + */ onbeforeexit(callback: introBeforeExitCallback) { + return this.onBeforeExit(callback); + } + + onBeforeExit(callback: introBeforeExitCallback) { if (isFunction(callback)) { this.callbacks.beforeExit = callback; } else { diff --git a/src/util/addClass.test.ts b/src/util/addClass.test.ts deleted file mode 100644 index 5bccb7fa1..000000000 --- a/src/util/addClass.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { addClass, setClass } from "./className"; - -describe("className", () => { - describe("setClass", () => { - it("should set class name to an element", () => { - const el = document.createElement("div"); - el.className = "firstClass"; - - setClass(el, "secondClass"); - - expect(el.className).toBe("secondClass"); - }); - - it("should set class name to an SVG element", () => { - const el = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - el.setAttribute("class", "firstClass"); - - setClass(el, "secondClass"); - - expect(el.getAttribute("class")).toBe("secondClass"); - }); - }); - - describe("addClass", () => { - test("should append when className is empty", () => { - const el = document.createElement("div"); - addClass(el, "myClass"); - expect(el.className).toBe("myClass"); - }); - - test("should append when className is NOT empty", () => { - const el = document.createElement("div"); - el.className = "firstClass"; - - addClass(el, "secondClass"); - - expect(el.className).toBe("firstClass secondClass"); - }); - - test("should not append duplicate classNames to elements", () => { - const el = document.createElement("div"); - el.className = "firstClass"; - - addClass(el, "firstClass"); - - expect(el.className).toBe("firstClass"); - }); - - test("should not append duplicate list of classNames to elements", () => { - const el = document.createElement("div"); - el.className = "firstClass firstClass"; - - addClass(el, "firstClass", "firstClass", "firstClass"); - - expect(el.className).toBe("firstClass"); - }); - - test("should not append duplicate list of classNames to an empty className", () => { - const el = document.createElement("div"); - - addClass(el, "firstClass", "firstClass", "firstClass"); - - expect(el.className).toBe("firstClass"); - }); - - test("should append lassNames to an SVG", () => { - const el = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - el.setAttribute("class", "firstClass"); - - addClass(el, "secondClass", "thirdClass"); - - expect(el.getAttribute('class')).toBe("firstClass secondClass thirdClass"); - }); - - test("should not append duplicate list of classNames to an empty className of SVG", () => { - const el = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - el.setAttribute("class", "firstClass"); - - addClass(el, "firstClass", "firstClass", "firstClass"); - - expect(el.getAttribute('class')).toBe("firstClass"); - }); - }); -}); diff --git a/src/util/className.ts b/src/util/className.ts index e44eb6100..db6d4ef1e 100644 --- a/src/util/className.ts +++ b/src/util/className.ts @@ -48,3 +48,27 @@ export const setClass = ( } } }; + +/** + * Remove a class from an element + * + * @api private + */ +export const removeClass = ( + element: HTMLElement | SVGElement, + classNameRegex: RegExp | string +) => { + if (element instanceof SVGElement) { + const pre = element.getAttribute("class") || ""; + + element.setAttribute( + "class", + pre.replace(classNameRegex, "").replace(/\s\s+/g, " ").trim() + ); + } else { + element.className = element.className + .replace(classNameRegex, "") + .replace(/\s\s+/g, " ") + .trim(); + } +}; diff --git a/src/util/clssName.test.ts b/src/util/clssName.test.ts new file mode 100644 index 000000000..0c254ccf4 --- /dev/null +++ b/src/util/clssName.test.ts @@ -0,0 +1,194 @@ +import { removeClass, addClass, setClass } from "./className"; + +describe("className", () => { + describe("setClass", () => { + it("should set class name to an element", () => { + const el = document.createElement("div"); + el.className = "firstClass"; + + setClass(el, "secondClass"); + + expect(el.className).toBe("secondClass"); + }); + + it("should set class name to an SVG element", () => { + const el = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + el.setAttribute("class", "firstClass"); + + setClass(el, "secondClass"); + + expect(el.getAttribute("class")).toBe("secondClass"); + }); + }); + + describe("addClass", () => { + test("should append when className is empty", () => { + const el = document.createElement("div"); + addClass(el, "myClass"); + expect(el.className).toBe("myClass"); + }); + + test("should append when className is NOT empty", () => { + const el = document.createElement("div"); + el.className = "firstClass"; + + addClass(el, "secondClass"); + + expect(el.className).toBe("firstClass secondClass"); + }); + + test("should not append duplicate classNames to elements", () => { + const el = document.createElement("div"); + el.className = "firstClass"; + + addClass(el, "firstClass"); + + expect(el.className).toBe("firstClass"); + }); + + test("should not append duplicate list of classNames to elements", () => { + const el = document.createElement("div"); + el.className = "firstClass firstClass"; + + addClass(el, "firstClass", "firstClass", "firstClass"); + + expect(el.className).toBe("firstClass"); + }); + + test("should not append duplicate list of classNames to an empty className", () => { + const el = document.createElement("div"); + + addClass(el, "firstClass", "firstClass", "firstClass"); + + expect(el.className).toBe("firstClass"); + }); + + test("should append lassNames to an SVG", () => { + const el = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + el.setAttribute("class", "firstClass"); + + addClass(el, "secondClass", "thirdClass"); + + expect(el.getAttribute('class')).toBe("firstClass secondClass thirdClass"); + }); + + test("should not append duplicate list of classNames to an empty className of SVG", () => { + const el = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + el.setAttribute("class", "firstClass"); + + addClass(el, "firstClass", "firstClass", "firstClass"); + + expect(el.getAttribute('class')).toBe("firstClass"); + }); + }); + + describe('removeClass', () => { + it('should do nothing when the class name is not found', () => { + // Arrange + const el = document.createElement('div'); + el.className = 'firstClass'; + + // Act + removeClass(el, 'secondClass'); + + // Assert + expect(el.className).toBe('firstClass'); + }); + + it('should remove middle class name from an element', () => { + // Arrange + const el = document.createElement('div'); + el.className = 'firstClass secondClass thirdClass'; + + // Act + removeClass(el, 'secondClass'); + + // Assert + expect(el.className).toBe('firstClass thirdClass'); + }); + + it('should remove the first class name from an element', () => { + // Arrange + const el = document.createElement('div'); + el.className = 'firstClass secondClass thirdClass'; + + // Act + removeClass(el, 'firstClass'); + + // Assert + expect(el.className).toBe('secondClass thirdClass'); + }); + + it('should remove the last class name from an element', () => { + // Arrange + const el = document.createElement('div'); + el.className = 'firstClass secondClass thirdClass'; + + // Act + removeClass(el, 'thirdClass'); + + // Assert + expect(el.className).toBe('firstClass secondClass'); + }); + + it('should remove the only class name from an element', () => { + // Arrange + const el = document.createElement('div'); + el.className = 'secondClass'; + + // Act + removeClass(el, 'secondClass'); + + // Assert + expect(el.className).toBe(''); + }); + + it('should remove the first class name from an SVG element', () => { + // Arrange + const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + el.setAttribute('class', 'firstClass secondClass thirdClass'); + + // Act + removeClass(el, 'firstClass'); + + // Assert + expect(el.getAttribute('class')).toBe('secondClass thirdClass'); + }); + + it('should remove middle class name from an SVG element', () => { + // Arrange + const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + el.setAttribute('class', 'firstClass secondClass thirdClass'); + + // Act + removeClass(el, 'secondClass'); + + // Assert + expect(el.getAttribute('class')).toBe('firstClass thirdClass'); + }); + + it('should remove the last class name from an SVG element', () => { + // Arrange + const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + el.setAttribute('class', 'firstClass secondClass'); + + // Act + removeClass(el, 'secondClass'); + + // Assert + expect(el.getAttribute('class')).toBe('firstClass'); + }); + + it('should remove the only class name from an SVG element', () => { + // Arrange + const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + el.setAttribute('class', 'secondClass'); + + // Act + removeClass(el, 'secondClass'); + + // Assert + expect(el.getAttribute('class')).toBe(''); + }); + }); +}); diff --git a/src/util/removeClass.ts b/src/util/removeClass.ts index 361b4183a..e69de29bb 100644 --- a/src/util/removeClass.ts +++ b/src/util/removeClass.ts @@ -1,22 +0,0 @@ -/** - * Remove a class from an element - * - * @api private - */ -export default function removeClass( - element: HTMLElement, - classNameRegex: RegExp | string -) { - if (element instanceof SVGElement) { - const pre = element.getAttribute("class") || ""; - - element.setAttribute( - "class", - pre.replace(classNameRegex, "").replace(/^\s+|\s+$/g, "") - ); - } else { - element.className = element.className - .replace(classNameRegex, "") - .replace(/^\s+|\s+$/g, ""); - } -} diff --git a/tests/jest/core/introForElement.test.ts b/tests/jest/core/introForElement.test.ts index ec5ef4a13..17463e614 100644 --- a/tests/jest/core/introForElement.test.ts +++ b/tests/jest/core/introForElement.test.ts @@ -1,6 +1,6 @@ -import introForElement from "../../../src/packages/tour/introForElement"; +import introForElement from "../../../src/packages/tour/render"; import * as fetchIntroSteps from "../../../src/packages/tour/fetchSteps"; -import * as addOverlayLayer from "../../../src/core/addOverlayLayer"; +import * as addOverlayLayer from "../../../src/packages/tour/addOverlayLayer"; import * as nextStep from "../../../src/packages/tour/steps"; import introJs from "../../../src";