chore: Add cache to improve widget performance (#11163)

- Add dynamic importing for routes.
- Added caching for `campaign`, `articles` and `inbox_members` API end
points.

---------

Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
Muhsin Keloth
2025-03-25 04:34:49 +05:30
committed by GitHub
parent 07d19362d2
commit 41d6f9a200
22 changed files with 589 additions and 405 deletions

View File

@@ -25,11 +25,6 @@ export const generateLabelForContactableInboxesList = ({
channelType === INBOX_TYPES.TWILIO ||
channelType === INBOX_TYPES.WHATSAPP
) {
// Handled separately for Twilio Inbox where phone number is not mandatory.
// You can send message to a contact with Messaging Service Id.
if (!phoneNumber) {
return name;
}
return `${name} (${phoneNumber})`;
}
return name;

View File

@@ -8,8 +8,8 @@ vi.mock('dashboard/api/contacts');
describe('composeConversationHelper', () => {
describe('generateLabelForContactableInboxesList', () => {
const contact = {
name: 'Priority Inbox',
email: 'hello@example.com',
name: 'John Doe',
email: 'john@example.com',
phoneNumber: '+1234567890',
};
@@ -19,7 +19,7 @@ describe('composeConversationHelper', () => {
...contact,
channelType: INBOX_TYPES.EMAIL,
})
).toBe('Priority Inbox (hello@example.com)');
).toBe('John Doe (john@example.com)');
});
it('generates label for twilio inbox', () => {
@@ -28,14 +28,7 @@ describe('composeConversationHelper', () => {
...contact,
channelType: INBOX_TYPES.TWILIO,
})
).toBe('Priority Inbox (+1234567890)');
expect(
helpers.generateLabelForContactableInboxesList({
name: 'Priority Inbox',
channelType: INBOX_TYPES.TWILIO,
})
).toBe('Priority Inbox');
).toBe('John Doe (+1234567890)');
});
it('generates label for whatsapp inbox', () => {
@@ -44,7 +37,7 @@ describe('composeConversationHelper', () => {
...contact,
channelType: INBOX_TYPES.WHATSAPP,
})
).toBe('Priority Inbox (+1234567890)');
).toBe('John Doe (+1234567890)');
});
it('generates label for other inbox types', () => {
@@ -53,7 +46,7 @@ describe('composeConversationHelper', () => {
...contact,
channelType: 'Channel::Api',
})
).toBe('Priority Inbox');
).toBe('John Doe');
});
});

View File

@@ -0,0 +1,43 @@
import { LocalStorage } from './localStorage';
// Default cache expiry is 24 hours
const DEFAULT_EXPIRY = 24 * 60 * 60 * 1000;
export const getFromCache = (key, expiry = DEFAULT_EXPIRY) => {
try {
const cached = LocalStorage.get(key);
if (!cached) return null;
const { data, timestamp } = cached;
const isExpired = Date.now() - timestamp > expiry;
if (isExpired) {
LocalStorage.remove(key);
return null;
}
return data;
} catch (error) {
return null;
}
};
export const setCache = (key, data) => {
try {
const cacheData = {
data,
timestamp: Date.now(),
};
LocalStorage.set(key, cacheData);
} catch (error) {
// Ignore cache errors
}
};
export const clearCache = key => {
try {
LocalStorage.remove(key);
} catch (error) {
// Ignore cache errors
}
};

View File

