Skip to content

Commit cff6580

Browse files
committed
feat: dontShowAgain checkbox and module
1 parent ca56884 commit cff6580

File tree

7 files changed

+301
-0
lines changed

7 files changed

+301
-0
lines changed

src/core/dontShowAgain.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { deleteCookie, getCookie, setCookie } from "../util/cookie";
2+
3+
const dontShowAgainCookieValue = "true";
4+
5+
/**
6+
* Set the "Don't show again" state
7+
*
8+
* @api private
9+
* @param {Boolean} dontShowAgain
10+
* @method setDontShowAgain
11+
*/
12+
export function setDontShowAgain(dontShowAgain) {
13+
if (dontShowAgain) {
14+
setCookie(
15+
this._options.dontShowAgainCookie,
16+
dontShowAgainCookieValue,
17+
this._options.dontShowAgainCookieDays
18+
);
19+
} else {
20+
deleteCookie(this._options.dontShowAgainCookie);
21+
}
22+
}
23+
24+
/**
25+
* Get the "Don't show again" state from cookies
26+
*
27+
* @api private
28+
* @method getDontShowAgain
29+
*/
30+
export function getDontShowAgain() {
31+
const dontShowCookie = getCookie(this._options.dontShowAgainCookie);
32+
return dontShowCookie && dontShowCookie === dontShowAgainCookieValue;
33+
}

