Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions __tests__/api/webchats_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* @jest-environment jsdom
*/

import WebchatsAPI from '../../src/api/webchats'
import Hellotext from '../../src/hellotext'
import { Configuration } from '../../src/core'

describe('WebchatsAPI', () => {
beforeEach(() => {
Configuration.webchat.style = {}
Configuration.webchat.appearance = {}
Configuration.webchat.whatsapp = {}
Configuration.webchat.placement = 'bottom-right'

Hellotext.business = {
id: 'business-id',
data: {},
}

jest.spyOn(Hellotext, 'session', 'get').mockReturnValue('session-123')

global.fetch = jest.fn().mockResolvedValue({
json: jest.fn().mockResolvedValue({
business: {},
html: '<article data-webchat></article>',
locale: 'en',
}),
})
})

afterEach(() => {
jest.restoreAllMocks()
})

const requestedParams = async () => {
await WebchatsAPI.get('webchat-id')

return new URL(global.fetch.mock.calls[0][0]).searchParams
}

it('serializes appearance and WhatsApp overrides for the webchat request', async () => {
Configuration.webchat.appearance = {
header: {
name: 'Acme Support',
},
launcher: {
iconUrl: 'https://example.com/icon.png',
},
}
Configuration.webchat.whatsapp = {
number: '+15551234567',
restrictToChannel: true,
}

const params = await requestedParams()

expect(params.get('webchat[appearance][header][name]')).toBe('Acme Support')
expect(params.get('webchat[appearance][launcher][icon_url]')).toBe('https://example.com/icon.png')
expect(params.get('webchat[handoff][identifier]')).toBe('+15551234567')
expect(params.get('webchat[handoff][restrict_to_channel]')).toBe('true')
})

it('serializes restrictToChannel when the supplied value is false', async () => {
Configuration.webchat.whatsapp = {
restrictToChannel: false,
}

const params = await requestedParams()

expect(params.get('webchat[handoff][restrict_to_channel]')).toBe('false')
})

it('does not serialize absent appearance or WhatsApp overrides', async () => {
const params = await requestedParams()

expect(params.has('webchat[appearance][header][name]')).toBe(false)
expect(params.has('webchat[appearance][launcher][icon_url]')).toBe(false)
expect(params.has('webchat[handoff][identifier]')).toBe(false)
expect(params.has('webchat[handoff][restrict_to_channel]')).toBe(false)
})

it('keeps existing style, placement, session, and locale params', async () => {
Configuration.webchat.style = {
primaryColor: '#EEEEEE',
typography: 'inherit',
}
Configuration.webchat.placement = 'top-left'

const params = await requestedParams()

expect(params.get('style[primaryColor]')).toBe('#EEEEEE')
expect(params.get('style[typography]')).toBe('inherit')
expect(params.get('placement')).toBe('top-left')
expect(params.get('session')).toBe('session-123')
expect(params.get('locale')).toBe('en')
})
})
21 changes: 21 additions & 0 deletions __tests__/controllers/mixins/usePopover_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,25 @@ describe('usePopover', () => {
strategy: 'fixed'
})
})

it('cancels a pending behaviour open before showing the popover', () => {
controller.cancelBehaviourOpen = jest.fn()

usePopover(controller)
controller.show()

expect(controller.cancelBehaviourOpen).toHaveBeenCalledTimes(1)
expect(controller.openValue).toBe(true)
})

it('cancels a pending behaviour open before toggling the popover', () => {
controller.openValue = false
controller.cancelBehaviourOpen = jest.fn()

usePopover(controller)
controller.toggle()

expect(controller.cancelBehaviourOpen).toHaveBeenCalledTimes(1)
expect(controller.openValue).toBe(true)
})
})
45 changes: 39 additions & 6 deletions __tests__/controllers/webchat/emoji_picker_controller_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ describe('EmojiPickerController', () => {
controller.buttonTarget = button
controller.popoverTarget = popover

Object.defineProperty(controller, 'pickerObject', {
get: () => picker,
configurable: true
})

usePopover.mockImplementation(controller => {
controller.setupFloatingUI = jest.fn()
})
Expand All @@ -36,14 +31,52 @@ describe('EmojiPickerController', () => {
usePopover.mockReset()
})

it('positions the picker absolutely against its button', () => {
it('positions the picker absolutely against its button without loading emoji assets immediately', () => {
controller.loadPickerDependencies = jest.fn()

controller.connect()

expect(controller.setupFloatingUI).toHaveBeenCalledWith({
trigger: button,
popover: popover,
strategy: 'absolute'
})
expect(controller.loadPickerDependencies).not.toHaveBeenCalled()
expect(popover.contains(picker)).toBe(false)
})

it('loads and appends the picker when the popover opens', async () => {
const Picker = jest.fn(() => picker)

controller.loadPickerDependencies = jest.fn().mockResolvedValue({
Picker,
i18n: { search: 'Search' },
})

controller.connect()
await controller.onPopoverOpened()

expect(controller.loadPickerDependencies).toHaveBeenCalledTimes(1)
expect(Picker).toHaveBeenCalledWith(expect.objectContaining({
onEmojiSelect: controller.onEmojiSelect,
i18n: { search: 'Search' },
}))
expect(popover.contains(picker)).toBe(true)
})

it('loads the picker only once', async () => {
const Picker = jest.fn(() => picker)

controller.loadPickerDependencies = jest.fn().mockResolvedValue({
Picker,
i18n: { search: 'Search' },
})

controller.connect()
await controller.onPopoverOpened()
await controller.onPopoverOpened()

expect(controller.loadPickerDependencies).toHaveBeenCalledTimes(1)
expect(Picker).toHaveBeenCalledTimes(1)
})
})
Loading