@@ -0,0 +1,136 @@
import { getFromCache, setCache, clearCache } from '../cache';
import { LocalStorage } from '../localStorage';
vi.mock('../localStorage');
describe('Cache Helpers', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
vi.setSystemTime(new Date(2023, 1, 1, 0, 0, 0));
});
afterEach(() => {
vi.useRealTimers();
});
describe('getFromCache', () => {
it('returns null when no data is cached', () => {
LocalStorage.get.mockReturnValue(null);
const result = getFromCache('test-key');
expect(result).toBeNull();
expect(LocalStorage.get).toHaveBeenCalledWith('test-key');
});
it('returns cached data when not expired', () => {
// Current time is 2023-02-01 00:00:00
// Cache timestamp is 1 hour ago
const oneHourAgo =
new Date(2023, 1, 1, 0, 0, 0).getTime() - 60 * 60 * 1000;
LocalStorage.get.mockReturnValue({
data: { foo: 'bar' },
timestamp: oneHourAgo,
});
// Default expiry is 24 hours
const result = getFromCache('test-key');
expect(result).toEqual({ foo: 'bar' });
expect(LocalStorage.get).toHaveBeenCalledWith('test-key');
expect(LocalStorage.remove).not.toHaveBeenCalled();
});
it('removes and returns null when data is expired', () => {
// Current time is 2023-02-01 00:00:00
// Cache timestamp is 25 hours ago (beyond the default 24-hour expiry)
const twentyFiveHoursAgo =
new Date(2023, 1, 1, 0, 0, 0).getTime() - 25 * 60 * 60 * 1000;
LocalStorage.get.mockReturnValue({
data: { foo: 'bar' },
timestamp: twentyFiveHoursAgo,
});
const result = getFromCache('test-key');
expect(result).toBeNull();
expect(LocalStorage.get).toHaveBeenCalledWith('test-key');
expect(LocalStorage.remove).toHaveBeenCalledWith('test-key');
});
it('respects custom expiry time', () => {
// Current time is 2023-02-01 00:00:00
// Cache timestamp is 2 hours ago
const twoHoursAgo =
new Date(2023, 1, 1, 0, 0, 0).getTime() - 2 * 60 * 60 * 1000;
LocalStorage.get.mockReturnValue({
data: { foo: 'bar' },
timestamp: twoHoursAgo,
});
// Set expiry to 1 hour
const result = getFromCache('test-key', 60 * 60 * 1000);
expect(result).toBeNull();
expect(LocalStorage.get).toHaveBeenCalledWith('test-key');
expect(LocalStorage.remove).toHaveBeenCalledWith('test-key');
});
it('handles errors gracefully', () => {
LocalStorage.get.mockImplementation(() => {
throw new Error('Storage error');
});
const result = getFromCache('test-key');
expect(result).toBeNull();
});
});
describe('setCache', () => {
it('stores data with timestamp', () => {
const data = { name: 'test' };
const expectedCacheData = {
data,
timestamp: new Date(2023, 1, 1, 0, 0, 0).getTime(),
};
setCache('test-key', data);
expect(LocalStorage.set).toHaveBeenCalledWith(
'test-key',
expectedCacheData
);
});
it('handles errors gracefully', () => {
LocalStorage.set.mockImplementation(() => {
throw new Error('Storage error');
});
// Should not throw
expect(() => setCache('test-key', { foo: 'bar' })).not.toThrow();
});
});
describe('clearCache', () => {
it('removes cached data', () => {
clearCache('test-key');
expect(LocalStorage.remove).toHaveBeenCalledWith('test-key');
});
it('handles errors gracefully', () => {
LocalStorage.remove.mockImplementation(() => {
throw new Error('Storage error');
});
// Should not throw
expect(() => clearCache('test-key')).not.toThrow();
});
});
});

View File

