Vendor Chatwoot Vue 3 frontend into frontend/
Copy the Chatwoot (v4.14.0) frontend runnable subset into frontend/ for customization: - app/javascript/ (Vue SPA: dashboard, widget, sdk, portal, superadmin) - app/views/ (ERB templates for vite-plugin-ruby entrypoint resolution) - app/helpers/, app/assets/ (Rails view helpers, static assets) - enterprise/ (Enterprise edition frontend overlay) - config/vite.json, vite.config.ts, bin/vite (Vite-Rails toolchain) - package.json, pnpm-lock.yaml, tailwind/postcss/eslint configs - Gemfile, Gemfile.lock (vite_rails gem for bin/vite binstub) Excluded Rails backend: controllers, models, services, jobs, mailers, policies, db, lib, spec, public, node_modules. Update references to the new frontend location: - .gitignore: exclude frontend build artifacts (node_modules, tmp, packs), keep frontend/bin/ and frontend/vendor/ via negation - backend/scripts/parity_frontend_smoke.sh: CHATWOOT_DIR default reference/chatwoot -> ../frontend - backend/scripts/parity_frontend_browser_smoke.mjs: same default update - AGENTS.md: add frontend section with Rails/Vite coupling notes - README: architecture tree includes frontend/
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
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', () => ({
|
||||
default: {
|
||||
initTimers: vi.fn().mockReturnValue({ mock: true }),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('#actions', () => {
|
||||
describe('#fetchCampaigns', () => {
|
||||
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 },
|
||||
{
|
||||
websiteToken: 'XDsafmADasd',
|
||||
currentURL: 'https://chatwoot.com',
|
||||
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],
|
||||
]);
|
||||
expect(campaignTimer.initTimers).toHaveBeenCalledWith(
|
||||
{
|
||||
campaigns: [
|
||||
{
|
||||
id: 11,
|
||||
timeOnPage: '20',
|
||||
url: 'https://chatwoot.com',
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
'XDsafmADasd'
|
||||
);
|
||||
});
|
||||
|
||||
it('sends correct actions if API is error', async () => {
|
||||
getFromCache.mockReturnValue(null);
|
||||
API.get.mockRejectedValue({ message: 'Authentication required' });
|
||||
await actions.fetchCampaigns(
|
||||
{ commit },
|
||||
{
|
||||
websiteToken: 'XDsafmADasd',
|
||||
currentURL: 'https://www.chatwoot.com',
|
||||
isInBusinessHours: true,
|
||||
}
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([['setError', true]]);
|
||||
});
|
||||
});
|
||||
describe('#initCampaigns', () => {
|
||||
const actionParams = {
|
||||
websiteToken: 'XDsafmADasd',
|
||||
currentURL: 'https://chatwoot.com',
|
||||
};
|
||||
it('sends correct actions if campaigns are empty', async () => {
|
||||
await actions.initCampaigns(
|
||||
{
|
||||
dispatch,
|
||||
getters: { getCampaigns: [], getUIFlags: { hasFetched: false } },
|
||||
},
|
||||
actionParams
|
||||
);
|
||||
expect(dispatch.mock.calls).toEqual([['fetchCampaigns', actionParams]]);
|
||||
expect(campaignTimer.initTimers).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('do not refetch if the campaigns are fetched once', async () => {
|
||||
await actions.initCampaigns(
|
||||
{
|
||||
dispatch,
|
||||
getters: { getCampaigns: [], getUIFlags: { hasFetched: true } },
|
||||
},
|
||||
actionParams
|
||||
);
|
||||
expect(dispatch.mock.calls).toEqual([]);
|
||||
expect(campaignTimer.initTimers).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('resets time if campaigns are available', async () => {
|
||||
await actions.initCampaigns(
|
||||
{
|
||||
dispatch,
|
||||
getters: {
|
||||
getCampaigns: campaigns,
|
||||
getUIFlags: { hasFetched: true },
|
||||
},
|
||||
},
|
||||
actionParams
|
||||
);
|
||||
expect(dispatch.mock.calls).toEqual([]);
|
||||
expect(campaignTimer.initTimers).toHaveBeenCalledWith(
|
||||
{
|
||||
campaigns: [
|
||||
{
|
||||
id: 11,
|
||||
timeOnPage: '20',
|
||||
url: 'https://chatwoot.com',
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
'XDsafmADasd'
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#startCampaign', () => {
|
||||
it('reset campaign if campaign id is not present in the campaign list', async () => {
|
||||
API.get.mockResolvedValue({ data: campaigns });
|
||||
await actions.startCampaign(
|
||||
{
|
||||
dispatch,
|
||||
getters: { getCampaigns: campaigns },
|
||||
commit,
|
||||
rootState: {
|
||||
appConfig: { isWidgetOpen: true },
|
||||
},
|
||||
},
|
||||
{ campaignId: 32 }
|
||||
);
|
||||
});
|
||||
it('start campaign if campaign id passed', async () => {
|
||||
API.get.mockResolvedValue({ data: campaigns });
|
||||
await actions.startCampaign(
|
||||
{
|
||||
dispatch,
|
||||
getters: { getCampaigns: campaigns },
|
||||
commit,
|
||||
rootState: {
|
||||
appConfig: { isWidgetOpen: false },
|
||||
},
|
||||
},
|
||||
{ campaignId: 1 }
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([['setActiveCampaign', campaigns[0]]]);
|
||||
});
|
||||
});
|
||||
describe('#executeCampaign', () => {
|
||||
it('sends correct actions if execute campaign API is success', async () => {
|
||||
const params = { campaignId: 12, websiteToken: 'XDsafmADasd' };
|
||||
API.post.mockResolvedValue({});
|
||||
await actions.executeCampaign({ commit }, params);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[
|
||||
'conversation/setConversationUIFlag',
|
||||
{
|
||||
isCreating: true,
|
||||
},
|
||||
{
|
||||
root: true,
|
||||
},
|
||||
],
|
||||
['setCampaignExecuted', true],
|
||||
['setActiveCampaign', {}],
|
||||
[
|
||||
'conversation/setConversationUIFlag',
|
||||
{
|
||||
isCreating: false,
|
||||
},
|
||||
{
|
||||
root: true,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
it('sends correct actions if execute campaign API is failed', async () => {
|
||||
const params = { campaignId: 12, websiteToken: 'XDsafmADasd' };
|
||||
API.post.mockRejectedValue({ message: 'Authentication required' });
|
||||
await actions.executeCampaign({ commit }, params);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[
|
||||
'conversation/setConversationUIFlag',
|
||||
{
|
||||
isCreating: true,
|
||||
},
|
||||
{
|
||||
root: true,
|
||||
},
|
||||
],
|
||||
['setError', true],
|
||||
[
|
||||
'conversation/setConversationUIFlag',
|
||||
{
|
||||
isCreating: false,
|
||||
},
|
||||
{
|
||||
root: true,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#resetCampaign', () => {
|
||||
it('sends correct actions if execute campaign API is success', async () => {
|
||||
API.post.mockResolvedValue({});
|
||||
await actions.resetCampaign({ commit });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
['setCampaignExecuted', false],
|
||||
['setActiveCampaign', {}],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
export const campaigns = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Welcome',
|
||||
description: null,
|
||||
account_id: 1,
|
||||
inbox: {
|
||||
id: 37,
|
||||
channel_id: 1,
|
||||
name: 'Chatwoot',
|
||||
channel_type: 'Channel::WebWidget',
|
||||
},
|
||||
sender: {
|
||||
account_id: 1,
|
||||
availability_status: 'offline',
|
||||
confirmed: true,
|
||||
email: 'sojan@chatwoot.com',
|
||||
available_name: 'Sojan',
|
||||
id: 10,
|
||||
name: 'Sojan',
|
||||
},
|
||||
message: 'Hey, What brings you today',
|
||||
enabled: true,
|
||||
trigger_rules: {
|
||||
url: 'https://github.com',
|
||||
time_on_page: 10,
|
||||
},
|
||||
created_at: '2021-05-03T04:53:36.354Z',
|
||||
updated_at: '2021-05-03T04:53:36.354Z',
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
title: 'Onboarding Campaign',
|
||||
description: null,
|
||||
account_id: 1,
|
||||
inbox: {
|
||||
id: 37,
|
||||
channel_id: 1,
|
||||
name: 'GitX',
|
||||
channel_type: 'Channel::WebWidget',
|
||||
},
|
||||
sender: {
|
||||
account_id: 1,
|
||||
availability_status: 'offline',
|
||||
confirmed: true,
|
||||
email: 'sojan@chatwoot.com',
|
||||
available_name: 'Sojan',
|
||||
id: 10,
|
||||
},
|
||||
message: 'Begin your onboarding campaign with a welcome message',
|
||||
enabled: true,
|
||||
trigger_rules: {
|
||||
url: 'https://chatwoot.com',
|
||||
time_on_page: '20',
|
||||
},
|
||||
created_at: '2021-05-03T08:15:35.828Z',
|
||||
updated_at: '2021-05-03T08:15:35.828Z',
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: 'Thanks',
|
||||
description: null,
|
||||
account_id: 1,
|
||||
inbox: {
|
||||
id: 37,
|
||||
channel_id: 1,
|
||||
name: 'Chatwoot',
|
||||
channel_type: 'Channel::WebWidget',
|
||||
},
|
||||
sender: {
|
||||
account_id: 1,
|
||||
availability_status: 'offline',
|
||||
confirmed: true,
|
||||
email: 'nithin@chatwoot.com',
|
||||
available_name: 'Nithin',
|
||||
},
|
||||
message: 'Thanks for coming to the show. How may I help you?',
|
||||
enabled: false,
|
||||
trigger_rules: {
|
||||
url: 'https://noshow.com',
|
||||
time_on_page: 10,
|
||||
},
|
||||
created_at: '2021-05-03T10:22:51.025Z',
|
||||
updated_at: '2021-05-03T10:22:51.025Z',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,133 @@
|
||||
import { getters } from '../../campaign';
|
||||
import { campaigns } from './data';
|
||||
vi.mock('widget/store/index.js', () => ({
|
||||
default: {},
|
||||
}));
|
||||
describe('#getters', () => {
|
||||
it('getCampaigns', () => {
|
||||
const state = {
|
||||
records: campaigns,
|
||||
};
|
||||
expect(getters.getCampaigns(state)).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
title: 'Welcome',
|
||||
description: null,
|
||||
account_id: 1,
|
||||
inbox: {
|
||||
id: 37,
|
||||
channel_id: 1,
|
||||
name: 'Chatwoot',
|
||||
channel_type: 'Channel::WebWidget',
|
||||
},
|
||||
sender: {
|
||||
account_id: 1,
|
||||
availability_status: 'offline',
|
||||
confirmed: true,
|
||||
email: 'sojan@chatwoot.com',
|
||||
available_name: 'Sojan',
|
||||
id: 10,
|
||||
name: 'Sojan',
|
||||
},
|
||||
message: 'Hey, What brings you today',
|
||||
enabled: true,
|
||||
trigger_rules: {
|
||||
url: 'https://github.com',
|
||||
time_on_page: 10,
|
||||
},
|
||||
created_at: '2021-05-03T04:53:36.354Z',
|
||||
updated_at: '2021-05-03T04:53:36.354Z',
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
title: 'Onboarding Campaign',
|
||||
description: null,
|
||||
account_id: 1,
|
||||
inbox: {
|
||||
id: 37,
|
||||
channel_id: 1,
|
||||
name: 'GitX',
|
||||
channel_type: 'Channel::WebWidget',
|
||||
},
|
||||
sender: {
|
||||
account_id: 1,
|
||||
availability_status: 'offline',
|
||||
confirmed: true,
|
||||
email: 'sojan@chatwoot.com',
|
||||
available_name: 'Sojan',
|
||||
id: 10,
|
||||
},
|
||||
message: 'Begin your onboarding campaign with a welcome message',
|
||||
enabled: true,
|
||||
trigger_rules: {
|
||||
url: 'https://chatwoot.com',
|
||||
time_on_page: '20',
|
||||
},
|
||||
created_at: '2021-05-03T08:15:35.828Z',
|
||||
updated_at: '2021-05-03T08:15:35.828Z',
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: 'Thanks',
|
||||
description: null,
|
||||
account_id: 1,
|
||||
inbox: {
|
||||
id: 37,
|
||||
channel_id: 1,
|
||||
name: 'Chatwoot',
|
||||
channel_type: 'Channel::WebWidget',
|
||||
},
|
||||
sender: {
|
||||
account_id: 1,
|
||||
availability_status: 'offline',
|
||||
confirmed: true,
|
||||
email: 'nithin@chatwoot.com',
|
||||
available_name: 'Nithin',
|
||||
},
|
||||
message: 'Thanks for coming to the show. How may I help you?',
|
||||
enabled: false,
|
||||
trigger_rules: {
|
||||
url: 'https://noshow.com',
|
||||
time_on_page: 10,
|
||||
},
|
||||
created_at: '2021-05-03T10:22:51.025Z',
|
||||
updated_at: '2021-05-03T10:22:51.025Z',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('getActiveCampaign', () => {
|
||||
const state = {
|
||||
records: campaigns[0],
|
||||
};
|
||||
expect(getters.getCampaigns(state)).toEqual({
|
||||
id: 1,
|
||||
title: 'Welcome',
|
||||
description: null,
|
||||
account_id: 1,
|
||||
inbox: {
|
||||
id: 37,
|
||||
channel_id: 1,
|
||||
name: 'Chatwoot',
|
||||
channel_type: 'Channel::WebWidget',
|
||||
},
|
||||
sender: {
|
||||
account_id: 1,
|
||||
availability_status: 'offline',
|
||||
confirmed: true,
|
||||
email: 'sojan@chatwoot.com',
|
||||
available_name: 'Sojan',
|
||||
id: 10,
|
||||
name: 'Sojan',
|
||||
},
|
||||
message: 'Hey, What brings you today',
|
||||
enabled: true,
|
||||
trigger_rules: {
|
||||
url: 'https://github.com',
|
||||
time_on_page: 10,
|
||||
},
|
||||
created_at: '2021-05-03T04:53:36.354Z',
|
||||
updated_at: '2021-05-03T04:53:36.354Z',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { mutations } from '../../campaign';
|
||||
import { campaigns } from './data';
|
||||
vi.mock('widget/store/index.js', () => ({
|
||||
default: {},
|
||||
}));
|
||||
|
||||
describe('#mutations', () => {
|
||||
describe('#setCampaigns', () => {
|
||||
it('set campaign records', () => {
|
||||
const state = { records: [], uiFlags: {} };
|
||||
mutations.setCampaigns(state, campaigns);
|
||||
expect(state.records).toEqual(campaigns);
|
||||
expect(state.uiFlags.hasFetched).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setError', () => {
|
||||
it('set error flag', () => {
|
||||
const state = { records: [], uiFlags: {} };
|
||||
mutations.setError(state, true);
|
||||
expect(state.uiFlags.isError).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setActiveCampaign', () => {
|
||||
it('set active campaign', () => {
|
||||
const state = { records: [] };
|
||||
mutations.setActiveCampaign(state, campaigns[0]);
|
||||
expect(state.activeCampaign).toEqual(campaigns[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setCampaignExecuted', () => {
|
||||
it('set campaign executed flag', () => {
|
||||
const state = { records: [], uiFlags: {}, campaignHasExecuted: false };
|
||||
mutations.setCampaignExecuted(state, true);
|
||||
expect(state.campaignHasExecuted).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user