From cfdf4a12c864bacaead59b4e495198334157a3d7 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Tue, 25 May 2021 14:00:21 +0530 Subject: [PATCH] chore: Add missing frontend specs (#2329) * complete dshboard api specs * code cleanup * add conversation mixin spec * add isadmin mixin spec * add agent details component spec * add notification badge spec * spec for thumbnail exist in agent details * fix the deprecation warnings * add agent details spec * add account selector specs * code cleanup * refactor contact spec * review fixes * review fixes * add shared spec helper * update api spec helper * review fixes --- .../dashboard/api/specs/account.spec.js | 26 +++ .../dashboard/api/specs/apiSpecHelper.js | 26 +++ .../api/specs/channel/fbChannel.spec.js | 26 +++ .../api/specs/channel/twilioChannel.spec.js | 13 ++ .../api/specs/channel/twitterClient.spec.js | 11 +- .../api/specs/channel/webChannel.spec.js | 13 ++ .../dashboard/api/specs/contacts.spec.js | 46 ++++- .../dashboard/api/specs/conversations.spec.js | 39 +++- .../dashboard/api/specs/endPoints.spec.js | 13 ++ .../api/specs/inbox/conversation.spec.js | 143 ++++++++++++-- .../dashboard/api/specs/inbox/message.spec.js | 32 +++ .../dashboard/api/specs/inboxes.spec.js | 32 ++- .../dashboard/api/specs/notifications.spec.js | 55 +++++- .../dashboard/api/specs/reports.spec.js | 68 +++++-- .../dashboard/api/specs/teams.spec.js | 53 ++++- .../dashboard/api/specs/webhook.spec.js | 13 ++ .../specs/AccountSelector.spec.js | 90 +++++++++ .../specs/AgentDetails.spec.js | 64 ++++++ .../specs/NotificationBell.spec.js | 67 +++++++ .../components/widgets/Thumbnail.spec.js | 8 +- .../mixins/specs/conversationFixtures.js | 185 ++++++++++++++++++ .../mixins/specs/conversaton.spec.js | 29 +++ .../dashboard/mixins/specs/isAdmin.spec.js | 28 +++ 23 files changed, 1009 insertions(+), 71 deletions(-) create mode 100644 app/javascript/dashboard/api/specs/account.spec.js create mode 100644 app/javascript/dashboard/api/specs/apiSpecHelper.js create mode 100644 app/javascript/dashboard/api/specs/channel/twilioChannel.spec.js create mode 100644 app/javascript/dashboard/api/specs/channel/webChannel.spec.js create mode 100644 app/javascript/dashboard/api/specs/endPoints.spec.js create mode 100644 app/javascript/dashboard/api/specs/inbox/message.spec.js create mode 100644 app/javascript/dashboard/api/specs/webhook.spec.js create mode 100644 app/javascript/dashboard/components/layout/sidebarComponents/specs/AccountSelector.spec.js create mode 100644 app/javascript/dashboard/components/layout/sidebarComponents/specs/AgentDetails.spec.js create mode 100644 app/javascript/dashboard/components/layout/sidebarComponents/specs/NotificationBell.spec.js create mode 100644 app/javascript/dashboard/mixins/specs/conversationFixtures.js create mode 100644 app/javascript/dashboard/mixins/specs/conversaton.spec.js create mode 100644 app/javascript/dashboard/mixins/specs/isAdmin.spec.js diff --git a/app/javascript/dashboard/api/specs/account.spec.js b/app/javascript/dashboard/api/specs/account.spec.js new file mode 100644 index 000000000..3d37c4206 --- /dev/null +++ b/app/javascript/dashboard/api/specs/account.spec.js @@ -0,0 +1,26 @@ +import accountAPI from '../account'; +import ApiClient from '../ApiClient'; +import describeWithAPIMock from './apiSpecHelper'; + +describe('#accountAPI', () => { + it('creates correct instance', () => { + expect(accountAPI).toBeInstanceOf(ApiClient); + expect(accountAPI).toHaveProperty('get'); + expect(accountAPI).toHaveProperty('show'); + expect(accountAPI).toHaveProperty('create'); + expect(accountAPI).toHaveProperty('update'); + expect(accountAPI).toHaveProperty('delete'); + expect(accountAPI).toHaveProperty('createAccount'); + }); + + describeWithAPIMock('API calls', context => { + it('#createAccount', () => { + accountAPI.createAccount({ + name: 'Chatwoot', + }); + expect(context.axiosMock.post).toHaveBeenCalledWith('/api/v1/accounts', { + name: 'Chatwoot', + }); + }); + }); +}); diff --git a/app/javascript/dashboard/api/specs/apiSpecHelper.js b/app/javascript/dashboard/api/specs/apiSpecHelper.js new file mode 100644 index 000000000..aedea13f8 --- /dev/null +++ b/app/javascript/dashboard/api/specs/apiSpecHelper.js @@ -0,0 +1,26 @@ +function apiSpecHelper() { + beforeEach(() => { + this.originalAxios = window.axios; + this.axiosMock = { + post: jest.fn(() => Promise.resolve()), + get: jest.fn(() => Promise.resolve()), + patch: jest.fn(() => Promise.resolve()), + }; + window.axios = this.axiosMock; + }); + + afterEach(() => { + window.axios = this.originalAxios; + }); +} +// https://stackoverflow.com/a/59344023/3901856 +const sharedWrapper = describe('sharedWrapper', () => {}); +export default function describeWithAPIMock(skillName, testFn) { + return describe(skillName, function configureContext() { + function Context() {} + Context.prototype = sharedWrapper.ctx; + this.ctx = new Context(); + apiSpecHelper.call(this); + testFn.call(this, this); + }); +} diff --git a/app/javascript/dashboard/api/specs/channel/fbChannel.spec.js b/app/javascript/dashboard/api/specs/channel/fbChannel.spec.js index 03f1cb649..67697f827 100644 --- a/app/javascript/dashboard/api/specs/channel/fbChannel.spec.js +++ b/app/javascript/dashboard/api/specs/channel/fbChannel.spec.js @@ -1,5 +1,6 @@ import fbChannel from '../../channel/fbChannel'; import ApiClient from '../../ApiClient'; +import describeWithAPIMock from '../apiSpecHelper'; describe('#FBChannel', () => { it('creates correct instance', () => { @@ -10,4 +11,29 @@ describe('#FBChannel', () => { expect(fbChannel).toHaveProperty('update'); expect(fbChannel).toHaveProperty('delete'); }); + describeWithAPIMock('API calls', context => { + it('#create', () => { + fbChannel.create({ omniauthToken: 'ASFM131CSF@#@$', appId: 'chatwoot' }); + expect(context.axiosMock.post).toHaveBeenCalledWith( + '/api/v1/callbacks/register_facebook_page', + { + omniauthToken: 'ASFM131CSF@#@$', + appId: 'chatwoot', + } + ); + }); + it('#reauthorize', () => { + fbChannel.reauthorizeFacebookPage({ + omniauthToken: 'ASFM131CSF@#@$', + inboxId: 1, + }); + expect(context.axiosMock.post).toHaveBeenCalledWith( + '/api/v1/callbacks/reauthorize_page', + { + omniauth_token: 'ASFM131CSF@#@$', + inbox_id: 1, + } + ); + }); + }); }); diff --git a/app/javascript/dashboard/api/specs/channel/twilioChannel.spec.js b/app/javascript/dashboard/api/specs/channel/twilioChannel.spec.js new file mode 100644 index 000000000..5025de44f --- /dev/null +++ b/app/javascript/dashboard/api/specs/channel/twilioChannel.spec.js @@ -0,0 +1,13 @@ +import twilioChannel from '../../channel/twilioChannel'; +import ApiClient from '../../ApiClient'; + +describe('#twilioChannel', () => { + it('creates correct instance', () => { + expect(twilioChannel).toBeInstanceOf(ApiClient); + expect(twilioChannel).toHaveProperty('get'); + expect(twilioChannel).toHaveProperty('show'); + expect(twilioChannel).toHaveProperty('create'); + expect(twilioChannel).toHaveProperty('update'); + expect(twilioChannel).toHaveProperty('delete'); + }); +}); diff --git a/app/javascript/dashboard/api/specs/channel/twitterClient.spec.js b/app/javascript/dashboard/api/specs/channel/twitterClient.spec.js index 0480aeb82..0768ee53e 100644 --- a/app/javascript/dashboard/api/specs/channel/twitterClient.spec.js +++ b/app/javascript/dashboard/api/specs/channel/twitterClient.spec.js @@ -1,9 +1,14 @@ -import TwitterClient from '../../channel/twitterClient'; +import twitterClient from '../../channel/twitterClient'; import ApiClient from '../../ApiClient'; describe('#TwitterClient', () => { it('creates correct instance', () => { - expect(TwitterClient).toBeInstanceOf(ApiClient); - expect(TwitterClient).toHaveProperty('generateAuthorization'); + expect(twitterClient).toBeInstanceOf(ApiClient); + expect(twitterClient).toHaveProperty('get'); + expect(twitterClient).toHaveProperty('show'); + expect(twitterClient).toHaveProperty('create'); + expect(twitterClient).toHaveProperty('update'); + expect(twitterClient).toHaveProperty('delete'); + expect(twitterClient).toHaveProperty('generateAuthorization'); }); }); diff --git a/app/javascript/dashboard/api/specs/channel/webChannel.spec.js b/app/javascript/dashboard/api/specs/channel/webChannel.spec.js new file mode 100644 index 000000000..c14e79ea7 --- /dev/null +++ b/app/javascript/dashboard/api/specs/channel/webChannel.spec.js @@ -0,0 +1,13 @@ +import webChannelClient from '../../channel/webChannel'; +import ApiClient from '../../ApiClient'; + +describe('#webChannelClient', () => { + it('creates correct instance', () => { + expect(webChannelClient).toBeInstanceOf(ApiClient); + expect(webChannelClient).toHaveProperty('get'); + expect(webChannelClient).toHaveProperty('show'); + expect(webChannelClient).toHaveProperty('create'); + expect(webChannelClient).toHaveProperty('update'); + expect(webChannelClient).toHaveProperty('delete'); + }); +}); diff --git a/app/javascript/dashboard/api/specs/contacts.spec.js b/app/javascript/dashboard/api/specs/contacts.spec.js index 2cc89ccc8..ae1e1e0e1 100644 --- a/app/javascript/dashboard/api/specs/contacts.spec.js +++ b/app/javascript/dashboard/api/specs/contacts.spec.js @@ -1,14 +1,44 @@ -import contacts from '../contacts'; +import contactAPI from '../contacts'; import ApiClient from '../ApiClient'; +import describeWithAPIMock from './apiSpecHelper'; describe('#ContactsAPI', () => { it('creates correct instance', () => { - expect(contacts).toBeInstanceOf(ApiClient); - expect(contacts).toHaveProperty('get'); - expect(contacts).toHaveProperty('show'); - expect(contacts).toHaveProperty('create'); - expect(contacts).toHaveProperty('update'); - expect(contacts).toHaveProperty('delete'); - expect(contacts).toHaveProperty('getConversations'); + expect(contactAPI).toBeInstanceOf(ApiClient); + expect(contactAPI).toHaveProperty('get'); + expect(contactAPI).toHaveProperty('show'); + expect(contactAPI).toHaveProperty('create'); + expect(contactAPI).toHaveProperty('update'); + expect(contactAPI).toHaveProperty('delete'); + expect(contactAPI).toHaveProperty('getConversations'); + }); + + describeWithAPIMock('API calls', context => { + it('#get', () => { + contactAPI.get(1, 'name'); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/contacts?page=1&sort=name' + ); + }); + + it('#getConversations', () => { + contactAPI.getConversations(1); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/contacts/1/conversations' + ); + }); + + it('#getContactableInboxes', () => { + contactAPI.getContactableInboxes(1); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/contacts/1/contactable_inboxes' + ); + }); + it('#search', () => { + contactAPI.search('leads', 1, 'date'); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/contacts/search?q=leads&page=1&sort=date' + ); + }); }); }); diff --git a/app/javascript/dashboard/api/specs/conversations.spec.js b/app/javascript/dashboard/api/specs/conversations.spec.js index 2ac815459..4b1a0a030 100644 --- a/app/javascript/dashboard/api/specs/conversations.spec.js +++ b/app/javascript/dashboard/api/specs/conversations.spec.js @@ -1,15 +1,36 @@ -import conversations from '../conversations'; +import conversationsAPI from '../conversations'; import ApiClient from '../ApiClient'; +import describeWithAPIMock from './apiSpecHelper'; describe('#ConversationApi', () => { it('creates correct instance', () => { - expect(conversations).toBeInstanceOf(ApiClient); - expect(conversations).toHaveProperty('get'); - expect(conversations).toHaveProperty('show'); - expect(conversations).toHaveProperty('create'); - expect(conversations).toHaveProperty('update'); - expect(conversations).toHaveProperty('delete'); - expect(conversations).toHaveProperty('getLabels'); - expect(conversations).toHaveProperty('updateLabels'); + expect(conversationsAPI).toBeInstanceOf(ApiClient); + expect(conversationsAPI).toHaveProperty('get'); + expect(conversationsAPI).toHaveProperty('show'); + expect(conversationsAPI).toHaveProperty('create'); + expect(conversationsAPI).toHaveProperty('update'); + expect(conversationsAPI).toHaveProperty('delete'); + expect(conversationsAPI).toHaveProperty('getLabels'); + expect(conversationsAPI).toHaveProperty('updateLabels'); + }); + + describeWithAPIMock('API calls', context => { + it('#getLabels', () => { + conversationsAPI.getLabels(1); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/conversations/1/labels' + ); + }); + + it('#updateLabels', () => { + const labels = ['support-query']; + conversationsAPI.updateLabels(1, labels); + expect(context.axiosMock.post).toHaveBeenCalledWith( + '/api/v1/conversations/1/labels', + { + labels, + } + ); + }); }); }); diff --git a/app/javascript/dashboard/api/specs/endPoints.spec.js b/app/javascript/dashboard/api/specs/endPoints.spec.js new file mode 100644 index 000000000..8c46735a9 --- /dev/null +++ b/app/javascript/dashboard/api/specs/endPoints.spec.js @@ -0,0 +1,13 @@ +import endPoints from '../endPoints'; + +describe('#endPoints', () => { + it('it should return register url details if register page passed ', () => { + expect(endPoints('register')).toEqual({ url: 'api/v1/accounts.json' }); + }); + it('it should inbox url details if getInbox page passed', () => { + expect(endPoints('getInbox')).toEqual({ + url: 'api/v1/conversations.json', + params: { inbox_id: null }, + }); + }); +}); diff --git a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js index 6d4cdeec4..b4951804a 100644 --- a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js +++ b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js @@ -1,5 +1,6 @@ import conversationAPI from '../../inbox/conversation'; import ApiClient from '../../ApiClient'; +import describeWithAPIMock from '../apiSpecHelper'; describe('#ConversationAPI', () => { it('creates correct instance', () => { @@ -20,27 +21,143 @@ describe('#ConversationAPI', () => { expect(conversationAPI).toHaveProperty('sendEmailTranscript'); }); - describe('API calls', () => { - let originalAxios = null; - let axiosMock = null; - - beforeEach(() => { - originalAxios = window.axios; - axiosMock = { post: jest.fn(() => Promise.resolve()) }; - - window.axios = axiosMock; + describeWithAPIMock('API calls', context => { + it('#get conversations', () => { + conversationAPI.get({ + inboxId: 1, + status: 'open', + assigneeType: 'me', + page: 1, + labels: [], + teamId: 1, + }); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/conversations', + { + params: { + inbox_id: 1, + team_id: 1, + status: 'open', + assignee_type: 'me', + page: 1, + labels: [], + }, + } + ); }); - afterEach(() => { - window.axios = originalAxios; + it('#search', () => { + conversationAPI.search({ + q: 'leads', + page: 1, + }); + + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/conversations/search', + { + params: { + q: 'leads', + page: 1, + }, + } + ); + }); + + it('#toggleStatus', () => { + conversationAPI.toggleStatus({ conversationId: 12, status: 'online' }); + expect(context.axiosMock.post).toHaveBeenCalledWith( + `/api/v1/conversations/12/toggle_status`, + { + status: 'online', + } + ); + }); + + it('#assignAgent', () => { + conversationAPI.assignAgent({ conversationId: 12, agentId: 34 }); + expect(context.axiosMock.post).toHaveBeenCalledWith( + `/api/v1/conversations/12/assignments?assignee_id=34`, + {} + ); + }); + + it('#assignTeam', () => { + conversationAPI.assignTeam({ conversationId: 12, teamId: 1 }); + expect(context.axiosMock.post).toHaveBeenCalledWith( + `/api/v1/conversations/12/assignments`, + { + team_id: 1, + } + ); + }); + + it('#markMessageRead', () => { + conversationAPI.markMessageRead({ id: 12 }); + expect(context.axiosMock.post).toHaveBeenCalledWith( + `/api/v1/conversations/12/update_last_seen` + ); + }); + + it('#toggleTyping', () => { + conversationAPI.toggleTyping({ + conversationId: 12, + status: 'typing_on', + }); + expect(context.axiosMock.post).toHaveBeenCalledWith( + `/api/v1/conversations/12/toggle_typing_status`, + { + typing_status: 'typing_on', + } + ); + }); + + it('#mute', () => { + conversationAPI.mute(45); + expect(context.axiosMock.post).toHaveBeenCalledWith( + '/api/v1/conversations/45/mute' + ); }); it('#unmute', () => { conversationAPI.unmute(45); - - expect(axiosMock.post).toHaveBeenCalledWith( + expect(context.axiosMock.post).toHaveBeenCalledWith( '/api/v1/conversations/45/unmute' ); }); + + it('#meta', () => { + conversationAPI.meta({ + inboxId: 1, + status: 'open', + assigneeType: 'me', + labels: [], + teamId: 1, + }); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/conversations/meta', + { + params: { + inbox_id: 1, + team_id: 1, + status: 'open', + assignee_type: 'me', + labels: [], + }, + } + ); + }); + + it('#sendEmailTranscript', () => { + conversationAPI.sendEmailTranscript({ + conversationId: 45, + email: 'john@acme.inc', + }); + expect(context.axiosMock.post).toHaveBeenCalledWith( + '/api/v1/conversations/45/transcript', + { + email: 'john@acme.inc', + } + ); + }); }); }); diff --git a/app/javascript/dashboard/api/specs/inbox/message.spec.js b/app/javascript/dashboard/api/specs/inbox/message.spec.js new file mode 100644 index 000000000..dd4814f23 --- /dev/null +++ b/app/javascript/dashboard/api/specs/inbox/message.spec.js @@ -0,0 +1,32 @@ +import messageAPI from '../../inbox/message'; +import ApiClient from '../../ApiClient'; +import describeWithAPIMock from '../apiSpecHelper'; + +describe('#ConversationAPI', () => { + it('creates correct instance', () => { + expect(messageAPI).toBeInstanceOf(ApiClient); + expect(messageAPI).toHaveProperty('get'); + expect(messageAPI).toHaveProperty('show'); + expect(messageAPI).toHaveProperty('create'); + expect(messageAPI).toHaveProperty('update'); + expect(messageAPI).toHaveProperty('delete'); + expect(messageAPI).toHaveProperty('getPreviousMessages'); + }); + + describeWithAPIMock('API calls', context => { + it('#getPreviousMessages', () => { + messageAPI.getPreviousMessages({ + conversationId: 12, + before: 4573, + }); + expect(context.axiosMock.get).toHaveBeenCalledWith( + `/api/v1/conversations/12/messages`, + { + params: { + before: 4573, + }, + } + ); + }); + }); +}); diff --git a/app/javascript/dashboard/api/specs/inboxes.spec.js b/app/javascript/dashboard/api/specs/inboxes.spec.js index c4927e2bb..6c5cf38ea 100644 --- a/app/javascript/dashboard/api/specs/inboxes.spec.js +++ b/app/javascript/dashboard/api/specs/inboxes.spec.js @@ -1,13 +1,31 @@ -import inboxes from '../inboxes'; +import inboxesAPI from '../inboxes'; import ApiClient from '../ApiClient'; +import describeWithAPIMock from './apiSpecHelper'; describe('#InboxesAPI', () => { it('creates correct instance', () => { - expect(inboxes).toBeInstanceOf(ApiClient); - expect(inboxes).toHaveProperty('get'); - expect(inboxes).toHaveProperty('show'); - expect(inboxes).toHaveProperty('create'); - expect(inboxes).toHaveProperty('update'); - expect(inboxes).toHaveProperty('delete'); + expect(inboxesAPI).toBeInstanceOf(ApiClient); + expect(inboxesAPI).toHaveProperty('get'); + expect(inboxesAPI).toHaveProperty('show'); + expect(inboxesAPI).toHaveProperty('create'); + expect(inboxesAPI).toHaveProperty('update'); + expect(inboxesAPI).toHaveProperty('delete'); + expect(inboxesAPI).toHaveProperty('getAssignableAgents'); + expect(inboxesAPI).toHaveProperty('getCampaigns'); + }); + describeWithAPIMock('API calls', context => { + it('#getAssignableAgents', () => { + inboxesAPI.getAssignableAgents(1); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/inboxes/1/assignable_agents' + ); + }); + + it('#getCampaigns', () => { + inboxesAPI.getCampaigns(2); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/inboxes/2/campaigns' + ); + }); }); }); diff --git a/app/javascript/dashboard/api/specs/notifications.spec.js b/app/javascript/dashboard/api/specs/notifications.spec.js index 14f472ce9..d20c0d526 100644 --- a/app/javascript/dashboard/api/specs/notifications.spec.js +++ b/app/javascript/dashboard/api/specs/notifications.spec.js @@ -1,13 +1,54 @@ -import notifications from '../notifications'; +import notificationsAPI from '../notifications'; import ApiClient from '../ApiClient'; +import describeWithAPIMock from './apiSpecHelper'; describe('#NotificationAPI', () => { it('creates correct instance', () => { - expect(notifications).toBeInstanceOf(ApiClient); - expect(notifications).toHaveProperty('get'); - expect(notifications).toHaveProperty('getNotifications'); - expect(notifications).toHaveProperty('getUnreadCount'); - expect(notifications).toHaveProperty('read'); - expect(notifications).toHaveProperty('readAll'); + expect(notificationsAPI).toBeInstanceOf(ApiClient); + expect(notificationsAPI).toHaveProperty('get'); + expect(notificationsAPI).toHaveProperty('getNotifications'); + expect(notificationsAPI).toHaveProperty('getUnreadCount'); + expect(notificationsAPI).toHaveProperty('read'); + expect(notificationsAPI).toHaveProperty('readAll'); + }); + describeWithAPIMock('API calls', context => { + it('#get', () => { + notificationsAPI.get(1); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/notifications?page=1' + ); + }); + + it('#getNotifications', () => { + notificationsAPI.getNotifications(1); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/notifications/1/notifications' + ); + }); + + it('#getUnreadCount', () => { + notificationsAPI.getUnreadCount(); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/notifications/unread_count' + ); + }); + + it('#read', () => { + notificationsAPI.read(48670, 'Conversation'); + expect(context.axiosMock.post).toHaveBeenCalledWith( + '/api/v1/notifications/read_all', + { + primary_actor_id: 'Conversation', + primary_actor_type: 48670, + } + ); + }); + + it('#readAll', () => { + notificationsAPI.readAll(); + expect(context.axiosMock.post).toHaveBeenCalledWith( + '/api/v1/notifications/read_all' + ); + }); }); }); diff --git a/app/javascript/dashboard/api/specs/reports.spec.js b/app/javascript/dashboard/api/specs/reports.spec.js index fabe22c94..0ca5f4be7 100644 --- a/app/javascript/dashboard/api/specs/reports.spec.js +++ b/app/javascript/dashboard/api/specs/reports.spec.js @@ -1,17 +1,63 @@ -import reports from '../reports'; +import reportsAPI from '../reports'; import ApiClient from '../ApiClient'; +import describeWithAPIMock from './apiSpecHelper'; describe('#Reports API', () => { it('creates correct instance', () => { - expect(reports).toBeInstanceOf(ApiClient); - expect(reports.apiVersion).toBe('/api/v2'); - expect(reports).toHaveProperty('get'); - expect(reports).toHaveProperty('show'); - expect(reports).toHaveProperty('create'); - expect(reports).toHaveProperty('update'); - expect(reports).toHaveProperty('delete'); - expect(reports).toHaveProperty('getAccountReports'); - expect(reports).toHaveProperty('getAccountSummary'); - expect(reports).toHaveProperty('getAgentReports'); + expect(reportsAPI).toBeInstanceOf(ApiClient); + expect(reportsAPI.apiVersion).toBe('/api/v2'); + expect(reportsAPI).toHaveProperty('get'); + expect(reportsAPI).toHaveProperty('show'); + expect(reportsAPI).toHaveProperty('create'); + expect(reportsAPI).toHaveProperty('update'); + expect(reportsAPI).toHaveProperty('delete'); + expect(reportsAPI).toHaveProperty('getAccountReports'); + expect(reportsAPI).toHaveProperty('getAccountSummary'); + expect(reportsAPI).toHaveProperty('getAgentReports'); + }); + describeWithAPIMock('API calls', context => { + it('#getAccountReports', () => { + reportsAPI.getAccountReports( + 'conversations_count', + 1621103400, + 1621621800 + ); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v2/reports/account', + { + params: { + metric: 'conversations_count', + since: 1621103400, + until: 1621621800, + }, + } + ); + }); + + it('#getAccountSummary', () => { + reportsAPI.getAccountSummary(1621103400, 1621621800); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v2/reports/account_summary', + { + params: { + since: 1621103400, + until: 1621621800, + }, + } + ); + }); + + it('#getAgentReports', () => { + reportsAPI.getAgentReports(1621103400, 1621621800); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v2/reports/agents', + { + params: { + since: 1621103400, + until: 1621621800, + }, + } + ); + }); }); }); diff --git a/app/javascript/dashboard/api/specs/teams.spec.js b/app/javascript/dashboard/api/specs/teams.spec.js index 6b01ab359..4f29af554 100644 --- a/app/javascript/dashboard/api/specs/teams.spec.js +++ b/app/javascript/dashboard/api/specs/teams.spec.js @@ -1,16 +1,49 @@ -import teams from '../teams'; +import teamsAPI from '../teams'; import ApiClient from '../ApiClient'; +import describeWithAPIMock from './apiSpecHelper'; describe('#TeamsAPI', () => { it('creates correct instance', () => { - expect(teams).toBeInstanceOf(ApiClient); - expect(teams).toHaveProperty('get'); - expect(teams).toHaveProperty('show'); - expect(teams).toHaveProperty('create'); - expect(teams).toHaveProperty('update'); - expect(teams).toHaveProperty('delete'); - expect(teams).toHaveProperty('getAgents'); - expect(teams).toHaveProperty('addAgents'); - expect(teams).toHaveProperty('updateAgents'); + expect(teamsAPI).toBeInstanceOf(ApiClient); + expect(teamsAPI).toHaveProperty('get'); + expect(teamsAPI).toHaveProperty('show'); + expect(teamsAPI).toHaveProperty('create'); + expect(teamsAPI).toHaveProperty('update'); + expect(teamsAPI).toHaveProperty('delete'); + expect(teamsAPI).toHaveProperty('getAgents'); + expect(teamsAPI).toHaveProperty('addAgents'); + expect(teamsAPI).toHaveProperty('updateAgents'); + }); + describeWithAPIMock('API calls', context => { + it('#getAgents', () => { + teamsAPI.getAgents({ teamId: 1 }); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v1/teams/1/team_members' + ); + }); + + it('#addAgents', () => { + teamsAPI.addAgents({ teamId: 1, agentsList: { user_ids: [1, 10, 21] } }); + expect(context.axiosMock.post).toHaveBeenCalledWith( + '/api/v1/teams/1/team_members', + { + user_ids: { user_ids: [1, 10, 21] }, + } + ); + }); + + it('#updateAgents', () => { + const agentsList = { user_ids: [1, 10, 21] }; + teamsAPI.updateAgents({ + teamId: 1, + agentsList, + }); + expect(context.axiosMock.patch).toHaveBeenCalledWith( + '/api/v1/teams/1/team_members', + { + user_ids: agentsList, + } + ); + }); }); }); diff --git a/app/javascript/dashboard/api/specs/webhook.spec.js b/app/javascript/dashboard/api/specs/webhook.spec.js new file mode 100644 index 000000000..5a1db9db6 --- /dev/null +++ b/app/javascript/dashboard/api/specs/webhook.spec.js @@ -0,0 +1,13 @@ +import webhooksAPI from '../webhooks'; +import ApiClient from '../ApiClient'; + +describe('#webhooksAPI', () => { + it('creates correct instance', () => { + expect(webhooksAPI).toBeInstanceOf(ApiClient); + expect(webhooksAPI).toHaveProperty('get'); + expect(webhooksAPI).toHaveProperty('show'); + expect(webhooksAPI).toHaveProperty('create'); + expect(webhooksAPI).toHaveProperty('update'); + expect(webhooksAPI).toHaveProperty('delete'); + }); +}); diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/specs/AccountSelector.spec.js b/app/javascript/dashboard/components/layout/sidebarComponents/specs/AccountSelector.spec.js new file mode 100644 index 000000000..09bd6f120 --- /dev/null +++ b/app/javascript/dashboard/components/layout/sidebarComponents/specs/AccountSelector.spec.js @@ -0,0 +1,90 @@ +import AccountSelector from '../AccountSelector'; +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'; +import WootModalHeader from 'dashboard/components/ModalHeader'; + +const localVue = createLocalVue(); +localVue.component('woot-modal', WootModal); +localVue.component('woot-modal-header', WootModalHeader); + +localVue.use(Vuex); +localVue.use(VueI18n); + +const i18nConfig = new VueI18n({ + locale: 'en', + messages: i18n, +}); + +describe('accountSelctor', () => { + let accountSelector = null; + const currentUser = { + accounts: [ + { + id: 1, + name: 'Chatwoot', + role: 'administrator', + }, + { + id: 2, + name: 'GitX', + role: 'agent', + }, + ], + }; + const accountId = 1; + const globalConfig = { createNewAccountFromDashboard: false }; + let store = null; + let actions = null; + let modules = null; + + beforeEach(() => { + actions = {}; + modules = { + auth: { + getters: { + getCurrentAccountId: () => accountId, + getCurrentUser: () => currentUser, + }, + }, + globalConfig: { + getters: { + 'globalConfig/get': () => globalConfig, + }, + }, + }; + + store = new Vuex.Store({ + actions, + modules, + }); + accountSelector = mount(AccountSelector, { + store, + localVue, + i18n: i18nConfig, + propsData: { + showAccountModal: true, + }, + }); + }); + + it('title and sub title exist', () => { + const headerComponent = accountSelector.findComponent(WootModalHeader); + const topBar = headerComponent.find('.page-top-bar'); + const titleComponent = topBar.find('.page-sub-title'); + expect(titleComponent.text()).toBe('Switch Account'); + const subTitleComponent = topBar.find('p'); + expect(subTitleComponent.text()).toBe( + 'Select an account from the following list' + ); + }); + + it('first account item is checked', () => { + const accountFirstItem = accountSelector.find('.account-selector .ion'); + expect(accountFirstItem.exists()).toBe(true); + }); +}); diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/specs/AgentDetails.spec.js b/app/javascript/dashboard/components/layout/sidebarComponents/specs/AgentDetails.spec.js new file mode 100644 index 000000000..142bbedfa --- /dev/null +++ b/app/javascript/dashboard/components/layout/sidebarComponents/specs/AgentDetails.spec.js @@ -0,0 +1,64 @@ +import AgentDetails from '../AgentDetails'; +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'; +const localVue = createLocalVue(); +localVue.use(Vuex); +localVue.use(VueI18n); +localVue.component('thumbnail', Thumbnail); + +const i18nConfig = new VueI18n({ + locale: 'en', + messages: i18n, +}); + +describe('agentDetails', () => { + const currentUser = { name: 'Neymar Junior', avatar_url: '' }; + const currentRole = 'agent'; + let store = null; + let actions = null; + let modules = null; + let agentDetails = null; + + beforeEach(() => { + actions = {}; + + modules = { + auth: { + getters: { + getCurrentUser: () => currentUser, + getCurrentRole: () => currentRole, + }, + }, + }; + + store = new Vuex.Store({ + actions, + modules, + }); + + agentDetails = shallowMount(AgentDetails, { + store, + localVue, + i18n: i18nConfig, + }); + }); + + it('shows the agent name', () => { + const agentTitle = agentDetails.find('.current-user--name'); + expect(agentTitle.text()).toBe('Neymar Junior'); + }); + + it('shows the agent role', () => { + const agentTitle = agentDetails.find('.current-user--role'); + expect(agentTitle.text()).toBe('Agent'); + }); + + it('agent thumbnail exists', () => { + const thumbnailComponent = agentDetails.findComponent(Thumbnail); + expect(thumbnailComponent.exists()).toBe(true); + }); +}); diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/specs/NotificationBell.spec.js b/app/javascript/dashboard/components/layout/sidebarComponents/specs/NotificationBell.spec.js new file mode 100644 index 000000000..947e5976b --- /dev/null +++ b/app/javascript/dashboard/components/layout/sidebarComponents/specs/NotificationBell.spec.js @@ -0,0 +1,67 @@ +import NotificationBell from '../NotificationBell'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import VueI18n from 'vue-i18n'; + +import i18n from 'dashboard/i18n'; + +const localVue = createLocalVue(); +localVue.use(Vuex); +localVue.use(VueI18n); + +const i18nConfig = new VueI18n({ + locale: 'en', + messages: i18n, +}); + +describe('notificationBell', () => { + const accountId = 1; + const notificationMetadata = { unreadCount: 19 }; + let store = null; + let actions = null; + let modules = null; + + beforeEach(() => { + actions = { + showNotification: jest.fn(), + }; + modules = { + auth: { + getters: { + getCurrentAccountId: () => accountId, + }, + }, + notifications: { + getters: { + 'notifications/getMeta': () => notificationMetadata, + }, + }, + }; + + store = new Vuex.Store({ + actions, + modules, + }); + }); + + it('it should return unread count 19 ', () => { + const notificationBell = shallowMount(NotificationBell, { + store, + localVue, + i18n: i18nConfig, + }); + const statusViewTitle = notificationBell.find('.unread-badge'); + expect(statusViewTitle.text()).toBe('19'); + }); + + it('it should return unread count 99+ ', async () => { + notificationMetadata.unreadCount = 101; + const notificationBell = shallowMount(NotificationBell, { + store, + localVue, + i18n: i18nConfig, + }); + const statusViewTitle = notificationBell.find('.unread-badge'); + expect(statusViewTitle.text()).toBe('99+'); + }); +}); diff --git a/app/javascript/dashboard/components/widgets/Thumbnail.spec.js b/app/javascript/dashboard/components/widgets/Thumbnail.spec.js index 5de534dbe..c10cd3d16 100644 --- a/app/javascript/dashboard/components/widgets/Thumbnail.spec.js +++ b/app/javascript/dashboard/components/widgets/Thumbnail.spec.js @@ -15,7 +15,8 @@ describe(`when there are NO errors loading the thumbnail`, () => { }, }); expect(wrapper.find('#image').exists()).toBe(true); - expect(wrapper.contains(Avatar)).toBe(false); + const avatarComponent = wrapper.findComponent(Avatar); + expect(avatarComponent.exists()).toBe(false); }); }); @@ -31,8 +32,9 @@ describe(`when there ARE errors loading the thumbnail`, () => { }; }, }); - expect(wrapper.contains('#image')).toBe(false); - expect(wrapper.contains(Avatar)).toBe(true); + expect(wrapper.find('#image').exists()).toBe(false); + const avatarComponent = wrapper.findComponent(Avatar); + expect(avatarComponent.exists()).toBe(true); }); }); diff --git a/app/javascript/dashboard/mixins/specs/conversationFixtures.js b/app/javascript/dashboard/mixins/specs/conversationFixtures.js new file mode 100644 index 000000000..3602ef95d --- /dev/null +++ b/app/javascript/dashboard/mixins/specs/conversationFixtures.js @@ -0,0 +1,185 @@ +export default { + conversation: { + meta: { + sender: { + additional_attributes: { + created_at_ip: '127.0.0.1', + }, + availability_status: 'offline', + email: null, + id: 5017687, + name: 'long-flower-143', + phone_number: null, + thumbnail: '', + custom_attributes: {}, + }, + channel: 'Channel::WebWidget', + assignee: { + account_id: 1, + availability_status: 'offline', + confirmed: true, + email: 'muhsin@chatwoot.com', + available_name: 'Muhsin Keloth', + id: 21, + name: 'Muhsin Keloth', + role: 'administrator', + thumbnail: + 'http://0.0.0.0:3000/rails/active_storage/representations/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBEQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7b95641540fadebc733ec9b42117d00bc09600be/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9MY21WemFYcGxTU0lNTWpVd2VESTFNQVk2QmtWVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--c13bd5229b2a2a692444606e22f76ad61c634661/me.jpg', + }, + }, + id: 5815, + messages: [ + { + id: 438072, + content: 'Campaign after 5 seconds', + account_id: 1, + inbox_id: 37, + conversation_id: 5811, + message_type: 1, + created_at: 1620980262, + updated_at: '2021-05-14T08:17:42.041Z', + private: false, + status: 'sent', + source_id: null, + content_type: null, + content_attributes: {}, + sender_type: 'User', + sender_id: 1, + external_source_ids: {}, + }, + { + id: 4382131101, + content: 'Hello', + account_id: 1, + inbox_id: 37, + conversation_id: 5815, + message_type: 0, + created_at: 1621145476, + updated_at: '2021-05-16T05:48:43.910Z', + private: false, + status: 'sent', + source_id: null, + content_type: 'text', + content_attributes: {}, + sender_type: null, + sender_id: null, + external_source_ids: {}, + }, + { + id: 438100, + content: 'Hey', + account_id: 1, + inbox_id: 37, + conversation_id: 5815, + message_type: 0, + created_at: 1621145476, + updated_at: '2021-05-16T05:48:43.910Z', + private: false, + status: 'sent', + source_id: null, + content_type: 'text', + content_attributes: {}, + sender_type: null, + sender_id: null, + external_source_ids: {}, + }, + ], + inbox_id: 37, + status: 'open', + muted: false, + can_reply: true, + timestamp: 1621144123, + contact_last_seen_at: 0, + agent_last_seen_at: 1621144123, + unread_count: 0, + additional_attributes: { + browser: { + device_name: 'Unknown', + browser_name: 'Chrome', + platform_name: 'macOS', + browser_version: '90.0.4430.212', + platform_version: '10.15.7', + }, + widget_language: null, + browser_language: 'en', + }, + account_id: 1, + labels: [], + }, + lastMessage: { + id: 438100, + content: 'Hey', + account_id: 1, + inbox_id: 37, + conversation_id: 5815, + message_type: 0, + created_at: 1621145476, + updated_at: '2021-05-16T05:48:43.910Z', + private: false, + status: 'sent', + source_id: null, + content_type: 'text', + content_attributes: {}, + sender_type: null, + sender_id: null, + external_source_ids: {}, + }, + readMessages: [ + { + id: 438072, + content: 'Campaign after 5 seconds', + account_id: 1, + inbox_id: 37, + conversation_id: 5811, + message_type: 1, + created_at: 1620980262, + updated_at: '2021-05-14T08:17:42.041Z', + private: false, + status: 'sent', + source_id: null, + content_type: null, + content_attributes: {}, + sender_type: 'User', + sender_id: 1, + external_source_ids: {}, + }, + ], + unReadMessages: [ + { + id: 4382131101, + content: 'Hello', + account_id: 1, + inbox_id: 37, + conversation_id: 5815, + message_type: 0, + created_at: 1621145476, + updated_at: '2021-05-16T05:48:43.910Z', + private: false, + status: 'sent', + source_id: null, + content_type: 'text', + content_attributes: {}, + sender_type: null, + sender_id: null, + external_source_ids: {}, + }, + { + id: 438100, + content: 'Hey', + account_id: 1, + inbox_id: 37, + conversation_id: 5815, + message_type: 0, + created_at: 1621145476, + updated_at: '2021-05-16T05:48:43.910Z', + private: false, + status: 'sent', + source_id: null, + content_type: 'text', + content_attributes: {}, + sender_type: null, + sender_id: null, + external_source_ids: {}, + }, + ], +}; diff --git a/app/javascript/dashboard/mixins/specs/conversaton.spec.js b/app/javascript/dashboard/mixins/specs/conversaton.spec.js new file mode 100644 index 000000000..eaa9aab97 --- /dev/null +++ b/app/javascript/dashboard/mixins/specs/conversaton.spec.js @@ -0,0 +1,29 @@ +import conversationMixin from '../conversations'; +import conversationFixture from './conversationFixtures'; +import commonHelpers from '../../helper/commons'; +commonHelpers(); + +describe('#conversationMixin', () => { + it('should return unread message count 2 if conversation is passed', () => { + expect( + conversationMixin.methods.unreadMessagesCount( + conversationFixture.conversation + ) + ).toEqual(2); + }); + it('should return last message if conversation is passed', () => { + expect( + conversationMixin.methods.lastMessage(conversationFixture.conversation) + ).toEqual(conversationFixture.lastMessage); + }); + it('should return read messages if conversation is passed', () => { + expect( + conversationMixin.methods.readMessages(conversationFixture.conversation) + ).toEqual(conversationFixture.readMessages); + }); + it('should return read messages if conversation is passed', () => { + expect( + conversationMixin.methods.unReadMessages(conversationFixture.conversation) + ).toEqual(conversationFixture.unReadMessages); + }); +}); diff --git a/app/javascript/dashboard/mixins/specs/isAdmin.spec.js b/app/javascript/dashboard/mixins/specs/isAdmin.spec.js new file mode 100644 index 000000000..50fd63d8f --- /dev/null +++ b/app/javascript/dashboard/mixins/specs/isAdmin.spec.js @@ -0,0 +1,28 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import isAdminMixin from '../isAdmin'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('isAdminMixin', () => { + let getters; + let store; + + beforeEach(() => { + getters = { + getCurrentRole: () => 'administrator', + }; + + store = new Vuex.Store({ getters }); + }); + it('set accountId properly', () => { + const Component = { + render() {}, + title: 'TestComponent', + mixins: [isAdminMixin], + }; + const wrapper = shallowMount(Component, { store, localVue }); + expect(wrapper.vm.isAdmin).toBe(true); + }); +});