@@ -1,11 +1,5 @@
import { createRouter, createWebHashHistory } from 'vue-router';
import ViewWithHeader from './components/layouts/ViewWithHeader.vue';
import UnreadMessages from './views/UnreadMessages.vue';
import Campaigns from './views/Campaigns.vue';
import Home from './views/Home.vue';
import PreChatForm from './views/PreChatForm.vue';
import Messages from './views/Messages.vue';
import ArticleViewer from './views/ArticleViewer.vue';
import store from './store';
const router = createRouter({
@@ -14,12 +8,12 @@ const router = createRouter({
{
path: '/unread-messages',
name: 'unread-messages',
component: UnreadMessages,
component: () => import('./views/UnreadMessages.vue'),
},
{
path: '/campaigns',
name: 'campaigns',
component: Campaigns,
component: () => import('./views/Campaigns.vue'),
},
{
path: '/',
@@ -28,22 +22,22 @@ const router = createRouter({
{
path: '',
name: 'home',
component: Home,
component: () => import('./views/Home.vue'),
},
{
path: '/prechat-form',
name: 'prechat-form',
component: PreChatForm,
component: () => import('./views/PreChatForm.vue'),
},
{
path: '/messages',
name: 'messages',
component: Messages,
component: () => import('./views/Messages.vue'),
},
{
path: '/article',
name: 'article-viewer',
component: ArticleViewer,
component: () => import('./views/ArticleViewer.vue'),
},
],
},

View File

@@ -1,5 +1,6 @@
import { getAvailableAgents } from 'widget/api/agent';
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
import { getFromCache, setCache } from 'shared/helpers/cache';
const state = {
records: [],
@@ -15,11 +16,22 @@ export const getters = {
$state.records.filter(agent => agent.availability_status === 'online'),
};
const CACHE_KEY_PREFIX = 'chatwoot_available_agents_';
export const actions = {
fetchAvailableAgents: async ({ commit }, websiteToken) => {
try {
const cachedData = getFromCache(`${CACHE_KEY_PREFIX}${websiteToken}`);
if (cachedData) {
commit('setAgents', cachedData);
commit('setError', false);
commit('setHasFetched', true);
return;
}
const { data } = await getAvailableAgents(websiteToken);
const { payload = [] } = data;
setCache(`${CACHE_KEY_PREFIX}${websiteToken}`, payload);
commit('setAgents', payload);
commit('setError', false);
commit('setHasFetched', true);

View File

@@ -1,4 +1,7 @@
import { getMostReadArticles } from 'widget/api/article';
import { getFromCache, setCache } from 'shared/helpers/cache';
const CACHE_KEY_PREFIX = 'chatwoot_most_read_articles_';
const state = {
records: [],
@@ -20,9 +23,16 @@ export const actions = {
commit('setError', false);
try {
const cachedData = getFromCache(`${CACHE_KEY_PREFIX}${slug}_${locale}`);
if (cachedData) {
commit('setArticles', cachedData);
return;
}
const { data } = await getMostReadArticles(slug, locale);
const { payload = [] } = data;
setCache(`${CACHE_KEY_PREFIX}${slug}_${locale}`, payload);
if (payload.length) {
commit('setArticles', payload);
}

View File

@@ -4,6 +4,10 @@ import {
formatCampaigns,
filterCampaigns,
} from 'widget/helpers/campaignHelper';
import { getFromCache, setCache } from 'shared/helpers/cache';
const CACHE_KEY_PREFIX = 'chatwoot_campaigns_';
const state = {
records: [],
uiFlags: {
@@ -41,7 +45,26 @@ export const actions = {
{ websiteToken, currentURL, isInBusinessHours }
) => {
try {
// Cache for 1 hour
const CACHE_EXPIRY = 60 * 60 * 1000;
const cachedData = getFromCache(
`${CACHE_KEY_PREFIX}${websiteToken}`,
CACHE_EXPIRY
);
if (cachedData) {
commit('setCampaigns', cachedData);
commit('setError', false);
resetCampaignTimers(
cachedData,
currentURL,
websiteToken,
isInBusinessHours
);
return;
}
const { data: campaigns } = await getCampaigns(websiteToken);
setCache(`${CACHE_KEY_PREFIX}${websiteToken}`, campaigns);
commit('setCampaigns', campaigns);
commit('setError', false);
resetCampaignTimers(

View File

@@ -1,14 +1,60 @@
import { API } from 'widget/helpers/axios';
import { actions } from '../../agent';
import { agents } from './data';
import { getFromCache, setCache } from 'shared/helpers/cache';
import { getAvailableAgents } from 'widget/api/agent';
const commit = vi.fn();
let commit = vi.fn();
vi.mock('widget/helpers/axios');
vi.mock('widget/api/agent');
vi.mock('shared/helpers/cache');
describe('#actions', () => {
describe('#fetchAvailableAgents', () => {
const websiteToken = 'test-token';
beforeEach(() => {
commit = vi.fn();
vi.clearAllMocks();
});
it('returns cached data if available', async () => {
getFromCache.mockReturnValue(agents);
await actions.fetchAvailableAgents({ commit }, websiteToken);
expect(getFromCache).toHaveBeenCalledWith(
`chatwoot_available_agents_${websiteToken}`
);
expect(getAvailableAgents).not.toHaveBeenCalled();
expect(setCache).not.toHaveBeenCalled();
expect(commit).toHaveBeenCalledWith('setAgents', agents);
expect(commit).toHaveBeenCalledWith('setError', false);
expect(commit).toHaveBeenCalledWith('setHasFetched', true);
});
it('fetches and caches data if no cache available', async () => {
getFromCache.mockReturnValue(null);
getAvailableAgents.mockReturnValue({ data: { payload: agents } });
await actions.fetchAvailableAgents({ commit }, websiteToken);
expect(getFromCache).toHaveBeenCalledWith(
`chatwoot_available_agents_${websiteToken}`
);
expect(getAvailableAgents).toHaveBeenCalledWith(websiteToken);
expect(setCache).toHaveBeenCalledWith(
`chatwoot_available_agents_${websiteToken}`,
agents
);
expect(commit).toHaveBeenCalledWith('setAgents', agents);
expect(commit).toHaveBeenCalledWith('setError', false);
expect(commit).toHaveBeenCalledWith('setHasFetched', true);
});
it('sends correct actions if API is success', async () => {
API.get.mockResolvedValue({ data: { payload: agents } });
getFromCache.mockReturnValue(null);
getAvailableAgents.mockReturnValue({ data: { payload: agents } });
await actions.fetchAvailableAgents({ commit }, 'Hi');
expect(commit.mock.calls).toEqual([
['setAgents', agents],
@@ -17,7 +63,11 @@ describe('#actions', () => {
]);
});
it('sends correct actions if API is error', async () => {
API.get.mockRejectedValue({ message: 'Authentication required' });
getFromCache.mockReturnValue(null);
getAvailableAgents.mockRejectedValue({
message: 'Authentication required',
});
await actions.fetchAvailableAgents({ commit }, 'Hi');
expect(commit.mock.calls).toEqual([
['setError', true],

View File

@@ -1,127 +0,0 @@
import { mutations, actions, getters } from '../../articles'; // update this import path to your actual module location
import { getMostReadArticles } from 'widget/api/article';
vi.mock('widget/api/article');
describe('Vuex Articles Module', () => {
let state;
beforeEach(() => {
state = {
records: [],
uiFlags: {
isError: false,
hasFetched: false,
isFetching: false,
},
};
});
describe('Mutations', () => {
it('sets articles correctly', () => {
const articles = [{ id: 1 }, { id: 2 }];
mutations.setArticles(state, articles);
expect(state.records).toEqual(articles);
});
it('sets error flag correctly', () => {
mutations.setError(state, true);
expect(state.uiFlags.isError).toBe(true);
});
it('sets fetching state correctly', () => {
mutations.setIsFetching(state, true);
expect(state.uiFlags.isFetching).toBe(true);
});
it('does not mutate records when no articles are provided', () => {
const previousState = { ...state };
mutations.setArticles(state, []);
expect(state.records).toEqual(previousState.records);
});
it('toggles the error state correctly', () => {
mutations.setError(state, true);
expect(state.uiFlags.isError).toBe(true);
mutations.setError(state, false);
expect(state.uiFlags.isError).toBe(false);
});
it('toggles the fetching state correctly', () => {
mutations.setIsFetching(state, true);
expect(state.uiFlags.isFetching).toBe(true);
mutations.setIsFetching(state, false);
expect(state.uiFlags.isFetching).toBe(false);
});
});
describe('Actions', () => {
it('fetches articles correctly', async () => {
const commit = vi.fn();
const articles = [{ id: 1 }, { id: 2 }];
getMostReadArticles.mockResolvedValueOnce({
data: { payload: articles },
});
await actions.fetch({ commit }, { slug: 'slug', locale: 'en' });
expect(commit).toHaveBeenCalledWith('setIsFetching', true);
expect(commit).toHaveBeenCalledWith('setError', false);
expect(commit).toHaveBeenCalledWith('setArticles', articles);
expect(commit).toHaveBeenCalledWith('setIsFetching', false);
});
it('handles fetch error correctly', async () => {
const commit = vi.fn();
getMostReadArticles.mockRejectedValueOnce(new Error('Error message'));
await actions.fetch(
{ commit },
{ websiteToken: 'token', slug: 'slug', locale: 'en' }
);
expect(commit).toHaveBeenCalledWith('setIsFetching', true);
expect(commit).toHaveBeenCalledWith('setError', true);
expect(commit).toHaveBeenCalledWith('setIsFetching', false);
});
it('does not mutate state when fetching returns an empty payload', async () => {
const commit = vi.fn();
getMostReadArticles.mockResolvedValueOnce({ data: { payload: [] } });
await actions.fetch({ commit }, { slug: 'slug', locale: 'en' });
expect(commit).toHaveBeenCalledWith('setIsFetching', true);
expect(commit).toHaveBeenCalledWith('setError', false);
expect(commit).not.toHaveBeenCalledWith('setArticles', expect.any(Array));
expect(commit).toHaveBeenCalledWith('setIsFetching', false);
});
it('sets error state when fetching fails', async () => {
const commit = vi.fn();
getMostReadArticles.mockRejectedValueOnce(new Error('Network error'));
await actions.fetch(
{ commit },
{ websiteToken: 'token', slug: 'slug', locale: 'en' }
);
expect(commit).toHaveBeenCalledWith('setIsFetching', true);
expect(commit).toHaveBeenCalledWith('setError', true);
expect(commit).not.toHaveBeenCalledWith('setArticles', expect.any(Array));
expect(commit).toHaveBeenCalledWith('setIsFetching', false);
});
});
describe('Getters', () => {
it('returns uiFlags correctly', () => {
const result = getters.uiFlags(state);
expect(result).toEqual(state.uiFlags);
});
it('returns popularArticles correctly', () => {
const result = getters.popularArticles(state);
expect(result).toEqual(state.records);
});
});
});

View File

@@ -0,0 +1,170 @@
import { mutations, actions, getters } from '../articles';
import { getMostReadArticles } from 'widget/api/article';
import { getFromCache, setCache } from 'shared/helpers/cache';
vi.mock('widget/api/article');
vi.mock('shared/helpers/cache');
describe('Vuex Articles Module', () => {
let state;
beforeEach(() => {
state = {
records: [],
uiFlags: {
isError: false,
hasFetched: false,
isFetching: false,
},
};
});
describe('Mutations', () => {
it('sets articles correctly', () => {
const articles = [{ id: 1 }, { id: 2 }];
mutations.setArticles(state, articles);
expect(state.records).toEqual(articles);
});
it('sets error flag correctly', () => {
mutations.setError(state, true);
expect(state.uiFlags.isError).toBe(true);
});
it('sets fetching state correctly', () => {
mutations.setIsFetching(state, true);
expect(state.uiFlags.isFetching).toBe(true);
});
it('does not mutate records when no articles are provided', () => {
const previousState = { ...state };
mutations.setArticles(state, []);
expect(state.records).toEqual(previousState.records);
});
it('toggles the error state correctly', () => {
mutations.setError(state, true);
expect(state.uiFlags.isError).toBe(true);
mutations.setError(state, false);
expect(state.uiFlags.isError).toBe(false);
});
it('toggles the fetching state correctly', () => {
mutations.setIsFetching(state, true);
expect(state.uiFlags.isFetching).toBe(true);
mutations.setIsFetching(state, false);
expect(state.uiFlags.isFetching).toBe(false);
});
});
describe('Actions', () => {
describe('#fetch', () => {
const slug = 'test-slug';
const locale = 'en';
const articles = [
{ id: 1, title: 'Test' },
{ id: 2, title: 'Test 2' },
];
let commit;
beforeEach(() => {
commit = vi.fn();
vi.clearAllMocks();
});
it('returns cached data if available', async () => {
getFromCache.mockReturnValue(articles);
await actions.fetch({ commit }, { slug, locale });
expect(getFromCache).toHaveBeenCalledWith(
`chatwoot_most_read_articles_${slug}_${locale}`
);
expect(getMostReadArticles).not.toHaveBeenCalled();
expect(setCache).not.toHaveBeenCalled();
expect(commit).toHaveBeenCalledWith('setArticles', articles);
expect(commit).toHaveBeenCalledWith('setError', false);
});
it('fetches and caches data if no cache available', async () => {
getFromCache.mockReturnValue(null);
getMostReadArticles.mockReturnValue({ data: { payload: articles } });
await actions.fetch({ commit }, { slug, locale });
expect(getFromCache).toHaveBeenCalledWith(
`chatwoot_most_read_articles_${slug}_${locale}`
);
expect(getMostReadArticles).toHaveBeenCalledWith(slug, locale);
expect(setCache).toHaveBeenCalledWith(
`chatwoot_most_read_articles_${slug}_${locale}`,
articles
);
expect(commit).toHaveBeenCalledWith('setArticles', articles);
expect(commit).toHaveBeenCalledWith('setError', false);
});
it('handles API errors correctly', async () => {
getFromCache.mockReturnValue(null);
getMostReadArticles.mockRejectedValue(new Error('API Error'));
await actions.fetch({ commit }, { slug, locale });
expect(commit).toHaveBeenCalledWith('setError', true);
expect(commit).toHaveBeenCalledWith('setIsFetching', false);
});
it('does not mutate state when fetching returns an empty payload', async () => {
getFromCache.mockReturnValue(null);
getMostReadArticles.mockReturnValue({ data: { payload: [] } });
await actions.fetch({ commit }, { slug, locale });
expect(commit).toHaveBeenCalledWith('setIsFetching', true);
expect(commit).toHaveBeenCalledWith('setError', false);
expect(commit).not.toHaveBeenCalledWith(
'setArticles',
expect.any(Array)
);
expect(commit).toHaveBeenCalledWith('setIsFetching', false);
});
it('sets loading state during fetch', async () => {
getFromCache.mockReturnValue(null);
getMostReadArticles.mockReturnValue({ data: { payload: articles } });
await actions.fetch({ commit }, { slug, locale });
expect(commit).toHaveBeenCalledWith('setIsFetching', true);
expect(commit).toHaveBeenCalledWith('setIsFetching', false);
});
});
it('sets error state when fetching fails', async () => {
const commit = vi.fn();
getMostReadArticles.mockRejectedValueOnce(new Error('Network error'));
await actions.fetch(
{ commit },
{ websiteToken: 'token', slug: 'slug', locale: 'en' }
);
expect(commit).toHaveBeenCalledWith('setIsFetching', true);
expect(commit).toHaveBeenCalledWith('setError', true);
expect(commit).not.toHaveBeenCalledWith('setArticles', expect.any(Array));
expect(commit).toHaveBeenCalledWith('setIsFetching', false);
});
});
describe('Getters', () => {
it('returns uiFlags correctly', () => {
const result = getters.uiFlags(state);
expect(result).toEqual(state.uiFlags);
});
it('returns popularArticles correctly', () => {
const result = getters.popularArticles(state);
expect(result).toEqual(state.records);
});
});
});

View File

@@ -1,10 +1,12 @@
import { API } from 'widget/helpers/axios';
import { actions } from '../../campaign';
import { campaigns } from './data';
import { getFromCache, setCache } from 'shared/helpers/cache';
const commit = vi.fn();
const dispatch = vi.fn();
vi.mock('widget/helpers/axios');
vi.mock('shared/helpers/cache');
import campaignTimer from 'widget/helpers/campaignTimer';
vi.mock('widget/helpers/campaignTimer', () => ({
@@ -15,8 +17,17 @@ vi.mock('widget/helpers/campaignTimer', () => ({
describe('#actions', () => {
describe('#fetchCampaigns', () => {
it('sends correct actions if API is success', async () => {
API.get.mockResolvedValue({ data: campaigns });
beforeEach(() => {
commit.mockClear();
getFromCache.mockClear();
setCache.mockClear();
API.get.mockClear();
campaignTimer.initTimers.mockClear();
});
it('uses cached data when available', async () => {
getFromCache.mockReturnValue(campaigns);
await actions.fetchCampaigns(
{ commit },
{
@@ -25,6 +36,54 @@ describe('#actions', () => {
isInBusinessHours: true,
}
);
expect(getFromCache).toHaveBeenCalledWith(
'chatwoot_campaigns_XDsafmADasd',
60 * 60 * 1000
);
expect(API.get).not.toHaveBeenCalled();
expect(setCache).not.toHaveBeenCalled();
expect(commit.mock.calls).toEqual([
['setCampaigns', campaigns],
['setError', false],
]);
expect(campaignTimer.initTimers).toHaveBeenCalledWith(
{
campaigns: [
{
id: 11,
timeOnPage: '20',
url: 'https://chatwoot.com',
triggerOnlyDuringBusinessHours: false,
},
],
},
'XDsafmADasd'
);
});
it('fetches and caches data when cache is not available', async () => {
getFromCache.mockReturnValue(null);
API.get.mockResolvedValue({ data: campaigns });
await actions.fetchCampaigns(
{ commit },
{
websiteToken: 'XDsafmADasd',
currentURL: 'https://chatwoot.com',
isInBusinessHours: true,
}
);
expect(getFromCache).toHaveBeenCalledWith(
'chatwoot_campaigns_XDsafmADasd',
60 * 60 * 1000
);
expect(API.get).toHaveBeenCalled();
expect(setCache).toHaveBeenCalledWith(
'chatwoot_campaigns_XDsafmADasd',
campaigns
);
expect(commit.mock.calls).toEqual([
['setCampaigns', campaigns],
['setError', false],
@@ -43,7 +102,9 @@ describe('#actions', () => {
'XDsafmADasd'
);
});
it('sends correct actions if API is error', async () => {
getFromCache.mockReturnValue(null);
API.get.mockRejectedValue({ message: 'Authentication required' });
await actions.fetchCampaigns(
{ commit },