mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 11:08:04 +00:00 
			
		
		
		
	 ee02923ace
			
		
	
	ee02923ace
	
	
	
		
			
			- 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>
		
			
				
	
	
		
			350 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { emitter } from 'shared/helpers/mitt';
 | |
| import { BUS_EVENTS } from 'shared/constants/busEvents';
 | |
| import { differenceInSeconds } from 'date-fns';
 | |
| import {
 | |
|   isAConversationRoute,
 | |
|   isAInboxViewRoute,
 | |
|   isNotificationRoute,
 | |
| } from 'dashboard/helper/routeHelpers';
 | |
| import ReconnectService from 'dashboard/helper/ReconnectService';
 | |
| 
 | |
| vi.mock('shared/helpers/mitt', () => ({
 | |
|   emitter: {
 | |
|     on: vi.fn(),
 | |
|     off: vi.fn(),
 | |
|     emit: vi.fn(),
 | |
|   },
 | |
| }));
 | |
| 
 | |
| vi.mock('date-fns', () => ({
 | |
|   differenceInSeconds: vi.fn(),
 | |
| }));
 | |
| 
 | |
| vi.mock('dashboard/helper/routeHelpers', () => ({
 | |
|   isAConversationRoute: vi.fn(),
 | |
|   isAInboxViewRoute: vi.fn(),
 | |
|   isNotificationRoute: vi.fn(),
 | |
| }));
 | |
| 
 | |
| const storeMock = {
 | |
|   dispatch: vi.fn(),
 | |
|   getters: {
 | |
|     getAppliedConversationFiltersQuery: [],
 | |
|     'customViews/getActiveConversationFolder': { query: {} },
 | |
|     'notifications/getNotificationFilters': {},
 | |
|   },
 | |
| };
 | |
| 
 | |
| const routerMock = {
 | |
|   currentRoute: {
 | |
|     value: {
 | |
|       name: '',
 | |
|       params: { conversation_id: null },
 | |
|     },
 | |
|   },
 | |
| };
 | |
| 
 | |
| describe('ReconnectService', () => {
 | |
|   let reconnectService;
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     window.addEventListener = vi.fn();
 | |
|     window.removeEventListener = vi.fn();
 | |
|     Object.defineProperty(window, 'location', {
 | |
|       configurable: true,
 | |
|       value: { reload: vi.fn() },
 | |
|     });
 | |
|     reconnectService = new ReconnectService(storeMock, routerMock);
 | |
|   });
 | |
| 
 | |
|   afterEach(() => {
 | |
|     vi.clearAllMocks();
 | |
|   });
 | |
| 
 | |
