From eb082a7c34acd542c9d8cdf4107d5869e7f6e832 Mon Sep 17 00:00:00 2001 From: Jarlem Red de Peralta Date: Wed, 12 Oct 2022 11:49:14 +0800 Subject: [PATCH] unit(consultation): ensure sorted chat messages --- components/consultation/chat_window.spec.ts | 1097 ++++++++++--------- components/consultation/chat_window.vue | 1 - 2 files changed, 589 insertions(+), 509 deletions(-) diff --git a/components/consultation/chat_window.spec.ts b/components/consultation/chat_window.spec.ts index af8ddc382..ebb64d31b 100644 --- a/components/consultation/chat_window.spec.ts +++ b/components/consultation/chat_window.spec.ts @@ -17,505 +17,624 @@ import ConsultationTimerManager from "$@/helpers/consultation_timer_manager" import Component from "./chat_window.vue" describe("Component: consultation/chat_window", () => { - describe("before", () => { - it("can toggle consultation list state", async() => { - const scheduledStartAt = new Date() - const consultant = { - "data": { - "id": "10", - "kind": "reachable_employee", - "type": "user" - } - } - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - const id = "1" - const fakeConsultation = { - "actionTaken": null, - "consultant": { + describe("consultation states", () => { + describe("before", () => { + it("can toggle consultation list state", async() => { + const scheduledStartAt = new Date() + const consultant = { "data": { - "id": "1", + "id": "10", + "kind": "reachable_employee", "type": "user" } - }, - "finishedAt": null, - id, - "reason": "", - scheduledStartAt, - "startedAt": null, - "type": "consultation" - } as DeserializedConsultationResource - const fakeChatMessage = { - "data": [] - } as DeserializedChatMessageListDocument - const wrapper = shallowMount(Component, { - "global": { - "provide": { - "pageContext": { - "pageProps": { - "userProfile": consultant + } + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + const id = "1" + const fakeConsultation = { + "actionTaken": null, + "consultant": { + "data": { + "id": "1", + "type": "user" + } + }, + "finishedAt": null, + id, + "reason": "", + scheduledStartAt, + "startedAt": null, + "type": "consultation" + } as DeserializedConsultationResource + const fakeChatMessage = { + "data": [] + } as DeserializedChatMessageListDocument + const wrapper = shallowMount(Component, { + "global": { + "provide": { + "pageContext": { + "pageProps": { + "userProfile": consultant + } } } + }, + "props": { + "chatMessages": fakeChatMessage, + "consultation": fakeConsultation, + "isConsultationListShown": false } - }, - "props": { - "chatMessages": fakeChatMessage, - "consultation": fakeConsultation, - "isConsultationListShown": false - } - }) - const toggleListBtn = wrapper.find(".toggle-list-btn") - expect(toggleListBtn.exists()).toBeTruthy() + }) + const toggleListBtn = wrapper.find(".toggle-list-btn") + expect(toggleListBtn.exists()).toBeTruthy() - await toggleListBtn.trigger("click") - const emits = wrapper.emitted() - expect(emits).toHaveProperty("toggleConsultationList") - }) + await toggleListBtn.trigger("click") + const emits = wrapper.emitted() + expect(emits).toHaveProperty("toggleConsultationList") + }) - it("should request to start consultation", async() => { - const scheduledStartAt = new Date() - const consultant = { - "data": { - "id": "10", - "kind": "reachable_employee", - "type": "user" - } - } - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - const id = "1" - const fakeConsultation = { - "actionTaken": null, - "consultant": { + it("should request to start consultation", async() => { + const scheduledStartAt = new Date() + const consultant = { "data": { - "id": "1", + "id": "10", + "kind": "reachable_employee", "type": "user" } - }, - "finishedAt": null, - id, - "reason": "", - scheduledStartAt, - "startedAt": null, - "type": "consultation" - } as DeserializedConsultationResource - const fakeChatMessage = { - "data": [] - } as DeserializedChatMessageListDocument - const wrapper = shallowMount(Component, { - "global": { - "provide": { - "pageContext": { - "pageProps": { - "userProfile": consultant + } + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + const id = "1" + const fakeConsultation = { + "actionTaken": null, + "consultant": { + "data": { + "id": "1", + "type": "user" + } + }, + "finishedAt": null, + id, + "reason": "", + scheduledStartAt, + "startedAt": null, + "type": "consultation" + } as DeserializedConsultationResource + const fakeChatMessage = { + "data": [] + } as DeserializedChatMessageListDocument + const wrapper = shallowMount(Component, { + "global": { + "provide": { + "pageContext": { + "pageProps": { + "userProfile": consultant + } } } + }, + "props": { + "chatMessages": fakeChatMessage, + "consultation": fakeConsultation, + "isConsultationListShown": false } - }, - "props": { - "chatMessages": fakeChatMessage, - "consultation": fakeConsultation, - "isConsultationListShown": false - } + }) + + const userController = wrapper.findComponent({ "name": "UserController" }) + await userController.trigger("start-consultation") + await flushPromises() + + const consultationHeader = wrapper.find(".selected-consultation-header") + expect(consultationHeader.exists()).toBeTruthy() + expect(consultationHeader.html()).toContain("5m") + const events = wrapper.emitted("updatedConsultationAttributes") + expect(events).toHaveLength(1) + const castFetch = fetch as jest.Mock + const [ [ firstRequest ] ] = castFetch.mock.calls + expect(firstRequest).toHaveProperty("method", "PATCH") + expect(firstRequest).toHaveProperty( + "url", + specializePath(CONSULTATION_LINK.bound, { id }) + ) + const firstRequestBody = await firstRequest.json() + expect(firstRequestBody).toHaveProperty("data.attributes.actionTaken", null) + expect(firstRequestBody).toHaveProperty("data.attributes.finishedAt", null) + expect(firstRequestBody).toHaveProperty("data.attributes.reason", "") + expect(firstRequestBody).toHaveProperty( + "data.attributes.scheduledStartAt", + scheduledStartAt.toJSON() + ) + expect(firstRequestBody).not.toHaveProperty("data.attributes.startedAt", null) + expect(firstRequestBody).toHaveProperty("data.id", "1") + expect(firstRequestBody).toHaveProperty("data.type", "consultation") }) - - const userController = wrapper.findComponent({ "name": "UserController" }) - await userController.trigger("start-consultation") - await flushPromises() - - const consultationHeader = wrapper.find(".selected-consultation-header") - expect(consultationHeader.exists()).toBeTruthy() - expect(consultationHeader.html()).toContain("5m") - const events = wrapper.emitted("updatedConsultationAttributes") - expect(events).toHaveLength(1) - const castFetch = fetch as jest.Mock - const [ [ firstRequest ] ] = castFetch.mock.calls - expect(firstRequest).toHaveProperty("method", "PATCH") - expect(firstRequest).toHaveProperty("url", specializePath(CONSULTATION_LINK.bound, { id })) - const firstRequestBody = await firstRequest.json() - expect(firstRequestBody).toHaveProperty("data.attributes.actionTaken", null) - expect(firstRequestBody).toHaveProperty("data.attributes.finishedAt", null) - expect(firstRequestBody).toHaveProperty("data.attributes.reason", "") - expect(firstRequestBody).toHaveProperty( - "data.attributes.scheduledStartAt", - scheduledStartAt.toJSON() - ) - expect(firstRequestBody).not.toHaveProperty("data.attributes.startedAt", null) - expect(firstRequestBody).toHaveProperty("data.id", "1") - expect(firstRequestBody).toHaveProperty("data.type", "consultation") }) - }) - describe("during", () => { - it("should automatically terminate the consultation", async() => { - const scheduledStartAt = new Date() - const consultant = { - "data": { - "id": "10", - "kind": "reachable_employee", - "type": "user" + describe("during", () => { + it("should automatically terminate the consultation", async() => { + const scheduledStartAt = new Date() + const consultant = { + "data": { + "id": "10", + "kind": "reachable_employee", + "type": "user" + } } - } - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - const id = "1" - const fakeConsultation = { - "actionTaken": null, - consultant, - "finishedAt": null, - id, - "reason": "", - scheduledStartAt, - "startedAt": null, - "type": "consultation" - } as DeserializedConsultationResource - const fakeChatMessage = { - "data": [] - } as DeserializedChatMessageListDocument - const wrapper = shallowMount(Component, { - "global": { - "provide": { - "pageContext": { - "pageProps": { - "userProfile": consultant + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + const id = "1" + const fakeConsultation = { + "actionTaken": null, + consultant, + "finishedAt": null, + id, + "reason": "", + scheduledStartAt, + "startedAt": null, + "type": "consultation" + } as DeserializedConsultationResource + const fakeChatMessage = { + "data": [] + } as DeserializedChatMessageListDocument + const wrapper = shallowMount(Component, { + "global": { + "provide": { + "pageContext": { + "pageProps": { + "userProfile": consultant + } } } + }, + "props": { + "chatMessages": fakeChatMessage, + "consultation": fakeConsultation } - }, - "props": { - "chatMessages": fakeChatMessage, - "consultation": fakeConsultation - } - }) + }) - const userController = wrapper.findComponent({ "name": "UserController" }) - await userController.trigger("start-consultation") - await flushPromises() - const updatedFakeConsultation = { - ...fakeConsultation, - "startedAt": new Date(Date.now() - convertTimeToMilliseconds("00:00:01")) - } as DeserializedConsultationResource - await wrapper.setProps({ - "chatMessages": fakeChatMessage, - "consultation": updatedFakeConsultation + const userController = wrapper.findComponent({ "name": "UserController" }) + await userController.trigger("start-consultation") + await flushPromises() + const updatedFakeConsultation = { + ...fakeConsultation, + "startedAt": new Date(Date.now() - convertTimeToMilliseconds("00:00:01")) + } as DeserializedConsultationResource + await wrapper.setProps({ + "chatMessages": fakeChatMessage, + "consultation": updatedFakeConsultation + }) + ConsultationTimerManager.forceFinish(updatedFakeConsultation) + await flushPromises() + + const consultationHeader = wrapper.find(".selected-consultation-header") + expect(consultationHeader.exists()).toBeTruthy() + expect(consultationHeader.html()).toContain("0m") + const events = wrapper.emitted("updatedConsultationAttributes") + expect(events).toHaveLength(2) + const castFetch = fetch as jest.Mock + const [ [ firstRequest ], [ secondRequest ] ] = castFetch.mock.calls + expect(firstRequest).toHaveProperty("method", "PATCH") + expect(firstRequest).toHaveProperty( + "url", + specializePath(CONSULTATION_LINK.bound, { id }) + ) + const firstRequestBody = await firstRequest.json() + expect(firstRequestBody).toHaveProperty("data.attributes.actionTaken", null) + expect(firstRequestBody).toHaveProperty("data.attributes.finishedAt", null) + expect(firstRequestBody).toHaveProperty("data.attributes.reason", "") + expect(firstRequestBody).toHaveProperty( + "data.attributes.scheduledStartAt", + scheduledStartAt.toJSON() + ) + expect(firstRequestBody).not.toHaveProperty("data.attributes.startedAt", null) + expect(firstRequestBody).toHaveProperty("data.id", "1") + expect(firstRequestBody).toHaveProperty("data.type", "consultation") + expect(secondRequest).toHaveProperty("method", "PATCH") + expect(secondRequest).toHaveProperty( + "url", + specializePath(CONSULTATION_LINK.bound, { id }) + ) + const secondRequestBody = await secondRequest.json() + expect(secondRequestBody).toHaveProperty("data.attributes.actionTaken", null) + expect(secondRequestBody).not.toHaveProperty("data.attributes.finishedAt", null) + expect(secondRequestBody).toHaveProperty("data.attributes.reason", "") + expect(secondRequestBody).toHaveProperty( + "data.attributes.scheduledStartAt", + scheduledStartAt.toJSON() + ) + expect(secondRequestBody).not.toHaveProperty("data.attributes.startedAt", null) + expect(secondRequestBody).toHaveProperty("data.id", "1") + expect(secondRequestBody).toHaveProperty("data.type", "consultation") }) - ConsultationTimerManager.forceFinish(updatedFakeConsultation) - await flushPromises() - - const consultationHeader = wrapper.find(".selected-consultation-header") - expect(consultationHeader.exists()).toBeTruthy() - expect(consultationHeader.html()).toContain("0m") - const events = wrapper.emitted("updatedConsultationAttributes") - expect(events).toHaveLength(2) - const castFetch = fetch as jest.Mock - const [ [ firstRequest ], [ secondRequest ] ] = castFetch.mock.calls - expect(firstRequest).toHaveProperty("method", "PATCH") - expect(firstRequest).toHaveProperty("url", specializePath(CONSULTATION_LINK.bound, { id })) - const firstRequestBody = await firstRequest.json() - expect(firstRequestBody).toHaveProperty("data.attributes.actionTaken", null) - expect(firstRequestBody).toHaveProperty("data.attributes.finishedAt", null) - expect(firstRequestBody).toHaveProperty("data.attributes.reason", "") - expect(firstRequestBody).toHaveProperty( - "data.attributes.scheduledStartAt", - scheduledStartAt.toJSON() - ) - expect(firstRequestBody).not.toHaveProperty("data.attributes.startedAt", null) - expect(firstRequestBody).toHaveProperty("data.id", "1") - expect(firstRequestBody).toHaveProperty("data.type", "consultation") - expect(secondRequest).toHaveProperty("method", "PATCH") - expect(secondRequest).toHaveProperty( - "url", - specializePath(CONSULTATION_LINK.bound, { id }) - ) - const secondRequestBody = await secondRequest.json() - expect(secondRequestBody).toHaveProperty("data.attributes.actionTaken", null) - expect(secondRequestBody).not.toHaveProperty("data.attributes.finishedAt", null) - expect(secondRequestBody).toHaveProperty("data.attributes.reason", "") - expect(secondRequestBody).toHaveProperty( - "data.attributes.scheduledStartAt", - scheduledStartAt.toJSON() - ) - expect(secondRequestBody).not.toHaveProperty("data.attributes.startedAt", null) - expect(secondRequestBody).toHaveProperty("data.id", "1") - expect(secondRequestBody).toHaveProperty("data.type", "consultation") - }) - it("should continue to started consultation", async() => { - const scheduledStartAt = new Date(Date.now() - convertTimeToMilliseconds("00:00:02")) - const consultant = { - "data": { - "id": "10", - "kind": "reachable_employee", - "type": "user" + it("should continue to started consultation", async() => { + const scheduledStartAt = new Date(Date.now() - convertTimeToMilliseconds("00:00:02")) + const consultant = { + "data": { + "id": "10", + "kind": "reachable_employee", + "type": "user" + } } - } - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - const fakeConsultation = { - "actionTaken": null, - "finishedAt": null, - "id": "1", - "reason": "", - scheduledStartAt, - "startedAt": scheduledStartAt, - "type": "consultation" - } as DeserializedConsultationResource - const fakeChatMessage = { - "data": [] - } as DeserializedChatMessageListDocument - const wrapper = shallowMount(Component, { - "global": { - "provide": { - "pageContext": { - "pageProps": { - "userProfile": consultant + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + const fakeConsultation = { + "actionTaken": null, + "finishedAt": null, + "id": "1", + "reason": "", + scheduledStartAt, + "startedAt": scheduledStartAt, + "type": "consultation" + } as DeserializedConsultationResource + const fakeChatMessage = { + "data": [] + } as DeserializedChatMessageListDocument + const wrapper = shallowMount(Component, { + "global": { + "provide": { + "pageContext": { + "pageProps": { + "userProfile": consultant + } } } + }, + "props": { + "chatMessages": fakeChatMessage, + "consultation": fakeConsultation } - }, - "props": { - "chatMessages": fakeChatMessage, - "consultation": fakeConsultation - } - }) + }) - await nextTick() + await nextTick() - const consultationHeader = wrapper.find(".selected-consultation-header") - expect(consultationHeader.exists()).toBeTruthy() - expect(consultationHeader.html()).toContain("5m") - ConsultationTimerManager.clearAllListeners() - }) + const consultationHeader = wrapper.find(".selected-consultation-header") + expect(consultationHeader.exists()).toBeTruthy() + expect(consultationHeader.html()).toContain("5m") + ConsultationTimerManager.clearAllListeners() + }) - it("should start consultation on other source's update", async() => { - const scheduledStartAt = new Date(Date.now() - convertTimeToMilliseconds("00:00:02")) - const consultant = { - "data": { - "id": "10", - "kind": "reachable_employee", - "type": "user" + it("should start consultation on other source's update", async() => { + const scheduledStartAt = new Date(Date.now() - convertTimeToMilliseconds("00:00:02")) + const consultant = { + "data": { + "id": "10", + "kind": "reachable_employee", + "type": "user" + } } - } - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - const fakeConsultation = { - "actionTaken": null, - "finishedAt": null, - "id": "1", - "reason": "", - scheduledStartAt, - "startedAt": null, - "type": "consultation" - } as DeserializedConsultationResource - const fakeChatMessage = { - "data": [] - } as DeserializedChatMessageListDocument - const wrapper = shallowMount(Component, { - "global": { - "provide": { - "pageContext": { - "pageProps": { - "userProfile": consultant + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + const fakeConsultation = { + "actionTaken": null, + "finishedAt": null, + "id": "1", + "reason": "", + scheduledStartAt, + "startedAt": null, + "type": "consultation" + } as DeserializedConsultationResource + const fakeChatMessage = { + "data": [] + } as DeserializedChatMessageListDocument + const wrapper = shallowMount(Component, { + "global": { + "provide": { + "pageContext": { + "pageProps": { + "userProfile": consultant + } } } + }, + "props": { + "chatMessages": fakeChatMessage, + "consultation": fakeConsultation } - }, - "props": { - "chatMessages": fakeChatMessage, - "consultation": fakeConsultation - } + }) + + await wrapper.setProps({ + "consultation": { + ...fakeConsultation, + "startedAt": new Date(Date.now() - convertTimeToMilliseconds("00:05:00")) + } + }) + ConsultationTimerManager.nextInterval() + await nextTick() + + const consultationHeader = wrapper.find(".selected-consultation-header") + expect(consultationHeader.exists()).toBeTruthy() + expect(consultationHeader.html()).toContain("4m") + expect(consultationHeader.html()).toContain("59s") + ConsultationTimerManager.clearAllListeners() }) - await wrapper.setProps({ - "consultation": { - ...fakeConsultation, - "startedAt": new Date(Date.now() - convertTimeToMilliseconds("00:05:00")) + it("should restart the timer", async() => { + jest.useFakeTimers() + const scheduledStartAt = new Date() + const consultant = { + "data": { + "id": "10", + "kind": "reachable_employee", + "type": "user" + } } + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + const id = "1" + const fakeConsultation = { + "actionTaken": null, + consultant, + "finishedAt": null, + id, + "reason": "", + scheduledStartAt, + "startedAt": null, + "type": "consultation" + } as DeserializedConsultationResource + const fakeChatMessage = { + "data": [] + } as DeserializedChatMessageListDocument + const wrapper = shallowMount(Component, { + "global": { + "provide": { + "pageContext": { + "pageProps": { + "userProfile": consultant + } + } + } + }, + "props": { + "chatMessages": fakeChatMessage, + "consultation": fakeConsultation, + "isConsultationListShown": false + } + }) + + const userController = wrapper.findComponent({ "name": "UserController" }) + await userController.trigger("start-consultation") + await flushPromises() + const updatedFakeConsultation = { + ...fakeConsultation, + "startedAt": new Date(Date.now() - convertTimeToMilliseconds("00:00:01")) + } as DeserializedConsultationResource + await wrapper.setProps({ "consultation": updatedFakeConsultation }) + ConsultationTimerManager.restartTimerFor(updatedFakeConsultation) + await nextTick() + + const castedWrapper = wrapper.vm as any + expect(castedWrapper.remainingTime.minutes).toEqual(5) + expect(castedWrapper.remainingTime.seconds).toEqual(0) + ConsultationTimerManager.nextInterval() + expect(castedWrapper.remainingTime.minutes).toEqual(4) + expect(castedWrapper.remainingTime.seconds).toEqual(59) + + const consultationHeader = wrapper.find(".selected-consultation-header") + expect(consultationHeader.exists()).toBeTruthy() + expect(consultationHeader.html()).toContain("5m") + const events = wrapper.emitted("updatedConsultationAttributes") + expect(events).toHaveLength(1) + const castFetch = fetch as jest.Mock + const [ [ firstRequest ] ] = castFetch.mock.calls + expect(firstRequest).toHaveProperty("method", "PATCH") + expect(firstRequest).toHaveProperty( + "url", + specializePath(CONSULTATION_LINK.bound, { id }) + ) + const firstRequestBody = await firstRequest.json() + expect(firstRequestBody).toHaveProperty("data.attributes.actionTaken", null) + expect(firstRequestBody).toHaveProperty("data.attributes.finishedAt", null) + expect(firstRequestBody).toHaveProperty("data.attributes.reason", "") + expect(firstRequestBody).toHaveProperty( + "data.attributes.scheduledStartAt", + scheduledStartAt.toJSON() + ) + expect(firstRequestBody).not.toHaveProperty("data.attributes.startedAt", null) + expect(firstRequestBody).toHaveProperty("data.id", "1") + expect(firstRequestBody).toHaveProperty("data.type", "consultation") + ConsultationTimerManager.clearAllListeners() }) - ConsultationTimerManager.nextInterval() - await nextTick() - - const consultationHeader = wrapper.find(".selected-consultation-header") - expect(consultationHeader.exists()).toBeTruthy() - expect(consultationHeader.html()).toContain("4m") - expect(consultationHeader.html()).toContain("59s") - ConsultationTimerManager.clearAllListeners() }) - it("should restart the timer", async() => { - jest.useFakeTimers() - const scheduledStartAt = new Date() - const consultant = { - "data": { - "id": "10", - "kind": "reachable_employee", - "type": "user" + describe("after", () => { + it("should automatically terminate the consultation", async() => { + const scheduledStartAt = new Date() + const consultant = { + "data": { + "id": "10", + "kind": "reachable_employee", + "type": "user" + } } - } - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - const id = "1" - const fakeConsultation = { - "actionTaken": null, - consultant, - "finishedAt": null, - id, - "reason": "", - scheduledStartAt, - "startedAt": null, - "type": "consultation" - } as DeserializedConsultationResource - const fakeChatMessage = { - "data": [] - } as DeserializedChatMessageListDocument - const wrapper = shallowMount(Component, { - "global": { - "provide": { - "pageContext": { - "pageProps": { - "userProfile": consultant + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + const id = "1" + const fakeConsultation = { + "actionTaken": null, + consultant, + "finishedAt": null, + id, + "reason": "", + scheduledStartAt, + "startedAt": null, + "type": "consultation" + } as DeserializedConsultationResource + const fakeChatMessage = { + "data": [] + } as DeserializedChatMessageListDocument + const wrapper = shallowMount(Component, { + "global": { + "provide": { + "pageContext": { + "pageProps": { + "userProfile": consultant + } } } + }, + "props": { + "chatMessages": fakeChatMessage, + "consultation": fakeConsultation } - }, - "props": { + }) + + const userController = wrapper.findComponent({ "name": "UserController" }) + await userController.trigger("start-consultation") + await flushPromises() + const firstUpdatedFakeConsultation = { + ...fakeConsultation, + "startedAt": new Date(Date.now() - convertTimeToMilliseconds("00:00:01")) + } as DeserializedConsultationResource + await wrapper.setProps({ "chatMessages": fakeChatMessage, - "consultation": fakeConsultation, - "isConsultationListShown": false - } + "consultation": firstUpdatedFakeConsultation + }) + ConsultationTimerManager.forceFinish(firstUpdatedFakeConsultation) + await flushPromises() + + const secondUpdatedFakeConsultation = { + ...firstUpdatedFakeConsultation, + "finishedAt": new Date() + } as DeserializedConsultationResource + await wrapper.setProps({ + "chatMessages": fakeChatMessage, + "consultation": secondUpdatedFakeConsultation + }) + await flushPromises() + + const events = wrapper.emitted("updatedConsultationAttributes") + expect(events).toHaveLength(2) + const castFetch = fetch as jest.Mock + const [ [ firstRequest ], [ secondRequest ] ] = castFetch.mock.calls + expect(firstRequest).toHaveProperty("method", "PATCH") + expect(firstRequest).toHaveProperty( + "url", + specializePath(CONSULTATION_LINK.bound, { id }) + ) + expect(firstRequest.headers.get("Content-Type")).toBe(JSON_API_MEDIA_TYPE) + expect(firstRequest.headers.get("Accept")).toBe(JSON_API_MEDIA_TYPE) + + expect(secondRequest).toHaveProperty("method", "PATCH") + expect(secondRequest).toHaveProperty( + "url", + specializePath(CONSULTATION_LINK.bound, { id }) + ) + expect(secondRequest.headers.get("Content-Type")).toBe(JSON_API_MEDIA_TYPE) + expect(secondRequest.headers.get("Accept")).toBe(JSON_API_MEDIA_TYPE) + + const body = await secondRequest.json() + expect(body).toHaveProperty("data.relationships.consultant.data.id") + expect(body).toHaveProperty("meta.doesAllowConflicts") + ConsultationTimerManager.clearAllListeners() }) - const userController = wrapper.findComponent({ "name": "UserController" }) - await userController.trigger("start-consultation") - await flushPromises() - const updatedFakeConsultation = { - ...fakeConsultation, - "startedAt": new Date(Date.now() - convertTimeToMilliseconds("00:00:01")) - } as DeserializedConsultationResource - await wrapper.setProps({ "consultation": updatedFakeConsultation }) - ConsultationTimerManager.restartTimerFor(updatedFakeConsultation) - await nextTick() - - const castedWrapper = wrapper.vm as any - expect(castedWrapper.remainingTime.minutes).toEqual(5) - expect(castedWrapper.remainingTime.seconds).toEqual(0) - ConsultationTimerManager.nextInterval() - expect(castedWrapper.remainingTime.minutes).toEqual(4) - expect(castedWrapper.remainingTime.seconds).toEqual(59) - - const consultationHeader = wrapper.find(".selected-consultation-header") - expect(consultationHeader.exists()).toBeTruthy() - expect(consultationHeader.html()).toContain("5m") - const events = wrapper.emitted("updatedConsultationAttributes") - expect(events).toHaveLength(1) - const castFetch = fetch as jest.Mock - const [ [ firstRequest ] ] = castFetch.mock.calls - expect(firstRequest).toHaveProperty("method", "PATCH") - expect(firstRequest).toHaveProperty("url", specializePath(CONSULTATION_LINK.bound, { id })) - const firstRequestBody = await firstRequest.json() - expect(firstRequestBody).toHaveProperty("data.attributes.actionTaken", null) - expect(firstRequestBody).toHaveProperty("data.attributes.finishedAt", null) - expect(firstRequestBody).toHaveProperty("data.attributes.reason", "") - expect(firstRequestBody).toHaveProperty( - "data.attributes.scheduledStartAt", - scheduledStartAt.toJSON() - ) - expect(firstRequestBody).not.toHaveProperty("data.attributes.startedAt", null) - expect(firstRequestBody).toHaveProperty("data.id", "1") - expect(firstRequestBody).toHaveProperty("data.type", "consultation") - ConsultationTimerManager.clearAllListeners() - }) - }) - - describe("after", () => { - it("should automatically terminate the consultation", async() => { - const scheduledStartAt = new Date() - const consultant = { - "data": { - "id": "10", - "kind": "reachable_employee", - "type": "user" + it("can be terminated by consultant with action taken", async() => { + const scheduledStartAt = new Date() + const consultant = { + "data": { + "id": "10", + "kind": "reachable_employee", + "type": "user" + } } - } - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - const id = "1" - const fakeConsultation = { - "actionTaken": null, - consultant, - "finishedAt": null, - id, - "reason": "", - scheduledStartAt, - "startedAt": null, - "type": "consultation" - } as DeserializedConsultationResource - const fakeChatMessage = { - "data": [] - } as DeserializedChatMessageListDocument - const wrapper = shallowMount(Component, { - "global": { - "provide": { - "pageContext": { - "pageProps": { - "userProfile": consultant + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) + const id = "1" + const fakeConsultation = { + "actionTaken": null, + consultant, + "finishedAt": null, + id, + "reason": "", + scheduledStartAt, + "startedAt": null, + "type": "consultation" + } as DeserializedConsultationResource + const fakeChatMessage = { + "data": [] + } as DeserializedChatMessageListDocument + const wrapper = shallowMount(Component, { + "global": { + "provide": { + "pageContext": { + "pageProps": { + "userProfile": consultant + } } + }, + "stubs": { + "Dropdown": false, + "Overlay": false } + }, + "props": { + "chatMessages": fakeChatMessage, + "consultation": fakeConsultation, + "isConsultationListShown": false } - }, - "props": { + }) + + const userController = wrapper.findComponent({ "name": "UserController" }) + await userController.trigger("start-consultation") + await flushPromises() + const firstUpdatedFakeConsultation = { + ...fakeConsultation, + "startedAt": new Date(Date.now() - convertTimeToMilliseconds("00:00:01")) + } as DeserializedConsultationResource + await wrapper.setProps({ "chatMessages": fakeChatMessage, - "consultation": fakeConsultation + "consultation": firstUpdatedFakeConsultation + }) + await flushPromises() + + const additionalControls = wrapper.find(".additional-controls") + const additionalControlsBtn = wrapper.find("#dropdown-btn") + await additionalControlsBtn.trigger("click") + const viewOverlayBtn = additionalControls.find(".view-action-taken-overlay-btn") + await viewOverlayBtn.trigger("click") + const actionTakenOverlay = wrapper.find(".action-taken") + const actionTakenField = actionTakenOverlay.findComponent(".action-taken-field") + await actionTakenField.setValue("action taken") + const secondUpdatedFakeConsultation = { + ...firstUpdatedFakeConsultation, + "actionTaken": actionTakenField.attributes("modelvalue") } + await wrapper.setProps({ + "chatMessages": fakeChatMessage, + "consultation": secondUpdatedFakeConsultation + }) + const finishBtn = actionTakenOverlay.find(".finish-btn") + await finishBtn.trigger("click") + await flushPromises() + + const events = wrapper.emitted("updatedConsultationAttributes") + expect(events).toHaveLength(2) + + const castFetch = fetch as jest.Mock + const [ [ firstRequest ], [ secondRequest ] ] = castFetch.mock.calls + expect(firstRequest).toHaveProperty("method", "PATCH") + expect(firstRequest).toHaveProperty( + "url", + specializePath(CONSULTATION_LINK.bound, { id }) + ) + expect(firstRequest.headers.get("Content-Type")).toBe(JSON_API_MEDIA_TYPE) + expect(firstRequest.headers.get("Accept")).toBe(JSON_API_MEDIA_TYPE) + + + expect(secondRequest).toHaveProperty("method", "PATCH") + expect(secondRequest).toHaveProperty( + "url", + specializePath(CONSULTATION_LINK.bound, { id }) + ) + expect(secondRequest.headers.get("Content-Type")).toBe(JSON_API_MEDIA_TYPE) + expect(secondRequest.headers.get("Accept")).toBe(JSON_API_MEDIA_TYPE) + const body = await secondRequest.json() + const { actionTaken } = body.data.attributes + expect(actionTaken).toEqual(actionTakenField.attributes("modelvalue")) + ConsultationTimerManager.clearAllListeners() }) - - const userController = wrapper.findComponent({ "name": "UserController" }) - await userController.trigger("start-consultation") - await flushPromises() - const firstUpdatedFakeConsultation = { - ...fakeConsultation, - "startedAt": new Date(Date.now() - convertTimeToMilliseconds("00:00:01")) - } as DeserializedConsultationResource - await wrapper.setProps({ - "chatMessages": fakeChatMessage, - "consultation": firstUpdatedFakeConsultation - }) - ConsultationTimerManager.forceFinish(firstUpdatedFakeConsultation) - await flushPromises() - - const secondUpdatedFakeConsultation = { - ...firstUpdatedFakeConsultation, - "finishedAt": new Date() - } as DeserializedConsultationResource - await wrapper.setProps({ - "chatMessages": fakeChatMessage, - "consultation": secondUpdatedFakeConsultation - }) - await flushPromises() - - const events = wrapper.emitted("updatedConsultationAttributes") - expect(events).toHaveLength(2) - const castFetch = fetch as jest.Mock - const [ [ firstRequest ], [ secondRequest ] ] = castFetch.mock.calls - expect(firstRequest).toHaveProperty("method", "PATCH") - expect(firstRequest).toHaveProperty( - "url", - specializePath(CONSULTATION_LINK.bound, { id }) - ) - expect(firstRequest.headers.get("Content-Type")).toBe(JSON_API_MEDIA_TYPE) - expect(firstRequest.headers.get("Accept")).toBe(JSON_API_MEDIA_TYPE) - - expect(secondRequest).toHaveProperty("method", "PATCH") - expect(secondRequest).toHaveProperty( - "url", - specializePath(CONSULTATION_LINK.bound, { id }) - ) - expect(secondRequest.headers.get("Content-Type")).toBe(JSON_API_MEDIA_TYPE) - expect(secondRequest.headers.get("Accept")).toBe(JSON_API_MEDIA_TYPE) - - const body = await secondRequest.json() - expect(body).toHaveProperty("data.relationships.consultant.data.id") - expect(body).toHaveProperty("meta.doesAllowConflicts") - ConsultationTimerManager.clearAllListeners() }) + }) - it("can be terminated by consultant with action taken", async() => { + describe.only("chat messages", () => { + it("is sorted properly", () => { const scheduledStartAt = new Date() const consultant = { "data": { @@ -525,11 +644,15 @@ describe("Component: consultation/chat_window", () => { } } fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) - fetchMock.mockResponseOnce("", { "status": RequestEnvironment.status.NO_CONTENT }) const id = "1" const fakeConsultation = { "actionTaken": null, - consultant, + "consultant": { + "data": { + "id": "1", + "type": "user" + } + }, "finishedAt": null, id, "reason": "", @@ -538,8 +661,23 @@ describe("Component: consultation/chat_window", () => { "type": "consultation" } as DeserializedConsultationResource const fakeChatMessage = { - "data": [] - } as DeserializedChatMessageListDocument + "data": [ + { + "createdAt": new Date("2022-10-4 17:35"), + "data": { + "value": "sample text" + }, + "id": "0" + }, + { + "createdAt": new Date("2022-10-4 05:35"), + "data": { + "value": "sample text" + }, + "id": "0" + } + ] + } const wrapper = shallowMount(Component, { "global": { "provide": { @@ -548,10 +686,6 @@ describe("Component: consultation/chat_window", () => { "userProfile": consultant } } - }, - "stubs": { - "Dropdown": false, - "Overlay": false } }, "props": { @@ -560,65 +694,12 @@ describe("Component: consultation/chat_window", () => { "isConsultationListShown": false } }) + const castedWrapper = wrapper.vm as any + const { sortedMessagesByTime } = castedWrapper - const userController = wrapper.findComponent({ "name": "UserController" }) - await userController.trigger("start-consultation") - await flushPromises() - const firstUpdatedFakeConsultation = { - ...fakeConsultation, - "startedAt": new Date(Date.now() - convertTimeToMilliseconds("00:00:01")) - } as DeserializedConsultationResource - await wrapper.setProps({ - "chatMessages": fakeChatMessage, - "consultation": firstUpdatedFakeConsultation - }) - await flushPromises() - - const additionalControls = wrapper.find(".additional-controls") - const additionalControlsBtn = wrapper.find("#dropdown-btn") - await additionalControlsBtn.trigger("click") - const viewOverlayBtn = additionalControls.find(".view-action-taken-overlay-btn") - await viewOverlayBtn.trigger("click") - const actionTakenOverlay = wrapper.find(".action-taken") - const actionTakenField = actionTakenOverlay.findComponent(".action-taken-field") - await actionTakenField.setValue("action taken") - const secondUpdatedFakeConsultation = { - ...firstUpdatedFakeConsultation, - "actionTaken": actionTakenField.attributes("modelvalue") - } - await wrapper.setProps({ - "chatMessages": fakeChatMessage, - "consultation": secondUpdatedFakeConsultation - }) - const finishBtn = actionTakenOverlay.find(".finish-btn") - await finishBtn.trigger("click") - await flushPromises() - - const events = wrapper.emitted("updatedConsultationAttributes") - expect(events).toHaveLength(2) - - const castFetch = fetch as jest.Mock - const [ [ firstRequest ], [ secondRequest ] ] = castFetch.mock.calls - expect(firstRequest).toHaveProperty("method", "PATCH") - expect(firstRequest).toHaveProperty( - "url", - specializePath(CONSULTATION_LINK.bound, { id }) - ) - expect(firstRequest.headers.get("Content-Type")).toBe(JSON_API_MEDIA_TYPE) - expect(firstRequest.headers.get("Accept")).toBe(JSON_API_MEDIA_TYPE) - - - expect(secondRequest).toHaveProperty("method", "PATCH") - expect(secondRequest).toHaveProperty( - "url", - specializePath(CONSULTATION_LINK.bound, { id }) - ) - expect(secondRequest.headers.get("Content-Type")).toBe(JSON_API_MEDIA_TYPE) - expect(secondRequest.headers.get("Accept")).toBe(JSON_API_MEDIA_TYPE) - const body = await secondRequest.json() - const { actionTaken } = body.data.attributes - expect(actionTaken).toEqual(actionTakenField.attributes("modelvalue")) - ConsultationTimerManager.clearAllListeners() + const [ expectedLastMessage, expectedFirstMessage ] = fakeChatMessage.data + expect(sortedMessagesByTime[0]).toEqual(expectedFirstMessage) + expect(sortedMessagesByTime[sortedMessagesByTime.length - 1]).toEqual(expectedLastMessage) }) }) }) diff --git a/components/consultation/chat_window.vue b/components/consultation/chat_window.vue index ab42f6ff5..b2d8ce0dd 100644 --- a/components/consultation/chat_window.vue +++ b/components/consultation/chat_window.vue @@ -6,7 +6,6 @@ @click="toggleConsultationList"> {{ `chevron_${isConsultationListShown ? "left" : "right"}` }} -