From 3965fd94ed44cead5460a85c4a616ec8ee951b61 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 7 Sep 2023 16:25:24 +0100 Subject: [PATCH] more unit tests --- .../app/components/mixins/baseSensitivity.ts | 19 ++- .../excel/wodinSensitivitySummaryDownload.ts | 2 +- .../components/mixins/baseSensitivity.test.ts | 101 ++++++++++++- .../multiSensitivityTab.test.ts | 10 +- .../sensitivity/sensitivityTab.test.ts | 25 ++-- .../wodinSensitivitySummaryDownload.test.ts | 138 +++++++++++++++++- .../unit/store/sensitivity/actions.test.ts | 52 +++---- 7 files changed, 296 insertions(+), 51 deletions(-) diff --git a/app/static/src/app/components/mixins/baseSensitivity.ts b/app/static/src/app/components/mixins/baseSensitivity.ts index e40529683..30b9b6a1e 100644 --- a/app/static/src/app/components/mixins/baseSensitivity.ts +++ b/app/static/src/app/components/mixins/baseSensitivity.ts @@ -1,5 +1,5 @@ -import { Store } from "vuex"; -import { computed } from "vue"; +import {Store} from "vuex"; +import {computed, ComputedRef, WritableComputedRef} from "vue"; import { AppState } from "../../store/appState/state"; import { ModelGetter } from "../../store/model/getters"; import userMessages from "../../userMessages"; @@ -7,9 +7,18 @@ import { anyTrue } from "../../utils"; import {sensitivityUpdateRequiredExplanation, verifyValidPlotSettingsTime} from "../sensitivity/support"; import { Dict } from "../../types/utilTypes"; import {BaseSensitivityMutation} from "../../store/sensitivity/mutations"; -import {BaseSensitivityAction, SensitivityAction} from "../../store/sensitivity/actions"; +import {BaseSensitivityAction} from "../../store/sensitivity/actions"; -export default (store: Store, multiSensitivity: boolean) => { +export interface BaseSensitivityMixin { + sensitivityPrerequisitesReady: ComputedRef, + updateMsg: ComputedRef, + downloading: ComputedRef, + canDownloadSummary: ComputedRef, + downloadSummaryUserFileName: WritableComputedRef, + downloadSummary: (payload: { fileName: string }) => void +} + +export default (store: Store, multiSensitivity: boolean): BaseSensitivityMixin => { const namespace = multiSensitivity ? "multiSensitivity" : "sensitivity"; const hasRunner = computed(() => store.getters[`model/${ModelGetter.hasRunner}`]); @@ -42,7 +51,7 @@ export default (store: Store, multiSensitivity: boolean) => { const downloading = computed(() => sensModule.downloading); // only allow download if update not required, and if we have run sensitivity - const canDownloadSummary = computed(() => !updateMsg.value && sensModule.result?.batch); + const canDownloadSummary = computed(() => !updateMsg.value && !!sensModule.result?.batch); const downloadSummaryUserFileName = computed({ get: () => sensModule.userSummaryDownloadFileName, diff --git a/app/static/src/app/excel/wodinSensitivitySummaryDownload.ts b/app/static/src/app/excel/wodinSensitivitySummaryDownload.ts index b947ce769..5e6662a31 100644 --- a/app/static/src/app/excel/wodinSensitivitySummaryDownload.ts +++ b/app/static/src/app/excel/wodinSensitivitySummaryDownload.ts @@ -1,6 +1,6 @@ import * as XLSX from "xlsx"; import { WodinExcelDownload } from "./wodinExcelDownload"; -import {BaseSensitivityState, SensitivityPlotExtreme, SensitivityPlotExtremePrefix} from "../store/sensitivity/state"; +import { SensitivityPlotExtreme, SensitivityPlotExtremePrefix} from "../store/sensitivity/state"; import { OdinUserType, OdinUserTypeSeriesSet } from "../types/responseTypes"; import {OdinSensitivityResult} from "../types/wrapperTypes"; diff --git a/app/static/tests/unit/components/mixins/baseSensitivity.test.ts b/app/static/tests/unit/components/mixins/baseSensitivity.test.ts index 1a3e175af..08b0851a4 100644 --- a/app/static/tests/unit/components/mixins/baseSensitivity.test.ts +++ b/app/static/tests/unit/components/mixins/baseSensitivity.test.ts @@ -1,11 +1,21 @@ import Vuex from "vuex"; import { ModelState } from "../../../../src/app/store/model/state"; -import baseSensitivity from "../../../../src/app/components/mixins/baseSensitivity"; +import baseSensitivity, {BaseSensitivityMixin} from "../../../../src/app/components/mixins/baseSensitivity"; import { BaseSensitivityState } from "../../../../src/app/store/sensitivity/state"; import { noSensitivityUpdateRequired } from "../../../../src/app/store/sensitivity/sensitivity"; import { AppState } from "../../../../src/app/store/appState/state"; +import {BaseSensitivityMutation} from "../../../../src/app/store/sensitivity/mutations"; +import {nextTick} from "vue"; +import {BaseSensitivityAction} from "../../../../src/app/store/sensitivity/actions"; +import mock = jest.mock; describe("baseSensitivity mixin", () => { + const mockSensSetUserSummaryDownloadFileName = jest.fn(); + const mockMultiSensSetUserSummaryDownloadFileName = jest.fn(); + + const mockSensDownloadSummary = jest.fn(); + const mockMultiSensDownloadSummary = jest.fn(); + const getStore = (hasRunner = true, modelState: Partial = {}, sensitivityState: Partial = {}, multiSensitivityState: Partial = {}) => { @@ -34,6 +44,13 @@ describe("baseSensitivity mixin", () => { } }, ...multiSensitivityState + }, + mutations: { + [BaseSensitivityMutation.SetUserSummaryDownloadFileName]: + mockMultiSensSetUserSummaryDownloadFileName + }, + actions: { + [BaseSensitivityAction.DownloadSummary]: mockSensDownloadSummary } }, sensitivity: { @@ -46,12 +63,24 @@ describe("baseSensitivity mixin", () => { } }, ...sensitivityState + }, + mutations: { + [BaseSensitivityMutation.SetUserSummaryDownloadFileName]: + mockSensSetUserSummaryDownloadFileName + }, + actions: { + [BaseSensitivityAction.DownloadSummary]: mockSensDownloadSummary } } } as any }); }; + afterEach(() => { + jest.clearAllMocks(); + }); + + it("sensitivityPrerequisitesReady returns true when all prerequisites have been met", () => { const store = getStore(); const sut = baseSensitivity(store, false); @@ -118,4 +147,74 @@ describe("baseSensitivity mixin", () => { "Plot is out of date: model code has been recompiled. Run Sensitivity to update.", "Status is out of date: model code has been recompiled. Run Multi-sensitivity to update."); }); + + const testForSensAndMultiSens = (state: Partial, test: (mixin: BaseSensitivityMixin) => void) => { + let store = getStore(true, {}, state, {}); + test(baseSensitivity(store, false)); + store = getStore(true, {}, {}, state); + test(baseSensitivity(store, true)); + }; + + it("returns downloading", () => { + testForSensAndMultiSens({downloading: true}, (mixin) => { + expect(mixin.downloading.value).toBe(true); + }); + testForSensAndMultiSens({}, (mixin) => { + expect(mixin.downloading.value).toBe(false); + }); + }); + + it("can download summary when no update required and batch exists", () => { + testForSensAndMultiSens({}, (mixin) => { + expect(mixin.canDownloadSummary.value).toBe(true); + }); + }); + + it("cannot download summary when update required", () => { + const state = { + sensitivityUpdateRequired: { + ...noSensitivityUpdateRequired(), + modelChanged: true + } + }; + testForSensAndMultiSens(state, (mixin) => { + expect(mixin.canDownloadSummary.value).toBe(false); + }); + }); + + it("cannot download summary when no batch exists", () => { + const state = { + result: {} + } as any; + testForSensAndMultiSens(state, (mixin) => { + expect(mixin.canDownloadSummary.value).toBe(false); + }); + }); + + it("returns download summary user file name", () => { + const state = { userSummaryDownloadFileName: "test.xlsx" }; + testForSensAndMultiSens(state, (mixin) => { + expect(mixin.downloadSummaryUserFileName.value).toBe("test.xlsx"); + }); + }); + + it("writes download summary user file name", () => { + const sensSut = baseSensitivity(getStore(), false); + sensSut.downloadSummaryUserFileName.value = "new.xlsx"; + expect(mockSensSetUserSummaryDownloadFileName.mock.calls[0][1]).toBe("new.xlsx"); + + const multiSensSut = baseSensitivity(getStore(), true); + multiSensSut.downloadSummaryUserFileName.value = "newMulti.xlsx"; + expect(mockMultiSensSetUserSummaryDownloadFileName.mock.calls[0][1]).toBe("newMulti.xlsx"); + }); + + it("downloadSummary dispatches action", () => { + const sensSut = baseSensitivity(getStore(), false); + sensSut.downloadSummary({ fileName: "test.xlsx" }); + expect(mockSensDownloadSummary.mock.calls[0][1]).toBe("test.xlsx"); + + const multiSensSut = baseSensitivity(getStore(), true); + multiSensSut.downloadSummary({ fileName: "testMulti.xlsx" }); + expect(mockMultiSensDownloadSummary.mock.calls[0][1]).toBe("testMulti.xlsx"); + }); }); diff --git a/app/static/tests/unit/components/multiSensitivity/multiSensitivityTab.test.ts b/app/static/tests/unit/components/multiSensitivity/multiSensitivityTab.test.ts index 93be4ff48..6a1641d74 100644 --- a/app/static/tests/unit/components/multiSensitivity/multiSensitivityTab.test.ts +++ b/app/static/tests/unit/components/multiSensitivity/multiSensitivityTab.test.ts @@ -1,13 +1,14 @@ import { shallowMount } from "@vue/test-utils"; import Vuex from "vuex"; import MultiSensitivityTab from "../../../../src/app/components/multiSensitivity/MultiSensitivityTab.vue"; -import { AppState } from "../../../../src/app/store/appState/state"; +import {AppState, AppType} from "../../../../src/app/store/appState/state"; import LoadingButton from "../../../../src/app/components/LoadingButton.vue"; import { ModelState } from "../../../../src/app/store/model/state"; import { MultiSensitivityAction } from "../../../../src/app/store/multiSensitivity/actions"; import ActionRequiredMessage from "../../../../src/app/components/ActionRequiredMessage.vue"; import { MultiSensitivityState } from "../../../../src/app/store/multiSensitivity/state"; import ErrorInfo from "../../../../src/app/components/ErrorInfo.vue"; +import SensitivitySummaryDownload from "../../../../src/app/components/sensitivity/SensitivitySummaryDownload.vue"; describe("MultiSensitivityTab", () => { const mockRunMultiSensitivity = jest.fn(); @@ -141,4 +142,11 @@ describe("MultiSensitivityTab", () => { }); expect(wrapper.findComponent(ErrorInfo).props("error")).toStrictEqual(error); }); + + it("renders SensitivitySummaryDownload", () => { + const wrapper = getWrapper(); + const download = wrapper.findComponent(SensitivitySummaryDownload); + expect(download.props("multiSensitivity")).toBe(true); + expect(download.props("downloadType")).toBe("Multi-sensitivity Summary"); + }); }); diff --git a/app/static/tests/unit/components/sensitivity/sensitivityTab.test.ts b/app/static/tests/unit/components/sensitivity/sensitivityTab.test.ts index f1ad58416..b56c8a383 100644 --- a/app/static/tests/unit/components/sensitivity/sensitivityTab.test.ts +++ b/app/static/tests/unit/components/sensitivity/sensitivityTab.test.ts @@ -1,19 +1,19 @@ -import {mount, shallowMount} from "@vue/test-utils"; +import {shallowMount} from "@vue/test-utils"; import Vuex from "vuex"; -import { nextTick } from "vue"; -import { ModelState } from "../../../../src/app/store/model/state"; +import {ModelState} from "../../../../src/app/store/model/state"; import SensitivityTab from "../../../../src/app/components/sensitivity/SensitivityTab.vue"; import ActionRequiredMessage from "../../../../src/app/components/ActionRequiredMessage.vue"; -import { BaseSensitivityGetter, SensitivityGetter } from "../../../../src/app/store/sensitivity/getters"; +import {BaseSensitivityGetter} from "../../../../src/app/store/sensitivity/getters"; import SensitivityTracesPlot from "../../../../src/app/components/sensitivity/SensitivityTracesPlot.vue"; -import { SensitivityPlotType, SensitivityState } from "../../../../src/app/store/sensitivity/state"; -import { SensitivityAction } from "../../../../src/app/store/sensitivity/actions"; +import {SensitivityPlotType, SensitivityState} from "../../../../src/app/store/sensitivity/state"; +import {SensitivityAction} from "../../../../src/app/store/sensitivity/actions"; import SensitivitySummaryPlot from "../../../../src/app/components/sensitivity/SensitivitySummaryPlot.vue"; import ErrorInfo from "../../../../src/app/components/ErrorInfo.vue"; -import { AppState, AppType } from "../../../../src/app/store/appState/state"; -import { ModelGetter } from "../../../../src/app/store/model/getters"; +import {AppState, AppType} from "../../../../src/app/store/appState/state"; +import {ModelGetter} from "../../../../src/app/store/model/getters"; import LoadingSpinner from "../../../../src/app/components/LoadingSpinner.vue"; -import { SensitivityMutation } from "../../../../src/app/store/sensitivity/mutations"; +import {SensitivityMutation} from "../../../../src/app/store/sensitivity/mutations"; +import SensitivitySummaryDownload from "../../../../src/app/components/sensitivity/SensitivitySummaryDownload.vue"; jest.mock("plotly.js-basic-dist-min", () => {}); @@ -145,6 +145,13 @@ describe("SensitivityTab", () => { expect(wrapper.findComponent(SensitivityTracesPlot).exists()).toBe(false); }); + it("renders SensitivitySummaryDownload", () => { + const wrapper = getWrapper(AppType.Basic); + const download = wrapper.findComponent(SensitivitySummaryDownload); + expect(download.props("multiSensitivity")).toBe(false); + expect(download.props("downloadType")).toBe("Sensitivity Summary"); + }); + it("renders error", () => { const testError = { error: "Test Error", detail: "test error detail" }; const sensitivityState = { diff --git a/app/static/tests/unit/excel/wodinSensitivitySummaryDownload.test.ts b/app/static/tests/unit/excel/wodinSensitivitySummaryDownload.test.ts index b8e779e93..11c09f0fc 100644 --- a/app/static/tests/unit/excel/wodinSensitivitySummaryDownload.test.ts +++ b/app/static/tests/unit/excel/wodinSensitivitySummaryDownload.test.ts @@ -8,6 +8,7 @@ const xValues = [ { beta: 1.2 } ]; const mockBatch = { + successfulVaryingParams: xValues, valueAtTime: jest.fn().mockImplementation(() => { return { x: xValues, @@ -27,23 +28,49 @@ const mockBatch = { }) }; +const xValuesMulti = [ + { beta: 1, N: 100 }, + { beta: 1, N: 1000 }, + { beta: 2, N: 100 }, + { beta: 2, N: 1000 } +]; + +const mockBatchMulti = { + successfulVaryingParams: xValuesMulti, + valueAtTime: jest.fn().mockImplementation(() => { + return { + x: xValuesMulti, + values: [ + { name: "S", y: [10, 20, 30, 40] }, + { name: "I", y: [50, 60, 70, 80] } + ] + }; + }), + extreme: jest.fn().mockImplementation((extremeType: string) => { + return { + x: xValuesMulti, + values: [ + { name: extremeType, y: [11, 22, 33, 44] } + ] + }; + }) +}; + describe("WodinSensitivitySummaryDownload", () => { beforeEach(() => { jest.clearAllMocks(); }); - it("downloads expected workbook", () => { + it("downloads expected workbook for single varying parameter", () => { + const result = { + batch: mockBatch + } as any; const rootState = mockBasicState({ sensitivity: mockSensitivityState({ - paramSettings: { - parameterToVary: "beta" - }, plotSettings: { time: 50 }, - result: { - batch: mockBatch - } + result } as any), run: mockRunState({ parameterValues: { @@ -56,7 +83,7 @@ describe("WodinSensitivitySummaryDownload", () => { const commit = jest.fn(); const context = { rootState, commit } as any; const sut = new WodinSensitivitySummaryDownload(context, "test.xlsx"); - sut.download(); + sut.download(result); expect(mockBookNew).toHaveBeenCalledTimes(1); expect(mockBatch.valueAtTime).toHaveBeenCalledWith(50); @@ -122,4 +149,99 @@ describe("WodinSensitivitySummaryDownload", () => { expect(mockWriteFile.mock.calls[0][1]).toBe("test.xlsx"); }); + + it("downloads expected workbook for multiple varying parameter", () => { + const result = { + batch: mockBatchMulti + } as any; + const rootState = mockBasicState({ + sensitivity: mockSensitivityState({ + plotSettings: { + time: 50 + } + } as any), + multiSensitivity: { + result + } as any, + run: mockRunState({ + parameterValues: { + beta: 1.1, + N: 1000, + D: 3 + } + }) + }); + const commit = jest.fn(); + const context = { rootState, commit } as any; + const sut = new WodinSensitivitySummaryDownload(context, "test.xlsx"); + sut.download(result); + + expect(mockBookNew).toHaveBeenCalledTimes(1); + expect(mockBatchMulti.valueAtTime).toHaveBeenCalledWith(50); + + expect(mockBookAppendSheet.mock.calls[0][1]).toStrictEqual({ + data: [ + { beta: 1, N: 100, S: 10, I: 50 }, + { beta: 1, N: 1000, S: 20, I: 60 }, + { beta: 2, N: 100, S: 30, I: 70 }, + { beta: 2, N: 1000, S: 40, I: 80 } + ], + type: "json" + }); + expect(mockBookAppendSheet.mock.calls[0][2]).toStrictEqual("ValueAtTime50"); + + expect(mockBookAppendSheet.mock.calls[1][1]).toStrictEqual({ + data: [ + { beta: 1, N: 100, yMin: 11 }, + { beta: 1, N: 1000, yMin: 22 }, + { beta: 2, N: 100, yMin: 33 }, + { beta: 2, N: 1000, yMin: 44 } + ], + type: "json" + }); + expect(mockBookAppendSheet.mock.calls[1][2]).toStrictEqual("ValueAtMin"); + + expect(mockBookAppendSheet.mock.calls[2][1]).toStrictEqual({ + data: [ + { beta: 1, N: 100, yMax: 11 }, + { beta: 1, N: 1000, yMax: 22 }, + { beta: 2, N: 100, yMax: 33 }, + { beta: 2, N: 1000, yMax: 44 } + ], + type: "json" + }); + expect(mockBookAppendSheet.mock.calls[2][2]).toStrictEqual("ValueAtMax"); + + expect(mockBookAppendSheet.mock.calls[3][1]).toStrictEqual({ + data: [ + { beta: 1, N: 100, tMin: 11 }, + { beta: 1, N: 1000, tMin: 22 }, + { beta: 2, N: 100, tMin: 33 }, + { beta: 2, N: 1000, tMin: 44 } + ], + type: "json" + }); + expect(mockBookAppendSheet.mock.calls[3][2]).toStrictEqual("TimeAtMin"); + + expect(mockBookAppendSheet.mock.calls[4][1]).toStrictEqual({ + data: [ + { beta: 1, N: 100, tMax: 11 }, + { beta: 1, N: 1000, tMax: 22 }, + { beta: 2, N: 100, tMax: 33 }, + { beta: 2, N: 1000, tMax: 44 } + ], + type: "json" + }); + expect(mockBookAppendSheet.mock.calls[4][2]).toStrictEqual("TimeAtMax"); + + expect(mockBookAppendSheet.mock.calls[5][1]).toStrictEqual({ + data: [ + { name: "D", value: 3 } + ], + type: "json" + }); + expect(mockBookAppendSheet.mock.calls[5][2]).toStrictEqual("Parameters"); + + expect(mockWriteFile.mock.calls[0][1]).toBe("test.xlsx"); + }); }); diff --git a/app/static/tests/unit/store/sensitivity/actions.test.ts b/app/static/tests/unit/store/sensitivity/actions.test.ts index b7784ae53..fea489a6f 100644 --- a/app/static/tests/unit/store/sensitivity/actions.test.ts +++ b/app/static/tests/unit/store/sensitivity/actions.test.ts @@ -129,6 +129,32 @@ describe("BaseSensitivity actions", () => { done(); }); }); + + it("downloads sensitivity summary", (done) => { + const mockDownload = jest.fn(); + const mockWodinSensitivitySummaryDownload = WodinSensitivitySummaryDownload as any as Mock; + mockWodinSensitivitySummaryDownload.mockImplementation(() => ({ download: mockDownload })); + + const commit = jest.fn(); + const context = { commit }; + const payload = "myFile.xlsx"; + (actions[BaseSensitivityAction.DownloadSummary] as any)(context, payload); + expect(commit).toHaveBeenCalledTimes(1); + expect(commit.mock.calls[0][0]).toBe(BaseSensitivityMutation.SetDownloading); + expect(commit.mock.calls[0][1]).toBe(true); + setTimeout(() => { + expect(mockWodinSensitivitySummaryDownload).toHaveBeenCalledTimes(1); // expect download constructor + expect(mockWodinSensitivitySummaryDownload.mock.calls[0][0]).toBe(context); + expect(mockWodinSensitivitySummaryDownload.mock.calls[0][1]).toBe("myFile.xlsx"); + expect(mockDownload).toHaveBeenCalledTimes(1); + + expect(commit).toHaveBeenCalledTimes(2); + expect(commit.mock.calls[1][0]).toBe(BaseSensitivityMutation.SetDownloading); + expect(commit.mock.calls[1][1]).toBe(false); + + done(); + }, 20); + }); }); export const testCommonRunSensitivity = (runSensitivityAction: Action) => { @@ -527,30 +553,4 @@ describe("Sensitivity actions", () => { expect(dispatch).not.toHaveBeenCalled(); }); - - it("downloads sensitivity summary", (done) => { - const mockDownload = jest.fn(); - const mockWodinSensitivitySummaryDownload = WodinSensitivitySummaryDownload as any as Mock; - mockWodinSensitivitySummaryDownload.mockImplementation(() => ({ download: mockDownload })); - - const commit = jest.fn(); - const context = { commit }; - const payload = "myFile.xlsx"; - (actions[SensitivityAction.DownloadSummary] as any)(context, payload); - expect(commit).toHaveBeenCalledTimes(1); - expect(commit.mock.calls[0][0]).toBe(SensitivityMutation.SetDownloading); - expect(commit.mock.calls[0][1]).toBe(true); - setTimeout(() => { - expect(mockWodinSensitivitySummaryDownload).toHaveBeenCalledTimes(1); // expect download constructor - expect(mockWodinSensitivitySummaryDownload.mock.calls[0][0]).toBe(context); - expect(mockWodinSensitivitySummaryDownload.mock.calls[0][1]).toBe("myFile.xlsx"); - expect(mockDownload).toHaveBeenCalledTimes(1); - - expect(commit).toHaveBeenCalledTimes(2); - expect(commit.mock.calls[1][0]).toBe(SensitivityMutation.SetDownloading); - expect(commit.mock.calls[1][1]).toBe(false); - - done(); - }, 20); - }); });