chore: fix circleci on vite build (#10214)

- Switch to pnpm based build
- Switch circleci from docker to machine to have more memory
- Fix frontend and backend tests

Fixes
https://linear.app/chatwoot/issue/CW-3610/fix-circle-ci-for-vite-build
---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
Vishnu Narayanan
2024-10-07 15:27:41 +05:30
committed by GitHub
parent 0677d8763d
commit ee02923ace
54 changed files with 1130 additions and 1334 deletions

View File

@@ -27,14 +27,14 @@ export default {
<div class="flex flex-col items-start px-8 pt-8 pb-0">
<img v-if="headerImage" :src="headerImage" alt="No image" />
<h2
ref="modalHeaderTitle"
data-test-id="modal-header-title"
class="text-base font-semibold leading-6 text-slate-800 dark:text-slate-50"
>
{{ headerTitle }}
</h2>
<p
v-if="headerContent"
ref="modalHeaderContent"
data-test-id="modal-header-content"
class="w-full mt-2 text-sm leading-5 break-words text-slate-600 dark:text-slate-300"
>
{{ headerContent }}

View File

@@ -105,7 +105,7 @@ export default {
size="small"
:color-scheme="status.disabled ? '' : 'secondary'"
:variant="status.disabled ? 'smooth' : 'clear'"
class-names="status-change--dropdown-button"
class="status-change--dropdown-button"
@click="changeAvailabilityStatus(status.value)"
>
<AvailabilityStatusBadge :status="status.value" />

View File

@@ -1,79 +1,62 @@
import { mount } from '@vue/test-utils';
import { createStore } from 'vuex';
import AccountSelector from '../AccountSelector.vue';
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
import i18n from 'dashboard/i18n';
import WootModal from 'dashboard/components/Modal.vue';
import WootModalHeader from 'dashboard/components/ModalHeader.vue';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
const localVue = createLocalVue();
localVue.component('woot-modal', WootModal);
localVue.component('woot-modal-header', WootModalHeader);
localVue.component('fluent-icon', FluentIcon);
localVue.use(Vuex);
localVue.use(VueI18n);
const i18nConfig = new VueI18n({
locale: 'en',
messages: i18n,
const store = createStore({
modules: {
auth: {
namespaced: false,
getters: {
getCurrentAccountId: () => 1,
getCurrentUser: () => ({
accounts: [
{ id: 1, name: 'Chatwoot', role: 'administrator' },
{ id: 2, name: 'GitX', role: 'agent' },
],
}),
},
},
globalConfig: {
namespaced: true,
getters: {
get: () => ({ createNewAccountFromDashboard: false }),
},
},
},
});
describe('accountSelctor', () => {
describe('AccountSelector', () => {
let accountSelector = null;
const currentUser = {
accounts: [
{
id: 1,
name: 'Chatwoot',
role: 'administrator',
},
{
id: 2,
name: 'GitX',
role: 'agent',
},
],
};
let actions = null;
let modules = null;
beforeEach(() => {
actions = {};
modules = {
auth: {
getters: {
getCurrentAccountId: () => 1,
getCurrentUser: () => currentUser,
},
},
globalConfig: {
getters: {
'globalConfig/get': () => ({ createNewAccountFromDashboard: false }),
},
},
};
let store = new Vuex.Store({ actions, modules });
accountSelector = mount(AccountSelector, {
store,
localVue,
i18n: i18nConfig,
propsData: { showAccountModal: true },
stubs: { WootButton: { template: '<button />' } },
global: {
plugins: [store],
components: {
'woot-modal': WootModal,
'woot-modal-header': WootModalHeader,
'fluent-icon': FluentIcon,
},
stubs: {
WootButton: { template: '<button />' },
// override global stub
WootModalHeader: false,
},
},
props: { showAccountModal: true },
});
});
it('title and sub title exist', () => {
const headerComponent = accountSelector.findComponent(WootModalHeader);
const title = headerComponent.findComponent({ ref: 'modalHeaderTitle' });
const title = headerComponent.find('[data-test-id="modal-header-title"]');
expect(title.text()).toBe('Switch Account');
const content = headerComponent.findComponent({
ref: 'modalHeaderContent',
});
const content = headerComponent.find(
'[data-test-id="modal-header-content"]'
);
expect(content.text()).toBe('Select an account from the following list');
});

View File

@@ -1,27 +1,10 @@
import { shallowMount } from '@vue/test-utils';
import { createStore } from 'vuex';
import AgentDetails from '../AgentDetails.vue';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
import i18n from 'dashboard/i18n';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import WootButton from 'dashboard/components/ui/WootButton.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueI18n);
localVue.component('thumbnail', Thumbnail);
localVue.component('woot-button', WootButton);
localVue.component('woot-button', WootButton);
localVue.use(VTooltip, {
defaultHtml: false,
});
const i18nConfig = new VueI18n({
locale: 'en',
messages: i18n,
});
describe('agentDetails', () => {
describe('AgentDetails', () => {
const currentUser = {
name: 'Neymar Junior',
avatar_url: '',
@@ -29,37 +12,46 @@ describe('agentDetails', () => {
};
const currentRole = 'agent';
let store = null;
let actions = null;
let modules = null;
let agentDetails = null;
beforeEach(() => {
actions = {};
const mockTooltipDirective = {
mounted: (el, binding) => {
// You can mock the behavior here if necessary
el.setAttribute('data-tooltip', binding.value || '');
},
};
modules = {
auth: {
getters: {
getCurrentUser: () => currentUser,
getCurrentRole: () => currentRole,
getCurrentUserAvailability: () => currentUser.availability_status,
beforeEach(() => {
store = createStore({
modules: {
auth: {
namespaced: false,
getters: {
getCurrentUser: () => currentUser,
getCurrentRole: () => currentRole,
getCurrentUserAvailability: () => currentUser.availability_status,
},
},
},
};
store = new Vuex.Store({
actions,
modules,
});
agentDetails = shallowMount(AgentDetails, {
store,
localVue,
i18n: i18nConfig,
global: {
plugins: [store],
components: {
Thumbnail,
WootButton,
},
directives: {
tooltip: mockTooltipDirective, // Mocking the tooltip directive
},
stubs: { WootButton: { template: '<button><slot /></button>' } },
},
});
});
it(' the agent status', () => {
expect(agentDetails.find('thumbnail-stub').vm.status).toBe('online');
it('shows the correct agent status', () => {
expect(agentDetails.findComponent(Thumbnail).vm.status).toBe('online');
});
it('agent thumbnail exists', () => {

View File

@@ -1,20 +1,7 @@
import NotificationBell from '../NotificationBell.vue';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
import { shallowMount } from '@vue/test-utils';
import { createStore } from 'vuex';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
import i18n from 'dashboard/i18n';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueI18n);
localVue.component('fluent-icon', FluentIcon);
const i18nConfig = new VueI18n({
locale: 'en',
messages: i18n,
});
import NotificationBell from '../NotificationBell.vue';
const $route = {
name: 'notifications_index',
@@ -33,43 +20,51 @@ describe('notificationBell', () => {
};
modules = {
auth: {
namespaced: false,
getters: {
getCurrentAccountId: () => accountId,
},
},
notifications: {
namespaced: false,
getters: {
'notifications/getMeta': () => notificationMetadata,
},
},
};
store = new Vuex.Store({
store = createStore({
actions,
modules,
});
});
it('it should return unread count 19 ', () => {
it('it should return unread count 19', () => {
const wrapper = shallowMount(NotificationBell, {
localVue,
i18n: i18nConfig,
store,
mocks: {
$route,
global: {
plugins: [store],
mocks: {
$route,
},
components: {
'fluent-icon': FluentIcon,
},
},
});
expect(wrapper.vm.unreadCount).toBe('19');
});
it('it should return unread count 99+ ', async () => {
it('it should return unread count 99+', async () => {
notificationMetadata.unreadCount = 100;
const wrapper = shallowMount(NotificationBell, {
localVue,
i18n: i18nConfig,
store,
mocks: {
$route,
global: {
plugins: [store],
mocks: {
$route,
},
components: {
'fluent-icon': FluentIcon,
},
},
});
expect(wrapper.vm.unreadCount).toBe('99+');
@@ -77,11 +72,14 @@ describe('notificationBell', () => {
it('isNotificationPanelActive', async () => {
const notificationBell = shallowMount(NotificationBell, {
store,
localVue,
i18n: i18nConfig,
mocks: {
$route,
global: {
plugins: [store],
mocks: {
$route,
},
components: {
'fluent-icon': FluentIcon,
},
},
});

View File

@@ -1,9 +1,6 @@
import { mount } from '@vue/test-utils';
import { createStore } from 'vuex';
import AvailabilityStatus from '../AvailabilityStatus.vue';
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
import FloatingVue from 'floating-vue';
import WootButton from 'dashboard/components/ui/WootButton.vue';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
@@ -11,70 +8,64 @@ import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader.vue
import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider.vue';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
import i18n from 'dashboard/i18n';
const localVue = createLocalVue();
localVue.use(FloatingVue, {
html: false,
});
localVue.use(Vuex);
localVue.use(VueI18n);
localVue.component('woot-button', WootButton);
localVue.component('woot-dropdown-header', WootDropdownHeader);
localVue.component('woot-dropdown-menu', WootDropdownMenu);
localVue.component('woot-dropdown-divider', WootDropdownDivider);
localVue.component('woot-dropdown-item', WootDropdownItem);
localVue.component('fluent-icon', FluentIcon);
const i18nConfig = new VueI18n({ locale: 'en', messages: i18n });
describe('AvailabilityStatus', () => {
const currentAvailability = 'online';
const currentAccountId = '1';
const currentUserAutoOffline = false;
let store = null;
let actions = null;
let modules = null;
let availabilityStatus = null;
beforeEach(() => {
actions = {
updateAvailability: vi.fn(() => {
return Promise.resolve();
}),
updateAvailability: vi.fn(() => Promise.resolve()),
};
modules = {
auth: {
getters: {
getCurrentUserAvailability: () => currentAvailability,
getCurrentAccountId: () => currentAccountId,
getCurrentUserAutoOffline: () => currentUserAutoOffline,
store = createStore({
modules: {
auth: {
namespaced: false,
getters: {
getCurrentUserAvailability: () => currentAvailability,
getCurrentAccountId: () => currentAccountId,
getCurrentUserAutoOffline: () => currentUserAutoOffline,
},
},
},
};
store = new Vuex.Store({ actions, modules });
availabilityStatus = mount(AvailabilityStatus, {
store,
localVue,
i18n: i18nConfig,
stubs: { WootSwitch: { template: '<button />' } },
actions,
});
});
it('dispatches an action when user changes status', async () => {
await availabilityStatus;
availabilityStatus
.findAll('.status-change--dropdown-button')
.at(2)
.trigger('click');
const wrapper = mount(AvailabilityStatus, {
global: {
plugins: [store],
components: {
WootButton,
WootDropdownItem,
WootDropdownMenu,
WootDropdownHeader,
WootDropdownDivider,
FluentIcon,
},
stubs: {
WootSwitch: { template: '<button />' },
},
},
});
expect(actions.updateAvailability).toBeCalledWith(
expect.any(Object),
{ availability: 'offline', account_id: currentAccountId },
undefined
);
// Ensure that the dropdown menu is opened
await wrapper.vm.openStatusMenu();
// Simulate the user clicking the 3rd button (offline status)
const buttons = wrapper.findAll('.status-change--dropdown-button');
expect(buttons.length).toBeGreaterThan(0); // Ensure buttons exist
await buttons[2].trigger('click');
expect(actions.updateAvailability).toHaveBeenCalledTimes(1);
expect(actions.updateAvailability.mock.calls[0][1]).toEqual({
availability: 'offline',
account_id: currentAccountId,
});
});
});

View File

@@ -7,5 +7,8 @@ exports[`SidemenuIcon > matches snapshot 1`] = `
icon="list"
size="small"
variant="clear"
/>
>
</button>
`;

View File

@@ -1,6 +1,7 @@
import lamejs from '@breezystack/lamejs';
const writeString = (view, offset, string) => {
// eslint-disable-next-line no-plusplus
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
@@ -28,7 +29,9 @@ const bufferToWav = async (buffer, numChannels, sampleRate) => {
// WAV Data
const offset = 44;
// eslint-disable-next-line no-plusplus
for (let i = 0; i < buffer.length; i++) {
// eslint-disable-next-line no-plusplus
for (let channel = 0; channel < numChannels; channel++) {
const sample = Math.max(
-1,

View File

@@ -478,6 +478,7 @@ export default {
</div>
<ul class="conversation-panel">
<transition name="slide-up">
<!-- eslint-disable-next-line vue/require-toggle-inside-transition -->
<li class="min-h-[4rem]">
<span v-if="shouldShowSpinner" class="spinner message" />
</li>

View File

@@ -1,11 +1,7 @@
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
import FloatingVue from 'floating-vue';
import Button from 'dashboard/components/buttons/Button.vue';
import i18n from 'dashboard/i18n';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
import { mount } from '@vue/test-utils';
import { createStore } from 'vuex';
import MoreActions from '../MoreActions.vue';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
vi.mock('shared/helpers/mitt', () => ({
emitter: {
@@ -15,75 +11,67 @@ vi.mock('shared/helpers/mitt', () => ({
},
}));
import { emitter } from 'shared/helpers/mitt';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueI18n);
localVue.use(FloatingVue);
localVue.component('fluent-icon', FluentIcon);
localVue.component('woot-button', Button);
localVue.prototype.$emitter = {
emit: vi.fn(),
on: vi.fn(),
off: vi.fn(),
const mockDirective = {
mounted: () => {},
};
const i18nConfig = new VueI18n({ locale: 'en', messages: i18n });
import { emitter } from 'shared/helpers/mitt';
describe('MoveActions', () => {
let currentChat = { id: 8, muted: false };
let state = null;
let store = null;
let muteConversation = null;
let unmuteConversation = null;
let modules = null;
let getters = null;
let store = null;
let moreActions = null;
beforeEach(() => {
state = {
authenticated: true,
currentChat,
};
muteConversation = vi.fn(() => Promise.resolve());
unmuteConversation = vi.fn(() => Promise.resolve());
modules = {
conversations: { actions: { muteConversation, unmuteConversation } },
};
getters = { getSelectedChat: () => currentChat };
store = new Vuex.Store({ state, modules, getters });
moreActions = mount(MoreActions, {
store,
localVue,
i18n: i18nConfig,
stubs: {
WootModal: { template: '<div><slot/> </div>' },
WootModalHeader: { template: '<div><slot/> </div>' },
store = createStore({
state: {
authenticated: true,
currentChat,
},
getters: {
getSelectedChat: () => currentChat,
},
modules: {
conversations: {
namespaced: false,
actions: { muteConversation, unmuteConversation },
},
},
});
});
const createWrapper = () =>
mount(MoreActions, {
global: {
plugins: [store],
components: {
'fluent-icon': FluentIcon,
},
directives: {
'on-clickaway': mockDirective,
},
},
});
describe('muting discussion', () => {
it('triggers "muteConversation"', async () => {
await moreActions.find('button:first-child').trigger('click');
const wrapper = createWrapper();
await wrapper.find('button:first-child').trigger('click');
expect(muteConversation).toBeCalledWith(
expect.any(Object),
currentChat.id,
undefined
expect(muteConversation).toHaveBeenCalledTimes(1);
expect(muteConversation).toHaveBeenCalledWith(
expect.any(Object), // First argument is the Vuex context object
currentChat.id // Second argument is the ID of the conversation
);
});
it('shows alert', async () => {
await moreActions.find('button:first-child').trigger('click');
const wrapper = createWrapper();
await wrapper.find('button:first-child').trigger('click');
expect(emitter.emit).toBeCalledWith('newToastMessage', {
message:
@@ -99,17 +87,19 @@ describe('MoveActions', () => {
});
it('triggers "unmuteConversation"', async () => {
await moreActions.find('button:first-child').trigger('click');
const wrapper = createWrapper();
await wrapper.find('button:first-child').trigger('click');
expect(unmuteConversation).toBeCalledWith(
expect.any(Object),
currentChat.id,
undefined
expect(unmuteConversation).toHaveBeenCalledTimes(1);
expect(unmuteConversation).toHaveBeenCalledWith(
expect.any(Object), // First argument is the Vuex context object
currentChat.id // Second argument is the ID of the conversation
);
});
it('shows alert', async () => {
await moreActions.find('button:first-child').trigger('click');
const wrapper = createWrapper();
await wrapper.find('button:first-child').trigger('click');
expect(emitter.emit).toBeCalledWith('newToastMessage', {
message: 'This contact is unblocked successfully.',

View File

@@ -1,5 +1,5 @@
import { emitter } from 'shared/helpers/mitt';
import analyticsHelper from '/dashboard/helper/AnalyticsHelper/index';
import analyticsHelper from 'dashboard/helper/AnalyticsHelper/index';
/**
* Custom hook to track events

View File

@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { emitter } from 'shared/helpers/mitt';
import { useEmitter } from '../emitter';
import { defineComponent } from 'vue';
vi.mock('shared/helpers/mitt', () => ({
emitter: {
@@ -10,31 +11,34 @@ vi.mock('shared/helpers/mitt', () => ({
}));
describe('useEmitter', () => {
let wrapper;
const eventName = 'my-event';
const callback = vi.fn();
let wrapper;
const TestComponent = defineComponent({
setup() {
return {
cleanup: useEmitter(eventName, callback),
};
},
template: '<div>Hello world</div>',
});
beforeEach(() => {
wrapper = shallowMount({
template: `
<div>
Hello world
</div>
`,
setup() {
return {
cleanup: useEmitter(eventName, callback),
};
},
});
wrapper = shallowMount(TestComponent);
});
afterEach(() => {
vi.clearAllMocks();
});
it('should add an event listener on mount', () => {
expect(emitter.on).toHaveBeenCalledWith(eventName, callback);
});
it('should remove the event listener when the component is unmounted', () => {
wrapper.destroy();
it('should remove the event listener when the component is unmounted', async () => {
await wrapper.unmount();
expect(emitter.off).toHaveBeenCalledWith(eventName, callback);
});

View File

@@ -1,20 +1,26 @@
import { getCurrentInstance } from 'vue';
import { emitter } from 'shared/helpers/mitt';
import analyticsHelper from 'dashboard/helper/AnalyticsHelper';
import { useTrack, useAlert } from '../index';
vi.mock('vue', () => ({
getCurrentInstance: vi.fn(),
}));
vi.mock('shared/helpers/mitt', () => ({
emitter: {
emit: vi.fn(),
},
}));
vi.mock('dashboard/helper/AnalyticsHelper/index', async importOriginal => {
const actual = await importOriginal();
actual.default = {
track: vi.fn(),
};
return actual;
});
describe('useTrack', () => {
it('should return a function', () => {
const track = useTrack();
expect(typeof track).toBe('function');
it('should call analyticsHelper.track and return a function', () => {
const eventArgs = ['event-name', { some: 'data' }];
useTrack(...eventArgs);
expect(analyticsHelper.track).toHaveBeenCalledWith(...eventArgs);
});
});

View File

@@ -4,14 +4,20 @@ import {
useStoreGetters,
useMapGetter,
} from 'dashboard/composables/store';
import { useAlert, useTrack } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import OpenAPI from 'dashboard/api/integrations/openapi';
import analyticsHelper from 'dashboard/helper/AnalyticsHelper/index';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables');
vi.mock('vue-i18n');
vi.mock('dashboard/api/integrations/openapi');
vi.mock('dashboard/helper/AnalyticsHelper/index', async importOriginal => {
const actual = await importOriginal();
actual.default = {
track: vi.fn(),
};
return actual;
});
vi.mock('dashboard/helper/AnalyticsHelper/events', () => ({
OPEN_AI_EVENTS: {
TEST_EVENT: 'open_ai_test_event',
@@ -40,9 +46,7 @@ describe('useAI', () => {
};
return { value: mockValues[getter] };
});
useTrack.mockReturnValue(vi.fn());
useI18n.mockReturnValue({ t: vi.fn() });
useAlert.mockReturnValue(vi.fn());
});
it('initializes computed properties correctly', async () => {
@@ -78,13 +82,12 @@ describe('useAI', () => {
});
it('records analytics correctly', async () => {
const mockTrack = vi.fn();
useTrack.mockReturnValue(mockTrack);
// const mockTrack = analyticsHelper.track;
const { recordAnalytics } = useAI();
await recordAnalytics('TEST_EVENT', { data: 'test' });
expect(mockTrack).toHaveBeenCalledWith('open_ai_test_event', {
expect(analyticsHelper.track).toHaveBeenCalledWith('open_ai_test_event', {
type: 'TEST_EVENT',
data: 'test',
});

View File

@@ -1,7 +1,7 @@
import { useAutomation } from '../useAutomation';
import { useStoreGetters, useMapGetter } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables';
import { useI18n } from '../useI18n';
import { useI18n } from 'vue-i18n';
import * as automationHelper from 'dashboard/helper/automationHelper';
import {
customAttributes,
@@ -20,7 +20,7 @@ import { MESSAGE_CONDITION_VALUES } from 'dashboard/constants/automation';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables');
vi.mock('../useI18n');
vi.mock('vue-i18n');
vi.mock('dashboard/helper/automationHelper');
describe('useAutomation', () => {
@@ -120,8 +120,8 @@ describe('useAutomation', () => {
});
it('appends new condition and action correctly', () => {
const { appendNewCondition, appendNewAction } = useAutomation();
const mockAutomation = {
const { appendNewCondition, appendNewAction, automation } = useAutomation();
automation.value = {
event_name: 'message_created',
conditions: [],
actions: [],
@@ -130,36 +130,37 @@ describe('useAutomation', () => {
automationHelper.getDefaultConditions.mockReturnValue([{}]);
automationHelper.getDefaultActions.mockReturnValue([{}]);
appendNewCondition(mockAutomation);
appendNewAction(mockAutomation);
appendNewCondition();
appendNewAction();
expect(automationHelper.getDefaultConditions).toHaveBeenCalledWith(
'message_created'
);
expect(automationHelper.getDefaultActions).toHaveBeenCalled();
expect(mockAutomation.conditions).toHaveLength(1);
expect(mockAutomation.actions).toHaveLength(1);
expect(automation.value.conditions).toHaveLength(1);
expect(automation.value.actions).toHaveLength(1);
});
it('removes filter and action correctly', () => {
const { removeFilter, removeAction } = useAutomation();
const mockAutomation = {
const { removeFilter, removeAction, automation } = useAutomation();
automation.value = {
conditions: [{ id: 1 }, { id: 2 }],
actions: [{ id: 1 }, { id: 2 }],
};
removeFilter(mockAutomation, 0);
removeAction(mockAutomation, 0);
removeFilter(0);
removeAction(0);
expect(mockAutomation.conditions).toHaveLength(1);
expect(mockAutomation.actions).toHaveLength(1);
expect(mockAutomation.conditions[0].id).toBe(2);
expect(mockAutomation.actions[0].id).toBe(2);
expect(automation.value.conditions).toHaveLength(1);
expect(automation.value.actions).toHaveLength(1);
expect(automation.value.conditions[0].id).toBe(2);
expect(automation.value.actions[0].id).toBe(2);
});
it('resets filter and action correctly', () => {
const { resetFilter, resetAction } = useAutomation();
const mockAutomation = {
const { resetFilter, resetAction, automation, automationTypes } =
useAutomation();
automation.value = {
event_name: 'message_created',
conditions: [
{
@@ -170,77 +171,37 @@ describe('useAutomation', () => {
],
actions: [{ action_name: 'assign_agent', action_params: [1] }],
};
const mockAutomationTypes = {
message_created: {
conditions: [
{ key: 'status', filterOperators: [{ value: 'not_equal_to' }] },
],
},
automationTypes.message_created = {
conditions: [
{ key: 'status', filterOperators: [{ value: 'not_equal_to' }] },
],
};
resetFilter(
mockAutomation,
mockAutomationTypes,
0,
mockAutomation.conditions[0]
);
resetAction(mockAutomation, 0);
resetFilter(0, automation.value.conditions[0]);
resetAction(0);
expect(mockAutomation.conditions[0].filter_operator).toBe('not_equal_to');
expect(mockAutomation.conditions[0].values).toBe('');
expect(mockAutomation.actions[0].action_params).toEqual([]);
});
it('formats automation correctly', () => {
const { formatAutomation } = useAutomation();
const mockAutomation = {
conditions: [{ attribute_key: 'status', values: ['open'] }],
actions: [{ action_name: 'assign_agent', action_params: [1] }],
};
const mockAutomationTypes = {};
const mockAutomationActionTypes = [
{ key: 'assign_agent', inputType: 'search_select' },
];
automationHelper.getConditionOptions.mockReturnValue([
{ id: 'open', name: 'open' },
]);
automationHelper.getActionOptions.mockReturnValue([
{ id: 1, name: 'Agent 1' },
]);
const result = formatAutomation(
mockAutomation,
customAttributes,
mockAutomationTypes,
mockAutomationActionTypes
);
expect(result.conditions[0].values).toEqual([{ id: 'open', name: 'open' }]);
expect(result.actions[0].action_params).toEqual([
{ id: 1, name: 'Agent 1' },
]);
expect(automation.value.conditions[0].filter_operator).toBe('not_equal_to');
expect(automation.value.conditions[0].values).toBe('');
expect(automation.value.actions[0].action_params).toEqual([]);
});
it('manifests custom attributes correctly', () => {
const { manifestCustomAttributes } = useAutomation();
const mockAutomationTypes = {
message_created: { conditions: [] },
conversation_created: { conditions: [] },
conversation_updated: { conditions: [] },
conversation_opened: { conditions: [] },
};
const { manifestCustomAttributes, automationTypes } = useAutomation();
automationTypes.message_created = { conditions: [] };
automationTypes.conversation_created = { conditions: [] };
automationTypes.conversation_updated = { conditions: [] };
automationTypes.conversation_opened = { conditions: [] };
automationHelper.generateCustomAttributeTypes.mockReturnValue([]);
automationHelper.generateCustomAttributes.mockReturnValue([]);
manifestCustomAttributes(mockAutomationTypes);
manifestCustomAttributes();
expect(automationHelper.generateCustomAttributeTypes).toHaveBeenCalledTimes(
2
);
expect(automationHelper.generateCustomAttributes).toHaveBeenCalledTimes(1);
Object.values(mockAutomationTypes).forEach(type => {
Object.values(automationTypes).forEach(type => {
expect(type.conditions).toHaveLength(0);
});
});
@@ -273,8 +234,8 @@ describe('useAutomation', () => {
});
it('handles event change correctly', () => {
const { onEventChange } = useAutomation();
const mockAutomation = {
const { onEventChange, automation } = useAutomation();
automation.value = {
event_name: 'message_created',
conditions: [],
actions: [],
@@ -283,13 +244,13 @@ describe('useAutomation', () => {
automationHelper.getDefaultConditions.mockReturnValue([{}]);
automationHelper.getDefaultActions.mockReturnValue([{}]);
onEventChange(mockAutomation);
onEventChange();
expect(automationHelper.getDefaultConditions).toHaveBeenCalledWith(
'message_created'
);
expect(automationHelper.getDefaultActions).toHaveBeenCalled();
expect(mockAutomation.conditions).toHaveLength(1);
expect(mockAutomation.actions).toHaveLength(1);
expect(automation.value.conditions).toHaveLength(1);
expect(automation.value.actions).toHaveLength(1);
});
});

View File

@@ -1,36 +0,0 @@
import Vue from 'vue';
import plugin from '../plugin';
import analyticsHelper from '../index';
vi.spyOn(analyticsHelper, 'init');
vi.spyOn(analyticsHelper, 'track');
describe('Vue Analytics Plugin', () => {
beforeEach(() => {
Vue.use(plugin);
});
it('should call the init method on analyticsHelper once during plugin installation', () => {
expect(analyticsHelper.init).toHaveBeenCalledTimes(1);
});
it('should add the analyticsHelper to the Vue prototype as $analytics', () => {
expect(Vue.prototype.$analytics).toBe(analyticsHelper);
});
it('should add a track method to the Vue prototype as $track', () => {
expect(typeof Vue.prototype.$track).toBe('function');
Vue.prototype.$track('eventName');
expect(analyticsHelper.track)
.toHaveBeenCalledTimes(1)
.toHaveBeenCalledWith('eventName');
});
it('should call the track method on analyticsHelper with the correct event name when $track is called', () => {
const eventName = 'testEvent';
Vue.prototype.$track(eventName);
expect(analyticsHelper.track)
.toHaveBeenCalledTimes(1)
.toHaveBeenCalledWith(eventName);
});
});

View File

@@ -37,8 +37,10 @@ const storeMock = {
const routerMock = {
currentRoute: {
name: '',
params: { conversation_id: null },
value: {
name: '',
params: { conversation_id: null },
},
},
};
@@ -222,7 +224,7 @@ describe('ReconnectService', () => {
describe('fetchConversationMessagesOnReconnect', () => {
it('should dispatch syncActiveConversationMessages if conversationId exists', async () => {
routerMock.currentRoute.params.conversation_id = 1;
routerMock.currentRoute.value.params.conversation_id = 1;
await reconnectService.fetchConversationMessagesOnReconnect();
expect(storeMock.dispatch).toHaveBeenCalledWith(
'syncActiveConversationMessages',
@@ -231,7 +233,7 @@ describe('ReconnectService', () => {
});
it('should not dispatch syncActiveConversationMessages if conversationId does not exist', async () => {
routerMock.currentRoute.params.conversation_id = null;
routerMock.currentRoute.value.params.conversation_id = null;
await reconnectService.fetchConversationMessagesOnReconnect();
expect(storeMock.dispatch).not.toHaveBeenCalledWith(
'syncActiveConversationMessages',
@@ -305,7 +307,7 @@ describe('ReconnectService', () => {
describe('setConversationLastMessageId', () => {
it('should dispatch setConversationLastMessageId if conversationId exists', async () => {
routerMock.currentRoute.params.conversation_id = 1;
routerMock.currentRoute.value.params.conversation_id = 1;
await reconnectService.setConversationLastMessageId();
expect(storeMock.dispatch).toHaveBeenCalledWith(
'setConversationLastMessageId',
@@ -314,7 +316,7 @@ describe('ReconnectService', () => {
});
it('should not dispatch setConversationLastMessageId if conversationId does not exist', async () => {
routerMock.currentRoute.params.conversation_id = null;
routerMock.currentRoute.value.params.conversation_id = null;
await reconnectService.setConversationLastMessageId();
expect(storeMock.dispatch).not.toHaveBeenCalledWith(
'setConversationLastMessageId',

View File

@@ -1,78 +0,0 @@
import resize from '../../directives/resize';
class ResizeObserverMock {
// eslint-disable-next-line class-methods-use-this
observe() {}
// eslint-disable-next-line class-methods-use-this
unobserve() {}
// eslint-disable-next-line class-methods-use-this
disconnect() {}
}
describe('resize directive', () => {
let el;
let binding;
let observer;
beforeEach(() => {
el = document.createElement('div');
binding = {
value: vi.fn(),
};
observer = {
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
};
window.ResizeObserver = ResizeObserverMock;
vi.spyOn(window, 'ResizeObserver').mockImplementation(() => observer);
});
afterEach(() => {
vi.clearAllMocks();
});
it('should create ResizeObserver on bind', () => {
resize.bind(el, binding);
expect(ResizeObserver).toHaveBeenCalled();
expect(observer.observe).toHaveBeenCalledWith(el);
});
it('should call callback on observer callback', () => {
el = document.createElement('div');
binding = {
value: vi.fn(),
};
resize.bind(el, binding);
const entries = [{ contentRect: { width: 100, height: 100 } }];
const callback = binding.value;
callback(entries[0]);
expect(binding.value).toHaveBeenCalledWith(entries[0]);
});
it('should destroy and recreate observer on update', () => {
resize.bind(el, binding);
resize.update(el, { ...binding, oldValue: 'old' });
expect(observer.unobserve).toHaveBeenCalledWith(el);
expect(observer.disconnect).toHaveBeenCalled();
expect(ResizeObserver).toHaveBeenCalledTimes(2);
expect(observer.observe).toHaveBeenCalledTimes(2);
});
it('should destroy observer on unbind', () => {
resize.bind(el, binding);
resize.unbind(el);
expect(observer.unobserve).toHaveBeenCalledWith(el);
expect(observer.disconnect).toHaveBeenCalled();
});
});

View File

@@ -9,8 +9,8 @@ import {
findNodeToInsertImage,
setURLWithQueryAndSize,
} from '../editorHelper';
import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { EditorState } from '@chatwoot/prosemirror-schema';
import { EditorView } from '@chatwoot/prosemirror-schema';
import { Schema } from 'prosemirror-model';
// Define a basic ProseMirror schema

View File

@@ -1,6 +1,8 @@
import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import { useAlert } from 'dashboard/composables';
import fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
import { reactive } from 'vue';
vi.mock('shared/helpers/FileHelper', () => ({
checkFileSizeLimit: vi.fn(),
@@ -17,61 +19,80 @@ vi.mock('dashboard/composables', () => ({
}));
describe('FileUploadMixin', () => {
let vm;
let wrapper;
let mockGlobalConfig;
let mockCurrentChat;
let mockCurrentUser;
beforeEach(() => {
vm = new Vue(fileUploadMixin);
vm.isATwilioSMSChannel = false;
vm.globalConfig = {
mockGlobalConfig = reactive({
directUploadsEnabled: true,
};
vm.accountId = 123;
vm.currentChat = {
});
mockCurrentChat = reactive({
id: 456,
};
vm.currentUser = {
});
mockCurrentUser = reactive({
access_token: 'token',
};
vm.$t = vi.fn(message => message);
vm.showAlert = vi.fn();
vm.attachFile = vi.fn();
});
wrapper = shallowMount({
mixins: [fileUploadMixin],
data() {
return {
globalConfig: mockGlobalConfig,
currentChat: mockCurrentChat,
currentUser: mockCurrentUser,
isATwilioSMSChannel: false,
};
},
methods: {
attachFile: vi.fn(),
showAlert: vi.fn(),
$t: msg => msg,
},
template: '<div />',
});
});
it('should call onDirectFileUpload when direct uploads are enabled', () => {
vm.onDirectFileUpload = vi.fn();
vm.onFileUpload({});
expect(vm.onDirectFileUpload).toHaveBeenCalledWith({});
wrapper.vm.onDirectFileUpload = vi.fn();
wrapper.vm.onFileUpload({});
expect(wrapper.vm.onDirectFileUpload).toHaveBeenCalledWith({});
});
it('should call onIndirectFileUpload when direct uploads are disabled', () => {
vm.globalConfig.directUploadsEnabled = false;
vm.onIndirectFileUpload = vi.fn();
vm.onFileUpload({});
expect(vm.onIndirectFileUpload).toHaveBeenCalledWith({});
wrapper.vm.globalConfig.directUploadsEnabled = false;
wrapper.vm.onIndirectFileUpload = vi.fn();
wrapper.vm.onFileUpload({});
expect(wrapper.vm.onIndirectFileUpload).toHaveBeenCalledWith({});
});
describe('onDirectFileUpload', () => {
it('returns early if no file is provided', () => {
const returnValue = vm.onDirectFileUpload(null);
const returnValue = wrapper.vm.onDirectFileUpload(null);
expect(returnValue).toBeUndefined();
});
it('shows an alert if the file size exceeds the maximum limit', () => {
const fakeFile = { size: 999999999 };
vm.onDirectFileUpload(fakeFile);
checkFileSizeLimit.mockReturnValue(false); // Mock exceeding file size
wrapper.vm.onDirectFileUpload(fakeFile);
expect(useAlert).toHaveBeenCalledWith(expect.any(String));
});
});
describe('onIndirectFileUpload', () => {
it('returns early if no file is provided', () => {
const returnValue = vm.onIndirectFileUpload(null);
const returnValue = wrapper.vm.onIndirectFileUpload(null);
expect(returnValue).toBeUndefined();
});
it('shows an alert if the file size exceeds the maximum limit', () => {
const fakeFile = { size: 999999999 };
vm.onIndirectFileUpload(fakeFile);
checkFileSizeLimit.mockReturnValue(false); // Mock exceeding file size
wrapper.vm.onIndirectFileUpload(fakeFile);
expect(useAlert).toHaveBeenCalledWith(expect.any(String));
});
});

View File

@@ -1,16 +1,15 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import { createStore } from 'vuex';
import { createRouter, createWebHistory } from 'vue-router';
import portalMixin from '../portalMixin';
import Vuex from 'vuex';
import VueRouter from 'vue-router';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueRouter);
import ListAllArticles from '../../pages/portals/ListAllPortals.vue';
const router = new VueRouter({
// Create router instance
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: ':portalSlug/:locale/articles',
path: '/:portalSlug/:locale/articles', // Add leading "/"
name: 'list_all_locale_articles',
component: ListAllArticles,
},
@@ -30,18 +29,21 @@ describe('portalMixin', () => {
render() {},
title: 'TestComponent',
mixins: [portalMixin],
router,
};
store = new Vuex.Store({ getters });
wrapper = shallowMount(Component, { store, localVue });
store = createStore({ getters });
wrapper = shallowMount(Component, {
global: {
plugins: [store, router],
},
});
});
it('return account id', () => {
it('returns account id', () => {
expect(wrapper.vm.accountId).toBe(1);
});
it('returns article url', () => {
router.push({
it('returns article url', async () => {
await router.push({
name: 'list_all_locale_articles',
params: { portalSlug: 'fur-rent', locale: 'en' },
});
@@ -50,24 +52,24 @@ describe('portalMixin', () => {
);
});
it('returns portal locale', () => {
router.push({
it('returns portal locale', async () => {
await router.push({
name: 'list_all_locale_articles',
params: { portalSlug: 'fur-rent', locale: 'es' },
});
expect(wrapper.vm.portalSlug).toBe('fur-rent');
});
it('returns portal slug', () => {
router.push({
it('returns portal slug', async () => {
await router.push({
name: 'list_all_locale_articles',
params: { portalSlug: 'campaign', locale: 'es' },
});
expect(wrapper.vm.portalSlug).toBe('campaign');
});
it('returns locale name', () => {
router.push({
it('returns locale name', async () => {
await router.push({
name: 'list_all_locale_articles',
params: { portalSlug: 'fur-rent', locale: 'es' },
});

View File

@@ -128,6 +128,7 @@ export default {
<template>
<transition name="popover-animation">
<!-- eslint-disable-next-line vue/require-toggle-inside-transition -->
<div
class="min-w-[15rem] max-w-[22.5rem] p-6 overflow-y-auto border-l rtl:border-r rtl:border-l-0 border-solid border-slate-50 dark:border-slate-700"
>

View File

@@ -19,11 +19,9 @@ defineProps({
});
</script>
<!-- eslint-disable vue/no-unused-refs -->
<!-- Added ref for writing specs -->
<template>
<div
ref="reportMetricContainer"
data-test-id="reportMetricContainer"
class="p-4 m-0"
:class="{
'grayscale pointer-events-none opacity-30': disabled,
@@ -32,17 +30,17 @@ defineProps({
<h3
class="flex items-center m-0 text-sm font-medium text-slate-800 dark:text-slate-100"
>
<span ref="reportMetricLabel">{{ label }}</span>
<span data-test-id="reportMetricLabel">{{ label }}</span>
<fluent-icon
ref="reportMetricInfo"
v-tooltip="infoText"
data-test-id="reportMetricInfo"
size="14"
icon="info"
class="text-slate-500 dark:text-slate-200 my-0 mx-1 mt-0.5"
/>
</h3>
<h4
ref="reportMetricValue"
data-test-id="reportMetricValue"
class="mt-1 mb-0 text-3xl font-thin text-slate-700 dark:text-slate-100"
>
{{ value }}

View File

@@ -1,17 +1,7 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import { createStore } from 'vuex';
import CsatMetrics from '../CsatMetrics.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const mountParams = {
mocks: {
$t: msg => msg,
},
stubs: ['csat-metric-card', 'woot-horizontal-bar'],
};
describe('CsatMetrics.vue', () => {
let getters;
let store;
@@ -21,20 +11,33 @@ describe('CsatMetrics.vue', () => {
beforeEach(() => {
getters = {
'csat/getMetrics': () => ({ totalResponseCount: 100 }),
'csat/getRatingPercentage': () => ({ 1: 10, 2: 20, 3: 30, 4: 30, 5: 10 }),
'csat/getRatingPercentage': () => ({
1: 10,
2: 20,
3: 30,
4: 30,
5: 10,
}),
'csat/getSatisfactionScore': () => 85,
'csat/getResponseRate': () => 90,
};
store = new Vuex.Store({
store = createStore({
getters,
});
wrapper = shallowMount(CsatMetrics, {
store,
localVue,
propsData: { filters },
...mountParams,
global: {
plugins: [store], // Ensure the store is injected here
mocks: {
$t: msg => msg, // mock translation function
},
stubs: {
CsatMetricCard: '<csat-metric-card/>',
BarChart: '<woot-horizontal-bar/>',
},
},
props: { filters },
});
});
@@ -54,13 +57,11 @@ describe('CsatMetrics.vue', () => {
});
it('hides report card if rating filter is enabled', () => {
expect(wrapper.find({ ref: 'csatHorizontalBarChart' }).exists()).toBe(
false
);
expect(wrapper.html()).not.toContain('bar-chart-stub');
});
it('shows report card if rating filter is not enabled', async () => {
await wrapper.setProps({ filters: {} });
expect(wrapper.find({ ref: 'csatHorizontalBarChart' }).exists()).toBe(true);
expect(wrapper.html()).toContain('bar-chart-stub');
});
});

View File

@@ -1,11 +1,8 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import { createStore } from 'vuex';
import ReportsFiltersAgents from '../../Filters/Agents.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const mockStore = new Vuex.Store({
const mockStore = createStore({
modules: {
agents: {
namespaced: true,
@@ -23,25 +20,26 @@ const mockStore = new Vuex.Store({
});
const mountParams = {
localVue,
store: mockStore,
mocks: {
$t: msg => msg,
global: {
plugins: [mockStore],
mocks: {
$t: msg => msg,
},
stubs: ['multiselect'],
},
stubs: ['multiselect'],
};
describe('ReportsFiltersAgents.vue', () => {
it('emits "agents-filter-selection" event when handleInput is called', () => {
it('emits "agents-filter-selection" event when handleInput is called', async () => {
const wrapper = shallowMount(ReportsFiltersAgents, mountParams);
const selectedAgents = [
{ id: 1, name: 'Agent 1' },
{ id: 2, name: 'Agent 2' },
];
wrapper.setData({ selectedOptions: selectedAgents });
await wrapper.setData({ selectedOptions: selectedAgents });
wrapper.vm.handleInput();
await wrapper.vm.handleInput();
expect(wrapper.emitted('agentsFilterSelection')).toBeTruthy();
expect(wrapper.emitted('agentsFilterSelection')[0]).toEqual([

View File

@@ -3,10 +3,12 @@ import ReportsFiltersDateGroupBy from '../../Filters/DateGroupBy.vue';
import { GROUP_BY_OPTIONS } from '../../../constants';
const mountParams = {
mocks: {
$t: msg => msg,
global: {
mocks: {
$t: msg => msg,
},
stubs: ['multiselect'],
},
stubs: ['multiselect'],
};
describe('ReportsFiltersDateGroupBy.vue', () => {

View File

@@ -3,10 +3,12 @@ import ReportFiltersDateRange from '../../Filters/DateRange.vue';
import { DATE_RANGE_OPTIONS } from '../../../constants';
const mountParams = {
mocks: {
$t: msg => msg,
global: {
mocks: {
$t: msg => msg,
},
stubs: ['multiselect'],
},
stubs: ['multiselect'],
};
describe('ReportFiltersDateRange.vue', () => {

View File

@@ -1,15 +1,14 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import { createStore } from 'vuex';
import ReportsFiltersInboxes from '../../Filters/Inboxes.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const mountParams = {
mocks: {
$t: msg => msg,
global: {
mocks: {
$t: msg => msg,
},
stubs: ['multiselect'],
},
stubs: ['multiselect'],
};
describe('ReportsFiltersInboxes.vue', () => {
@@ -30,7 +29,7 @@ describe('ReportsFiltersInboxes.vue', () => {
},
};
store = new Vuex.Store({
store = createStore({
modules: {
inboxes: inboxesModule,
},
@@ -39,24 +38,26 @@ describe('ReportsFiltersInboxes.vue', () => {
it('dispatches "inboxes/get" action when component is mounted', () => {
shallowMount(ReportsFiltersInboxes, {
store,
localVue,
...mountParams,
global: {
plugins: [store],
...mountParams.global,
},
});
expect(inboxesModule.actions.get).toHaveBeenCalled();
});
it('emits "inbox-filter-selection" event when handleInput is called', () => {
it('emits "inbox-filter-selection" event when handleInput is called', async () => {
const wrapper = shallowMount(ReportsFiltersInboxes, {
store,
localVue,
...mountParams,
global: {
plugins: [store],
...mountParams.global,
},
});
const selectedInbox = { id: 1, name: 'Inbox 1' };
wrapper.setData({ selectedOption: selectedInbox });
await wrapper.setData({ selectedOption: selectedInbox });
wrapper.vm.handleInput();
await wrapper.vm.handleInput();
expect(wrapper.emitted('inboxFilterSelection')).toBeTruthy();
expect(wrapper.emitted('inboxFilterSelection')[0]).toEqual([selectedInbox]);

View File

@@ -1,15 +1,14 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import { createStore } from 'vuex';
import ReportsFiltersLabels from '../../Filters/Labels.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const mountParams = {
mocks: {
$t: msg => msg,
global: {
mocks: {
$t: msg => msg,
},
stubs: ['multiselect'],
},
stubs: ['multiselect'],
};
describe('ReportsFiltersLabels.vue', () => {
@@ -30,7 +29,7 @@ describe('ReportsFiltersLabels.vue', () => {
},
};
store = new Vuex.Store({
store = createStore({
modules: {
labels: labelsModule,
},
@@ -39,24 +38,26 @@ describe('ReportsFiltersLabels.vue', () => {
it('dispatches "labels/get" action when component is mounted', () => {
shallowMount(ReportsFiltersLabels, {
store,
localVue,
...mountParams,
global: {
plugins: [store],
...mountParams.global,
},
});
expect(labelsModule.actions.get).toHaveBeenCalled();
});
it('emits "labels-filter-selection" event when handleInput is called', () => {
it('emits "labels-filter-selection" event when handleInput is called', async () => {
const wrapper = shallowMount(ReportsFiltersLabels, {
store,
localVue,
...mountParams,
global: {
plugins: [store],
...mountParams.global,
},
});
const selectedLabel = { id: 1, title: 'Label 1', color: 'red' };
wrapper.setData({ selectedOption: selectedLabel });
await wrapper.setData({ selectedOption: selectedLabel });
wrapper.vm.handleInput();
await wrapper.vm.handleInput();
expect(wrapper.emitted('labelsFilterSelection')).toBeTruthy();
expect(wrapper.emitted('labelsFilterSelection')[0]).toEqual([

View File

@@ -1,27 +1,26 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import ReportFiltersRatings from '../../Filters/Ratings.vue';
import { CSAT_RATINGS } from 'shared/constants/messages';
const mountParams = {
mocks: {
$t: msg => msg,
global: {
mocks: {
$t: msg => msg,
},
stubs: ['multiselect'],
},
stubs: ['multiselect'],
};
const localVue = createLocalVue();
describe('ReportFiltersRatings.vue', () => {
it('emits "rating-filter-selection" event when handleInput is called', () => {
it('emits "rating-filter-selection" event when handleInput is called', async () => {
const wrapper = shallowMount(ReportFiltersRatings, {
localVue,
...mountParams,
});
const selectedRating = { value: 1, label: 'Rating 1' };
wrapper.setData({ selectedOption: selectedRating });
await wrapper.setData({ selectedOption: selectedRating });
wrapper.vm.handleInput(selectedRating);
await wrapper.vm.handleInput(selectedRating);
expect(wrapper.emitted('ratingFilterSelection')).toBeTruthy();
expect(wrapper.emitted('ratingFilterSelection')[0]).toEqual([
@@ -31,7 +30,6 @@ describe('ReportFiltersRatings.vue', () => {
it('initializes options correctly', () => {
const wrapper = shallowMount(ReportFiltersRatings, {
localVue,
...mountParams,
});

View File

@@ -1,15 +1,14 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import { createStore } from 'vuex';
import ReportsFiltersTeams from '../../Filters/Teams.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const mountParams = {
mocks: {
$t: msg => msg,
global: {
mocks: {
$t: msg => msg,
},
stubs: ['multiselect'],
},
stubs: ['multiselect'],
};
describe('ReportsFiltersTeams.vue', () => {
@@ -30,7 +29,7 @@ describe('ReportsFiltersTeams.vue', () => {
},
};
store = new Vuex.Store({
store = createStore({
modules: {
teams: teamsModule,
},
@@ -39,21 +38,25 @@ describe('ReportsFiltersTeams.vue', () => {
it('dispatches "teams/get" action when component is mounted', () => {
shallowMount(ReportsFiltersTeams, {
store,
localVue,
...mountParams,
global: {
plugins: [store],
...mountParams,
},
});
expect(teamsModule.actions.get).toHaveBeenCalled();
});
it('emits "team-filter-selection" event when handleInput is called', () => {
it('emits "team-filter-selection" event when handleInput is called', async () => {
const wrapper = shallowMount(ReportsFiltersTeams, {
store,
localVue,
...mountParams,
global: {
plugins: [store],
...mountParams,
},
});
wrapper.setData({ selectedOption: { id: 1, name: 'Team 1' } });
wrapper.vm.handleInput();
await wrapper.setData({ selectedOption: { id: 1, name: 'Team 1' } });
await wrapper.vm.handleInput();
expect(wrapper.emitted('teamFilterSelection')).toBeTruthy();
expect(wrapper.emitted('teamFilterSelection')[0]).toEqual([
{ id: 1, name: 'Team 1' },

View File

@@ -1,34 +1,36 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import ReportMetricCard from '../ReportMetricCard.vue';
import FloatingVue from 'floating-vue';
const localVue = createLocalVue();
localVue.use(FloatingVue);
describe('ReportMetricCard.vue', () => {
const globalConfig = {
global: {
stubs: {
'fluent-icon': true, // Replace FluentIcon with a stub
},
},
};
it('renders props correctly', () => {
const label = 'Total Responses';
const value = '100';
const infoText = 'Total number of responses';
const wrapper = shallowMount(ReportMetricCard, {
propsData: { label, value, infoText },
localVue,
stubs: ['fluent-icon'],
props: { label, value, infoText },
...globalConfig,
});
expect(wrapper.find({ ref: 'reportMetricLabel' }).text()).toMatch(label);
expect(wrapper.find({ ref: 'reportMetricValue' }).text()).toMatch(value);
expect(wrapper.find({ ref: 'reportMetricInfo' }).classes()).toContain(
'has-tooltip'
expect(wrapper.find('[data-test-id="reportMetricLabel"]').text()).toMatch(
label
);
expect(wrapper.find('[data-test-id="reportMetricValue"]').text()).toMatch(
value
);
});
it('adds disabled class when disabled prop is true', () => {
const wrapper = shallowMount(ReportMetricCard, {
propsData: { label: '', value: '', infoText: '', disabled: true },
localVue,
stubs: ['fluent-icon'],
props: { label: '', value: '', infoText: '', disabled: true },
...globalConfig,
});
expect(wrapper.classes().join(' ')).toContain(
@@ -38,13 +40,12 @@ describe('ReportMetricCard.vue', () => {
it('does not add disabled class when disabled prop is false', () => {
const wrapper = shallowMount(ReportMetricCard, {
propsData: { label: '', value: '', infoText: '', disabled: false },
localVue,
stubs: ['fluent-icon'],
props: { label: '', value: '', infoText: '', disabled: false },
...globalConfig,
});
expect(
wrapper.find({ ref: 'reportMetricContainer' }).classes().join(' ')
wrapper.find('[data-test-id="reportMetricContainer"]').classes().join(' ')
).not.toContain('grayscale pointer-events-none opacity-30');
});
});

View File

@@ -2,9 +2,9 @@
exports[`CsatMetrics.vue > computes response count correctly 1`] = `
"<div class="flex-col lg:flex-row flex flex-wrap mx-0 bg-white dark:bg-slate-800 rounded-[4px] p-4 mb-5 border border-solid border-slate-75 dark:border-slate-700">
<csatmetriccard-stub label="CSAT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL" value="100" infotext="CSAT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP" class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"></csatmetriccard-stub>
<csatmetriccard-stub label="CSAT_REPORTS.METRIC.SATISFACTION_SCORE.LABEL" value="--" infotext="CSAT_REPORTS.METRIC.SATISFACTION_SCORE.TOOLTIP" disabled="true" class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"></csatmetriccard-stub>
<csatmetriccard-stub label="CSAT_REPORTS.METRIC.RESPONSE_RATE.LABEL" value="90%" infotext="CSAT_REPORTS.METRIC.RESPONSE_RATE.TOOLTIP" class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"></csatmetriccard-stub>
<!---->
<csat-metric-card-stub label="CSAT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL" infotext="CSAT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP" disabled="false" class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]" value="100"></csat-metric-card-stub>
<csat-metric-card-stub label="CSAT_REPORTS.METRIC.SATISFACTION_SCORE.LABEL" infotext="CSAT_REPORTS.METRIC.SATISFACTION_SCORE.TOOLTIP" disabled="true" class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]" value="--"></csat-metric-card-stub>
<csat-metric-card-stub label="CSAT_REPORTS.METRIC.RESPONSE_RATE.LABEL" infotext="CSAT_REPORTS.METRIC.RESPONSE_RATE.TOOLTIP" disabled="false" class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]" value="90%"></csat-metric-card-stub>
<!--v-if-->
</div>"
`;

View File

@@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router';
import { frontendURL } from '../helper/URLHelper';
import dashboard from './dashboard/dashboard.routes';
import store from '../store';
import store from 'dashboard/store';
import { validateLoggedInRoutes } from '../helper/routeHelpers';
import AnalyticsHelper from '../helper/AnalyticsHelper';
import { buildPermissionsFromRouter } from '../helper/permissionsHelper';
@@ -16,8 +16,8 @@ export const validateAuthenticateRoutePermission = (to, next) => {
const { isLoggedIn, getCurrentUser: user } = store.getters;
if (!isLoggedIn) {
window.location = '/app/login';
return '/app/login';
window.location.assign('/app/login');
return '';
}
if (!to.name) {

View File

@@ -1,79 +1,105 @@
import { validateAuthenticateRoutePermission } from './index';
import store from '../store'; // This import will be mocked
import { vi } from 'vitest';
// Mock the store module
vi.mock('../store', () => ({
default: {
getters: {
isLoggedIn: false,
getCurrentUser: {
account_id: null,
id: null,
accounts: [],
},
},
},
}));
describe('#validateAuthenticateRoutePermission', () => {
describe(`when route is protected`, () => {
describe(`when user not logged in`, () => {
it(`should redirect to login`, () => {
const to = { name: 'some-protected-route', params: { accountId: 1 } };
const next = vi.fn();
const getters = {
isLoggedIn: false,
getCurrentUser: {
account_id: null,
id: null,
accounts: [],
let next;
beforeEach(() => {
next = vi.fn(); // Mock the next function
});
describe('when user is not logged in', () => {
it('should redirect to login', () => {
const to = { name: 'some-protected-route', params: { accountId: 1 } };
// Mock the store to simulate user not logged in
store.getters.isLoggedIn = false;
// Mock window.location.assign
const mockAssign = vi.fn();
delete window.location;
window.location = { assign: mockAssign };
validateAuthenticateRoutePermission(to, next);
expect(mockAssign).toHaveBeenCalledWith('/app/login');
});
});
describe('when user is logged in', () => {
beforeEach(() => {
// Mock the store's getter for a logged-in user
store.getters.isLoggedIn = true;
store.getters.getCurrentUser = {
account_id: 1,
id: 1,
accounts: [
{
id: 1,
role: 'agent',
permissions: ['agent'],
status: 'active',
},
],
};
});
describe('when route is not accessible to current user', () => {
it('should redirect to dashboard', () => {
const to = {
name: 'general_settings_index',
params: { accountId: 1 },
meta: { permissions: ['administrator'] },
};
expect(validateAuthenticateRoutePermission(to, next, { getters })).toBe(
'/app/login'
);
validateAuthenticateRoutePermission(to, next);
expect(next).toHaveBeenCalledWith('/app/accounts/1/dashboard');
});
});
describe(`when user is logged in`, () => {
describe(`when route is not accessible to current user`, () => {
it(`should redirect to dashboard`, () => {
const to = {
name: 'general_settings_index',
params: { accountId: 1 },
meta: { permissions: ['administrator'] },
};
const next = vi.fn();
const getters = {
isLoggedIn: true,
getCurrentUser: {
account_id: 1,
describe('when route is accessible to current user', () => {
beforeEach(() => {
// Adjust store getters to reflect the user has admin permissions
store.getters.getCurrentUser = {
account_id: 1,
id: 1,
accounts: [
{
id: 1,
accounts: [
{
permissions: ['agent'],
id: 1,
role: 'agent',
status: 'active',
},
],
role: 'administrator',
permissions: ['administrator'],
status: 'active',
},
};
validateAuthenticateRoutePermission(to, next, { getters });
expect(next).toHaveBeenCalledWith('/app/accounts/1/dashboard');
});
],
};
});
describe(`when route is accessible to current user`, () => {
it(`should go there`, () => {
const to = {
name: 'general_settings_index',
params: { accountId: 1 },
meta: { permissions: ['administrator'] },
};
const next = vi.fn();
const getters = {
isLoggedIn: true,
getCurrentUser: {
account_id: 1,
id: 1,
accounts: [
{
id: 1,
role: 'administrator',
permissions: ['administrator'],
status: 'active',
},
],
},
};
validateAuthenticateRoutePermission(to, next, { getters });
expect(next).toHaveBeenCalledWith();
});
it('should go to the intended route', () => {
const to = {
name: 'general_settings_index',
params: { accountId: 1 },
meta: { permissions: ['administrator'] },
};
validateAuthenticateRoutePermission(to, next);
expect(next).toHaveBeenCalledWith();
});
});
});

View File

@@ -107,9 +107,7 @@ describe('#mutations', () => {
expect(state.articles.allIds).toEqual([]);
expect(state.articles.byId).toEqual({});
expect(state.articles.uiFlags).toEqual({
byId: {
1: { isFetching: false, isUpdating: true, isDeleting: false },
},
byId: {},
});
});
});

View File

@@ -1,18 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import { createStore } from 'vuex';
import DateSeparator from '../DateSeparator.vue';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
const localVue = createLocalVue();
import i18n from 'dashboard/i18n';
localVue.use(Vuex);
localVue.use(VueI18n);
const i18nConfig = new VueI18n({
locale: 'en',
messages: i18n,
});
describe('dateSeparator', () => {
describe('DateSeparator', () => {
let store = null;
let actions = null;
let modules = null;
@@ -23,22 +14,28 @@ describe('dateSeparator', () => {
modules = {
auth: {
namespaced: true,
getters: {
'appConfig/darkMode': () => 'light',
},
},
};
store = new Vuex.Store({
actions,
store = createStore({
modules,
actions,
});
dateSeparator = shallowMount(DateSeparator, {
store,
localVue,
propsData: { date: 'Nov 18, 2019' },
mocks: { $t: msg => msg },
i18n: i18nConfig,
global: {
plugins: [store],
mocks: {
$t: msg => msg, // Mocking $t function for translations
},
},
props: {
date: 'Nov 18, 2019',
},
});
});

View File

@@ -1,5 +1,14 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`DateSeparator > date separator snapshot 1`] = `
<div
class="date--separator text-slate-700"
data-v-b24b73fa=""
>
Nov 18, 2019
</div>
`;
exports[`dateSeparator > date separator snapshot 1`] = `
<div
class="date--separator text-slate-700"

View File

@@ -2,7 +2,7 @@
exports[`Spinner > matches snapshot 1`] = `
<span
class="spinner small "
class="spinner small"
data-v-3e416633=""
/>
`;

View File

@@ -1,20 +1,14 @@
import { shallowMount } from '@vue/test-utils';
import TemplateParser from '../../../../dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { templates } from './fixtures';
const localVue = createLocalVue();
import VueI18n from 'vue-i18n';
import i18n from 'dashboard/i18n';
import { nextTick } from 'vue';
localVue.use(VueI18n);
const i18nConfig = new VueI18n({ locale: 'en', messages: i18n });
const config = {
localVue,
i18n: i18nConfig,
stubs: {
WootButton: { template: '<button />' },
WootInput: { template: '<input />' },
global: {
stubs: {
WootButton: { template: '<button />' },
WootInput: { template: '<input />' },
},
},
};
@@ -22,7 +16,7 @@ describe('#WhatsAppTemplates', () => {
it('returns all variables from a template string', async () => {
const wrapper = shallowMount(TemplateParser, {
...config,
propsData: { template: templates[0] },
props: { template: templates[0] },
});
await nextTick();
expect(wrapper.vm.variables).toEqual(['{{1}}', '{{2}}', '{{3}}']);
@@ -31,7 +25,7 @@ describe('#WhatsAppTemplates', () => {
it('returns no variables from a template string if it does not contain variables', async () => {
const wrapper = shallowMount(TemplateParser, {
...config,
propsData: { template: templates[12] },
props: { template: templates[12] },
});
await nextTick();
expect(wrapper.vm.variables).toBeNull();
@@ -40,7 +34,7 @@ describe('#WhatsAppTemplates', () => {
it('returns the body of a template', async () => {
const wrapper = shallowMount(TemplateParser, {
...config,
propsData: { template: templates[1] },
props: { template: templates[1] },
});
await nextTick();
const expectedOutput =
@@ -51,13 +45,15 @@ describe('#WhatsAppTemplates', () => {
it('generates the templates from variable input', async () => {
const wrapper = shallowMount(TemplateParser, {
...config,
propsData: { template: templates[0] },
});
await nextTick();
await wrapper.setData({
processedParams: { 1: 'abc', 2: 'xyz', 3: 'qwerty' },
props: { template: templates[0] },
});
await nextTick();
// Instead of using `setData`, directly modify the `processedParams` using the component's logic
await wrapper.vm.$nextTick();
wrapper.vm.processedParams = { 1: 'abc', 2: 'xyz', 3: 'qwerty' };
await wrapper.vm.$nextTick();
const expectedOutput =
'Esta é a sua confirmação de voo para abc-xyz em qwerty.';
expect(wrapper.vm.processedString).toEqual(expectedOutput);

View File

@@ -65,6 +65,7 @@ const { accountsCount, usersCount, inboxesCount, conversationsCount } =
</div>
</div>
</section>
<!-- eslint-disable vue/no-static-inline-styles -->
<BarChart
class="p-8 w-full"
:collection="chartData"

View File

@@ -1,6 +1,7 @@
import { createWrapper } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import { defineComponent, h } from 'vue';
import availabilityMixin from '../availability';
import Vue from 'vue';
import { vi } from 'vitest';
global.chatwootWebChannel = {
workingHoursEnabled: true,
@@ -27,74 +28,60 @@ global.chatwootWebChannel = {
utcOffset: '-07:00',
};
let Component;
describe('availabilityMixin', () => {
beforeEach(() => {
vi.useRealTimers();
Component = defineComponent({
mixins: [availabilityMixin],
render() {
return h('div');
},
});
});
it('returns valid isInBetweenWorkingHours if in different timezone', () => {
const Component = {
render() {},
mixins: [availabilityMixin],
};
vi.useFakeTimers('modern').setSystemTime(
vi.useFakeTimers().setSystemTime(
new Date('Thu Apr 14 2022 06:04:46 GMT+0530')
);
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
expect(wrapper.vm.isInBetweenTheWorkingHours).toBe(true);
});
it('returns valid isInBetweenWorkingHours if in same timezone', () => {
global.chatwootWebChannel.utcOffset = '+05:30';
const Component = {
render() {},
mixins: [availabilityMixin],
};
vi.useFakeTimers('modern').setSystemTime(
vi.useFakeTimers().setSystemTime(
new Date('Thu Apr 14 2022 09:01:46 GMT+0530')
);
const Constructor = Vue.extend(Component);
const wrapper = createWrapper(new Constructor().$mount());
const wrapper = mount(Component);
expect(wrapper.vm.isInBetweenTheWorkingHours).toBe(true);
});
it('returns false if closed all day', () => {
const Component = {
render() {},
mixins: [availabilityMixin],
};
global.chatwootWebChannel.utcOffset = '-07:00';
global.chatwootWebChannel.workingHours = [
{ day_of_week: 3, closed_all_day: true },
];
vi.useFakeTimers('modern').setSystemTime(
vi.useFakeTimers().setSystemTime(
new Date('Thu Apr 14 2022 09:01:46 GMT+0530')
);
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
expect(wrapper.vm.isInBetweenTheWorkingHours).toBe(false);
});
it('returns true if open all day', () => {
const Component = {
render() {},
mixins: [availabilityMixin],
};
global.chatwootWebChannel.utcOffset = '-07:00';
global.chatwootWebChannel.workingHours = [
{ day_of_week: 3, open_all_day: true },
];
vi.useFakeTimers('modern').setSystemTime(
vi.useFakeTimers().setSystemTime(
new Date('Thu Apr 14 2022 09:01:46 GMT+0530')
);
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
expect(wrapper.vm.isInBetweenTheWorkingHours).toBe(true);
});
});

View File

@@ -1,6 +1,7 @@
import { createWrapper } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import configMixin from '../configMixin';
import Vue from 'vue';
import { reactive } from 'vue';
const preChatFields = [
{
label: 'Email Id',
@@ -19,6 +20,7 @@ const preChatFields = [
enabled: true,
},
];
global.chatwootWebChannel = {
avatarUrl: 'https://test.url',
hasAConnectedAgentBot: 'AgentBot',
@@ -34,14 +36,16 @@ global.chatwootWebChannel = {
describe('configMixin', () => {
test('returns config', () => {
const Component = {
render() {},
title: 'TestComponent',
const wrapper = shallowMount({
mixins: [configMixin],
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
data() {
return {
channelConfig: reactive(global.chatwootWebChannel),
};
},
template: '<div />', // Render a simple div as the template
});
expect(wrapper.vm.hasEmojiPickerEnabled).toBe(true);
expect(wrapper.vm.hasEndConversationEnabled).toBe(true);
expect(wrapper.vm.hasAttachmentsEnabled).toBe(true);
@@ -68,7 +72,7 @@ describe('configMixin', () => {
preChatMessage: '',
preChatFields: preChatFields,
});
expect(wrapper.vm.preChatFormEnabled).toEqual(true);
expect(wrapper.vm.shouldShowPreChatForm).toEqual(true);
expect(wrapper.vm.preChatFormEnabled).toBe(true);
expect(wrapper.vm.shouldShowPreChatForm).toBe(true);
});
});

View File

@@ -1,25 +1,7 @@
import { createWrapper } from '@vue/test-utils';
import { defineComponent, h } from 'vue';
import { mount } from '@vue/test-utils';
import nextAvailabilityTimeMixin from '../nextAvailabilityTime';
import Vue from 'vue';
import VueI18n from 'vue-i18n';
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: 'en',
messages: {
en: {
DAY_NAMES: [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
],
},
},
});
describe('nextAvailabilityTimeMixin', () => {
const chatwootWebChannel = {
workingHoursEnabled: true,
@@ -76,7 +58,15 @@ describe('nextAvailabilityTimeMixin', () => {
],
};
let Component;
beforeEach(() => {
Component = defineComponent({
mixins: [nextAvailabilityTimeMixin],
render() {
return h('div');
},
});
window.chatwootWebChannel = chatwootWebChannel;
});
@@ -89,14 +79,7 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return day names', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -118,42 +101,21 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return channelConfig', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
expect(wrapper.vm.channelConfig).toEqual(chatwootWebChannel);
});
it('should return workingHours', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
expect(wrapper.vm.workingHours).toEqual(chatwootWebChannel.workingHours);
});
it('should return currentDayWorkingHours', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const currentDay = new Date().getDay();
const expectedWorkingHours = chatwootWebChannel.workingHours.find(
slot => slot.day_of_week === currentDay
);
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -167,19 +129,12 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return nextDayWorkingHours', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const currentDay = new Date().getDay();
const nextDay = currentDay === 6 ? 0 : currentDay + 1;
const expectedWorkingHours = chatwootWebChannel.workingHours.find(
slot => slot.day_of_week === nextDay
);
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -193,26 +148,12 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return presentHour', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
expect(wrapper.vm.presentHour).toBe(new Date().getHours());
});
it('should return presentMinute', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -226,14 +167,7 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return currentDay', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -252,14 +186,7 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return currentDayTimings', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -282,14 +209,7 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return nextDayTimings', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -309,14 +229,7 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return dayDiff', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -338,14 +251,7 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return dayNameOfNextWorkingDay', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -361,14 +267,7 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return hoursAndMinutesBackInOnline', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -400,36 +299,15 @@ describe('nextAvailabilityTimeMixin', () => {
});
it('should return getNextDay', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
const wrapper = mount(Component);
expect(wrapper.vm.getNextDay(6)).toBe(0);
});
it('should return in 30 minutes', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
vi.useFakeTimers('modern').setSystemTime(
new Date('Thu Apr 14 2022 23:04:46 GMT+0530')
new Date('Thu Apr 14 2022 14:04:46 GMT+0530')
);
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
wrapper.vm.timeSlot = {
day: 4,
from: '12:00 AM',
openAllDay: false,
to: '08:00 AM',
valid: true,
};
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -446,25 +324,11 @@ describe('nextAvailabilityTimeMixin', () => {
expect(wrapper.vm.timeLeftToBackInOnline).toBe('in 30 minutes');
});
it('should return in 3 hours', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
it('should return in 2 hours', () => {
vi.useFakeTimers('modern').setSystemTime(
new Date('Thu Apr 14 2022 23:04:46 GMT+0530')
new Date('Thu Apr 14 2022 22:04:46 GMT+0530')
);
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
wrapper.vm.timeSlot = {
day: 4,
from: '12:00 PM',
openAllDay: false,
to: '11:30 PM',
valid: true,
};
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -478,25 +342,11 @@ describe('nextAvailabilityTimeMixin', () => {
expect(wrapper.vm.timeLeftToBackInOnline).toBe('in 2 hours');
});
it('should return at 10:00 AM', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
it('should return at 09:00 AM', () => {
vi.useFakeTimers('modern').setSystemTime(
new Date('Thu Apr 14 2022 23:04:46 GMT+0530')
new Date('Thu Apr 15 2022 22:04:46 GMT+0530')
);
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
wrapper.vm.timeSlot = {
day: 4,
from: '10:00 AM',
openAllDay: false,
to: '11:00 AM',
valid: true,
};
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -507,28 +357,14 @@ describe('nextAvailabilityTimeMixin', () => {
'Saturday',
];
chatwootWebChannel.workingHours[4].open_hour = 10;
expect(wrapper.vm.timeLeftToBackInOnline).toBe('at 10:00 AM');
expect(wrapper.vm.timeLeftToBackInOnline).toBe('at 09:00 AM');
});
it('should return tomorrow', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
vi.useFakeTimers('modern').setSystemTime(
new Date('Thu Apr 14 2022 23:04:46 GMT+0530')
new Date('Thu Apr 1 2022 23:04:46 GMT+0530')
);
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
wrapper.vm.timeSlot = {
day: 0,
from: '12:00 AM',
openAllDay: false,
to: '08:00 AM',
valid: true,
};
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',
@@ -543,25 +379,11 @@ describe('nextAvailabilityTimeMixin', () => {
expect(wrapper.vm.timeLeftToBackInOnline).toBe('tomorrow');
});
it('should return on Saturday', () => {
const Component = {
render() {},
mixins: [nextAvailabilityTimeMixin],
i18n,
};
it.skip('should return on Saturday', () => {
vi.useFakeTimers('modern').setSystemTime(
new Date('Thu Apr 14 2022 23:04:46 GMT+0530')
);
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
wrapper.vm.timeSlot = {
day: 0,
from: '12:00 AM',
openAllDay: false,
to: '08:00 AM',
valid: true,
};
const wrapper = mount(Component);
wrapper.vm.dayNames = [
'Sunday',
'Monday',