|   describe('constructor', () => {
 | |
|     it('should initialize with store, router, and setup event listeners', () => {
 | |
|       expect(reconnectService.store).toBe(storeMock);
 | |
|       expect(reconnectService.router).toBe(routerMock);
 | |
|       expect(window.addEventListener).toHaveBeenCalledWith(
 | |
|         'online',
 | |
|         reconnectService.handleOnlineEvent
 | |
|       );
 | |
|       expect(emitter.on).toHaveBeenCalledWith(
 | |
|         BUS_EVENTS.WEBSOCKET_RECONNECT,
 | |
|         reconnectService.onReconnect
 | |
|       );
 | |
|       expect(emitter.on).toHaveBeenCalledWith(
 | |
|         BUS_EVENTS.WEBSOCKET_DISCONNECT,
 | |
|         reconnectService.onDisconnect
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('disconnect', () => {
 | |
|     it('should remove event listeners', () => {
 | |
|       reconnectService.disconnect();
 | |
|       expect(window.removeEventListener).toHaveBeenCalledWith(
 | |
|         'online',
 | |
|         reconnectService.handleOnlineEvent
 | |
|       );
 | |
|       expect(emitter.off).toHaveBeenCalledWith(
 | |
|         BUS_EVENTS.WEBSOCKET_RECONNECT,
 | |
|         reconnectService.onReconnect
 | |
|       );
 | |
|       expect(emitter.off).toHaveBeenCalledWith(
 | |
|         BUS_EVENTS.WEBSOCKET_DISCONNECT,
 | |
|         reconnectService.onDisconnect
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('getSecondsSinceDisconnect', () => {
 | |
|     it('should return 0 if disconnectTime is null', () => {
 | |
|       reconnectService.disconnectTime = null;
 | |
|       expect(reconnectService.getSecondsSinceDisconnect()).toBe(0);
 | |
|     });
 | |
| 
 | |
|     it('should return the number of seconds + threshold since disconnect', () => {
 | |
|       reconnectService.disconnectTime = new Date();
 | |
|       differenceInSeconds.mockReturnValue(100);
 | |
|       expect(reconnectService.getSecondsSinceDisconnect()).toBe(100);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('handleOnlineEvent', () => {
 | |
|     it('should reload the page if disconnected for more than 3 hours', () => {
 | |
|       reconnectService.getSecondsSinceDisconnect = vi
 | |
|         .fn()
 | |
|         .mockReturnValue(10801);
 | |
|       reconnectService.handleOnlineEvent();
 | |
|       expect(window.location.reload).toHaveBeenCalled();
 | |
|     });
 | |
| 
 | |
|     it('should not reload the page if disconnected for less than 3 hours', () => {
 | |
|       reconnectService.getSecondsSinceDisconnect = vi
 | |
|         .fn()
 | |
|         .mockReturnValue(10799);
 | |
|       reconnectService.handleOnlineEvent();
 | |
|       expect(window.location.reload).not.toHaveBeenCalled();
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('fetchConversations', () => {
 | |
|     it('should update the filters with disconnected time and the threshold', async () => {
 | |
|       reconnectService.getSecondsSinceDisconnect = vi.fn().mockReturnValue(100);
 | |
|       await reconnectService.fetchConversations();
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith('updateChatListFilters', {
 | |
|         page: null,
 | |
|         updatedWithin: 115,
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('should dispatch updateChatListFilters and fetchAllConversations', async () => {
 | |
|       reconnectService.getSecondsSinceDisconnect = vi.fn().mockReturnValue(100);
 | |
|       await reconnectService.fetchConversations();
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith('updateChatListFilters', {
 | |
|         page: null,
 | |
|         updatedWithin: 115,
 | |
|       });
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith('fetchAllConversations');
 | |
|     });
 | |
| 
 | |
|     it('should dispatch updateChatListFilters and reset updatedWithin', async () => {
 | |
|       reconnectService.getSecondsSinceDisconnect = vi.fn().mockReturnValue(100);
 | |
|       await reconnectService.fetchConversations();
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith('updateChatListFilters', {
 | |
|         updatedWithin: null,
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('fetchFilteredOrSavedConversations', () => {
 | |
|     it('should dispatch fetchFilteredConversations', async () => {
 | |
|       const payload = { test: 'data' };
 | |
|       await reconnectService.fetchFilteredOrSavedConversations(payload);
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith(
 | |
|         'fetchFilteredConversations',
 | |
|         { queryData: payload, page: 1 }
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('fetchConversationsOnReconnect', () => {
 | |
|     it('should fetch filtered or saved conversations if query exists', async () => {
 | |
|       storeMock.getters.getAppliedConversationFiltersQuery = {
 | |
|         payload: [
 | |
|           {
 | |
|             attribute_key: 'status',
 | |
|             filter_operator: 'equal_to',
 | |
|             values: ['open'],
 | |
|           },
 | |
|         ],
 | |
|       };
 | |
|       const spy = vi.spyOn(
 | |
|         reconnectService,
 | |
|         'fetchFilteredOrSavedConversations'
 | |
|       );
 | |
| 
 | |
|       await reconnectService.fetchConversationsOnReconnect();
 | |
| 
 | |
|       expect(spy).toHaveBeenCalledWith(
 | |
|         storeMock.getters.getAppliedConversationFiltersQuery
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should fetch all conversations if no query exists', async () => {
 | |
|       storeMock.getters.getAppliedConversationFiltersQuery = [];
 | |
|       storeMock.getters['customViews/getActiveConversationFolder'] = {
 | |
|         query: null,
 | |
|       };
 | |
| 
 | |
|       const spy = vi.spyOn(reconnectService, 'fetchConversations');
 | |
| 
 | |
|       await reconnectService.fetchConversationsOnReconnect();
 | |
| 
 | |
|       expect(spy).toHaveBeenCalled();
 | |
|     });
 | |
| 
 | |
|     it('should fetch filtered or saved conversations if active folder query exists and no applied query', async () => {
 | |
|       storeMock.getters.getAppliedConversationFiltersQuery = [];
 | |
|       storeMock.getters['customViews/getActiveConversationFolder'] = {
 | |
|         query: { test: 'activeFolderQuery' },
 | |
|       };
 | |
| 
 | |
|       const spy = vi.spyOn(
 | |
|         reconnectService,
 | |
|         'fetchFilteredOrSavedConversations'
 | |
|       );
 | |
| 
 | |
|       await reconnectService.fetchConversationsOnReconnect();
 | |
| 
 | |
|       expect(spy).toHaveBeenCalledWith({ test: 'activeFolderQuery' });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('fetchConversationMessagesOnReconnect', () => {
 | |
|     it('should dispatch syncActiveConversationMessages if conversationId exists', async () => {
 | |
|       routerMock.currentRoute.value.params.conversation_id = 1;
 | |
|       await reconnectService.fetchConversationMessagesOnReconnect();
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith(
 | |
|         'syncActiveConversationMessages',
 | |
|         { conversationId: 1 }
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should not dispatch syncActiveConversationMessages if conversationId does not exist', async () => {
 | |
|       routerMock.currentRoute.value.params.conversation_id = null;
 | |
|       await reconnectService.fetchConversationMessagesOnReconnect();
 | |
|       expect(storeMock.dispatch).not.toHaveBeenCalledWith(
 | |
|         'syncActiveConversationMessages',
 | |
|         expect.anything()
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('fetchNotificationsOnReconnect', () => {
 | |
|     it('should dispatch notifications/index', async () => {
 | |
|       const filter = { test: 'filter' };
 | |
|       await reconnectService.fetchNotificationsOnReconnect(filter);
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith('notifications/index', {
 | |
|         ...filter,
 | |
|         page: 1,
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('revalidateCaches', () => {
 | |
|     it('should dispatch revalidate actions for labels, inboxes, and teams', async () => {
 | |
|       storeMock.dispatch.mockResolvedValueOnce({
 | |
|         label: 'labelKey',
 | |
|         inbox: 'inboxKey',
 | |
|         team: 'teamKey',
 | |
|       });
 | |
|       await reconnectService.revalidateCaches();
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith('accounts/getCacheKeys');
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith('labels/revalidate', {
 | |
|         newKey: 'labelKey',
 | |
|       });
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith('inboxes/revalidate', {
 | |
|         newKey: 'inboxKey',
 | |
|       });
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith('teams/revalidate', {
 | |
|         newKey: 'teamKey',
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('handleRouteSpecificFetch', () => {
 | |
|     it('should fetch conversations and messages if current route is a conversation route', async () => {
 | |
|       isAConversationRoute.mockReturnValue(true);
 | |
|       const spyConversations = vi.spyOn(
 | |
|         reconnectService,
 | |
|         'fetchConversationsOnReconnect'
 | |
|       );
 | |
|       const spyMessages = vi.spyOn(
 | |
|         reconnectService,
 | |
|         'fetchConversationMessagesOnReconnect'
 | |
|       );
 | |
|       await reconnectService.handleRouteSpecificFetch();
 | |
|       expect(spyConversations).toHaveBeenCalled();
 | |
|       expect(spyMessages).toHaveBeenCalled();
 | |
|     });
 | |
| 
 | |
|     it('should fetch notifications if current route is an inbox view route', async () => {
 | |
|       isAInboxViewRoute.mockReturnValue(true);
 | |
|       const spy = vi.spyOn(reconnectService, 'fetchNotificationsOnReconnect');
 | |
|       await reconnectService.handleRouteSpecificFetch();
 | |
|       expect(spy).toHaveBeenCalled();
 | |
|     });
 | |
| 
 | |
|     it('should fetch notifications if current route is a notification route', async () => {
 | |
|       isNotificationRoute.mockReturnValue(true);
 | |
|       const spy = vi.spyOn(reconnectService, 'fetchNotificationsOnReconnect');
 | |
|       await reconnectService.handleRouteSpecificFetch();
 | |
|       expect(spy).toHaveBeenCalled();
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('setConversationLastMessageId', () => {
 | |
|     it('should dispatch setConversationLastMessageId if conversationId exists', async () => {
 | |
|       routerMock.currentRoute.value.params.conversation_id = 1;
 | |
|       await reconnectService.setConversationLastMessageId();
 | |
|       expect(storeMock.dispatch).toHaveBeenCalledWith(
 | |
|         'setConversationLastMessageId',
 | |
|         { conversationId: 1 }
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should not dispatch setConversationLastMessageId if conversationId does not exist', async () => {
 | |
|       routerMock.currentRoute.value.params.conversation_id = null;
 | |
|       await reconnectService.setConversationLastMessageId();
 | |
|       expect(storeMock.dispatch).not.toHaveBeenCalledWith(
 | |
|         'setConversationLastMessageId',
 | |
|         expect.anything()
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('onDisconnect', () => {
 | |
|     it('should set disconnectTime and call setConversationLastMessageId', () => {
 | |
|       reconnectService.setConversationLastMessageId = vi.fn();
 | |
|       reconnectService.onDisconnect();
 | |
|       expect(reconnectService.disconnectTime).toBeInstanceOf(Date);
 | |
|       expect(reconnectService.setConversationLastMessageId).toHaveBeenCalled();
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('onReconnect', () => {
 | |
|     it('should handle route-specific fetch, revalidate caches, and emit WEBSOCKET_RECONNECT_COMPLETED event', async () => {
 | |
|       reconnectService.handleRouteSpecificFetch = vi.fn();
 | |
|       reconnectService.revalidateCaches = vi.fn();
 | |
|       await reconnectService.onReconnect();
 | |
|       expect(reconnectService.handleRouteSpecificFetch).toHaveBeenCalled();
 | |
|       expect(reconnectService.revalidateCaches).toHaveBeenCalled();
 | |
|       expect(emitter.emit).toHaveBeenCalledWith(
 | |
|         BUS_EVENTS.WEBSOCKET_RECONNECT_COMPLETED
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| });
 |