2
0

feat(lib): Add auto open delay for bubble embed

This commit is contained in:
Baptiste Arnaud
2022-03-14 11:38:57 +01:00
parent 80679dfbd0
commit d6b94130cb
8 changed files with 289 additions and 278 deletions

View File

@@ -3,7 +3,7 @@ name: Publish package to NPM
on: on:
push: push:
tags: tags:
- 'v*.*.*' - 'js-lib-v*.*.*'
jobs: jobs:
publish: publish:
@@ -15,10 +15,11 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 12 node-version: 14
- run: yarn - run: yarn
- run: yarn test - run: yarn test
- run: yarn build - run: yarn build
- uses: JS-DevTools/npm-publish@v1 - uses: JS-DevTools/npm-publish@v1
with: with:
package: './packages/typebot-js/package.json'
token: ${{ secrets.NPM_TOKEN }} token: ${{ secrets.NPM_TOKEN }}

View File

@@ -1,6 +1,6 @@
{ {
"name": "typebot-js", "name": "typebot-js",
"version": "2.1.3", "version": "2.1.4",
"main": "dist/index.js", "main": "dist/index.js",
"unpkg": "dist/index.umd.min.js", "unpkg": "dist/index.umd.min.js",
"license": "MIT", "license": "MIT",

View File

@@ -3,85 +3,91 @@ import {
BubbleParams, BubbleParams,
localStorageKeys, localStorageKeys,
ProactiveMessageParams, ProactiveMessageParams,
} from "../../types"; } from '../../types'
import { createButton } from "./button"; import { createButton } from './button'
import { import {
closeIframe, closeIframe,
createIframeContainer, createIframeContainer,
loadTypebotIfFirstOpen, loadTypebotIfFirstOpen,
openIframe, openIframe,
} from "./iframe"; } from './iframe'
import { import {
createProactiveMessage, createProactiveMessage,
openProactiveMessage, openProactiveMessage,
} from "./proactiveMessage"; } from './proactiveMessage'
import "./style.css"; import './style.css'
export const initBubble = (params: BubbleParams): BubbleActions => { export const initBubble = (params: BubbleParams): BubbleActions => {
if (document.readyState !== "complete") { if (document.readyState !== 'complete') {
window.addEventListener("load", () => initBubble(params)); window.addEventListener('load', () => initBubble(params))
return { close: () => {}, open: () => {} }; return { close: () => {}, open: () => {} }
} }
const existingBubble = document.getElementById("typebot-bubble") as const existingBubble = document.getElementById('typebot-bubble') as
| HTMLDivElement | HTMLDivElement
| undefined; | undefined
if (existingBubble) existingBubble.remove(); if (existingBubble) existingBubble.remove()
const { bubbleElement, proactiveMessageElement, iframeElement } = const { bubbleElement, proactiveMessageElement, iframeElement } =
createBubble(params); createBubble(params)
if (
(params.autoOpenDelay || params.autoOpenDelay === 0) &&
!hasBeenClosed()
) {
setRememberCloseInStorage()
setTimeout(
() => openIframe(bubbleElement, iframeElement),
params.autoOpenDelay
)
}
!document.body !document.body
? (window.onload = () => document.body.appendChild(bubbleElement)) ? (window.onload = () => document.body.appendChild(bubbleElement))
: document.body.appendChild(bubbleElement); : document.body.appendChild(bubbleElement)
return getBubbleActions( return getBubbleActions(bubbleElement, iframeElement, proactiveMessageElement)
bubbleElement, }
iframeElement,
proactiveMessageElement
);
};
const createBubble = ( const createBubble = (
params: BubbleParams params: BubbleParams
): { ): {
bubbleElement: HTMLDivElement; bubbleElement: HTMLDivElement
iframeElement: HTMLIFrameElement; iframeElement: HTMLIFrameElement
proactiveMessageElement?: HTMLDivElement; proactiveMessageElement?: HTMLDivElement
} => { } => {
const bubbleElement = document.createElement("div"); const bubbleElement = document.createElement('div')
bubbleElement.id = "typebot-bubble"; bubbleElement.id = 'typebot-bubble'
const buttonElement = createButton(params.button); const buttonElement = createButton(params.button)
bubbleElement.appendChild(buttonElement); bubbleElement.appendChild(buttonElement)
const proactiveMessageElement = const proactiveMessageElement =
params.proactiveMessage && !hasBeenClosed() params.proactiveMessage && !hasBeenClosed()
? addProactiveMessage(params.proactiveMessage, bubbleElement) ? addProactiveMessage(params.proactiveMessage, bubbleElement)
: undefined; : undefined
const iframeElement = createIframeContainer(params); const iframeElement = createIframeContainer(params)
buttonElement.addEventListener("click", () => buttonElement.addEventListener('click', () =>
onBubbleButtonClick(bubbleElement, iframeElement) onBubbleButtonClick(bubbleElement, iframeElement)
); )
if (proactiveMessageElement) if (proactiveMessageElement)
proactiveMessageElement.addEventListener("click", () => proactiveMessageElement.addEventListener('click', () =>
onProactiveMessageClick(bubbleElement, iframeElement) onProactiveMessageClick(bubbleElement, iframeElement)
); )
bubbleElement.appendChild(iframeElement); bubbleElement.appendChild(iframeElement)
return { bubbleElement, proactiveMessageElement, iframeElement }; return { bubbleElement, proactiveMessageElement, iframeElement }
}; }
const onBubbleButtonClick = ( const onBubbleButtonClick = (
bubble: HTMLDivElement, bubble: HTMLDivElement,
iframe: HTMLIFrameElement iframe: HTMLIFrameElement
): void => { ): void => {
loadTypebotIfFirstOpen(iframe); loadTypebotIfFirstOpen(iframe)
bubble.classList.toggle("iframe-opened"); bubble.classList.toggle('iframe-opened')
bubble.classList.remove("message-opened"); bubble.classList.remove('message-opened')
}; }
const onProactiveMessageClick = ( const onProactiveMessageClick = (
bubble: HTMLDivElement, bubble: HTMLDivElement,
iframe: HTMLIFrameElement iframe: HTMLIFrameElement
): void => { ): void => {
loadTypebotIfFirstOpen(iframe); loadTypebotIfFirstOpen(iframe)
bubble.classList.add("iframe-opened"); bubble.classList.add('iframe-opened')
bubble.classList.remove("message-opened"); bubble.classList.remove('message-opened')
}; }
export const getBubbleActions = ( export const getBubbleActions = (
bubbleElement?: HTMLDivElement, bubbleElement?: HTMLDivElement,
@@ -90,29 +96,29 @@ export const getBubbleActions = (
): BubbleActions => { ): BubbleActions => {
const existingBubbleElement = const existingBubbleElement =
bubbleElement ?? bubbleElement ??
(document.querySelector("#typebot-bubble") as HTMLDivElement); (document.querySelector('#typebot-bubble') as HTMLDivElement)
const existingIframeElement = const existingIframeElement =
iframeElement ?? iframeElement ??
(existingBubbleElement.querySelector( (existingBubbleElement.querySelector(
".typebot-iframe" '.typebot-iframe'
) as HTMLIFrameElement); ) as HTMLIFrameElement)
const existingProactiveMessage = const existingProactiveMessage =
proactiveMessageElement ?? proactiveMessageElement ??
document.querySelector("#typebot-bubble .proactive-message"); document.querySelector('#typebot-bubble .proactive-message')
return { return {
openProactiveMessage: existingProactiveMessage openProactiveMessage: existingProactiveMessage
? () => { ? () => {
openProactiveMessage(existingBubbleElement); openProactiveMessage(existingBubbleElement)
} }
: undefined, : undefined,
open: () => { open: () => {
openIframe(existingBubbleElement, existingIframeElement); openIframe(existingBubbleElement, existingIframeElement)
}, },
close: () => { close: () => {
closeIframe(existingBubbleElement); closeIframe(existingBubbleElement)
}, },
}; }
}; }
const addProactiveMessage = ( const addProactiveMessage = (
proactiveMessage: ProactiveMessageParams, proactiveMessage: ProactiveMessageParams,
@@ -121,14 +127,17 @@ const addProactiveMessage = (
const proactiveMessageElement = createProactiveMessage( const proactiveMessageElement = createProactiveMessage(
proactiveMessage, proactiveMessage,
bubbleElement bubbleElement
); )
bubbleElement.appendChild(proactiveMessageElement); bubbleElement.appendChild(proactiveMessageElement)
return proactiveMessageElement; return proactiveMessageElement
}; }
const hasBeenClosed = () => { const hasBeenClosed = () => {
const closeDecisionFromStorage = localStorage.getItem( const closeDecisionFromStorage = localStorage.getItem(
localStorageKeys.rememberClose localStorageKeys.rememberClose
); )
return closeDecisionFromStorage ? true : false; return closeDecisionFromStorage ? true : false
}; }
export const setRememberCloseInStorage = () =>
localStorage.setItem(localStorageKeys.rememberClose, 'true')

View File

@@ -1,66 +1,64 @@
import { localStorageKeys, ProactiveMessageParams } from "../../types"; import { setRememberCloseInStorage } from '../chat/index'
import { closeSvgPath } from "./button"; import { ProactiveMessageParams } from '../../types'
import { closeSvgPath } from './button'
const createProactiveMessage = ( const createProactiveMessage = (
params: ProactiveMessageParams, params: ProactiveMessageParams,
bubble: HTMLDivElement bubble: HTMLDivElement
): HTMLDivElement => { ): HTMLDivElement => {
const container = document.createElement("div"); const container = document.createElement('div')
container.classList.add("proactive-message"); container.classList.add('proactive-message')
if (params.delay !== undefined) setOpenTimeout(bubble, params); if (params.delay !== undefined) setOpenTimeout(bubble, params)
if (params.avatarUrl) container.appendChild(createAvatar(params.avatarUrl)); if (params.avatarUrl) container.appendChild(createAvatar(params.avatarUrl))
if (params.rememberClose) setRememberCloseInStorage(); container.appendChild(createTextElement(params.textContent))
container.appendChild(createTextElement(params.textContent)); container.appendChild(createCloseButton(bubble))
container.appendChild(createCloseButton(bubble)); return container
return container; }
};
const setOpenTimeout = ( const setOpenTimeout = (
bubble: HTMLDivElement, bubble: HTMLDivElement,
params: ProactiveMessageParams params: ProactiveMessageParams
) => { ) => {
setTimeout(() => { setTimeout(() => {
openProactiveMessage(bubble); openProactiveMessage(bubble)
}, params.delay); }, params.delay)
}; }
const createAvatar = (avatarUrl: string): HTMLImageElement => { const createAvatar = (avatarUrl: string): HTMLImageElement => {
const element = document.createElement("img"); const element = document.createElement('img')
element.src = avatarUrl; element.src = avatarUrl
return element; return element
}; }
const createTextElement = (text: string): HTMLParagraphElement => { const createTextElement = (text: string): HTMLParagraphElement => {
const element = document.createElement("p"); const element = document.createElement('p')
element.innerHTML = text; element.innerHTML = text
return element; return element
}; }
const createCloseButton = (bubble: HTMLDivElement): HTMLButtonElement => { const createCloseButton = (bubble: HTMLDivElement): HTMLButtonElement => {
const button = document.createElement("button"); const button = document.createElement('button')
button.classList.add("close-button"); button.classList.add('close-button')
button.innerHTML = `<svg viewBox="0 0 512 512">${closeSvgPath}</svg>`; button.innerHTML = `<svg viewBox="0 0 512 512">${closeSvgPath}</svg>`
button.addEventListener("click", (e) => onCloseButtonClick(e, bubble)); button.addEventListener('click', (e) => onCloseButtonClick(e, bubble))
return button; return button
}; }
const openProactiveMessage = (bubble: HTMLDivElement): void => { const openProactiveMessage = (bubble: HTMLDivElement): void => {
bubble.classList.add("message-opened"); bubble.classList.add('message-opened')
}; }
const onCloseButtonClick = ( const onCloseButtonClick = (
e: Event, e: Event,
proactiveMessageElement: HTMLDivElement proactiveMessageElement: HTMLDivElement
) => { ) => {
e.stopPropagation(); e.stopPropagation()
closeProactiveMessage(proactiveMessageElement); closeProactiveMessage(proactiveMessageElement)
}; }
const closeProactiveMessage = (bubble: HTMLDivElement): void => { const closeProactiveMessage = (bubble: HTMLDivElement): void => {
bubble.classList.remove("message-opened"); setRememberCloseInStorage()
}; bubble.classList.remove('message-opened')
}
const setRememberCloseInStorage = () => export { createProactiveMessage, openProactiveMessage, closeProactiveMessage }
localStorage.setItem(localStorageKeys.rememberClose, "true");
export { createProactiveMessage, openProactiveMessage, closeProactiveMessage };

View File

@@ -1,58 +1,58 @@
import { DataFromTypebot, IframeCallbacks, IframeParams } from "../types"; import { DataFromTypebot, IframeCallbacks, IframeParams } from '../types'
import "./style.css"; import './style.css'
export const createIframe = ({ export const createIframe = ({
backgroundColor, backgroundColor,
viewerHost = "https://typebot-viewer.vercel.app", viewerHost = 'https://typebot-viewer.vercel.app',
isV1,
...iframeParams ...iframeParams
}: IframeParams): HTMLIFrameElement => { }: IframeParams): HTMLIFrameElement => {
const { publishId, loadWhenVisible, hiddenVariables } = iframeParams; const { publishId, loadWhenVisible, hiddenVariables } = iframeParams
const iframeUrl = `${viewerHost}/${publishId}${parseQueryParams( const host = isV1 ? `https://bot.typebot.io` : viewerHost
hiddenVariables const iframeUrl = `${host}/${publishId}${parseQueryParams(hiddenVariables)}`
)}`; const iframe = document.createElement('iframe')
const iframe = document.createElement("iframe"); iframe.setAttribute(loadWhenVisible ? 'data-src' : 'src', iframeUrl)
iframe.setAttribute(loadWhenVisible ? "data-src" : "src", iframeUrl); iframe.setAttribute('data-id', iframeParams.publishId)
iframe.setAttribute("data-id", iframeParams.publishId); const randomThreeLettersId = Math.random().toString(36).substring(7)
const randomThreeLettersId = Math.random().toString(36).substring(7); const uniqueId = `${publishId}-${randomThreeLettersId}`
const uniqueId = `${publishId}-${randomThreeLettersId}`; iframe.setAttribute('id', uniqueId)
iframe.setAttribute("id", uniqueId); if (backgroundColor) iframe.style.backgroundColor = backgroundColor
if (backgroundColor) iframe.style.backgroundColor = backgroundColor; iframe.classList.add('typebot-iframe')
iframe.classList.add("typebot-iframe"); const { onNewVariableValue, onVideoPlayed } = iframeParams
const { onNewVariableValue, onVideoPlayed } = iframeParams; listenForTypebotMessages({ onNewVariableValue, onVideoPlayed })
listenForTypebotMessages({ onNewVariableValue, onVideoPlayed }); return iframe
return iframe; }
};
const parseQueryParams = (starterVariables?: { const parseQueryParams = (starterVariables?: {
[key: string]: string | undefined; [key: string]: string | undefined
}): string => { }): string => {
return parseHostnameQueryParam() + parseStarterVariables(starterVariables); return parseHostnameQueryParam() + parseStarterVariables(starterVariables)
}; }
const parseHostnameQueryParam = () => { const parseHostnameQueryParam = () => {
return `?hn=${window.location.hostname}`; return `?hn=${window.location.hostname}`
}; }
const parseStarterVariables = (starterVariables?: { const parseStarterVariables = (starterVariables?: {
[key: string]: string | undefined; [key: string]: string | undefined
}) => }) =>
starterVariables starterVariables
? `&${Object.keys(starterVariables) ? `&${Object.keys(starterVariables)
.filter((key) => starterVariables[key]) .filter((key) => starterVariables[key])
.map((key) => `${key}=${starterVariables[key]}`) .map((key) => `${key}=${starterVariables[key]}`)
.join("&")}` .join('&')}`
: ""; : ''
export const listenForTypebotMessages = (callbacks: IframeCallbacks) => { export const listenForTypebotMessages = (callbacks: IframeCallbacks) => {
window.addEventListener("message", (event) => { window.addEventListener('message', (event) => {
const data = event.data as { from?: "typebot" } & DataFromTypebot; const data = event.data as { from?: 'typebot' } & DataFromTypebot
if (data.from === "typebot") processMessage(event.data, callbacks); if (data.from === 'typebot') processMessage(event.data, callbacks)
}); })
}; }
const processMessage = (data: DataFromTypebot, callbacks: IframeCallbacks) => { const processMessage = (data: DataFromTypebot, callbacks: IframeCallbacks) => {
if (data.redirectUrl) window.open(data.redirectUrl); if (data.redirectUrl) window.open(data.redirectUrl)
if (data.newVariableValue && callbacks.onNewVariableValue) if (data.newVariableValue && callbacks.onNewVariableValue)
callbacks.onNewVariableValue(data.newVariableValue); callbacks.onNewVariableValue(data.newVariableValue)
if (data.videoPlayed && callbacks.onVideoPlayed) callbacks.onVideoPlayed(); if (data.videoPlayed && callbacks.onVideoPlayed) callbacks.onVideoPlayed()
}; }

View File

@@ -1,60 +1,62 @@
export type IframeParams = { export type IframeParams = {
publishId: string; publishId: string
viewerHost?: string; isV1?: boolean
backgroundColor?: string; viewerHost?: string
hiddenVariables?: { [key: string]: string | undefined }; backgroundColor?: string
customDomain?: string; hiddenVariables?: { [key: string]: string | undefined }
loadWhenVisible?: boolean; customDomain?: string
} & IframeCallbacks; loadWhenVisible?: boolean
} & IframeCallbacks
export type IframeCallbacks = { export type IframeCallbacks = {
onNewVariableValue?: (v: Variable) => void; onNewVariableValue?: (v: Variable) => void
onVideoPlayed?: () => void; onVideoPlayed?: () => void
}; }
export type PopupParams = { export type PopupParams = {
delay?: number; delay?: number
} & IframeParams; } & IframeParams
export type PopupActions = { export type PopupActions = {
open: () => void; open: () => void
close: () => void; close: () => void
}; }
export type BubbleParams = { export type BubbleParams = {
button?: ButtonParams; button?: ButtonParams
proactiveMessage?: ProactiveMessageParams; proactiveMessage?: ProactiveMessageParams
} & IframeParams; autoOpenDelay?: number
} & IframeParams
export type ButtonParams = { export type ButtonParams = {
color?: string; color?: string
iconUrl?: string; iconUrl?: string
}; }
export type ProactiveMessageParams = { export type ProactiveMessageParams = {
avatarUrl?: string; avatarUrl?: string
textContent: string; textContent: string
delay?: number; delay?: number
rememberClose?: boolean; rememberClose?: boolean
}; }
export type BubbleActions = { export type BubbleActions = {
open: () => void; open: () => void
close: () => void; close: () => void
openProactiveMessage?: () => void; openProactiveMessage?: () => void
}; }
export type Variable = { export type Variable = {
name: string; name: string
value: string; value: string
}; }
export type DataFromTypebot = { export type DataFromTypebot = {
redirectUrl?: string; redirectUrl?: string
newVariableValue?: Variable; newVariableValue?: Variable
videoPlayed?: boolean; videoPlayed?: boolean
}; }
export const localStorageKeys = { export const localStorageKeys = {
rememberClose: "rememberClose", rememberClose: 'rememberClose',
}; }

View File

@@ -1,30 +1,58 @@
import * as Typebot from "../../src"; import * as Typebot from '../../src'
describe("initBubble", () => { describe('initBubble', () => {
beforeEach(() => { beforeEach(() => {
document.body.innerHTML = ""; document.body.innerHTML = ''
}); })
it("should initialize a bubble embed", () => { it('should initialize a bubble embed', () => {
expect.assertions(2); expect.assertions(2)
Typebot.initBubble({ publishId: "typebot-id" }); Typebot.initBubble({ publishId: 'typebot-id' })
const bubbleElement = document.getElementById("typebot-bubble"); const bubbleElement = document.getElementById('typebot-bubble')
const frame = document.getElementsByTagName("iframe")[0]; const frame = document.getElementsByTagName('iframe')[0]
expect(frame).toBeDefined(); expect(frame).toBeDefined()
expect(bubbleElement).toBeDefined(); expect(bubbleElement).toBeDefined()
}); })
it("should overwrite bubble if exists", () => { it('should overwrite bubble if exists', () => {
expect.assertions(2); expect.assertions(2)
Typebot.initBubble({ Typebot.initBubble({
publishId: "typebot-id", publishId: 'typebot-id',
hiddenVariables: { var1: "test" }, hiddenVariables: { var1: 'test' },
}); })
Typebot.initBubble({ publishId: "typebot-id2" }); Typebot.initBubble({ publishId: 'typebot-id2' })
const frames = document.getElementsByTagName("iframe"); const frames = document.getElementsByTagName('iframe')
expect(frames).toHaveLength(1); expect(frames).toHaveLength(1)
expect(frames[0].dataset.src).toBe( expect(frames[0].dataset.src).toBe(
"https://typebot-viewer.vercel.app/typebot-id2?hn=localhost" 'https://typebot-viewer.vercel.app/typebot-id2?hn=localhost'
); )
}); })
});
it('show open after the corresponding delay', async () => {
expect.assertions(3)
Typebot.initBubble({
autoOpenDelay: 1000,
publishId: 'typebot-id',
})
const bubble = document.querySelector('#typebot-bubble') as HTMLDivElement
expect(bubble.classList.contains('iframe-opened')).toBe(false)
await new Promise((r) => setTimeout(r, 1000))
expect(bubble.classList.contains('iframe-opened')).toBe(true)
const rememberCloseDecisionFromStorage = localStorage.getItem(
Typebot.localStorageKeys.rememberClose
)
expect(rememberCloseDecisionFromStorage).toBe('true')
})
it('should remember close decision if set to true', async () => {
expect.assertions(1)
localStorage.setItem(Typebot.localStorageKeys.rememberClose, 'true')
Typebot.initBubble({
autoOpenDelay: 1000,
publishId: 'typebot-id',
})
const bubble = document.querySelector('#typebot-bubble') as HTMLDivElement
await new Promise((r) => setTimeout(r, 1500))
expect(bubble.classList.contains('iframe-opened')).toBe(false)
})
})

View File

@@ -1,104 +1,77 @@
import * as Typebot from "../../src"; import * as Typebot from '../../src'
beforeEach(() => { beforeEach(() => {
document.body.innerHTML = ""; document.body.innerHTML = ''
}); })
it("should create the message", () => { it('should create the message', () => {
expect.assertions(2); expect.assertions(2)
Typebot.initBubble({ Typebot.initBubble({
proactiveMessage: { textContent: "Hi click here!" }, proactiveMessage: { textContent: 'Hi click here!' },
publishId: "typebot-id", publishId: 'typebot-id',
}); })
const paragraphElement = document.querySelector( const paragraphElement = document.querySelector(
"#typebot-bubble > .proactive-message > p" '#typebot-bubble > .proactive-message > p'
); )
const closeButton = document.querySelector( const closeButton = document.querySelector(
"#typebot-bubble > .proactive-message > .close-button" '#typebot-bubble > .proactive-message > .close-button'
); )
expect(paragraphElement?.textContent).toBe("Hi click here!"); expect(paragraphElement?.textContent).toBe('Hi click here!')
expect(closeButton).toBeTruthy(); expect(closeButton).toBeTruthy()
}); })
it("should have the corresponding avatar", () => { it('should have the corresponding avatar', () => {
expect.assertions(1); expect.assertions(1)
Typebot.initBubble({ Typebot.initBubble({
proactiveMessage: { proactiveMessage: {
textContent: "Hi click here!", textContent: 'Hi click here!',
avatarUrl: "https://website.com/my-avatar.png", avatarUrl: 'https://website.com/my-avatar.png',
}, },
publishId: "typebot-id", publishId: 'typebot-id',
}); })
const avatarElement = document.querySelector( const avatarElement = document.querySelector(
"#typebot-bubble > .proactive-message > img" '#typebot-bubble > .proactive-message > img'
) as HTMLImageElement; ) as HTMLImageElement
expect(avatarElement.src).toBe("https://website.com/my-avatar.png"); expect(avatarElement.src).toBe('https://website.com/my-avatar.png')
}); })
it("shouldn't have opened class if delay not defined", () => { it("shouldn't have opened class if delay not defined", () => {
expect.assertions(1); expect.assertions(1)
Typebot.initBubble({ Typebot.initBubble({
proactiveMessage: { proactiveMessage: {
textContent: "Hi click here!", textContent: 'Hi click here!',
}, },
publishId: "typebot-id", publishId: 'typebot-id',
}); })
const bubble = document.querySelector("#typebot-bubble") as HTMLDivElement; const bubble = document.querySelector('#typebot-bubble') as HTMLDivElement
expect(bubble.classList.contains("message-opened")).toBe(false); expect(bubble.classList.contains('message-opened')).toBe(false)
}); })
it("should show almost immediately if delay is 0", async () => { it('should show almost immediately if delay is 0', async () => {
expect.assertions(1); expect.assertions(1)
Typebot.initBubble({ Typebot.initBubble({
proactiveMessage: { proactiveMessage: {
textContent: "Hi click here!", textContent: 'Hi click here!',
delay: 0, delay: 0,
}, },
publishId: "typebot-id", publishId: 'typebot-id',
}); })
const bubble = document.querySelector("#typebot-bubble") as HTMLDivElement; const bubble = document.querySelector('#typebot-bubble') as HTMLDivElement
await new Promise((r) => setTimeout(r, 1)); await new Promise((r) => setTimeout(r, 1))
expect(bubble.classList.contains("message-opened")).toBe(true); expect(bubble.classList.contains('message-opened')).toBe(true)
}); })
it("show after the corresponding delay", async () => { it('show after the corresponding delay', async () => {
expect.assertions(2); expect.assertions(2)
Typebot.initBubble({ Typebot.initBubble({
proactiveMessage: { proactiveMessage: {
textContent: "Hi click here!", textContent: 'Hi click here!',
delay: 1000, delay: 1000,
}, },
publishId: "typebot-id", publishId: 'typebot-id',
}); })
const bubble = document.querySelector("#typebot-bubble") as HTMLDivElement; const bubble = document.querySelector('#typebot-bubble') as HTMLDivElement
expect(bubble.classList.contains("message-opened")).toBe(false); expect(bubble.classList.contains('message-opened')).toBe(false)
await new Promise((r) => setTimeout(r, 1000)); await new Promise((r) => setTimeout(r, 1000))
expect(bubble.classList.contains("message-opened")).toBe(true); expect(bubble.classList.contains('message-opened')).toBe(true)
}); })
it("should remember close decision if set to true", () => {
expect.assertions(2);
Typebot.initBubble({
proactiveMessage: {
textContent: "Hi click here!",
delay: 1000,
},
publishId: "typebot-id",
});
const rememberCloseDecisionFromStorage = localStorage.getItem(
Typebot.localStorageKeys.rememberClose
);
expect(rememberCloseDecisionFromStorage).toBeNull();
Typebot.initBubble({
proactiveMessage: {
textContent: "Hi click here!",
delay: 1000,
rememberClose: true,
},
publishId: "typebot-id",
});
const refreshedRememberCloseDecisionFromStorage = localStorage.getItem(
Typebot.localStorageKeys.rememberClose
);
expect(refreshedRememberCloseDecisionFromStorage).toBe("true");
});