src/core/showElement.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,30 @@ export default function _showElement(targetElement) {
356356
tooltipHeaderLayer.appendChild(tooltipTitleLayer);
357357
tooltipLayer.appendChild(tooltipHeaderLayer);
358358
tooltipLayer.appendChild(tooltipTextLayer);
359+
360+
// "Do not show again" checkbox
361+
if (this._options.dontShowAgain) {
362+
const dontShowAgainWrapper = createElement("div", {
363+
className: "introjs-dontShowAgain",
364+
});
365+
const dontShowAgainCheckbox = createElement("input", {
366+
type: "checkbox",
367+
id: "introjs-dontShowAgain",
368+
name: "introjs-dontShowAgain",
369+
});
370+
dontShowAgainCheckbox.onchange = (e) => {
371+
this.setDontShowAgain(e.target.checked);
372+
};
373+
const dontShowAgainCheckboxLabel = createElement("label", {
374+
htmlFor: "introjs-dontShowAgain",
375+
});
376+
dontShowAgainCheckboxLabel.innerText = this._options.dontShowAgainLabel;
377+
dontShowAgainWrapper.appendChild(dontShowAgainCheckbox);
378+
dontShowAgainWrapper.appendChild(dontShowAgainCheckboxLabel);
379+
380+
tooltipLayer.appendChild(dontShowAgainWrapper);
381+
}
382+
359383
tooltipLayer.appendChild(_createBullets.call(this, targetElement));
360384
tooltipLayer.appendChild(_createProgressBar.call(this));
361385

src/index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import stamp from "./util/stamp";
33
import exitIntro from "./core/exitIntro";
44
import refresh from "./core/refresh";
55
import introForElement from "./core/introForElement";
6+
import { getDontShowAgain, setDontShowAgain } from "./core/dontShowAgain";
67
import { version } from "../package.json";
78
import {
89
populateHints,
@@ -88,6 +89,12 @@ function IntroJs(obj) {
8889
positionPrecedence: ["bottom", "top", "right", "left"],
8990
/* Disable an interaction with element? */
9091
disableInteraction: false,
92+
/* To display the "Don't show again" checkbox in the tour */
93+
dontShowAgain: false,
94+
dontShowAgainLabel: "Don't show this again",
95+
/* "Don't show again" cookie name and expiry (in days) */
96+
dontShowAgainCookie: "introjs-dontShowAgain",
97+
dontShowAgainCookieDays: 365,
9198
/* Set how much padding to be used around helper element */
9299
helperElementPadding: 10,
93100
/* Default hint position */
@@ -152,6 +159,10 @@ introJs.instances = {};
152159
//Prototype
153160
introJs.fn = IntroJs.prototype = {
154161
isActive() {
162+
if (this._options.dontShowAgain && getDontShowAgain.call(this)) {
163+
return false;
164+
}
165+
155166
return this._options.isActive;
156167
},
157168
clone() {
@@ -215,6 +226,10 @@ introJs.fn = IntroJs.prototype = {
215226
refresh.call(this, refreshSteps);
216227
return this;
217228
},
229+
setDontShowAgain(dontShowAgain) {
230+
setDontShowAgain.call(this, dontShowAgain);
231+
return this;
232+
},
218233
onbeforechange(providedCallback) {
219234
if (typeof providedCallback === "function") {
220235
this._introBeforeChangeCallback = providedCallback;

src/styles/introjs.scss

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,32 @@ tr.introjs-showElement {
175175
padding: 20px;
176176
}
177177

178+
.introjs-dontShowAgain {
179+
padding-left: 20px;
180+
padding-right: 20px;
181+
}
182+
183+
.introjs-dontShowAgain input {
184+
padding: 0;
185+
margin: 0;
186+
margin-bottom: 2px;
187+
display: inline;
188+
width: 10px;
189+
height: 10px;
190+
}
191+
192+
.introjs-dontShowAgain label {
193+
font-size: 14px;
194+
display: inline-block;
195+
font-weight: normal;
196+
display: inline-block;
197+
margin: 0 0 0 5px;
198+
padding: 0;
199+
background-color: $white;
200+
color: $black600;
201+
user-select: none;
202+
}
203+
178204
.introjs-tooltip-title {
179205
font-size: 18px;
180206
margin: 0;

tests/core/dontShowAgain.test.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as cookie from "../../src/util/cookie";
2+
import {
3+
setDontShowAgain,
4+
getDontShowAgain,
5+
} from "../../src/core/dontShowAgain";
6+
7+
describe("dontShowAgain", () => {
8+
test("should call set cookie", () => {
9+
const setCookieMock = jest.spyOn(cookie, "setCookie");
10+
11+
setDontShowAgain.call(
12+
{
13+
_options: {
14+
dontShowAgainCookie: "cookie-name",
15+
dontShowAgainCookieDays: 7,
16+
},
17+
},
18+
true
19+
);
20+
21+
expect(setCookieMock).toBeCalledTimes(1);
22+
expect(setCookieMock).toBeCalledWith("cookie-name", "true", 7);
23+
});
24+
25+
test("should call delete cookie", () => {
26+
const setCookieMock = jest.spyOn(cookie, "setCookie");
27+
const deleteCookieMock = jest.spyOn(cookie, "deleteCookie");
28+
29+
setDontShowAgain.call(
30+
{
31+
_options: {
32+
dontShowAgainCookie: "cookie-name",
33+
dontShowAgainCookieDays: 7,
34+
},
35+
},
36+
false
37+
);
38+
39+
expect(setCookieMock).toBeCalledTimes(0);
40+
expect(deleteCookieMock).toBeCalledTimes(1);
41+
expect(deleteCookieMock).toBeCalledWith("cookie-name");
42+
});
43+
44+
test("should return true when cookie is valid", () => {
45+
jest.spyOn(cookie, "getCookie").mockReturnValue("true");
46+
47+
expect(
48+
getDontShowAgain.call({
49+
_options: {
50+
dontShowAgainCookie: "cookie-name",
51+
dontShowAgainCookieDays: 7,
52+
},
53+
})
54+
).toBe(true);
55+
});
56+
57+
test("should return false when cookie is invalid", () => {
58+
jest.spyOn(cookie, "getCookie").mockReturnValue("invalid-state");
59+
60+
expect(
61+
getDontShowAgain.call({
62+
_options: {
63+
dontShowAgainCookie: "cookie-name",
64+
dontShowAgainCookieDays: 7,
65+
},
66+
})
67+
).toBe(false);
68+
});
69+
});

tests/index.test.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import introJs from "../src";
22
import {
3+
find,
34
content,
45
className,
56
skipButton,
@@ -10,6 +11,10 @@ import {
1011
appendDummyElement,
1112
} from "./helper";
1213

14+
jest.mock("../src/core/dontShowAgain");
15+
16+
import { getDontShowAgain, setDontShowAgain } from "../src/core/dontShowAgain";
17+
1318
describe("intro", () => {
1419
beforeEach(() => {
1520
document.getElementsByTagName("html")[0].innerHTML = "";
@@ -241,4 +246,85 @@ describe("intro", () => {
241246
expect(intro1["introjs-instance"]).not.toBe(intro2["introjs-instance"]);
242247
expect(intro2["introjs-instance"]).not.toBe(intro3["introjs-instance"]);
243248
});
249+
250+
test("should not append the dontShowAgain checkbox when its inactive", () => {
251+
introJs()
252+
.setOptions({
253+
dontShowAgain: false,
254+
steps: [
255+
{
256+
intro: "hello world",
257+
},
258+
],
259+
})
260+
.start();
261+
262+
expect(find(".introjs-dontShowAgain")).toBeNull();
263+
});
264+
265+
test("should append the dontShowAgain checkbox", () => {
266+
introJs()
267+
.setOptions({
268+
dontShowAgain: true,
269+
steps: [
270+
{
271+
intro: "hello world",
272+
},
273+
],
274+
})
275+
.start();
276+
277+
expect(find(".introjs-dontShowAgain")).not.toBeNull();
278+
});
279+
280+
test("should call setDontShowAgain when then checkbox is clicked", () => {
281+
introJs()
282+
.setOptions({
283+
dontShowAgain: true,
284+
steps: [
285+
{
286+
intro: "hello world",
287+
},
288+
],
289+
})
290+
.start();
291+
292+
const checkbox = find(".introjs-dontShowAgain input");
293+
294+
checkbox.click();
295+
296+
expect(setDontShowAgain).toBeCalledTimes(1);
297+
expect(setDontShowAgain).toBeCalledWith(true);
298+
});
299+
300+
describe("isActive", () => {
301+
test("should be false if isActive flag is false", () => {
302+
const intro = introJs().setOptions({
303+
isActive: false,
304+
});
305+
306+
expect(intro.isActive()).toBeFalsy();
307+
});
308+
test("should be true if dontShowAgain is active but cookie is missing", () => {
309+
getDontShowAgain.mockReturnValueOnce(false);
310+
311+
const intro = introJs().setOptions({
312+
isActive: true,
313+
dontShowAgain: true,
314+
});
315+
316+
expect(intro.isActive()).toBeTruthy();
317+
});
318+
319+
test("should be false if dontShowAgain is active but isActive is true", () => {
320+
getDontShowAgain.mockReturnValueOnce(true);
321+
322+
const intro = introJs().setOptions({
323+
isActive: true,
324+
dontShowAgain: true,
325+
});
326+
327+
expect(intro.isActive()).toBeFalsy();
328+
});
329+
});
244330
});

tests/util/cookie.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {
2+
getAllCookies,
3+
getCookie,
4+
setCookie,
5+
deleteCookie,
6+
} from "../../src/util/cookie";
7+
8+
describe("cookie", () => {
9+
let _cookie = "";
10+
11+
beforeAll(() => {
12+
Object.defineProperty(window.document, "cookie", {
13+
get: () => _cookie,
14+
set: (v) => (_cookie = v),
15+
split: (s) => _cookie.split(s),
16+
});
17+
});
18+
19+
beforeEach(() => {
20+
_cookie = "hello=world;abc=bar";
21+
});
22+
23+
test("should return undefined when cookie doesnt exist", () => {
24+
expect(getCookie("doesntExist")).toBe(undefined);
25+
});
26+
27+
test("should return the cookie name", () => {
28+
expect(getCookie("abc")).toBe("bar");
29+
});
30+
31+
test("should return all cookies", () => {
32+
expect(getAllCookies()).toEqual({
33+
abc: "bar",
34+
hello: "world",
35+
});
36+
});
37+
38+
test("should set cookie", () => {
39+
setCookie("new", "foo");
40+
expect(getCookie("new")).toEqual("foo");
41+
});
42+
43+
test("should delete cookie", () => {
44+
expect(getCookie("hello")).toEqual("world");
45+
deleteCookie("hello");
46+
expect(getCookie("hello")).toEqual("");
47+
});
48+
});

0 commit comments

Comments
 (0)