From b3e17263daba4416aeefce84177400fa8966b7db Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Wed, 26 Jun 2024 18:35:33 +0100 Subject: [PATCH 01/80] migrate changes from POC --- app/static/package-lock.json | 175 ++++++++++++------ app/static/package.json | 2 + .../src/app/components/code/CodeTab.vue | 36 +++- .../app/components/code/HiddenVariables.vue | 108 +++++++++++ .../app/components/code/SelectedVariables.vue | 107 +++++++---- app/static/src/app/components/run/RunPlot.vue | 10 +- .../app/components/run/RunStochasticPlot.vue | 10 +- app/static/src/app/components/run/RunTab.vue | 18 +- .../src/app/excel/wodinModelOutputDownload.ts | 1 - app/static/src/app/store/graphs/actions.ts | 8 +- app/static/src/app/store/graphs/getters.ts | 15 +- app/static/src/app/store/graphs/mutations.ts | 9 +- app/static/src/scss/style.scss | 15 ++ 13 files changed, 399 insertions(+), 115 deletions(-) create mode 100644 app/static/src/app/components/code/HiddenVariables.vue diff --git a/app/static/package-lock.json b/app/static/package-lock.json index 97ca57d6..54aeea1a 100644 --- a/app/static/package-lock.json +++ b/app/static/package-lock.json @@ -13,6 +13,7 @@ "@types/d3-format": "^3.0.1", "axios": "^0.26.0", "bootstrap": "^5.1.3", + "color": "^4.2.3", "csv-parse": "^5.2.1", "d3-format": "^3.1.0", "i18next": "^22.4.11", @@ -32,6 +33,7 @@ "devDependencies": { "@playwright/test": "1.37.0", "@types/bootstrap": "^5.2.6", + "@types/color": "^3.0.6", "@types/jest": "^27.4.0", "@types/markdown-it": "^12.2.3", "@types/plotly.js-basic-dist-min": "^2.12.1", @@ -3388,6 +3390,30 @@ "@popperjs/core": "^2.9.2" } }, + "node_modules/@types/color": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.6.tgz", + "integrity": "sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==", + "dev": true, + "dependencies": { + "@types/color-convert": "*" + } + }, + "node_modules/@types/color-convert": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.3.tgz", + "integrity": "sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==", + "dev": true, + "dependencies": { + "@types/color-name": "*" + } + }, + "node_modules/@types/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-hulKeREDdLFesGQjl96+4aoJSHY5b2GRjagzzcqCfIrWhe5vkCqIvrLbqzBaI1q94Vg8DNJZZqTR5ocdWmWclg==", + "dev": true + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -10102,20 +10128,21 @@ } }, "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dev": true, + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" } }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -10126,34 +10153,17 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-string": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", - "dev": true, "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, - "node_modules/color/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -21904,6 +21914,31 @@ "node": ">=6.9.0" } }, + "node_modules/postcss-colormin/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/postcss-colormin/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/postcss-colormin/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, "node_modules/postcss-colormin/node_modules/picocolors": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", @@ -25110,7 +25145,6 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dev": true, "dependencies": { "is-arrayish": "^0.3.1" } @@ -25118,8 +25152,7 @@ "node_modules/simple-swizzle/node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, "node_modules/sisteransi": { "version": "1.0.5", @@ -32080,6 +32113,30 @@ "@popperjs/core": "^2.9.2" } }, + "@types/color": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.6.tgz", + "integrity": "sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==", + "dev": true, + "requires": { + "@types/color-convert": "*" + } + }, + "@types/color-convert": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.3.tgz", + "integrity": "sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==", + "dev": true, + "requires": { + "@types/color-name": "*" + } + }, + "@types/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-hulKeREDdLFesGQjl96+4aoJSHY5b2GRjagzzcqCfIrWhe5vkCqIvrLbqzBaI1q94Vg8DNJZZqTR5ocdWmWclg==", + "dev": true + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -37398,37 +37455,18 @@ } }, "color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dev": true, + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "requires": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - }, - "dependencies": { - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - } + "color-convert": "^2.0.1", + "color-string": "^1.9.0" } }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -37436,14 +37474,12 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "color-string": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", - "dev": true, "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -46863,6 +46899,31 @@ "postcss-value-parser": "^3.0.0" }, "dependencies": { + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dev": true, + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, "picocolors": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", @@ -49425,7 +49486,6 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dev": true, "requires": { "is-arrayish": "^0.3.1" }, @@ -49433,8 +49493,7 @@ "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" } } }, diff --git a/app/static/package.json b/app/static/package.json index b68b3811..b3b57a9e 100644 --- a/app/static/package.json +++ b/app/static/package.json @@ -31,6 +31,7 @@ "@types/d3-format": "^3.0.1", "axios": "^0.26.0", "bootstrap": "^5.1.3", + "color": "^4.2.3", "csv-parse": "^5.2.1", "d3-format": "^3.1.0", "i18next": "^22.4.11", @@ -50,6 +51,7 @@ "devDependencies": { "@playwright/test": "1.37.0", "@types/bootstrap": "^5.2.6", + "@types/color": "^3.0.6", "@types/jest": "^27.4.0", "@types/markdown-it": "^12.2.3", "@types/plotly.js-basic-dist-min": "^2.12.1", diff --git a/app/static/src/app/components/code/CodeTab.vue b/app/static/src/app/components/code/CodeTab.vue index f72d3260..ef0d7812 100644 --- a/app/static/src/app/components/code/CodeTab.vue +++ b/app/static/src/app/components/code/CodeTab.vue @@ -16,14 +16,30 @@
- +
+ Drag variables to move to another graph, or to hide variable. Press the Ctrl key on drag to make a + copy of a variable. +
+ + +
+ + diff --git a/app/static/src/app/components/code/SelectedVariables.vue b/app/static/src/app/components/code/SelectedVariables.vue index d1eb6d62..8b581be9 100644 --- a/app/static/src/app/components/code/SelectedVariables.vue +++ b/app/static/src/app/components/code/SelectedVariables.vue @@ -1,18 +1,22 @@ @@ -21,12 +25,24 @@ import { computed, defineComponent } from "vue"; import { useStore } from "vuex"; import { GraphsAction } from "../../store/graphs/actions"; +// TODO: rename this component? export default defineComponent({ name: "SelectedVariables", - setup() { + props: { + dragging: { + type: Boolean, + required: true + }, + graphIndex: { + type: Number, + required: true + } + }, + setup(props, { emit }) { const store = useStore(); - const allVariables = computed(() => store.state.model.odinModelResponse?.metadata?.variables || []); - const selectedVariables = computed(() => store.state.graphs.config[0].selectedVariables); + const selectedVariables = computed( + () => store.state.graphs.config[props.graphIndex].selectedVariables + ); const palette = computed(() => store.state.model.paletteModel!); const getStyle = (variable: string) => { @@ -37,37 +53,58 @@ export default defineComponent({ return { "background-color": bgcolor }; }; - const updateSelectedVariables = (newVariables: string[]) => { + const updateSelectedVariables = (index: number, newVariables: string[]) => { store.dispatch(`graphs/${GraphsAction.UpdateSelectedVariables}`, { - index: 0, + index, selectedVariables: newVariables }); }; - const toggleVariable = (variable: string) => { - let newVars: string[]; - if (selectedVariables.value.includes(variable)) { - newVars = selectedVariables.value.filter((v) => v !== variable); - } else { - newVars = [...selectedVariables.value, variable]; - } - updateSelectedVariables(newVars); + const startDrag = (evt: DragEvent, variable: string) => { + const { dataTransfer } = evt; + dataTransfer!!.dropEffect = "move"; + dataTransfer!!.effectAllowed = "move"; + dataTransfer!!.setData("variable", variable); + dataTransfer!!.setData("srcGraph", props.graphIndex.toString()); + emit("setDragging", true); }; - const selectAll = () => { - updateSelectedVariables([...allVariables.value]); + // TODO: share the drag stuff with HiddenVariables + const endDrag = () => { + emit("setDragging", false); }; - const selectNone = () => { - updateSelectedVariables([]); + // Remove variable from the graph it was dragged from + const removeVariable = (srcGraphIdx: number, variable: string) => { + const srcVariables = [...store.state.graphs.config[srcGraphIdx].selectedVariables].filter( + (v) => v !== variable + ); + updateSelectedVariables(srcGraphIdx, srcVariables); + }; + + const onDrop = (evt: DragEvent) => { + const { dataTransfer } = evt; + const variable = dataTransfer!!.getData("variable"); + const srcGraph = dataTransfer!!.getData("srcGraph"); + if (srcGraph !== props.graphIndex.toString()) { + // remove from source graph + if (srcGraph !== "hidden") { + removeVariable(parseInt(srcGraph), variable); + } + // add to this graph if necessary + if (!selectedVariables.value.includes(variable)) { + const newVars = [...selectedVariables.value, variable]; + updateSelectedVariables(props.graphIndex, newVars); + } + } }; return { - allVariables, + selectedVariables, getStyle, - toggleVariable, - selectAll, - selectNone + startDrag, + endDrag, + onDrop }; } }); diff --git a/app/static/src/app/components/run/RunPlot.vue b/app/static/src/app/components/run/RunPlot.vue index 51841f68..696aee44 100644 --- a/app/static/src/app/components/run/RunPlot.vue +++ b/app/static/src/app/components/run/RunPlot.vue @@ -26,12 +26,16 @@ import { ParameterSet } from "../../store/run/state"; export default defineComponent({ name: "RunPlot", props: { - fadePlot: Boolean + fadePlot: Boolean, + graphIndex: { + type: Number, + default: 0 + } }, components: { WodinPlot }, - setup() { + setup(props) { const store = useStore(); const solution = computed(() => store.state.run.resultOde?.solution); @@ -58,7 +62,7 @@ export default defineComponent({ const allFitData = computed(() => store.getters[`fitData/${FitDataGetter.allData}`]); - const selectedVariables = computed(() => store.state.graphs.config[0].selectedVariables); + const selectedVariables = computed(() => store.state.graphs.config[props.graphIndex].selectedVariables); const placeholderMessage = computed(() => runPlaceholderMessage(selectedVariables.value, false)); const allPlotData = (start: number, end: number, points: number): WodinPlotData => { diff --git a/app/static/src/app/components/run/RunStochasticPlot.vue b/app/static/src/app/components/run/RunStochasticPlot.vue index a655090e..cf2313a1 100644 --- a/app/static/src/app/components/run/RunStochasticPlot.vue +++ b/app/static/src/app/components/run/RunStochasticPlot.vue @@ -21,15 +21,19 @@ import { StochasticConfig } from "../../types/responseTypes"; export default defineComponent({ name: "RunStochasticPlot", props: { - fadePlot: Boolean + fadePlot: Boolean, + graphIndex: { + type: Number, + default: 0 + } }, components: { WodinPlot }, - setup() { + setup(props) { const store = useStore(); - const selectedVariables = computed(() => store.state.graphs.config[0].selectedVariables); + const selectedVariables = computed(() => store.state.graphs.config[props.graphIndex].selectedVariables); const placeholderMessage = computed(() => runPlaceholderMessage(selectedVariables.value, false)); const solution = computed(() => store.state.run.resultDiscrete?.solution); diff --git a/app/static/src/app/components/run/RunTab.vue b/app/static/src/app/components/run/RunTab.vue index 805abc78..c4929dfd 100644 --- a/app/static/src/app/components/run/RunTab.vue +++ b/app/static/src/app/components/run/RunTab.vue @@ -4,12 +4,14 @@ - - -
- Sum of squares: {{ sumOfSquares }} -
-
+
-
diff --git a/app/static/src/app/components/code/SelectedVariables.vue b/app/static/src/app/components/code/SelectedVariables.vue index ed3f30a8..a1caf357 100644 --- a/app/static/src/app/components/code/SelectedVariables.vue +++ b/app/static/src/app/components/code/SelectedVariables.vue @@ -11,6 +11,9 @@ @dragend="endDrag" > {{ variable }} + + +
@@ -101,6 +104,7 @@ export default defineComponent({ return { selectedVariables, + removeVariable, getStyle, startDrag, endDrag, diff --git a/app/static/src/scss/style.scss b/app/static/src/scss/style.scss index 3cd4c025..b76fc3ed 100644 --- a/app/static/src/scss/style.scss +++ b/app/static/src/scss/style.scss @@ -130,4 +130,19 @@ $grey: #ccc; .drop-zone-inactive { border-color: #fff; +} + +.variable-delete { + font-weight: normal; + margin-left: 0.3rem; + span { + opacity: 0.5; + } + + button { + background-color: transparent; + border-width: 0; + color: white; + padding: 0; + } } \ No newline at end of file From 78623f60fc9cc7ba9c93a720cbbddc2e8af4362e Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 27 Jun 2024 14:05:59 +0100 Subject: [PATCH 04/80] refactor selectedVariabled component into graphConfigs --- .../src/app/components/code/CodeTab.vue | 47 ++------------- .../GraphConfig.vue} | 3 +- .../components/graphConfig/GraphConfigs.vue | 60 +++++++++++++++++++ .../{code => graphConfig}/HiddenVariables.vue | 0 .../components/code/selectedVariables.test.ts | 2 +- 5 files changed, 66 insertions(+), 46 deletions(-) rename app/static/src/app/components/{code/SelectedVariables.vue => graphConfig/GraphConfig.vue} (98%) create mode 100644 app/static/src/app/components/graphConfig/GraphConfigs.vue rename app/static/src/app/components/{code => graphConfig}/HiddenVariables.vue (100%) diff --git a/app/static/src/app/components/code/CodeTab.vue b/app/static/src/app/components/code/CodeTab.vue index 06b2f33e..8612f0cf 100644 --- a/app/static/src/app/components/code/CodeTab.vue +++ b/app/static/src/app/components/code/CodeTab.vue @@ -15,27 +15,7 @@
- -
- Drag variables to 'Hidden variables' to remove them from your graph, or click 'Add Graph' to create a - new graph to move them to. -
- - - -
+
@@ -50,26 +30,19 @@ import CodeEditor from "./CodeEditor.vue"; import { ModelAction } from "../../store/model/actions"; import userMessages from "../../userMessages"; import ErrorInfo from "../ErrorInfo.vue"; -import SelectedVariables from "./SelectedVariables.vue"; -import HiddenVariables from "./HiddenVariables.vue"; -import VerticalCollapse from "../VerticalCollapse.vue"; +import GraphConfigs from "../graphConfig/GraphConfigs.vue"; import GenericHelp from "../help/GenericHelp.vue"; -import { GraphsAction } from "../../store/graphs/actions"; export default defineComponent({ name: "CodeTab", components: { GenericHelp, - SelectedVariables, - HiddenVariables, + GraphConfigs, ErrorInfo, CodeEditor, - VueFeather, - VerticalCollapse + VueFeather }, setup() { - // TODO: move all graphs stuff into another component - const draggingVariable = ref(false); // indicates whether a child component is dragging a variable const store = useStore(); const codeIsValid = computed(() => store.state.model.odinModelResponse?.valid); const codeValidating = computed(() => store.state.code.loading); @@ -78,16 +51,9 @@ export default defineComponent({ const validIcon = computed(() => (codeIsValid.value ? "check" : "x")); const iconValidatedClass = computed(() => (codeIsValid.value ? "text-success" : "text-danger")); const iconClass = computed(() => (codeValidating.value ? "code-validating-icon" : iconValidatedClass.value)); - const allVariables = computed(() => store.state.model.odinModelResponse?.metadata?.variables || []); - const showSelectedVariables = computed(() => allVariables.value.length && !store.state.model.compileRequired); const appIsConfigured = computed(() => store.state.configured); - const graphsConfig = computed(() => store.state.graphs.config); const compile = () => store.dispatch(`model/${ModelAction.CompileModel}`); - const addGraph = () => { - store.dispatch(`graphs/${GraphsAction.NewGraph}`); - }; const loadingMessage = userMessages.code.isValidating; - const setDraggingVariable = (value: boolean) => (draggingVariable.value = value); return { appIsConfigured, @@ -97,14 +63,9 @@ export default defineComponent({ iconClass, compile, error, - showSelectedVariables, codeHelp, codeValidating, loadingMessage, - graphsConfig, - addGraph, - draggingVariable, - setDraggingVariable }; } }); diff --git a/app/static/src/app/components/code/SelectedVariables.vue b/app/static/src/app/components/graphConfig/GraphConfig.vue similarity index 98% rename from app/static/src/app/components/code/SelectedVariables.vue rename to app/static/src/app/components/graphConfig/GraphConfig.vue index a1caf357..64b505a5 100644 --- a/app/static/src/app/components/code/SelectedVariables.vue +++ b/app/static/src/app/components/graphConfig/GraphConfig.vue @@ -28,9 +28,8 @@ import { computed, defineComponent } from "vue"; import { useStore } from "vuex"; import { GraphsAction } from "../../store/graphs/actions"; -// TODO: rename this component? export default defineComponent({ - name: "SelectedVariables", + name: "GraphConfig", props: { dragging: { type: Boolean, diff --git a/app/static/src/app/components/graphConfig/GraphConfigs.vue b/app/static/src/app/components/graphConfig/GraphConfigs.vue new file mode 100644 index 00000000..530ca5c0 --- /dev/null +++ b/app/static/src/app/components/graphConfig/GraphConfigs.vue @@ -0,0 +1,60 @@ + + + \ No newline at end of file diff --git a/app/static/src/app/components/code/HiddenVariables.vue b/app/static/src/app/components/graphConfig/HiddenVariables.vue similarity index 100% rename from app/static/src/app/components/code/HiddenVariables.vue rename to app/static/src/app/components/graphConfig/HiddenVariables.vue diff --git a/app/static/tests/unit/components/code/selectedVariables.test.ts b/app/static/tests/unit/components/code/selectedVariables.test.ts index e6a6c3a1..42b7d92d 100644 --- a/app/static/tests/unit/components/code/selectedVariables.test.ts +++ b/app/static/tests/unit/components/code/selectedVariables.test.ts @@ -1,7 +1,7 @@ import Vuex from "vuex"; import { shallowMount } from "@vue/test-utils"; import { BasicState } from "../../../../src/app/store/basic/state"; -import SelectedVariables from "../../../../src/app/components/code/SelectedVariables.vue"; +import SelectedVariables from "../../../../src/app/components/graphConfig/SelectedVariables.vue"; import { ModelAction } from "../../../../src/app/store/model/actions"; import { GraphsAction } from "../../../../src/app/store/graphs/actions"; From 4fdf8cbfdbf522b9b6d3008046672a2071fefe34 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 27 Jun 2024 14:08:59 +0100 Subject: [PATCH 05/80] add graphs UI to options tab --- app/static/src/app/components/options/OptionsTab.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/static/src/app/components/options/OptionsTab.vue b/app/static/src/app/components/options/OptionsTab.vue index f271e519..9519292d 100644 --- a/app/static/src/app/components/options/OptionsTab.vue +++ b/app/static/src/app/components/options/OptionsTab.vue @@ -30,6 +30,7 @@ + @@ -46,10 +47,12 @@ import { AppType, VisualisationTab } from "../../store/appState/state"; import GraphSettings from "./GraphSettings.vue"; import ParameterSets from "./ParameterSets.vue"; import AdvancedSettings from "./AdvancedSettings.vue"; +import GraphConfigs from "@/app/components/graphConfig/GraphConfigs.vue"; export default defineComponent({ name: "OptionsTab", components: { + GraphConfigs, ParameterSets, LinkData, OptimisationOptions, From 723dcd96f527efea5e61adb88138c14affee1cda Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 27 Jun 2024 14:30:34 +0100 Subject: [PATCH 06/80] use mixin for drag variables --- .../components/graphConfig/GraphConfig.vue | 16 ++-------- .../graphConfig/HiddenVariables.vue | 17 ++-------- .../app/components/mixins/dragVariables.ts | 32 +++++++++++++++++++ 3 files changed, 36 insertions(+), 29 deletions(-) create mode 100644 app/static/src/app/components/mixins/dragVariables.ts diff --git a/app/static/src/app/components/graphConfig/GraphConfig.vue b/app/static/src/app/components/graphConfig/GraphConfig.vue index 64b505a5..bb44e851 100644 --- a/app/static/src/app/components/graphConfig/GraphConfig.vue +++ b/app/static/src/app/components/graphConfig/GraphConfig.vue @@ -27,6 +27,7 @@ import { computed, defineComponent } from "vue"; import { useStore } from "vuex"; import { GraphsAction } from "../../store/graphs/actions"; +import DragVariables from "../mixins/dragVariables"; export default defineComponent({ name: "GraphConfig", @@ -42,6 +43,7 @@ export default defineComponent({ }, setup(props, { emit }) { const store = useStore(); + const { startDrag, endDrag } = DragVariables(store, emit, false, props.graphIndex); const selectedVariables = computed( () => store.state.graphs.config[props.graphIndex].selectedVariables ); @@ -62,20 +64,6 @@ export default defineComponent({ }); }; - const startDrag = (evt: DragEvent, variable: string) => { - const { dataTransfer } = evt; - dataTransfer!!.dropEffect = "move"; - dataTransfer!!.effectAllowed = "move"; - dataTransfer!!.setData("variable", variable); - dataTransfer!!.setData("srcGraph", props.graphIndex.toString()); - emit("setDragging", true); - }; - - // TODO: share the drag stuff with HiddenVariables - const endDrag = () => { - emit("setDragging", false); - }; - // Remove variable from the graph it was dragged from const removeVariable = (srcGraphIdx: number, variable: string) => { const srcVariables = [...store.state.graphs.config[srcGraphIdx].selectedVariables].filter( diff --git a/app/static/src/app/components/graphConfig/HiddenVariables.vue b/app/static/src/app/components/graphConfig/HiddenVariables.vue index 30085415..317fd20e 100644 --- a/app/static/src/app/components/graphConfig/HiddenVariables.vue +++ b/app/static/src/app/components/graphConfig/HiddenVariables.vue @@ -28,6 +28,7 @@ import tooltip from "../../directives/tooltip"; import * as Color from "color"; import { GraphsGetter } from "../../store/graphs/getters"; import { GraphsAction } from "../../store/graphs/actions"; +import DragVariables from "../mixins/dragVariables"; export default defineComponent({ name: "HiddenVariables", @@ -45,6 +46,7 @@ export default defineComponent({ }, setup(props, { emit }) { const store = useStore(); + const { startDrag, endDrag } = DragVariables(store, emit, true); const hiddenVariables = computed(() => store.getters[`graphs/${GraphsGetter.hiddenVariables}`]); const palette = computed(() => store.state.model.paletteModel!); @@ -54,21 +56,6 @@ export default defineComponent({ return { "background-color": desatBgColor }; }; - // TODO: share code with selectedVariables - but remember hidden won't need copy but Selected will, and some other differences - const startDrag = (evt: DragEvent, variable: string) => { - const { dataTransfer } = evt; - dataTransfer!!.dropEffect = "move"; - dataTransfer!!.effectAllowed = "move"; - dataTransfer!!.setData("variable", variable); - dataTransfer!!.setData("srcGraph", "hidden"); - - emit("setDragging", true); - }; - - const endDrag = () => { - emit("setDragging", false); - }; - const onDrop = (evt: DragEvent) => { const { dataTransfer } = evt; const variable = dataTransfer!!.getData("variable"); diff --git a/app/static/src/app/components/mixins/dragVariables.ts b/app/static/src/app/components/mixins/dragVariables.ts new file mode 100644 index 00000000..0ca01d52 --- /dev/null +++ b/app/static/src/app/components/mixins/dragVariables.ts @@ -0,0 +1,32 @@ +import {AppState} from "../../store/appState/state"; +import {Store} from "vuex"; + +export interface DragVariablesMixin { + startDrag: (evt: DragEvent, variable: string) => void; + endDrag: () => void, +} + +export default ( + store: Store, + emit: (event: string, ...args: any[]) => void, + hiddenVariables: boolean, + graphIndex?: number): DragVariablesMixin => { + const startDrag = (evt: DragEvent, variable: string) => { + const { dataTransfer } = evt; + dataTransfer!!.dropEffect = "move"; + dataTransfer!!.effectAllowed = "move"; + dataTransfer!!.setData("variable", variable); + dataTransfer!!.setData("srcGraph", hiddenVariables ? "hidden" : graphIndex!.toString()); + + emit("setDragging", true); + }; + + const endDrag = () => { + emit("setDragging", false); + }; + + return { + startDrag, + endDrag, + }; +} \ No newline at end of file From 1d7ea2d87891561ee4aedd229054e1fabf34e231 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 27 Jun 2024 14:55:36 +0100 Subject: [PATCH 07/80] use mixin for drop --- .../components/graphConfig/GraphConfig.vue | 37 +-------- .../graphConfig/HiddenVariables.vue | 22 +----- .../app/components/mixins/dragVariables.ts | 32 -------- .../app/components/mixins/selectVariables.ts | 77 +++++++++++++++++++ 4 files changed, 81 insertions(+), 87 deletions(-) delete mode 100644 app/static/src/app/components/mixins/dragVariables.ts create mode 100644 app/static/src/app/components/mixins/selectVariables.ts diff --git a/app/static/src/app/components/graphConfig/GraphConfig.vue b/app/static/src/app/components/graphConfig/GraphConfig.vue index bb44e851..b6daea43 100644 --- a/app/static/src/app/components/graphConfig/GraphConfig.vue +++ b/app/static/src/app/components/graphConfig/GraphConfig.vue @@ -26,8 +26,7 @@ \ No newline at end of file diff --git a/app/static/src/app/components/graphConfig/HiddenVariables.vue b/app/static/src/app/components/graphConfig/HiddenVariables.vue index 00878a06..186a7b6c 100644 --- a/app/static/src/app/components/graphConfig/HiddenVariables.vue +++ b/app/static/src/app/components/graphConfig/HiddenVariables.vue @@ -24,9 +24,9 @@ import { computed, defineComponent } from "vue"; import { useStore } from "vuex"; import VueFeather from "vue-feather"; -import * as Color from "color"; import { GraphsGetter } from "../../store/graphs/getters"; import SelectVariables from "../mixins/selectVariables"; +import {fadeColor} from "./utils"; export default defineComponent({ name: "HiddenVariables", @@ -45,8 +45,7 @@ export default defineComponent({ const getStyle = (variable: string) => { const bgcolor = palette.value ? palette.value[variable] : "#eee"; - const desatBgColor = Color(bgcolor).desaturate(0.6).fade(0.4).rgb().string(); - return { "background-color": desatBgColor }; + return { "background-color": fadeColor(bgcolor)}; }; return { diff --git a/app/static/src/app/components/graphConfig/utils.ts b/app/static/src/app/components/graphConfig/utils.ts new file mode 100644 index 00000000..afa50149 --- /dev/null +++ b/app/static/src/app/components/graphConfig/utils.ts @@ -0,0 +1,5 @@ +import * as Color from "color"; + +export const fadeColor = (color: string) => { + return Color(color).desaturate(0.6).fade(0.4).rgb().string(); +}; \ No newline at end of file diff --git a/app/static/src/app/components/options/OptionsTab.vue b/app/static/src/app/components/options/OptionsTab.vue index 9519292d..745f5143 100644 --- a/app/static/src/app/components/options/OptionsTab.vue +++ b/app/static/src/app/components/options/OptionsTab.vue @@ -30,7 +30,7 @@ - + @@ -48,10 +48,12 @@ import GraphSettings from "./GraphSettings.vue"; import ParameterSets from "./ParameterSets.vue"; import AdvancedSettings from "./AdvancedSettings.vue"; import GraphConfigs from "@/app/components/graphConfig/GraphConfigs.vue"; +import GraphConfigsCollapsible from "@/app/components/graphConfig/GraphConfigsCollapsible.vue"; export default defineComponent({ name: "OptionsTab", components: { + GraphConfigsCollapsible, GraphConfigs, ParameterSets, LinkData, diff --git a/app/static/src/scss/style.scss b/app/static/src/scss/style.scss index c013587d..7fe36a78 100644 --- a/app/static/src/scss/style.scss +++ b/app/static/src/scss/style.scss @@ -137,6 +137,11 @@ $grey: #ccc; background-color: #eee } +.variable { + font-size: large; + cursor: pointer; +} + .variable-delete { font-weight: normal; margin-left: 0.3rem; diff --git a/app/static/tests/color-shim.js b/app/static/tests/color-shim.js new file mode 100644 index 00000000..f6438b98 --- /dev/null +++ b/app/static/tests/color-shim.js @@ -0,0 +1,6 @@ +const Color = require("color/index.js"); +const color2 = Color.default; +//module.exports = color2; +console.log("Color from shim") +console.log(typeof Color) +module.exports = Color; \ No newline at end of file diff --git a/app/static/tests/unit/components/code/codeTab.test.ts b/app/static/tests/unit/components/code/codeTab.test.ts index 5dc5e3e3..8b7357df 100644 --- a/app/static/tests/unit/components/code/codeTab.test.ts +++ b/app/static/tests/unit/components/code/codeTab.test.ts @@ -7,8 +7,8 @@ import { BasicState } from "../../../../src/app/store/basic/state"; import { mockBasicState, mockCodeState, mockModelState } from "../../../mocks"; import ErrorInfo from "../../../../src/app/components/ErrorInfo.vue"; import { ModelState } from "../../../../src/app/store/model/state"; -import VerticalCollapse from "../../../../src/app/components/VerticalCollapse.vue"; import GenericHelp from "../../../../src/app/components/help/GenericHelp.vue"; +import GraphConfigsCollapsible from "../../../../src/app/components/graphConfig/GraphConfigsCollapsible.vue"; describe("CodeTab", () => { const defaultModelState = { @@ -63,7 +63,7 @@ describe("CodeTab", () => { const statusIcon = wrapper.find("#code-status").findComponent(VueFeather); expect(statusIcon.attributes("type")).toBe("check"); expect(statusIcon.classes()).toContain("text-success"); - expect(wrapper.findComponent(VerticalCollapse).props("collapseId")).toBe("select-variables"); + expect(wrapper.findComponent(GraphConfigsCollapsible).exists()).toBe(true); expect(wrapper.findComponent(GenericHelp).props("title")).toBe("Write odin code"); expect(wrapper.findComponent(GenericHelp).props("markdown")).toContain("Write code in this editor"); }); @@ -101,27 +101,6 @@ describe("CodeTab", () => { expect(wrapper.findComponent(ErrorInfo).props("error")).toStrictEqual(odinModelResponseError); }); - it("does not render selected variables when no variables in model", () => { - const wrapper = getWrapper({ - ...defaultModelState, - odinModelResponse: { - metadata: { - variables: [] - }, - valid: true - } as any - }); - expect(wrapper.findComponent(VerticalCollapse).exists()).toBe(false); - }); - - it("does not render selected variables when compile required", () => { - const wrapper = getWrapper({ - ...defaultModelState, - compileRequired: true - }); - expect(wrapper.findComponent(VerticalCollapse).exists()).toBe(false); - }); - it("renders nothing when app state is not configured", () => { const store = new Vuex.Store({ state: mockBasicState() @@ -134,8 +113,4 @@ describe("CodeTab", () => { }); expect(wrapper.findAll("div").length).toBe(0); }); - - // TODO - // Graphs renders nothing if no variables - // Graphs renders nothing if compile required }); diff --git a/app/static/tests/unit/components/graphConfig/graphConfigsCollapsible.test.ts b/app/static/tests/unit/components/graphConfig/graphConfigsCollapsible.test.ts new file mode 100644 index 00000000..214adeef --- /dev/null +++ b/app/static/tests/unit/components/graphConfig/graphConfigsCollapsible.test.ts @@ -0,0 +1,64 @@ +import {ModelState} from "../../../../src/app/store/model/state"; +import Vuex from "vuex"; +import {BasicState} from "../../../../src/app/store/basic/state"; +import {mockBasicState, mockCodeState, mockModelState} from "../../../mocks"; +import {shallowMount} from "@vue/test-utils"; +import GraphConfigsCollapsible from "../../../../src/app/components/graphConfig/GraphConfigsCollapsible.vue"; +import VerticalCollapse from "../../../../src/app/components/VerticalCollapse.vue"; + +describe("GraphConfigsCollapsible", () => { + const defaultModelState = { + compileRequired: false, + odinModelResponse: { + metadata: { + variables: ["S", "I", "R"] + }, + valid: true + } as any + }; + + const getWrapper = (modelState: Partial = {}) => { + const store = new Vuex.Store({ + state: mockBasicState({ + configured: true + }), + modules: { + model: { + namespaced: true, + state: mockModelState({...defaultModelState, ...modelState}) + } + } + }); + + return shallowMount(GraphConfigsCollapsible, { + global: { + plugins: [store] + } + }); + }; + + it("renders collapsible when graph configs should be shown", () => { + const wrapper = getWrapper(); + expect(wrapper.findComponent(VerticalCollapse).props("title")).toBe("Graphs"); + }); + + it("does not render collapsible when no variables in model", () => { + const wrapper = getWrapper({ + odinModelResponse: { + metadata: { + variables: [] + }, + valid: true + } as any + }); + expect(wrapper.findComponent(VerticalCollapse).exists()).toBe(false); + }); + + it("does not render collapsible when compile required", () => { + const wrapper = getWrapper({ + compileRequired: true + }); + expect(wrapper.findComponent(VerticalCollapse).exists()).toBe(false); + }); + +}); \ No newline at end of file diff --git a/app/static/tests/unit/components/graphConfig/hiddenVariables.test.ts b/app/static/tests/unit/components/graphConfig/hiddenVariables.test.ts index 093bd375..e65dc7cb 100644 --- a/app/static/tests/unit/components/graphConfig/hiddenVariables.test.ts +++ b/app/static/tests/unit/components/graphConfig/hiddenVariables.test.ts @@ -1,3 +1,17 @@ +// Mock the fadeColor util which uses color package, which doesn't play nicely with jest +function mockfun() { return "Hello"; }; +jest.mock("../../../../src/app/components/graphConfig/utils", () => { + return { + fadeColor: (input: string) => { + console.log(`calling fade w ${input}`) + const match = input.match(/rgb\(([0-9]*), ([0-9]*), ([0-9]*)\)/); + const result = `rgba(${match![1]}, ${match![2]}, ${match![3]}, 0.5)`; + console.log(result); + return result; + } + } +}); + import Vuex from "vuex"; import {BasicState} from "../../../../src/app/store/basic/state"; import {GraphsAction} from "../../../../src/app/store/graphs/actions"; @@ -12,7 +26,7 @@ describe("HiddenVariables", () => { const mockUpdateSelectedVariables = jest.fn(); - const getWrapper = () => { + const getWrapper = (hiddenVariables = ["I", "R"]) => { const store = new Vuex.Store({ state: {} as any, modules: { @@ -21,7 +35,7 @@ describe("HiddenVariables", () => { state: { config: [ { - selectedVariables: ["S"] + selectedVariables: ["S", "J"] } ] }, @@ -29,16 +43,17 @@ describe("HiddenVariables", () => { [GraphsAction.UpdateSelectedVariables]: mockUpdateSelectedVariables }, getters: { - [GraphsGetter.hiddenVariables]: ["I", "R"] + [GraphsGetter.hiddenVariables]: () => hiddenVariables } as any }, model: { namespaced: true, state: { paletteModel: { - S: "#ff0000", - I: "#00ff00", - R: "#0000ff" + S: "rgb(255, 0, 0)", + I: "rgb(0, 255, 0)", + R: "rgb(0, 0, 255)", + J: "rgb(0, 255, 255)" } } } @@ -58,22 +73,61 @@ describe("HiddenVariables", () => { it("renders as expected", () => { const wrapper = getWrapper(); + expect(wrapper.find("h5").text()).toBe("Hidden variables"); + expect(wrapper.find(".drop-zone").classes()).toStrictEqual(["drop-zone", "drop-zone-inactive"]); + const variables = wrapper.findAll(".hidden-variables-panel .variable"); + expect(variables.length).toBe(2); + expect(variables.at(0)!.text()).toBe("I"); + expect((variables.at(0)!.element as HTMLElement).style.backgroundColor).toBe("rgba(0, 255, 0, 0.5)"); + expect(variables.at(1)!.text()).toBe("R"); + expect((variables.at(1)!.element as HTMLElement).style.backgroundColor).toBe("rgba(0, 0, 255, 0.5)"); }); - /* - it("starting drag sets values in event and emits setDragging"); - it("ending drag emits setDragging"); + it("starting drag sets values in event and emits setDragging", async () => { + const wrapper = getWrapper(); + const s = wrapper.findAll(".hidden-variables-panel .variable").at(0)!; + const setData = jest.fn(); + await s.trigger("dragstart", { dataTransfer: { setData } }); + expect(setData.mock.calls[0][0]).toBe("variable"); + expect(setData.mock.calls[0][1]).toStrictEqual("I"); + expect(setData.mock.calls[1][0]).toStrictEqual("srcGraph"); + expect(setData.mock.calls[1][1]).toStrictEqual("hidden"); + expect(wrapper.emitted("setDragging")![0]).toStrictEqual([true]); + }); + + it("ending drag emits setDragging", async () => { + const wrapper = getWrapper(); + const s = wrapper.findAll(".hidden-variables-panel .variable").at(0)!; + await s.trigger("dragend"); + expect(wrapper.emitted("setDragging")![0]).toStrictEqual([false]); + }); - it("onDrop removes variable from source"); - it("shows drop zone when dragging", () => { - const wrapper = getWrapper({dragging: true}); + it("onDrop removes variable from source", async () => { + const wrapper = getWrapper(); + const dataTransfer = { + getData: (s: string) => { + if (s === "variable") return "S"; + if (s === "srcGraph") return "0"; + return null; + } + }; + const dropPanel = wrapper.find(".hidden-variables-panel"); + await dropPanel.trigger("drop", { dataTransfer }); + expect(mockUpdateSelectedVariables.mock.calls.length).toBe(1); + expect(mockUpdateSelectedVariables.mock.calls[0][1]).toStrictEqual({ graphIndex: 0, selectedVariables: ["J"] }); + }); + + + it("shows drop zone when dragging", async () => { + const wrapper = getWrapper(); + await wrapper.setProps({dragging: true}); expect(wrapper.find(".drop-zone").classes()).toStrictEqual(["drop-zone", "drop-zone-active"]); }); it("shows instruction if no hidden variables", () =>{ - const wrapper = getWrapper({}, []); - expect(wrapper.find(".drop-zone-instruction").text()).toBe("Drag variables here to select them for this graph."); - });*/ + const wrapper = getWrapper([]); + expect(wrapper.find(".drop-zone-instruction").text()).toBe("Drag variables here to hide them on all graphs."); + }); }); \ No newline at end of file diff --git a/app/static/tests/unit/components/options/optionsTab.test.ts b/app/static/tests/unit/components/options/optionsTab.test.ts index d94e3131..da53b7e5 100644 --- a/app/static/tests/unit/components/options/optionsTab.test.ts +++ b/app/static/tests/unit/components/options/optionsTab.test.ts @@ -16,6 +16,7 @@ import GraphSettings from "../../../../src/app/components/options/GraphSettings. import ParameterSets from "../../../../src/app/components/options/ParameterSets.vue"; import { getters as runGetters } from "../../../../src/app/store/run/getters"; import AdvancedSettings from "../../../../src/app/components/options/AdvancedSettings.vue"; +import GraphConfigsCollapsible from "../../../../src/app/components/graphConfig/GraphConfigsCollapsible.vue"; describe("OptionsTab", () => { const mockTooltipDirective = jest.fn(); @@ -93,6 +94,7 @@ describe("OptionsTab", () => { expect(collapses.at(4)!.findComponent(ParameterSets).exists()).toBe(true); expect(wrapper.find("#reset-params-btn").exists()).toBe(true); expect(wrapper.find("#reset-params-btn").text()).toBe("Reset"); + expect(wrapper.findComponent(GraphConfigsCollapsible).exists()).toBe(true); }); it("can reset model parameters", async () => { diff --git a/app/static/tests/unit/components/run/runPlot.test.ts b/app/static/tests/unit/components/run/runPlot.test.ts index a605b9df..234144f1 100644 --- a/app/static/tests/unit/components/run/runPlot.test.ts +++ b/app/static/tests/unit/components/run/runPlot.test.ts @@ -122,7 +122,8 @@ describe("RunPlot", () => { }); const wrapper = shallowMount(RunPlot, { props: { - fadePlot: false + fadePlot: false, + graphIndex: 0 }, global: { plugins: [store] diff --git a/app/static/tests/unit/components/run/runStochasticPlot.test.ts b/app/static/tests/unit/components/run/runStochasticPlot.test.ts index a7eacefe..5a455417 100644 --- a/app/static/tests/unit/components/run/runStochasticPlot.test.ts +++ b/app/static/tests/unit/components/run/runStochasticPlot.test.ts @@ -56,7 +56,8 @@ describe("RunPlot for stochastic", () => { }); const wrapper = shallowMount(RunStochasticPlot, { props: { - fadePlot: false + fadePlot: false, + graphIndex: 0 }, global: { plugins: [store] diff --git a/app/static/tests/unit/components/run/runTab.test.ts b/app/static/tests/unit/components/run/runTab.test.ts index 8f704738..4b79edee 100644 --- a/app/static/tests/unit/components/run/runTab.test.ts +++ b/app/static/tests/unit/components/run/runTab.test.ts @@ -65,7 +65,10 @@ describe("RunTab", () => { graphs: { namespaced: true, state: mockGraphsState({ - config: [{ selectedVariables, unselectedVariables: [] }] + config: [ + { selectedVariables, unselectedVariables: [] }, + { selectedVariables: [], unselectedVariables: [] } + ] }), getters: graphGetters }, @@ -108,7 +111,10 @@ describe("RunTab", () => { graphs: { namespaced: true, state: mockGraphsState({ - config: [{ selectedVariables: ["S"], unselectedVariables: [] }] + config: [ + { selectedVariables: ["S"], unselectedVariables: [] }, + { selectedVariables: [], unselectedVariables: [] } + ] }), getters: graphGetters }, @@ -144,7 +150,12 @@ describe("RunTab", () => { expect(wrapper.find("button#run-btn").text()).toBe("Run model"); expect((wrapper.find("button#run-btn").element as HTMLButtonElement).disabled).toBe(false); expect(wrapper.findComponent(ActionRequiredMessage).props("message")).toBe(""); - expect(wrapper.findComponent(RunPlot).props("fadePlot")).toBe(false); + const plots = wrapper.findAllComponents(RunPlot); + expect(plots.length).toBe(2); + expect(plots.at(0)!.props("graphIndex")).toBe(0); + expect(plots.at(0)!.props("fadePlot")).toBe(false); + expect(plots.at(1)!.props("graphIndex")).toBe(1); + expect(plots.at(1)!.props("fadePlot")).toBe(false); // Download button disabled because there is no model solution const downloadBtn = wrapper.find("button#download-btn"); @@ -168,7 +179,12 @@ describe("RunTab", () => { solution: jest.fn() } ); - expect(wrapper.findComponent(RunStochasticPlot).props("fadePlot")).toBe(false); + const plots = wrapper.findAllComponents(RunStochasticPlot); + expect(plots.length).toBe(2); + expect(plots.at(0)!.props("graphIndex")).toBe(0); + expect(plots.at(0)!.props("fadePlot")).toBe(false); + expect(plots.at(1)!.props("graphIndex")).toBe(1); + expect(plots.at(1)!.props("fadePlot")).toBe(false); expect(wrapper.findComponent(ActionRequiredMessage).props("message")).toBe(""); expect(wrapper.findComponent(RunPlot).exists()).toBe(false); expect((wrapper.find("button#run-btn").element as HTMLButtonElement).disabled).toBe(false); @@ -224,7 +240,9 @@ describe("RunTab", () => { expect(wrapper.findComponent(ActionRequiredMessage).props("message")).toBe( "Model code has been updated. Compile code and Run Model to update." ); - expect(wrapper.findComponent(RunPlot).props("fadePlot")).toBe(true); + const plots = wrapper.findAllComponents(RunPlot); + expect(plots.at(0)!.props("fadePlot")).toBe(true); + expect(plots.at(1)!.props("fadePlot")).toBe(true); }); it("fades plot and shows message when model run required", () => { @@ -239,7 +257,9 @@ describe("RunTab", () => { expect(wrapper.findComponent(ActionRequiredMessage).props("message")).toBe( "Plot is out of date: model code has been recompiled. Run model to update." ); - expect(wrapper.findComponent(RunPlot).props("fadePlot")).toBe(true); + const plots = wrapper.findAllComponents(RunPlot); + expect(plots.at(0)!.props("fadePlot")).toBe(true); + expect(plots.at(1)!.props("fadePlot")).toBe(true); }); it("fades plot and show message when no selected variables", () => { @@ -247,12 +267,16 @@ describe("RunTab", () => { expect(wrapper.findComponent(ActionRequiredMessage).props("message")).toBe( "Please select at least one variable." ); - expect(wrapper.findComponent(RunPlot).props("fadePlot")).toBe(true); + const plots = wrapper.findAllComponents(RunPlot); + expect(plots.at(0)!.props("fadePlot")).toBe(true); + expect(plots.at(1)!.props("fadePlot")).toBe(true); }); it("fades plot when compile required when stochastic", () => { const wrapper = getStochasticWrapper({}, {}, true); - expect(wrapper.findComponent(RunStochasticPlot).props("fadePlot")).toBe(true); + const plots = wrapper.findAllComponents(RunStochasticPlot); + expect(plots.at(0)!.props("fadePlot")).toBe(true); + expect(plots.at(1)!.props("fadePlot")).toBe(true); }); it("invokes run model action when run button is clicked", () => { diff --git a/app/static/tests/unit/store/graphs/actions.test.ts b/app/static/tests/unit/store/graphs/actions.test.ts index caba3f8e..6ac9a0d3 100644 --- a/app/static/tests/unit/store/graphs/actions.test.ts +++ b/app/static/tests/unit/store/graphs/actions.test.ts @@ -8,7 +8,7 @@ describe("Graphs actions", () => { const modelState = { odinModelResponse: { metadata: { - variables: ["a", "b", "c"] + variables: ["b", "a", "c"] } } } as any; @@ -40,7 +40,7 @@ describe("Graphs actions", () => { expect(commit.mock.calls[0][0]).toBe(GraphsMutation.SetSelectedVariables); expect(commit.mock.calls[0][1]).toStrictEqual({ graphIndex: 1, - selectedVariables: ["a", "b"], + selectedVariables: ["b", "a"], // should have reordered variables to match model unselectedVariables: ["c"] }); expect(dispatch).not.toHaveBeenCalled(); @@ -67,7 +67,7 @@ describe("Graphs actions", () => { expect(commit.mock.calls[0][0]).toBe(GraphsMutation.SetSelectedVariables); expect(commit.mock.calls[0][1]).toStrictEqual({ graphIndex: 1, - selectedVariables: ["a", "b"], + selectedVariables: ["b", "a"], unselectedVariables: ["c"] }); expect(dispatch).toHaveBeenCalledTimes(1); @@ -75,4 +75,20 @@ describe("Graphs actions", () => { expect(dispatch.mock.calls[0][1]).toBe(null); expect(dispatch.mock.calls[0][2]).toStrictEqual({ root: true }); }); + it("NewGraph adds empty graph", () => { + const commit = jest.fn(); + + (actions[GraphsAction.NewGraph] as any)( + { + commit, + rootState + } + ); + expect(commit).toHaveBeenCalledTimes(1); + expect(commit.mock.calls[0][0]).toBe(GraphsMutation.AddGraph); + expect(commit.mock.calls[0][1]).toStrictEqual({ + selectedVariables: [], + unselectedVariables: ["b", "a", "c"] + }); + }); }); diff --git a/app/static/tests/unit/store/graphs/getters.test.ts b/app/static/tests/unit/store/graphs/getters.test.ts new file mode 100644 index 00000000..d77e61b1 --- /dev/null +++ b/app/static/tests/unit/store/graphs/getters.test.ts @@ -0,0 +1,30 @@ +import {mockGraphsState} from "../../../mocks"; +import {getters, GraphsGetter} from "../../../../src/app/store/graphs/getters"; + +describe("GraphsGetters", () => { + it("gets allSelectedVariables", () => { + const state = mockGraphsState({ + config: [ + { selectedVariables: ["a", "b"], unselectedVariables: ["c", "d"] }, + { selectedVariables: ["d"], unselectedVariables: ["a", "b", "c"] } + ] + }); + expect((getters[GraphsGetter.allSelectedVariables] as any)(state)).toStrictEqual(["a", "b", "d"]); + }); + + it("gets hiddenVariables", () => { + const testGetters = { + allSelectedVariables: ["a", "b", "d"] + }; + const rootState = { + model: { + odinModelResponse: { + metadata: { + variables: ["e", "d", "c", "b", "a"] + } + } + } + } as any; + expect((getters[GraphsGetter.hiddenVariables] as any)({}, testGetters, rootState)).toStrictEqual(["e", "c"]); + }); +}); \ No newline at end of file diff --git a/app/static/tests/unit/store/graphs/mutations.test.ts b/app/static/tests/unit/store/graphs/mutations.test.ts index fd590c10..66aa1ea9 100644 --- a/app/static/tests/unit/store/graphs/mutations.test.ts +++ b/app/static/tests/unit/store/graphs/mutations.test.ts @@ -41,4 +41,17 @@ describe("Graphs mutations", () => { expect(testState.config[1]).toStrictEqual({ selectedVariables: ["x", "z"], unselectedVariables: ["y"] }); expect(testState.config[0]).toStrictEqual({ selectedVariables: [], unselectedVariables: [] }); }); + + it("AddGraph pushed new graph to config", () => { + const testState = mockGraphsState({ + config: [ + { selectedVariables: ["a"], unselectedVariables: ["b", "c"] } + ] + }); + mutations.AddGraph(testState, { selectedVariables: [], unselectedVariables: ["b", "a", "c"] }); + expect(testState.config).toStrictEqual([ + { selectedVariables: ["a"], unselectedVariables: ["b", "c"] }, + { selectedVariables: [], unselectedVariables: ["b", "a", "c"] } + ]); + }); }); From 1fdd74e27b66d9936dd73e08b6fadd03b1a3ea93 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Mon, 1 Jul 2024 13:30:03 +0100 Subject: [PATCH 12/80] playwright tests --- app/static/tests/e2e/code.etest.ts | 53 ++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/app/static/tests/e2e/code.etest.ts b/app/static/tests/e2e/code.etest.ts index d1c210e0..a0e06565 100644 --- a/app/static/tests/e2e/code.etest.ts +++ b/app/static/tests/e2e/code.etest.ts @@ -1,6 +1,7 @@ import { expect, test, Page } from "@playwright/test"; import PlaywrightConfig from "../../playwright.config"; import { saveSessionTimeout, writeCode } from "./utils"; +import {plot} from "plotly.js-basic-dist-min"; export const newValidCode = `## Derivatives deriv(y1) <- sigma * (y2 - y1) @@ -292,10 +293,11 @@ test.describe("Code Tab tests", () => { ); }); - test("can select and unselect variables", async ({ page }) => { - // unselect I - const iVariable = await page.locator(":nth-match(.selected-variables-panel span.variable, 2)"); - await iVariable.click(); + test("can hide and unhide variables", async ({ page }) => { + // drag I to Hidden Variables + const iVariable = await page.locator(":nth-match(.graph-config-panel span.variable, 2)"); + const hiddenVariables = await page.locator(".hidden-variables-panel"); + await iVariable.dragTo(hiddenVariables); // check plot no longer contains R series await expect(await page.locator(".wodin-plot-data-summary-series").count()).toBe(2); @@ -306,8 +308,10 @@ test.describe("Code Tab tests", () => { "R" ); - // re-select I - await iVariable.click(); + // drag I back to Graph panel + const iVariableHidden = await page.locator(".hidden-variables-panel .variable"); + const graphVariablesPanel = await page.locator(".graph-config-panel .drop-zone"); + await iVariableHidden.dragTo(graphVariablesPanel); await expect(await page.locator(".wodin-plot-data-summary-series").count()).toBe(3); await expect(await page.locator(":nth-match(.wodin-plot-data-summary-series, 1)").getAttribute("name")).toBe( "S" @@ -320,6 +324,43 @@ test.describe("Code Tab tests", () => { ); }); + test("can add a graph, and drag variable onto it", async ({ page }) => { + // Add graph + await page.click("#add-graph-btn"); + + // Check second graph has appeared with placeholder text, and second graph config panel is there + expect(await page.locator(":nth-match(.wodin-plot-container, 2)").textContent()).toBe("No variables are selected."); + expect(await page.locator(":nth-match(.graph-config-panel h5, 2)").textContent()).toBe("Graph 2"); + expect(await page.locator(":nth-match(.graph-config-panel .drop-zone, 2)").textContent()) + .toContain("Drag variables here to select them for this graph."); + + // Drag variable to second graph + const sVariable = await page.locator(":nth-match(.graph-config-panel .variable, 1)"); + await sVariable.dragTo(page.locator(":nth-match(.graph-config-panel .drop-zone, 2)")); + + // Check correct variables on first and second config panels, none in Hidden + const configPanel1 = await page.locator(":nth-match(.graph-config-panel, 1)"); + const panel1Vars = await configPanel1.locator(".variable .variable-name"); + await expect(panel1Vars).toHaveCount(2); + await expect(panel1Vars.nth(0)).toHaveText("I"); + await expect(panel1Vars.nth(1)).toHaveText("R"); + const configPanel2 = await page.locator(":nth-match(.graph-config-panel, 2)"); + const panel2Vars = await configPanel2.locator(".variable .variable-name"); + await expect(panel2Vars).toHaveCount(1); + await expect(panel2Vars.nth(0)).toHaveText("S"); + + // Check correct variables on first and second graphs + const plotSummaries = await page.locator(".wodin-plot-container .wodin-plot-data-summary"); + await expect(plotSummaries).toHaveCount(2); + const plot1Series = await plotSummaries.nth(0).locator(".wodin-plot-data-summary-series"); + await expect(plot1Series).toHaveCount(2); + await expect(plot1Series.nth(0)).toHaveAttribute("name", "I"); + await expect(plot1Series.nth(1)).toHaveAttribute("name", "R"); + const plot2Series = await plotSummaries.nth(1).locator(".wodin-plot-data-summary-series"); + await expect(plot2Series).toHaveCount(1); + await expect(plot2Series.nth(0)).toHaveAttribute("name", "S"); + }); + test("can display help dialog", async ({ page }) => { await page.click("div.code-tab i.generic-help-icon"); expect((await page.innerText(".draggable-dialog .dragtarget")).trim()).toBe("Write odin code"); From 1872477eee3ee62559b3a33546d867816b478747 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Mon, 1 Jul 2024 13:39:40 +0100 Subject: [PATCH 13/80] lint and format --- app/static/jest.config.js | 2 +- .../src/app/components/code/CodeTab.vue | 6 +- .../components/graphConfig/GraphConfig.vue | 2 +- .../components/graphConfig/GraphConfigs.vue | 67 +++++++++---------- .../graphConfig/GraphConfigsCollapsible.vue | 39 +++++------ .../graphConfig/HiddenVariables.vue | 8 +-- .../src/app/components/graphConfig/utils.ts | 4 +- .../app/components/mixins/selectVariables.ts | 44 ++++++------ .../src/app/components/options/OptionsTab.vue | 6 +- app/static/src/app/components/run/RunPlot.vue | 8 +-- .../app/components/run/RunStochasticPlot.vue | 8 +-- app/static/src/app/components/run/RunTab.vue | 18 +++-- app/static/src/app/store/graphs/actions.ts | 5 +- app/static/src/app/store/model/actions.ts | 6 +- app/static/src/scss/style.scss | 4 +- app/static/tests/color-shim.js | 6 -- app/static/tests/e2e/code.etest.ts | 15 +++-- .../graphConfig/graphConfig.test.ts | 18 +++-- .../graphConfig/graphConfigs.test.ts | 19 +++--- .../graphConfigsCollapsible.test.ts | 13 ++-- .../graphConfig/hiddenVariables.test.ts | 34 +++++----- .../tests/unit/store/graphs/actions.test.ts | 10 ++- .../tests/unit/store/graphs/getters.test.ts | 6 +- .../tests/unit/store/graphs/mutations.test.ts | 4 +- 24 files changed, 177 insertions(+), 175 deletions(-) delete mode 100644 app/static/tests/color-shim.js diff --git a/app/static/jest.config.js b/app/static/jest.config.js index e88f717a..e872026b 100644 --- a/app/static/jest.config.js +++ b/app/static/jest.config.js @@ -9,7 +9,7 @@ module.exports = { coveragePathIgnorePatterns: ["./tests/mocks.ts"], transformIgnorePatterns: ["node_modules/(?!(d3-format))"], moduleNameMapper: { - //"^color$": "/tests/color-shim.js", + // "^color$": "/tests/color-shim.js", "raw-loader!.*/help/(.*)$": "/src/app/help/$1" } }; diff --git a/app/static/src/app/components/code/CodeTab.vue b/app/static/src/app/components/code/CodeTab.vue index cace4ba6..dccf09a6 100644 --- a/app/static/src/app/components/code/CodeTab.vue +++ b/app/static/src/app/components/code/CodeTab.vue @@ -15,13 +15,13 @@
- +
\ No newline at end of file + diff --git a/app/static/src/app/components/graphConfig/GraphConfigsCollapsible.vue b/app/static/src/app/components/graphConfig/GraphConfigsCollapsible.vue index cbe9418d..3d8c1d81 100644 --- a/app/static/src/app/components/graphConfig/GraphConfigsCollapsible.vue +++ b/app/static/src/app/components/graphConfig/GraphConfigsCollapsible.vue @@ -1,27 +1,28 @@ \ No newline at end of file + diff --git a/app/static/src/app/components/graphConfig/HiddenVariables.vue b/app/static/src/app/components/graphConfig/HiddenVariables.vue index 186a7b6c..12282a6e 100644 --- a/app/static/src/app/components/graphConfig/HiddenVariables.vue +++ b/app/static/src/app/components/graphConfig/HiddenVariables.vue @@ -23,14 +23,12 @@ diff --git a/app/static/src/scss/style.scss b/app/static/src/scss/style.scss index 5586575e..bb4a94f6 100644 --- a/app/static/src/scss/style.scss +++ b/app/static/src/scss/style.scss @@ -121,7 +121,6 @@ $grey: #ccc; border-width: 4px; padding: 4px; border-style: dashed; - margin-right: 6px; } .drop-zone-active { @@ -133,8 +132,9 @@ $grey: #ccc; } .drop-zone-instruction { - height: 3rem; + min-height: 3rem; background-color: #eee; + width: 100%; } .variable { From 8f49dcf5f50ba4d8384b27940190022dd3e5253e Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Tue, 16 Jul 2024 15:55:09 +0100 Subject: [PATCH 43/80] PR comments --- app/static/src/app/components/run/RunPlot.vue | 198 ++++++++---------- app/static/src/app/store/graphs/getters.ts | 5 +- 2 files changed, 92 insertions(+), 111 deletions(-) diff --git a/app/static/src/app/components/run/RunPlot.vue b/app/static/src/app/components/run/RunPlot.vue index fe5cf83a..a8cc5039 100644 --- a/app/static/src/app/components/run/RunPlot.vue +++ b/app/static/src/app/components/run/RunPlot.vue @@ -12,8 +12,8 @@ - diff --git a/app/static/src/app/store/graphs/getters.ts b/app/static/src/app/store/graphs/getters.ts index 9a891998..6cd947f5 100644 --- a/app/static/src/app/store/graphs/getters.ts +++ b/app/static/src/app/store/graphs/getters.ts @@ -20,6 +20,9 @@ export interface GraphsGettersValues { [GraphsGetter.legendWidth]: number; } +const LEGEND_LINE_PADDING = 40; +const LEGEND_WIDTH_PER_CHAR = 10; + export const getters: GraphsGetters & GetterTree = { [GraphsGetter.allSelectedVariables]: (state: GraphsState): string[] => { return state.config.flatMap((c) => c.selectedVariables); // TODO: dedupe, in mrc-5443 @@ -33,6 +36,6 @@ export const getters: GraphsGetters & GetterTree = { const longestVar = graphsGetters[GraphsGetter.allSelectedVariables].sort((a: string, b: string) => a.length < b.length ? 1 : -1 )[0]; - return longestVar.length * 10 + 40; + return longestVar.length * LEGEND_WIDTH_PER_CHAR + LEGEND_LINE_PADDING; } }; From 4d1581fbdeccf5ebadbf3603a478983f2f8abfb1 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Tue, 16 Jul 2024 16:36:57 +0100 Subject: [PATCH 44/80] fix test --- app/static/tests/unit/components/run/runPlot.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/static/tests/unit/components/run/runPlot.test.ts b/app/static/tests/unit/components/run/runPlot.test.ts index a294067b..1ed908e6 100644 --- a/app/static/tests/unit/components/run/runPlot.test.ts +++ b/app/static/tests/unit/components/run/runPlot.test.ts @@ -9,7 +9,7 @@ import WodinPlot from "../../../../src/app/components/WodinPlot.vue"; import { BasicState } from "../../../../src/app/store/basic/state"; import { FitDataGetter } from "../../../../src/app/store/fitData/getters"; import { getters as runGetters } from "../../../../src/app/store/run/getters"; -import { mockBasicState, mockGraphsState, mockRunState } from "../../../mocks"; +import {mockBasicState, mockGraphsState, mockModelState, mockRunState} from "../../../mocks"; describe("RunPlot", () => { const mockSolution = jest.fn().mockReturnValue({ @@ -418,7 +418,8 @@ describe("RunPlot", () => { const store = new Vuex.Store({ state: { graphs: graphsState, - run: mockRunState() + run: mockRunState(), + model: mockModelState() } as any }); const wrapper = shallowMount(RunPlot, { @@ -558,7 +559,8 @@ describe("RunPlot", () => { graphs: mockGraphsState({ config: [{ selectedVariables: [] }] } as any), - run: mockRunState() + run: mockRunState(), + model: mockModelState() } as any }); const wrapper = shallowMount(RunPlot, { From ff143a4fa97ab1027acf5f8360b6a69a13ae6872 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Tue, 16 Jul 2024 16:44:39 +0100 Subject: [PATCH 45/80] lint --- app/static/tests/unit/components/run/runPlot.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/static/tests/unit/components/run/runPlot.test.ts b/app/static/tests/unit/components/run/runPlot.test.ts index 1ed908e6..f398e2e6 100644 --- a/app/static/tests/unit/components/run/runPlot.test.ts +++ b/app/static/tests/unit/components/run/runPlot.test.ts @@ -9,7 +9,7 @@ import WodinPlot from "../../../../src/app/components/WodinPlot.vue"; import { BasicState } from "../../../../src/app/store/basic/state"; import { FitDataGetter } from "../../../../src/app/store/fitData/getters"; import { getters as runGetters } from "../../../../src/app/store/run/getters"; -import {mockBasicState, mockGraphsState, mockModelState, mockRunState} from "../../../mocks"; +import { mockBasicState, mockGraphsState, mockModelState, mockRunState } from "../../../mocks"; describe("RunPlot", () => { const mockSolution = jest.fn().mockReturnValue({ From 0e3f9037b5fa5596e05f0fbbb11298dd59d1912b Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Wed, 17 Jul 2024 10:07:38 +0100 Subject: [PATCH 46/80] fix delete bug, provide graph config to plots as props --- app/static/src/app/components/WodinPlot.vue | 8 ++++++-- app/static/src/app/components/fit/FitPlot.vue | 1 + app/static/src/app/components/run/RunPlot.vue | 8 +++++++- app/static/src/app/components/run/RunStochasticPlot.vue | 8 +++++++- app/static/src/app/components/run/RunTab.vue | 2 ++ .../app/components/sensitivity/SensitivityTracesPlot.vue | 3 +++ 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/static/src/app/components/WodinPlot.vue b/app/static/src/app/components/WodinPlot.vue index 6fe39995..2c0d0886 100644 --- a/app/static/src/app/components/WodinPlot.vue +++ b/app/static/src/app/components/WodinPlot.vue @@ -26,7 +26,7 @@ import { import { WodinPlotData, fadePlotStyle, margin, config } from "../plot"; import WodinPlotDataSummary from "./WodinPlotDataSummary.vue"; import { GraphsMutation } from "../store/graphs/mutations"; -import { YAxisRange } from "../store/graphs/state"; +import {GraphConfig, YAxisRange} from "../store/graphs/state"; import { GraphsGetter } from "../store/graphs/getters"; export default defineComponent({ @@ -65,6 +65,10 @@ export default defineComponent({ required: false, default: -1 }, + graphConfig: { + type: Object as PropType, + required: true + }, fitPlot: { type: Boolean, required: true @@ -85,7 +89,7 @@ export default defineComponent({ const hasPlotData = computed(() => !!baseData.value?.length); const settings = computed(() => props.fitPlot ? store.state.graphs.fitGraphSettings : - store.state.graphs.config[props.graphIndex].settings); + props.graphConfig!.settings); const yAxisType = computed(() => (settings.value.logScaleYAxis ? "log" : ("linear" as AxisType))); const lockYAxis = computed(() => settings.value.lockYAxis); const yAxisRange = computed(() => settings.value.yAxisRange as YAxisRange); diff --git a/app/static/src/app/components/fit/FitPlot.vue b/app/static/src/app/components/fit/FitPlot.vue index 235193ab..a636ae3c 100644 --- a/app/static/src/app/components/fit/FitPlot.vue +++ b/app/static/src/app/components/fit/FitPlot.vue @@ -6,6 +6,7 @@ :plot-data="allPlotData" :redrawWatches="solution ? [solution] : []" :fit-plot="true" + :graph-config="null" > diff --git a/app/static/src/app/components/run/RunPlot.vue b/app/static/src/app/components/run/RunPlot.vue index a47ba75d..98fa7ac1 100644 --- a/app/static/src/app/components/run/RunPlot.vue +++ b/app/static/src/app/components/run/RunPlot.vue @@ -8,6 +8,7 @@ :linked-x-axis="linkedXAxis" :fit-plot="false" :graph-index="graphIndex" + :graph-config="graphConfig" @updateXAxis="updateXAxis" > @@ -26,6 +27,7 @@ import { OdinSolution, Times } from "../../types/responseTypes"; import { Dict } from "../../types/utilTypes"; import { runPlaceholderMessage } from "../../utils"; import { ParameterSet } from "../../store/run/state"; +import {GraphConfig} from "../../store/graphs/state"; export default defineComponent({ name: "RunPlot", @@ -35,6 +37,10 @@ export default defineComponent({ type: Number, default: 0 }, + graphConfig: { + type: Object as PropType, + required: true + }, linkedXAxis: { type: Object as PropType | null>, required: true @@ -71,7 +77,7 @@ export default defineComponent({ const allFitData = computed(() => store.getters[`fitData/${FitDataGetter.allData}`]); - const selectedVariables = computed(() => store.state.graphs.config[props.graphIndex].selectedVariables); + const selectedVariables = computed(() => props.graphConfig.selectedVariables); const placeholderMessage = computed(() => runPlaceholderMessage(selectedVariables.value, false)); const allPlotData = (start: number, end: number, points: number): WodinPlotData => { diff --git a/app/static/src/app/components/run/RunStochasticPlot.vue b/app/static/src/app/components/run/RunStochasticPlot.vue index 59376156..d6acc63e 100644 --- a/app/static/src/app/components/run/RunStochasticPlot.vue +++ b/app/static/src/app/components/run/RunStochasticPlot.vue @@ -8,6 +8,7 @@ :linked-x-axis="linkedXAxis" :fit-plot="false" :graph-index="graphIndex" + :graph-config="graphConfig" @updateXAxis="updateXAxis" > @@ -22,6 +23,7 @@ import { WodinPlotData, discreteSeriesSetToPlotly, filterSeriesSet } from "../.. import WodinPlot from "../WodinPlot.vue"; import { runPlaceholderMessage } from "../../utils"; import { StochasticConfig } from "../../types/responseTypes"; +import {GraphConfig} from "../../store/graphs/state"; export default defineComponent({ name: "RunStochasticPlot", @@ -31,6 +33,10 @@ export default defineComponent({ type: Number, default: 0 }, + graphConfig: { + type: Object as PropType, + required: true + }, linkedXAxis: { type: Object as PropType | null>, required: true @@ -43,7 +49,7 @@ export default defineComponent({ setup(props, { emit }) { const store = useStore(); - const selectedVariables = computed(() => store.state.graphs.config[props.graphIndex].selectedVariables); + const selectedVariables = computed(() => props.graphConfig.selectedVariables); const placeholderMessage = computed(() => runPlaceholderMessage(selectedVariables.value, false)); const solution = computed(() => store.state.run.resultDiscrete?.solution); diff --git a/app/static/src/app/components/run/RunTab.vue b/app/static/src/app/components/run/RunTab.vue index ecce3640..c6ad6ddd 100644 --- a/app/static/src/app/components/run/RunTab.vue +++ b/app/static/src/app/components/run/RunTab.vue @@ -8,6 +8,7 @@ @@ -50,6 +51,7 @@ export default defineComponent({ const store = useStore(); const solutions = computed(() => store.state.sensitivity.result?.batch?.solutions || []); + const graphConfig = computed(() => store.state.graphs.config[0]); const visibleParameterSetNames = computed(() => store.getters[`run/${RunGetter.visibleParameterSetNames}`]); const parameterSets = computed(() => store.state.run.parameterSets as ParameterSet[]); @@ -195,6 +197,7 @@ export default defineComponent({ }; return { + graphConfig, placeholderMessage, endTime, solutions, From bf59118fee910aac04a5e313e8dd23b75584344a Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Tue, 23 Jul 2024 17:12:37 +0100 Subject: [PATCH 47/80] do not show graph configs on Options Tab if Fit tab is open --- app/static/src/app/components/options/OptionsTab.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/static/src/app/components/options/OptionsTab.vue b/app/static/src/app/components/options/OptionsTab.vue index e4efcb7d..1ee21164 100644 --- a/app/static/src/app/components/options/OptionsTab.vue +++ b/app/static/src/app/components/options/OptionsTab.vue @@ -16,9 +16,6 @@ - - - - + + + + From 0ae34f56918c37e41a703793efc5882420d83e38 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Wed, 24 Jul 2024 17:15:06 +0100 Subject: [PATCH 48/80] unit tests --- app/static/tests/mocks.ts | 11 +- .../graphConfig/graphConfig.test.ts | 7 +- .../unit/components/graphSettings.test.ts | 115 +++++++++++++++ .../components/options/graphSettings.test.ts | 71 ---------- .../components/options/optionsTab.test.ts | 47 +++---- .../tests/unit/components/run/runPlot.test.ts | 34 ++++- .../components/run/runStochasticPlot.test.ts | 33 ++++- .../tests/unit/components/run/runTab.test.ts | 14 +- .../tests/unit/components/wodinPlot.test.ts | 131 ++++++++++++++---- app/static/tests/unit/serialiser.test.ts | 36 +++-- .../tests/unit/store/graphs/actions.test.ts | 2 + .../tests/unit/store/graphs/mutations.test.ts | 81 +++++++---- 12 files changed, 402 insertions(+), 180 deletions(-) create mode 100644 app/static/tests/unit/components/graphSettings.test.ts delete mode 100644 app/static/tests/unit/components/options/graphSettings.test.ts diff --git a/app/static/tests/mocks.ts b/app/static/tests/mocks.ts index 7149bb87..597cd6f8 100644 --- a/app/static/tests/mocks.ts +++ b/app/static/tests/mocks.ts @@ -26,7 +26,7 @@ import { SensitivityVariationType } from "../src/app/store/sensitivity/state"; import { VersionsState } from "../src/app/store/versions/state"; -import { GraphsState } from "../src/app/store/graphs/state"; +import {defaultGraphSettings, GraphsState} from "../src/app/store/graphs/state"; import { LanguageState } from "../translationPackage/store/state"; import { Language } from "../src/app/types/languageTypes"; import { noSensitivityUpdateRequired } from "../src/app/store/sensitivity/sensitivity"; @@ -128,14 +128,11 @@ export const mockGraphsState = (state: Partial = {}): GraphsState = { id: "123", selectedVariables: [], - unselectedVariables: [] + unselectedVariables: [], + settings: defaultGraphSettings() } ], - settings: { - logScaleYAxis: false, - lockYAxis: false, - yAxisRange: [0, 0] - }, + fitGraphSettings: defaultGraphSettings(), ...state }; }; diff --git a/app/static/tests/unit/components/graphConfig/graphConfig.test.ts b/app/static/tests/unit/components/graphConfig/graphConfig.test.ts index c80a79ce..53f40766 100644 --- a/app/static/tests/unit/components/graphConfig/graphConfig.test.ts +++ b/app/static/tests/unit/components/graphConfig/graphConfig.test.ts @@ -5,6 +5,7 @@ import GraphConfig from "../../../../src/app/components/graphConfig/GraphConfig. import { GraphsAction } from "../../../../src/app/store/graphs/actions"; import { GraphsState } from "../../../../src/app/store/graphs/state"; import { GraphsMutation } from "../../../../src/app/store/graphs/mutations"; +import GraphSettings from "../../../../src/app/components/GraphSettings.vue"; describe("GraphConfig", () => { const mockUpdateSelectedVariables = jest.fn(); @@ -72,8 +73,7 @@ describe("GraphConfig", () => { const wrapper = getWrapper(); const varBadges = wrapper.findAll(".graph-config-panel .badge"); const varNames = wrapper.findAll(".graph-config-panel span.variable-name"); - expect(wrapper.find("h5").text()).toBe("Graph 1"); - expect(wrapper.find("h5 button.delete-graph").exists()).toBe(true); + expect(wrapper.find("button.delete-graph").exists()).toBe(true); expect(varBadges.length).toBe(2); expect(varNames.length).toBe(2); expect(varNames.at(0)!.text()).toBe("S"); @@ -84,6 +84,9 @@ describe("GraphConfig", () => { expect(wrapper.find(".drop-zone").classes()).toStrictEqual(["drop-zone", "drop-zone-inactive"]); // instruction not shown if at least one selected variable expect(wrapper.findAll(".drop-zone-instruction").length).toBe(0); + const settings = wrapper.findComponent(GraphSettings); + expect(settings.props("fitPlot")).toBe(false); + expect(settings.props("graphIndex")).toBe(0); }); it("starting drag sets values in event and emits setDragging", async () => { diff --git a/app/static/tests/unit/components/graphSettings.test.ts b/app/static/tests/unit/components/graphSettings.test.ts new file mode 100644 index 00000000..3efb17e0 --- /dev/null +++ b/app/static/tests/unit/components/graphSettings.test.ts @@ -0,0 +1,115 @@ +import Vuex from "vuex"; +import { shallowMount } from "@vue/test-utils"; +import { BasicState } from "../../../src/app/store/basic/state"; +import GraphSettings from "../../../src/app/components/GraphSettings.vue"; +import { GraphsMutation } from "../../../src/app/store/graphs/mutations"; +import {defaultGraphSettings} from "../../../src/app/store/graphs/state"; + +describe("GraphSettings", () => { + const mockSetLogScaleYAxis = jest.fn(); + const mockSetLockYAxis = jest.fn(); + + const mockSetFitLogScaleYAxis = jest.fn(); + const mockSetFitLockYAxis = jest.fn(); + + const getWrapper = ( + graphIndex: number | undefined = 1, + fitPlot = false, + graphSettings = [defaultGraphSettings(), defaultGraphSettings()], + fitGraphSettings = defaultGraphSettings() + ) => { + const store = new Vuex.Store({ + modules: { + graphs: { + namespaced: true, + state: { + fitGraphSettings, + config: graphSettings.map((settings) => ({ settings })) + } as any, + mutations: { + [GraphsMutation.SetLogScaleYAxis]: mockSetLogScaleYAxis, + [GraphsMutation.SetLockYAxis]: mockSetLockYAxis, + [GraphsMutation.SetFitLogScaleYAxis]: mockSetFitLogScaleYAxis, + [GraphsMutation.SetFitLockYAxis]: mockSetFitLockYAxis + } + } + } + }); + return shallowMount(GraphSettings, { + props: { graphIndex, fitPlot }, + global: { + plugins: [store] + } + }); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("renders as expected when not fit plot", () => { + const wrapper = getWrapper(1, false, [ + defaultGraphSettings(), + { lockYAxis: true, logScaleYAxis: true, yAxisRange: [0, 10] } + ]); + const labels = wrapper.findAll("label"); + const inputs = wrapper.findAll("input"); + expect(labels.length).toBe(2); + expect(inputs.length).toBe(2); + expect(labels[0].text()).toBe("Log scale y axis"); + expect(inputs[0].attributes("type")).toBe("checkbox"); + expect((inputs[0].element as HTMLInputElement).checked).toBe(true); + expect(labels[1].text()).toBe("Lock y axis"); + expect(inputs[1].attributes("type")).toBe("checkbox"); + expect((inputs[1].element as HTMLInputElement).checked).toBe(true); + }); + + it("renders as expected when fit plot", () => { + const wrapper = getWrapper(undefined, true, [ + { lockYAxis: true, logScaleYAxis: true, yAxisRange: [0, 10] } + ]); + const inputs = wrapper.findAll("input"); + expect((inputs[0].element as HTMLInputElement).checked).toBe(false); + expect((inputs[1].element as HTMLInputElement).checked).toBe(false); + }); + + it("commits change to log scale y axis setting, when not fit plot", async () => { + const wrapper = getWrapper(1, false); + const inputs = wrapper.findAll("input"); + expect((inputs[0].element as HTMLInputElement).checked).toBe(false); + await inputs[0].setValue(true); + expect(mockSetLogScaleYAxis).toHaveBeenCalledTimes(1); + expect(mockSetLogScaleYAxis.mock.calls[0][1]).toStrictEqual({graphIndex: 1, value: true}); + expect(mockSetFitLogScaleYAxis).not.toHaveBeenCalled(); + }) + + it("commits change to log scale y axis setting, when fit plot", async () => { + const wrapper = getWrapper(undefined, true); + const inputs = wrapper.findAll("input"); + expect((inputs[0].element as HTMLInputElement).checked).toBe(false); + await inputs[0].setValue(true); + expect(mockSetFitLogScaleYAxis).toHaveBeenCalledTimes(1); + expect(mockSetFitLogScaleYAxis.mock.calls[0][1]).toBe(true); + expect(mockSetLogScaleYAxis).not.toHaveBeenCalled(); + }); + + it("commits change to log scale y axis setting, when not fit plot", async () => { + const wrapper = getWrapper(1, false); + const inputs = wrapper.findAll("input"); + expect((inputs[1].element as HTMLInputElement).checked).toBe(false); + await inputs[1].setValue(true); + expect(mockSetLockYAxis).toHaveBeenCalledTimes(1); + expect(mockSetLockYAxis.mock.calls[0][1]).toStrictEqual({graphIndex: 1, value: true}); + expect(mockSetFitLockYAxis).not.toHaveBeenCalled(); + }); + + it("commits change to log scale y axis setting, when fit plot", async () => { + const wrapper = getWrapper(undefined, true); + const inputs = wrapper.findAll("input"); + expect((inputs[1].element as HTMLInputElement).checked).toBe(false); + await inputs[1].setValue(true); + expect(mockSetFitLockYAxis).toHaveBeenCalledTimes(1); + expect(mockSetFitLockYAxis.mock.calls[0][1]).toBe(true); + expect(mockSetLockYAxis).not.toHaveBeenCalled(); + }); +}); diff --git a/app/static/tests/unit/components/options/graphSettings.test.ts b/app/static/tests/unit/components/options/graphSettings.test.ts deleted file mode 100644 index b6f508e4..00000000 --- a/app/static/tests/unit/components/options/graphSettings.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import Vuex from "vuex"; -import { shallowMount } from "@vue/test-utils"; -import { BasicState } from "../../../../src/app/store/basic/state"; -import GraphSettings from "../../../../src/app/components/GraphSettings.vue"; -import { GraphsMutation } from "../../../../src/app/store/graphs/mutations"; - -describe("GraphSettings", () => { - const mockSetLogScaleYAxis = jest.fn(); - const mockSetLockYAxis = jest.fn(); - - const getWrapper = (logScaleYAxis = true, lockYAxis = true) => { - const store = new Vuex.Store({ - modules: { - graphs: { - namespaced: true, - state: { - settings: { - logScaleYAxis, - lockYAxis - } - } as any, - mutations: { - [GraphsMutation.SetLogScaleYAxis]: mockSetLogScaleYAxis, - [GraphsMutation.SetLockYAxis]: mockSetLockYAxis - } - } - } - }); - return shallowMount(GraphSettings, { - global: { - plugins: [store] - } - }); - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it("renders as expected", () => { - const wrapper = getWrapper(); - const labels = wrapper.findAll("label"); - const inputs = wrapper.findAll("input"); - expect(labels.length).toBe(2); - expect(inputs.length).toBe(2); - expect(labels[0].text()).toBe("Log scale y axis"); - expect(inputs[0].attributes("type")).toBe("checkbox"); - expect((inputs[0].element as HTMLInputElement).checked).toBe(true); - expect(labels[1].text()).toBe("Lock y axis"); - expect(inputs[1].attributes("type")).toBe("checkbox"); - expect((inputs[1].element as HTMLInputElement).checked).toBe(true); - }); - - it("commits change to log scale y axis setting", async () => { - const wrapper = getWrapper(false); - const inputs = wrapper.findAll("input"); - expect((inputs[0].element as HTMLInputElement).checked).toBe(false); - await inputs[0].setValue(true); - expect(mockSetLogScaleYAxis).toHaveBeenCalledTimes(1); - expect(mockSetLogScaleYAxis.mock.calls[0][1]).toBe(true); - }); - - it("commits change to log scale y axis setting", async () => { - const wrapper = getWrapper(false, false); - const inputs = wrapper.findAll("input"); - expect((inputs[1].element as HTMLInputElement).checked).toBe(false); - await inputs[1].setValue(true); - expect(mockSetLockYAxis).toHaveBeenCalledTimes(1); - expect(mockSetLockYAxis.mock.calls[0][1]).toBe(true); - }); -}); diff --git a/app/static/tests/unit/components/options/optionsTab.test.ts b/app/static/tests/unit/components/options/optionsTab.test.ts index c8e60be3..3de4f040 100644 --- a/app/static/tests/unit/components/options/optionsTab.test.ts +++ b/app/static/tests/unit/components/options/optionsTab.test.ts @@ -74,7 +74,7 @@ describe("OptionsTab", () => { }); const wrapper = getWrapper(store); const collapses = wrapper.findAllComponents(VerticalCollapse); - expect(collapses.length).toBe(5); + expect(collapses.length).toBe(4); expect(collapses.at(0)!.props("title")).toBe("Model Parameters"); expect(collapses.at(0)!.props("collapseId")).toBe("model-params"); const paramValues = collapses.at(0)!.findComponent(ParameterValues); @@ -82,16 +82,13 @@ describe("OptionsTab", () => { expect(collapses.at(1)!.props("title")).toBe("Run Options"); expect(collapses.at(1)!.props("collapseId")).toBe("run-options"); expect(collapses.at(1)!.findComponent(RunOptions).exists()).toBe(true); - expect(collapses.at(2)!.props("title")).toBe("Graph Settings"); - expect(collapses.at(2)!.props("collapseId")).toBe("graph-settings"); - expect(collapses.at(2)!.findComponent(GraphSettings).exists()).toBe(true); - expect(collapses.at(3)!.props("title")).toBe("Advanced Settings"); - expect(collapses.at(3)!.props("collapseId")).toBe("advanced-settings"); - expect(collapses.at(3)!.props("collapsedDefault")).toBe(true); - expect(collapses.at(3)!.findComponent(AdvancedSettings).exists()).toBe(true); - expect(collapses.at(4)!.props("title")).toBe("Saved Parameter Sets"); - expect(collapses.at(4)!.props("collapseId")).toBe("parameter-sets"); - expect(collapses.at(4)!.findComponent(ParameterSets).exists()).toBe(true); + expect(collapses.at(2)!.props("title")).toBe("Advanced Settings"); + expect(collapses.at(2)!.props("collapseId")).toBe("advanced-settings"); + expect(collapses.at(2)!.props("collapsedDefault")).toBe(true); + expect(collapses.at(2)!.findComponent(AdvancedSettings).exists()).toBe(true); + expect(collapses.at(3)!.props("title")).toBe("Saved Parameter Sets"); + expect(collapses.at(3)!.props("collapseId")).toBe("parameter-sets"); + expect(collapses.at(3)!.findComponent(ParameterSets).exists()).toBe(true); expect(wrapper.find("#reset-params-btn").exists()).toBe(true); expect(wrapper.find("#reset-params-btn").text()).toBe("Reset"); expect(wrapper.findComponent(GraphConfigsCollapsible).exists()).toBe(true); @@ -157,7 +154,7 @@ describe("OptionsTab", () => { columnToFit: null }, graphs: { - settings: { + fitGraphSettings: { logScaleYAxis: false } } @@ -189,16 +186,17 @@ describe("OptionsTab", () => { expect(collapses.at(3)!.props("title")).toBe("Optimisation"); expect(collapses.at(3)!.props("collapseId")).toBe("optimisation"); expect(collapses.at(3)!.findComponent(OptimisationOptions).exists()).toBe(true); - expect(collapses.at(4)!.props("title")).toBe("Graph Settings"); - expect(collapses.at(4)!.props("collapseId")).toBe("graph-settings"); - expect(collapses.at(4)!.findComponent(GraphSettings).exists()).toBe(true); - expect(collapses.at(5)!.props("title")).toBe("Advanced Settings"); - expect(collapses.at(5)!.props("collapseId")).toBe("advanced-settings"); - expect(collapses.at(5)!.props("collapsedDefault")).toBe(true); - expect(collapses.at(5)!.findComponent(AdvancedSettings).exists()).toBe(true); - expect(collapses.at(6)!.props("title")).toBe("Saved Parameter Sets"); - expect(collapses.at(6)!.props("collapseId")).toBe("parameter-sets"); - expect(collapses.at(6)!.findComponent(ParameterSets).exists()).toBe(true); + expect(collapses.at(4)!.props("title")).toBe("Advanced Settings"); + expect(collapses.at(4)!.props("collapseId")).toBe("advanced-settings"); + expect(collapses.at(4)!.props("collapsedDefault")).toBe(true); + expect(collapses.at(4)!.findComponent(AdvancedSettings).exists()).toBe(true); + expect(collapses.at(5)!.props("title")).toBe("Saved Parameter Sets"); + expect(collapses.at(5)!.props("collapseId")).toBe("parameter-sets"); + expect(collapses.at(5)!.findComponent(ParameterSets).exists()).toBe(true); + expect(collapses.at(6)!.props("title")).toBe("Fit Graph Settings"); + expect(collapses.at(6)!.props("collapseId")).toBe("graph-settings"); + expect(collapses.at(6)!.findComponent(GraphSettings).props("fitPlot")).toBe(true); + expect(wrapper.find("#reset-params-btn").exists()).toBe(true); expect(wrapper.find("#reset-params-btn").text()).toBe("Reset"); }); @@ -221,7 +219,7 @@ describe("OptionsTab", () => { }); const wrapper = getWrapper(store); const collapses = wrapper.findAllComponents(VerticalCollapse); - expect(collapses.length).toBe(3); + expect(collapses.length).toBe(2); expect(collapses.at(0)!.props("title")).toBe("Model Parameters"); expect(collapses.at(0)!.props("collapseId")).toBe("model-params"); const paramValues = collapses.at(0)!.findComponent(ParameterValues); @@ -229,9 +227,6 @@ describe("OptionsTab", () => { expect(collapses.at(1)!.props("title")).toBe("Run Options"); expect(collapses.at(1)!.props("collapseId")).toBe("run-options"); expect(collapses.at(1)!.findComponent(RunOptions).exists()).toBe(true); - expect(collapses.at(2)!.props("title")).toBe("Graph Settings"); - expect(collapses.at(2)!.props("collapseId")).toBe("graph-settings"); - expect(collapses.at(2)!.findComponent(GraphSettings).exists()).toBe(true); expect(wrapper.find("#reset-params-btn").exists()).toBe(true); expect(wrapper.find("#reset-params-btn").text()).toBe("Reset"); }); diff --git a/app/static/tests/unit/components/run/runPlot.test.ts b/app/static/tests/unit/components/run/runPlot.test.ts index a294067b..eb86e01f 100644 --- a/app/static/tests/unit/components/run/runPlot.test.ts +++ b/app/static/tests/unit/components/run/runPlot.test.ts @@ -1,4 +1,6 @@ // Mock plotly before import RunTab, which indirectly imports plotly via WodinPlot +import {defaultGraphSettings} from "../../../../src/app/store/graphs/state"; + jest.mock("plotly.js-basic-dist-min", () => {}); /* eslint-disable import/first */ @@ -111,6 +113,12 @@ describe("RunPlot", () => { ] }); + const graphConfig = { + selectedVariables, + unselectedVariables: [], + settings: defaultGraphSettings() + }; + afterEach(() => { jest.clearAllMocks(); }); @@ -129,7 +137,8 @@ describe("RunPlot", () => { props: { fadePlot: false, graphIndex: 0, - linkedXAxis + linkedXAxis, + graphConfig }, global: { plugins: [store] @@ -205,7 +214,8 @@ describe("RunPlot", () => { props: { fadePlot: false, graphIndex: 0, - linkedXAxis + linkedXAxis, + graphConfig }, global: { plugins: [store] @@ -263,7 +273,8 @@ describe("RunPlot", () => { }); const wrapper = shallowMount(RunPlot, { props: { - fadePlot: false + fadePlot: false, + graphConfig }, global: { plugins: [store] @@ -274,6 +285,8 @@ describe("RunPlot", () => { expect(wodinPlot.props("fadePlot")).toBe(false); expect(wodinPlot.props("placeholderMessage")).toBe("Model has not been run."); expect(wodinPlot.props("endTime")).toBe(99); + expect(wodinPlot.props("graphIndex")).toBe(0); + expect(wodinPlot.props("graphConfig")).toStrictEqual(graphConfig); expect(wodinPlot.props("redrawWatches")).toStrictEqual([ mockSolution, mockAllFitData, @@ -397,7 +410,9 @@ describe("RunPlot", () => { }); const wrapper = shallowMount(RunPlot, { props: { - fadePlot: false + fadePlot: false, + graphConfig, + graphIndex: 0 }, global: { plugins: [store] @@ -408,6 +423,8 @@ describe("RunPlot", () => { expect(wodinPlot.props("placeholderMessage")).toBe("Model has not been run."); expect(wodinPlot.props("endTime")).toBe(99); expect(wodinPlot.props("redrawWatches")).toStrictEqual([]); + expect(wodinPlot.props("graphIndex")).toBe(0); + expect(wodinPlot.props("graphConfig")).toStrictEqual(graphConfig); const plotData = wodinPlot.props("plotData"); const data = plotData(0, 1, 10); @@ -423,7 +440,8 @@ describe("RunPlot", () => { }); const wrapper = shallowMount(RunPlot, { props: { - fadePlot: true + fadePlot: true, + graphConfig }, global: { plugins: [store] @@ -482,7 +500,8 @@ describe("RunPlot", () => { }); const wrapper = shallowMount(RunPlot, { props: { - fadePlot: false + fadePlot: false, + graphConfig }, global: { plugins: [store] @@ -563,7 +582,8 @@ describe("RunPlot", () => { }); const wrapper = shallowMount(RunPlot, { props: { - fadePlot: true + fadePlot: true, + graphConfig: {...graphConfig, selectedVariables: []} }, global: { plugins: [store] diff --git a/app/static/tests/unit/components/run/runStochasticPlot.test.ts b/app/static/tests/unit/components/run/runStochasticPlot.test.ts index a812eb37..a64b12df 100644 --- a/app/static/tests/unit/components/run/runStochasticPlot.test.ts +++ b/app/static/tests/unit/components/run/runStochasticPlot.test.ts @@ -1,4 +1,6 @@ // Mock plotly before import RunStochasticTab, which indirectly imports plotly via WodinPlot +import {defaultGraphSettings} from "../../../../src/app/store/graphs/state"; + jest.mock("plotly.js-basic-dist-min", () => {}); /* eslint-disable import/first */ @@ -37,11 +39,18 @@ describe("RunPlot for stochastic", () => { { id: "1234", selectedVariables, - unselectedVariables: [] + unselectedVariables: [], + settings: defaultGraphSettings() } ] }); + const graphConfig = { + selectedVariables, + unselectedVariables: [], + settings: defaultGraphSettings() + }; + const linkedXAxis = { autorange: false, range: [1, 2] }; afterEach(() => { @@ -68,7 +77,8 @@ describe("RunPlot for stochastic", () => { props: { fadePlot: false, graphIndex: 0, - linkedXAxis + linkedXAxis, + graphConfig }, global: { plugins: [store] @@ -81,6 +91,8 @@ describe("RunPlot for stochastic", () => { expect(wodinPlot.props("redrawWatches")).toStrictEqual([mockSolution]); expect(wodinPlot.props("recalculateOnRelayout")).toBe(true); expect(wodinPlot.props("linkedXAxis")).toStrictEqual(linkedXAxis); + expect(wodinPlot.props("graphIndex")).toBe(0); + expect(wodinPlot.props("graphConfig")).toStrictEqual(graphConfig); // Generates expected plot data from model const plotData = wodinPlot.props("plotData"); @@ -135,7 +147,8 @@ describe("RunPlot for stochastic", () => { props: { fadePlot: false, graphIndex: 0, - linkedXAxis + linkedXAxis, + graphConfig }, global: { plugins: [store] @@ -159,7 +172,8 @@ describe("RunPlot for stochastic", () => { }); const wrapper = shallowMount(RunPlot, { props: { - fadePlot: false + fadePlot: false, + graphConfig }, global: { plugins: [store] @@ -170,6 +184,8 @@ describe("RunPlot for stochastic", () => { expect(wodinPlot.props("placeholderMessage")).toBe("Model has not been run."); expect(wodinPlot.props("endTime")).toBe(99); expect(wodinPlot.props("redrawWatches")).toStrictEqual([]); + expect(wodinPlot.props("graphIndex")).toBe(0); + expect(wodinPlot.props("graphConfig")).toStrictEqual(graphConfig); const plotData = wodinPlot.props("plotData"); const data = plotData(); @@ -193,7 +209,8 @@ describe("RunPlot for stochastic", () => { }); const wrapper = shallowMount(RunStochasticPlot, { props: { - fadePlot: false + fadePlot: false, + graphConfig }, global: { plugins: [store] @@ -216,7 +233,8 @@ describe("RunPlot for stochastic", () => { }); const wrapper = shallowMount(RunPlot, { props: { - fadePlot: true + fadePlot: true, + graphConfig }, global: { plugins: [store] @@ -240,7 +258,8 @@ describe("RunPlot for stochastic", () => { }); const wrapper = shallowMount(RunStochasticPlot, { props: { - fadePlot: false + fadePlot: false, + graphConfig }, global: { plugins: [store] diff --git a/app/static/tests/unit/components/run/runTab.test.ts b/app/static/tests/unit/components/run/runTab.test.ts index c2c9ecbc..63b25498 100644 --- a/app/static/tests/unit/components/run/runTab.test.ts +++ b/app/static/tests/unit/components/run/runTab.test.ts @@ -69,7 +69,7 @@ describe("RunTab", () => { { id: "123", selectedVariables, unselectedVariables: [] }, { id: "456", selectedVariables: [], unselectedVariables: [] } ] - }), + } as any), getters: graphGetters }, model: { @@ -115,7 +115,7 @@ describe("RunTab", () => { { id: "123", selectedVariables: ["S"], unselectedVariables: [] }, { id: "456", selectedVariables: [], unselectedVariables: [] } ] - }), + } as any), getters: graphGetters }, model: { @@ -155,9 +155,13 @@ describe("RunTab", () => { expect(plots.at(0)!.props("graphIndex")).toBe(0); expect(plots.at(0)!.props("fadePlot")).toBe(false); expect(plots.at(0)!.props("linkedXAxis")).toStrictEqual({ autorange: true }); + expect(plots.at(0)!.props("graphConfig")) + .toStrictEqual({ id: "123", selectedVariables: ["S"], unselectedVariables: [] }); expect(plots.at(1)!.props("graphIndex")).toBe(1); expect(plots.at(1)!.props("fadePlot")).toBe(false); expect(plots.at(1)!.props("linkedXAxis")).toStrictEqual({ autorange: true }); + expect(plots.at(1)!.props("graphConfig")) + .toStrictEqual({ id: "456", selectedVariables: [], unselectedVariables: [] }); // Download button disabled because there is no model solution const downloadBtn = wrapper.find("button#download-btn"); @@ -197,9 +201,15 @@ describe("RunTab", () => { expect(plots.at(0)!.props("graphIndex")).toBe(0); expect(plots.at(0)!.props("fadePlot")).toBe(false); expect(plots.at(0)!.props("linkedXAxis")).toStrictEqual({ autorange: true }); + expect(plots.at(0)!.props("graphConfig")) + .toStrictEqual({ id: "123", selectedVariables: ["S"], unselectedVariables: [] }); + expect(plots.at(1)!.props("graphIndex")).toBe(1); expect(plots.at(1)!.props("fadePlot")).toBe(false); expect(plots.at(1)!.props("linkedXAxis")).toStrictEqual({ autorange: true }); + expect(plots.at(1)!.props("graphConfig")) + .toStrictEqual({ id: "456", selectedVariables: [], unselectedVariables: [] }); + expect(wrapper.findComponent(ActionRequiredMessage).props("message")).toBe(""); expect(wrapper.findComponent(RunPlot).exists()).toBe(false); expect((wrapper.find("button#run-btn").element as HTMLButtonElement).disabled).toBe(false); diff --git a/app/static/tests/unit/components/wodinPlot.test.ts b/app/static/tests/unit/components/wodinPlot.test.ts index ff7f3421..aed29016 100644 --- a/app/static/tests/unit/components/wodinPlot.test.ts +++ b/app/static/tests/unit/components/wodinPlot.test.ts @@ -1,4 +1,6 @@ // Mock the import of plotly so we can mock Plotly methods +import {defaultGraphSettings} from "../../../src/app/store/graphs/state"; + jest.mock("plotly.js-basic-dist-min", () => ({ newPlot: jest.fn(), react: jest.fn(), @@ -55,25 +57,28 @@ describe("WodinPlot", () => { endTime: 99, redrawWatches: [], plotData: mockPlotDataFn, - placeholderMessage: "No data available" + placeholderMessage: "No data available", + fitPlot: false, + graphIndex: 0, + graphConfig: { settings: defaultGraphSettings() } }; const mockSetYAxisRange = jest.fn(); + const mockSetFitYAxisRange = jest.fn(); - const getStore = (logScaleYAxis = false, lockYAxis = false) => { + const getStore = ( + fitGraphSettings = defaultGraphSettings() + ) => { return new Vuex.Store({ modules: { graphs: { namespaced: true, state: { - settings: { - logScaleYAxis, - lockYAxis, - yAxisRange: [10, 20] - } + fitGraphSettings }, mutations: { - [GraphsMutation.SetYAxisRange]: mockSetYAxisRange + [GraphsMutation.SetYAxisRange]: mockSetYAxisRange, + [GraphsMutation.SetFitYAxisRange]: mockSetFitYAxisRange } } } @@ -110,7 +115,6 @@ describe("WodinPlot", () => { afterEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); - mockSetYAxisRange.mockReset(); }); it("renders plot ref element", () => { @@ -256,8 +260,16 @@ describe("WodinPlot", () => { }); it("relayout preserves locked y axis range", async () => { - const store = getStore(false, true); - const wrapper = getWrapper({}, store); + const store = getStore(); + const wrapper = getWrapper({ + graphConfig: { + settings: { + ...defaultGraphSettings(), + lockYAxis: true, + yAxisRange: [10, 20] + } + } + }, store); const { relayout } = wrapper.vm as any; const relayoutEvent = { "xaxis.autorange": false, @@ -400,8 +412,26 @@ describe("WodinPlot", () => { }); it("renders y axis log scale if graph setting is set", async () => { - const store = getStore(true); - const wrapper = getWrapper({}, store); + const store = getStore(); + const wrapper = getWrapper({ + graphConfig: { + settings: {...defaultGraphSettings(), logScaleYAxis: true} + } + }, store); + mockPlotElementOn(wrapper); + + wrapper.setProps({ redrawWatches: [{} as any] }); + await nextTick(); + expect(mockPlotlyNewPlot.mock.calls[0][2]).toStrictEqual({ + margin: { t: 25 }, + xaxis: { title: "Time" }, + yaxis: { type: "log" } + }); + }); + + it("renders y axis log scale if fit graph setting is set, and fitPlot is true", async () => { + const store = getStore({...defaultGraphSettings(), logScaleYAxis: true}); + const wrapper = getWrapper({ fitPlot: true }, store); mockPlotElementOn(wrapper); wrapper.setProps({ redrawWatches: [{} as any] }); @@ -414,8 +444,34 @@ describe("WodinPlot", () => { }); it("locks axes if graph setting is set", async () => { - const store = getStore(false, true); - const wrapper = getWrapper({}, store); + const store = getStore(); + const wrapper = getWrapper({ + graphConfig: { + settings: { + ...defaultGraphSettings(), + lockYAxis: true, + yAxisRange: [10, 20] + } + } + }, store); + mockPlotElementOn(wrapper); + + wrapper.setProps({ redrawWatches: [{} as any] }); + await nextTick(); + expect(mockPlotlyNewPlot.mock.calls[0][2]).toStrictEqual({ + margin: { t: 25 }, + xaxis: { title: "Time" }, + yaxis: { type: "linear", autorange: false, range: [10, 20] } + }); + }); + + it("locks axes if fit graph setting is set, and fitPlot is true", async () => { + const store = getStore({ + ...defaultGraphSettings(), + lockYAxis: true, + yAxisRange: [10, 20] + }); + const wrapper = getWrapper({ fitPlot: true }, store); mockPlotElementOn(wrapper); wrapper.setProps({ redrawWatches: [{} as any] }); @@ -428,7 +484,7 @@ describe("WodinPlot", () => { }); it("commits SetYAxisRange on drawPlot", async () => { - const store = getStore(false, false); + const store = getStore(); const wrapper = getWrapper({}, store); mockPlotElementOn(wrapper); @@ -439,12 +495,33 @@ describe("WodinPlot", () => { await nextTick(); await wrapper.setProps({ redrawWatches: [{} as any] }); await nextTick(); - expect(mockSetYAxisRange.mock.calls[0][1]).toStrictEqual([1, 2]); + expect(mockSetYAxisRange.mock.calls[0][1]).toStrictEqual({graphIndex: 0, value: [1, 2]}); + expect(mockSetFitYAxisRange).not.toHaveBeenCalled(); + }); + + it("commits SetFitYAxisRange on drawPlot, when fitPlot is true", async () => { + const store = getStore(); + const wrapper = getWrapper({fitPlot: true}, store); + mockPlotElementOn(wrapper); + + (wrapper.vm as any).plot = { + layout: mockLayout, + on: jest.fn() + }; + await nextTick(); + await wrapper.setProps({ redrawWatches: [{} as any] }); + await nextTick(); + expect(mockSetFitYAxisRange.mock.calls[0][1]).toStrictEqual([1, 2]); + expect(mockSetYAxisRange).not.toHaveBeenCalled(); }); it("relayout uses graph settings log scale y axis value", async () => { - const store = getStore(true); - const wrapper = getWrapper({}, store); + const store = getStore(); + const wrapper = getWrapper({ + graphConfig: { + settings: {...defaultGraphSettings(), logScaleYAxis: true} + } + }, store); mockPlotElementOn(wrapper); wrapper.setProps({ redrawWatches: [{} as any] }); @@ -491,8 +568,11 @@ describe("WodinPlot", () => { expect(mockPlotlyNewPlot).toBeCalledTimes(1); expect(mockOn).toBeCalledTimes(1); - const store = (wrapper.vm as any).$store; - store.state.graphs.settings.logScaleYAxis = true; + await wrapper.setProps({ + graphConfig: { + settings: {...defaultGraphSettings(), logScaleYAxis: true} + } + }); await nextTick(); expect(mockPlotDataFn).toBeCalledTimes(2); @@ -509,9 +589,12 @@ describe("WodinPlot", () => { expect(mockPlotlyNewPlot).toBeCalledTimes(1); expect(mockOn).toBeCalledTimes(1); - await wrapper.setProps({ fadePlot: true }); - const store = (wrapper.vm as any).$store; - store.state.graphs.settings.logScaleYAxis = true; + await wrapper.setProps({ + fadePlot: true, + graphConfig: { + settings: {...defaultGraphSettings(), logScaleYAxis: true} + } + }); await nextTick(); expect(mockPlotDataFn).toBeCalledTimes(1); diff --git a/app/static/tests/unit/serialiser.test.ts b/app/static/tests/unit/serialiser.test.ts index c1af0cf4..8ac8ce84 100644 --- a/app/static/tests/unit/serialiser.test.ts +++ b/app/static/tests/unit/serialiser.test.ts @@ -25,6 +25,7 @@ import { Language } from "../../src/app/types/languageTypes"; import { AdvancedOptions } from "../../src/app/types/responseTypes"; import { AdvancedComponentType } from "../../src/app/store/run/state"; import { noSensitivityUpdateRequired } from "../../src/app/store/sensitivity/sensitivity"; +import {defaultGraphSettings} from "../../src/app/store/graphs/state"; jest.mock("../../src/app/utils", () => { return { @@ -370,7 +371,7 @@ describe("serialise", () => { }); const graphsState = mockGraphsState({ - settings: { + fitGraphSettings: { logScaleYAxis: true, lockYAxis: true, yAxisRange: [1, 2] @@ -379,7 +380,12 @@ describe("serialise", () => { { id: "123", selectedVariables: ["S", "I"], - unselectedVariables: ["R"] + unselectedVariables: ["R"], + settings: { + logScaleYAxis: false, + lockYAxis: true, + yAxisRange: [10, 20] + } } ] }); @@ -544,7 +550,7 @@ describe("serialise", () => { }; const expectedGraphs = { - settings: { + fitGraphSettings: { logScaleYAxis: true, lockYAxis: true, yAxisRange: [1, 2] @@ -552,7 +558,12 @@ describe("serialise", () => { config: [ { selectedVariables: ["S", "I"], - unselectedVariables: ["R"] + unselectedVariables: ["R"], + settings: { + logScaleYAxis: false, + lockYAxis: true, + yAxisRange: [10, 20] + } } ] }; @@ -726,9 +737,11 @@ describe("serialise", () => { { id: "12345", // id from mocked newUid selectedVariables: [], - unselectedVariables: [] + unselectedVariables: [], + settings: defaultGraphSettings() } - ] + ], + fitGraphSettings: defaultGraphSettings() }) }); }); @@ -771,9 +784,11 @@ describe("serialise", () => { { id: "12345", selectedVariables: ["S", "I", "R"], - unselectedVariables: [] + unselectedVariables: [], + settings: defaultGraphSettings() } ]); + expect(target.graphs.fitGraphSettings).toStrictEqual(defaultGraphSettings()); }); it("deserialises default graph settings when undefined in serialised state", () => { @@ -806,9 +821,12 @@ describe("serialise", () => { graphs: defaultGraphsState() } as any; // sanity check - expect(target.graphs.settings.logScaleYAxis).toBe(false); + expect(target.graphs.fitGraphSettings.logScaleYAxis).toBe(false); + expect(target.graphs.config[0].settings.logScaleYAxis).toBe(false); deserialiseState(target, serialised); - expect(target.graphs.settings.logScaleYAxis).toBe(false); + expect(target.graphs.fitGraphSettings.logScaleYAxis).toBe(false); + expect(target.graphs.config[0].settings.logScaleYAxis).toBe(false); + }); }); diff --git a/app/static/tests/unit/store/graphs/actions.test.ts b/app/static/tests/unit/store/graphs/actions.test.ts index c3863eef..22a0f92b 100644 --- a/app/static/tests/unit/store/graphs/actions.test.ts +++ b/app/static/tests/unit/store/graphs/actions.test.ts @@ -3,6 +3,7 @@ import { actions, GraphsAction } from "../../../../src/app/store/graphs/actions" import { GraphsMutation } from "../../../../src/app/store/graphs/mutations"; import { AppType } from "../../../../src/app/store/appState/state"; import { FitDataAction } from "../../../../src/app/store/fitData/actions"; +import {defaultGraphSettings} from "../../../../src/app/store/graphs/state"; describe("Graphs actions", () => { const modelState = { @@ -87,5 +88,6 @@ describe("Graphs actions", () => { expect(commit.mock.calls[0][1].selectedVariables).toStrictEqual([]); expect(commit.mock.calls[0][1].unselectedVariables).toStrictEqual(["b", "a", "c"]); expect(commit.mock.calls[0][1].id.length).toBe(32); + expect(commit.mock.calls[0][1].settings).toStrictEqual(defaultGraphSettings()); }); }); diff --git a/app/static/tests/unit/store/graphs/mutations.test.ts b/app/static/tests/unit/store/graphs/mutations.test.ts index 9da4996b..0d969823 100644 --- a/app/static/tests/unit/store/graphs/mutations.test.ts +++ b/app/static/tests/unit/store/graphs/mutations.test.ts @@ -1,36 +1,64 @@ import { mutations } from "../../../../src/app/store/graphs/mutations"; -import { GraphsState } from "../../../../src/app/store/graphs/state"; +import {defaultGraphSettings, GraphsState} from "../../../../src/app/store/graphs/state"; import { mockGraphsState, mockModelState } from "../../../mocks"; describe("Graphs mutations", () => { - const state: GraphsState = mockGraphsState(); + const state: GraphsState = mockGraphsState({ + config: [ + { + id: "123", + selectedVariables: ["S"], + unselectedVariables: [], + settings: defaultGraphSettings() + }, + { + id: "456", + selectedVariables: ["I"], + unselectedVariables: [], + settings: defaultGraphSettings() + } + ] + }); it("sets logScaleYAxis", () => { - mutations.SetLogScaleYAxis(state, true); - expect(state.settings.logScaleYAxis).toBe(true); + mutations.SetLogScaleYAxis(state, {graphIndex: 1, value: true}); + expect(state.config[1].settings.logScaleYAxis).toBe(true); }); it("sets lockYAxis", () => { - mutations.SetLockYAxis(state, true); - expect(state.settings.lockYAxis).toBe(true); + mutations.SetLockYAxis(state, {graphIndex: 1, value: true}); + expect(state.config[1].settings.lockYAxis).toBe(true); }); it("sets yAxisRange", () => { mutations.SetYAxisRange(state, { - x: [1, 2], - y: [3, 4] - }); - expect(state.settings.yAxisRange).toStrictEqual({ - x: [1, 2], - y: [3, 4] + graphIndex: 1, + value: [3, 4] }); + expect(state.config[1].settings.yAxisRange).toStrictEqual([3, 4]); + }); + + it("sets logScaleYAxis for fit graph", () => { + mutations.SetFitLogScaleYAxis(state, true); + expect(state.fitGraphSettings.logScaleYAxis).toBe(true); + }); + + it("sets lockYAxis for fit graph", () => { + mutations.SetFitLockYAxis(state, true); + expect(state.fitGraphSettings.lockYAxis).toBe(true); + }); + + it("sets yAxisRange", () => { + mutations.SetFitYAxisRange(state, [3, 4]); + expect(state.fitGraphSettings.yAxisRange).toStrictEqual([3, 4]); }); it("SetSelectedVariables sets selected and unselected variables", () => { + const settings = defaultGraphSettings(); const testState = mockGraphsState({ config: [ - { id: "123", selectedVariables: [], unselectedVariables: [] }, - { id: "456", selectedVariables: [], unselectedVariables: [] } + { id: "123", selectedVariables: [], unselectedVariables: [], settings }, + { id: "456", selectedVariables: [], unselectedVariables: [], settings } ] }); mutations.SetSelectedVariables(testState, { @@ -41,34 +69,37 @@ describe("Graphs mutations", () => { expect(testState.config[1]).toStrictEqual({ id: "456", selectedVariables: ["x", "z"], - unselectedVariables: ["y"] + unselectedVariables: ["y"], + settings }); - expect(testState.config[0]).toStrictEqual({ id: "123", selectedVariables: [], unselectedVariables: [] }); + expect(testState.config[0]).toStrictEqual({ id: "123", selectedVariables: [], unselectedVariables: [], settings }); }); it("AddGraph pushes new graph to config", () => { + const settings = defaultGraphSettings(); const testState = mockGraphsState({ - config: [{ id: "123", selectedVariables: ["a"], unselectedVariables: ["b", "c"] }] + config: [{ id: "123", selectedVariables: ["a"], unselectedVariables: ["b", "c"], settings }] }); - mutations.AddGraph(testState, { id: "456", selectedVariables: [], unselectedVariables: ["b", "a", "c"] }); + mutations.AddGraph(testState, { id: "456", selectedVariables: [], unselectedVariables: ["b", "a", "c"], settings }); expect(testState.config).toStrictEqual([ - { id: "123", selectedVariables: ["a"], unselectedVariables: ["b", "c"] }, - { id: "456", selectedVariables: [], unselectedVariables: ["b", "a", "c"] } + { id: "123", selectedVariables: ["a"], unselectedVariables: ["b", "c"], settings }, + { id: "456", selectedVariables: [], unselectedVariables: ["b", "a", "c"], settings } ]); }); it("DeleteGraph removes graph from config", () => { + const settings = defaultGraphSettings(); const testState = mockGraphsState({ config: [ - { id: "1", selectedVariables: ["a"], unselectedVariables: ["b", "c"] }, - { id: "2", selectedVariables: ["b"], unselectedVariables: ["a", "c"] }, - { id: "3", selectedVariables: ["c"], unselectedVariables: ["a", "b"] } + { id: "1", selectedVariables: ["a"], unselectedVariables: ["b", "c"], settings }, + { id: "2", selectedVariables: ["b"], unselectedVariables: ["a", "c"], settings }, + { id: "3", selectedVariables: ["c"], unselectedVariables: ["a", "b"], settings } ] }); mutations.DeleteGraph(testState, 1); expect(testState.config).toStrictEqual([ - { id: "1", selectedVariables: ["a"], unselectedVariables: ["b", "c"] }, - { id: "3", selectedVariables: ["c"], unselectedVariables: ["a", "b"] } + { id: "1", selectedVariables: ["a"], unselectedVariables: ["b", "c"], settings }, + { id: "3", selectedVariables: ["c"], unselectedVariables: ["a", "b"], settings } ]); }); }); From e8f63d63b511533d6c20e11f7514aa374261c953 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 25 Jul 2024 08:06:02 +0100 Subject: [PATCH 49/80] fix e2e tests --- .../src/app/components/GraphSettings.vue | 4 ++-- .../sensitivity/SensitivitySummaryPlot.vue | 3 ++- app/static/tests/e2e/code.etest.ts | 2 -- app/static/tests/e2e/fit.etest.ts | 2 +- app/static/tests/e2e/options.etest.ts | 18 ++++++++---------- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/app/static/src/app/components/GraphSettings.vue b/app/static/src/app/components/GraphSettings.vue index d0bfc679..152860fd 100644 --- a/app/static/src/app/components/GraphSettings.vue +++ b/app/static/src/app/components/GraphSettings.vue @@ -1,6 +1,6 @@ diff --git a/app/static/src/app/components/run/RunPlot.vue b/app/static/src/app/components/run/RunPlot.vue index 98fa7ac1..4acde1ce 100644 --- a/app/static/src/app/components/run/RunPlot.vue +++ b/app/static/src/app/components/run/RunPlot.vue @@ -27,7 +27,7 @@ import { OdinSolution, Times } from "../../types/responseTypes"; import { Dict } from "../../types/utilTypes"; import { runPlaceholderMessage } from "../../utils"; import { ParameterSet } from "../../store/run/state"; -import {GraphConfig} from "../../store/graphs/state"; +import { GraphConfig } from "../../store/graphs/state"; export default defineComponent({ name: "RunPlot", @@ -38,8 +38,8 @@ export default defineComponent({ default: 0 }, graphConfig: { - type: Object as PropType, - required: true + type: Object as PropType, + required: true }, linkedXAxis: { type: Object as PropType | null>, diff --git a/app/static/src/app/components/run/RunStochasticPlot.vue b/app/static/src/app/components/run/RunStochasticPlot.vue index d6acc63e..fe253757 100644 --- a/app/static/src/app/components/run/RunStochasticPlot.vue +++ b/app/static/src/app/components/run/RunStochasticPlot.vue @@ -23,7 +23,7 @@ import { WodinPlotData, discreteSeriesSetToPlotly, filterSeriesSet } from "../.. import WodinPlot from "../WodinPlot.vue"; import { runPlaceholderMessage } from "../../utils"; import { StochasticConfig } from "../../types/responseTypes"; -import {GraphConfig} from "../../store/graphs/state"; +import { GraphConfig } from "../../store/graphs/state"; export default defineComponent({ name: "RunStochasticPlot", @@ -34,8 +34,8 @@ export default defineComponent({ default: 0 }, graphConfig: { - type: Object as PropType, - required: true + type: Object as PropType, + required: true }, linkedXAxis: { type: Object as PropType | null>, diff --git a/app/static/src/app/store/graphs/graphs.ts b/app/static/src/app/store/graphs/graphs.ts index d83e3ee1..649cd02f 100644 --- a/app/static/src/app/store/graphs/graphs.ts +++ b/app/static/src/app/store/graphs/graphs.ts @@ -1,4 +1,4 @@ -import {defaultGraphSettings, GraphsState} from "./state"; +import { defaultGraphSettings, GraphsState } from "./state"; import { actions } from "./actions"; import { getters } from "./getters"; import { mutations } from "./mutations"; diff --git a/app/static/src/app/store/graphs/mutations.ts b/app/static/src/app/store/graphs/mutations.ts index 966665d5..8cc812d1 100644 --- a/app/static/src/app/store/graphs/mutations.ts +++ b/app/static/src/app/store/graphs/mutations.ts @@ -20,15 +20,15 @@ export interface SetSelectedVariablesPayload { } export const mutations: MutationTree = { - [GraphsMutation.SetLogScaleYAxis](state: GraphsState, payload: {graphIndex: number, value: boolean}) { + [GraphsMutation.SetLogScaleYAxis](state: GraphsState, payload: { graphIndex: number; value: boolean }) { state.config[payload.graphIndex].settings.logScaleYAxis = payload.value; }, - [GraphsMutation.SetLockYAxis](state: GraphsState, payload: {graphIndex: number, value: boolean}) { + [GraphsMutation.SetLockYAxis](state: GraphsState, payload: { graphIndex: number; value: boolean }) { state.config[payload.graphIndex].settings.lockYAxis = payload.value; }, - [GraphsMutation.SetYAxisRange](state: GraphsState, payload: {graphIndex: number, value: YAxisRange}) { + [GraphsMutation.SetYAxisRange](state: GraphsState, payload: { graphIndex: number; value: YAxisRange }) { state.config[payload.graphIndex].settings.yAxisRange = payload.value; }, diff --git a/app/static/src/app/store/graphs/state.ts b/app/static/src/app/store/graphs/state.ts index fe2fed0f..f10e528b 100644 --- a/app/static/src/app/store/graphs/state.ts +++ b/app/static/src/app/store/graphs/state.ts @@ -23,4 +23,3 @@ export const defaultGraphSettings = (): GraphSettings => ({ lockYAxis: false, yAxisRange: [0, 0] }); - diff --git a/app/static/tests/e2e/options.etest.ts b/app/static/tests/e2e/options.etest.ts index 7eefaec0..990ffb02 100644 --- a/app/static/tests/e2e/options.etest.ts +++ b/app/static/tests/e2e/options.etest.ts @@ -232,7 +232,7 @@ test.describe("Options Tab tests", () => { }); test("overrides axes lock if log scale toggle changes", async ({ page }) => { - await page.locator(".lock-y-axis input").click(); + await page.locator(".lock-y-axis input").click(); const tickSelector = ":nth-match(.plotly .ytick text, 2)"; await expect(await page.innerHTML(tickSelector)).toBe("0.2M"); diff --git a/app/static/tests/mocks.ts b/app/static/tests/mocks.ts index 597cd6f8..470b462d 100644 --- a/app/static/tests/mocks.ts +++ b/app/static/tests/mocks.ts @@ -26,7 +26,7 @@ import { SensitivityVariationType } from "../src/app/store/sensitivity/state"; import { VersionsState } from "../src/app/store/versions/state"; -import {defaultGraphSettings, GraphsState} from "../src/app/store/graphs/state"; +import { defaultGraphSettings, GraphsState } from "../src/app/store/graphs/state"; import { LanguageState } from "../translationPackage/store/state"; import { Language } from "../src/app/types/languageTypes"; import { noSensitivityUpdateRequired } from "../src/app/store/sensitivity/sensitivity"; diff --git a/app/static/tests/unit/components/graphSettings.test.ts b/app/static/tests/unit/components/graphSettings.test.ts index 3efb17e0..8fc3ee6f 100644 --- a/app/static/tests/unit/components/graphSettings.test.ts +++ b/app/static/tests/unit/components/graphSettings.test.ts @@ -3,7 +3,7 @@ import { shallowMount } from "@vue/test-utils"; import { BasicState } from "../../../src/app/store/basic/state"; import GraphSettings from "../../../src/app/components/GraphSettings.vue"; import { GraphsMutation } from "../../../src/app/store/graphs/mutations"; -import {defaultGraphSettings} from "../../../src/app/store/graphs/state"; +import { defaultGraphSettings } from "../../../src/app/store/graphs/state"; describe("GraphSettings", () => { const mockSetLogScaleYAxis = jest.fn(); @@ -65,9 +65,7 @@ describe("GraphSettings", () => { }); it("renders as expected when fit plot", () => { - const wrapper = getWrapper(undefined, true, [ - { lockYAxis: true, logScaleYAxis: true, yAxisRange: [0, 10] } - ]); + const wrapper = getWrapper(undefined, true, [{ lockYAxis: true, logScaleYAxis: true, yAxisRange: [0, 10] }]); const inputs = wrapper.findAll("input"); expect((inputs[0].element as HTMLInputElement).checked).toBe(false); expect((inputs[1].element as HTMLInputElement).checked).toBe(false); @@ -79,9 +77,9 @@ describe("GraphSettings", () => { expect((inputs[0].element as HTMLInputElement).checked).toBe(false); await inputs[0].setValue(true); expect(mockSetLogScaleYAxis).toHaveBeenCalledTimes(1); - expect(mockSetLogScaleYAxis.mock.calls[0][1]).toStrictEqual({graphIndex: 1, value: true}); + expect(mockSetLogScaleYAxis.mock.calls[0][1]).toStrictEqual({ graphIndex: 1, value: true }); expect(mockSetFitLogScaleYAxis).not.toHaveBeenCalled(); - }) + }); it("commits change to log scale y axis setting, when fit plot", async () => { const wrapper = getWrapper(undefined, true); @@ -99,7 +97,7 @@ describe("GraphSettings", () => { expect((inputs[1].element as HTMLInputElement).checked).toBe(false); await inputs[1].setValue(true); expect(mockSetLockYAxis).toHaveBeenCalledTimes(1); - expect(mockSetLockYAxis.mock.calls[0][1]).toStrictEqual({graphIndex: 1, value: true}); + expect(mockSetLockYAxis.mock.calls[0][1]).toStrictEqual({ graphIndex: 1, value: true }); expect(mockSetFitLockYAxis).not.toHaveBeenCalled(); }); diff --git a/app/static/tests/unit/components/mixins/baseSensitivity.test.ts b/app/static/tests/unit/components/mixins/baseSensitivity.test.ts index eddcc8cd..292f6575 100644 --- a/app/static/tests/unit/components/mixins/baseSensitivity.test.ts +++ b/app/static/tests/unit/components/mixins/baseSensitivity.test.ts @@ -8,6 +8,7 @@ import { BaseSensitivityMutation } from "../../../../src/app/store/sensitivity/m import { BaseSensitivityAction } from "../../../../src/app/store/sensitivity/actions"; import { getters as graphGetters } from "../../../../src/app/store/graphs/getters"; import { mockGraphsState } from "../../../mocks"; +import { defaultGraphSettings } from "../../../../src/app/store/graphs/state"; describe("baseSensitivity mixin", () => { const mockSensSetUserSummaryDownloadFileName = jest.fn(); @@ -29,7 +30,14 @@ describe("baseSensitivity mixin", () => { graphs: { namespaced: true, state: mockGraphsState({ - config: [{ id: "123", selectedVariables, unselectedVariables: [] }] + config: [ + { + id: "123", + selectedVariables, + unselectedVariables: [], + settings: defaultGraphSettings() + } + ] }), getters: graphGetters }, diff --git a/app/static/tests/unit/components/options/linkData.test.ts b/app/static/tests/unit/components/options/linkData.test.ts index 31901c54..8a6d55b2 100644 --- a/app/static/tests/unit/components/options/linkData.test.ts +++ b/app/static/tests/unit/components/options/linkData.test.ts @@ -6,6 +6,7 @@ import { getters } from "../../../../src/app/store/fitData/getters"; import { mockFitDataState, mockFitState, mockGraphsState, mockModelState } from "../../../mocks"; import { FitDataAction } from "../../../../src/app/store/fitData/actions"; import { getters as graphGetters } from "../../../../src/app/store/graphs/getters"; +import { defaultGraphSettings } from "../../../../src/app/store/graphs/state"; describe("LinkData", () => { const getWrapper = (includeColumns = true, includeValidModel = true, mockUpdateLinkedVariable = jest.fn()) => { @@ -33,7 +34,14 @@ describe("LinkData", () => { graphs: { namespaced: true, state: mockGraphsState({ - config: [{ id: "123", selectedVariables: ["I", "R"], unselectedVariables: [] }] + config: [ + { + id: "123", + selectedVariables: ["I", "R"], + unselectedVariables: [], + settings: defaultGraphSettings() + } + ] }), getters: graphGetters }, diff --git a/app/static/tests/unit/components/run/runPlot.test.ts b/app/static/tests/unit/components/run/runPlot.test.ts index eb86e01f..4ce886c9 100644 --- a/app/static/tests/unit/components/run/runPlot.test.ts +++ b/app/static/tests/unit/components/run/runPlot.test.ts @@ -1,6 +1,4 @@ // Mock plotly before import RunTab, which indirectly imports plotly via WodinPlot -import {defaultGraphSettings} from "../../../../src/app/store/graphs/state"; - jest.mock("plotly.js-basic-dist-min", () => {}); /* eslint-disable import/first */ @@ -11,7 +9,8 @@ import WodinPlot from "../../../../src/app/components/WodinPlot.vue"; import { BasicState } from "../../../../src/app/store/basic/state"; import { FitDataGetter } from "../../../../src/app/store/fitData/getters"; import { getters as runGetters } from "../../../../src/app/store/run/getters"; -import { mockBasicState, mockGraphsState, mockRunState } from "../../../mocks"; +import { mockGraphsState, mockRunState } from "../../../mocks"; +import { defaultGraphSettings } from "../../../../src/app/store/graphs/state"; describe("RunPlot", () => { const mockSolution = jest.fn().mockReturnValue({ @@ -583,7 +582,7 @@ describe("RunPlot", () => { const wrapper = shallowMount(RunPlot, { props: { fadePlot: true, - graphConfig: {...graphConfig, selectedVariables: []} + graphConfig: { ...graphConfig, selectedVariables: [] } }, global: { plugins: [store] diff --git a/app/static/tests/unit/components/run/runStochasticPlot.test.ts b/app/static/tests/unit/components/run/runStochasticPlot.test.ts index a64b12df..1d515bf0 100644 --- a/app/static/tests/unit/components/run/runStochasticPlot.test.ts +++ b/app/static/tests/unit/components/run/runStochasticPlot.test.ts @@ -1,6 +1,4 @@ // Mock plotly before import RunStochasticTab, which indirectly imports plotly via WodinPlot -import {defaultGraphSettings} from "../../../../src/app/store/graphs/state"; - jest.mock("plotly.js-basic-dist-min", () => {}); /* eslint-disable import/first */ @@ -11,6 +9,7 @@ import WodinPlot from "../../../../src/app/components/WodinPlot.vue"; import { StochasticState } from "../../../../src/app/store/stochastic/state"; import RunPlot from "../../../../src/app/components/run/RunPlot.vue"; import { mockGraphsState, mockModelState, mockRunState } from "../../../mocks"; +import { defaultGraphSettings } from "../../../../src/app/store/graphs/state"; describe("RunPlot for stochastic", () => { const mockSolution = jest.fn().mockReturnValue({ diff --git a/app/static/tests/unit/components/run/runTab.test.ts b/app/static/tests/unit/components/run/runTab.test.ts index 63b25498..2acb4961 100644 --- a/app/static/tests/unit/components/run/runTab.test.ts +++ b/app/static/tests/unit/components/run/runTab.test.ts @@ -155,13 +155,19 @@ describe("RunTab", () => { expect(plots.at(0)!.props("graphIndex")).toBe(0); expect(plots.at(0)!.props("fadePlot")).toBe(false); expect(plots.at(0)!.props("linkedXAxis")).toStrictEqual({ autorange: true }); - expect(plots.at(0)!.props("graphConfig")) - .toStrictEqual({ id: "123", selectedVariables: ["S"], unselectedVariables: [] }); + expect(plots.at(0)!.props("graphConfig")).toStrictEqual({ + id: "123", + selectedVariables: ["S"], + unselectedVariables: [] + }); expect(plots.at(1)!.props("graphIndex")).toBe(1); expect(plots.at(1)!.props("fadePlot")).toBe(false); expect(plots.at(1)!.props("linkedXAxis")).toStrictEqual({ autorange: true }); - expect(plots.at(1)!.props("graphConfig")) - .toStrictEqual({ id: "456", selectedVariables: [], unselectedVariables: [] }); + expect(plots.at(1)!.props("graphConfig")).toStrictEqual({ + id: "456", + selectedVariables: [], + unselectedVariables: [] + }); // Download button disabled because there is no model solution const downloadBtn = wrapper.find("button#download-btn"); @@ -201,14 +207,20 @@ describe("RunTab", () => { expect(plots.at(0)!.props("graphIndex")).toBe(0); expect(plots.at(0)!.props("fadePlot")).toBe(false); expect(plots.at(0)!.props("linkedXAxis")).toStrictEqual({ autorange: true }); - expect(plots.at(0)!.props("graphConfig")) - .toStrictEqual({ id: "123", selectedVariables: ["S"], unselectedVariables: [] }); + expect(plots.at(0)!.props("graphConfig")).toStrictEqual({ + id: "123", + selectedVariables: ["S"], + unselectedVariables: [] + }); expect(plots.at(1)!.props("graphIndex")).toBe(1); expect(plots.at(1)!.props("fadePlot")).toBe(false); expect(plots.at(1)!.props("linkedXAxis")).toStrictEqual({ autorange: true }); - expect(plots.at(1)!.props("graphConfig")) - .toStrictEqual({ id: "456", selectedVariables: [], unselectedVariables: [] }); + expect(plots.at(1)!.props("graphConfig")).toStrictEqual({ + id: "456", + selectedVariables: [], + unselectedVariables: [] + }); expect(wrapper.findComponent(ActionRequiredMessage).props("message")).toBe(""); expect(wrapper.findComponent(RunPlot).exists()).toBe(false); diff --git a/app/static/tests/unit/components/sensitivity/sensitivitySummaryDownload.test.ts b/app/static/tests/unit/components/sensitivity/sensitivitySummaryDownload.test.ts index fcdeecf1..cc8ddf85 100644 --- a/app/static/tests/unit/components/sensitivity/sensitivitySummaryDownload.test.ts +++ b/app/static/tests/unit/components/sensitivity/sensitivitySummaryDownload.test.ts @@ -15,6 +15,7 @@ import LoadingSpinner from "../../../../src/app/components/LoadingSpinner.vue"; import { ModelGetter } from "../../../../src/app/store/model/getters"; import { getters as graphsGetters } from "../../../../src/app/store/graphs/getters"; import { mockGraphsState } from "../../../mocks"; +import { defaultGraphSettings } from "../../../../src/app/store/graphs/state"; describe("SensitivitySummaryDownload", () => { const mockSetUserSummaryDownloadFileName = jest.fn(); @@ -88,7 +89,8 @@ describe("SensitivitySummaryDownload", () => { { id: "123", selectedVariables: ["S"], - unselectedVariables: [] + unselectedVariables: [], + settings: defaultGraphSettings() } ] }), diff --git a/app/static/tests/unit/components/sensitivity/sensitivitySummaryPlot.test.ts b/app/static/tests/unit/components/sensitivity/sensitivitySummaryPlot.test.ts index 36a3f3b4..6263edd0 100644 --- a/app/static/tests/unit/components/sensitivity/sensitivitySummaryPlot.test.ts +++ b/app/static/tests/unit/components/sensitivity/sensitivitySummaryPlot.test.ts @@ -202,8 +202,12 @@ describe("SensitivitySummaryPlot", () => { graphs: { namespaced: true, state: { - settings: { logScaleYAxis }, - config: [{ selectedVariables }] + config: [ + { + selectedVariables, + settings: { logScaleYAxis } + } + ] } } } @@ -530,7 +534,7 @@ describe("SensitivitySummaryPlot", () => { it("redraws plot if graph setting changes", async () => { const wrapper = getWrapper(); - (store!.state as any).graphs.settings.logScaleYAxis = true; + (store!.state as any).graphs.config[0].settings.logScaleYAxis = true; await nextTick(); expect(mockPlotlyNewPlot).toHaveBeenCalledTimes(2); }); diff --git a/app/static/tests/unit/components/wodinPlot.test.ts b/app/static/tests/unit/components/wodinPlot.test.ts index aed29016..6c837fde 100644 --- a/app/static/tests/unit/components/wodinPlot.test.ts +++ b/app/static/tests/unit/components/wodinPlot.test.ts @@ -1,6 +1,4 @@ // Mock the import of plotly so we can mock Plotly methods -import {defaultGraphSettings} from "../../../src/app/store/graphs/state"; - jest.mock("plotly.js-basic-dist-min", () => ({ newPlot: jest.fn(), react: jest.fn(), @@ -12,12 +10,13 @@ jest.mock("plotly.js-basic-dist-min", () => ({ /* eslint-disable import/first */ import { shallowMount, VueWrapper } from "@vue/test-utils"; import { nextTick } from "vue"; -import * as plotly from "plotly.js-basic-dist-min"; import Vuex, { Store } from "vuex"; +import * as plotly from "plotly.js-basic-dist-min"; import WodinPlot from "../../../src/app/components/WodinPlot.vue"; import WodinPlotDataSummary from "../../../src/app/components/WodinPlotDataSummary.vue"; import { BasicState } from "../../../src/app/store/basic/state"; import { GraphsMutation } from "../../../src/app/store/graphs/mutations"; +import { defaultGraphSettings } from "../../../src/app/store/graphs/state"; describe("WodinPlot", () => { const mockPlotlyNewPlot = jest.spyOn(plotly, "newPlot"); @@ -66,9 +65,7 @@ describe("WodinPlot", () => { const mockSetYAxisRange = jest.fn(); const mockSetFitYAxisRange = jest.fn(); - const getStore = ( - fitGraphSettings = defaultGraphSettings() - ) => { + const getStore = (fitGraphSettings = defaultGraphSettings()) => { return new Vuex.Store({ modules: { graphs: { @@ -261,15 +258,18 @@ describe("WodinPlot", () => { it("relayout preserves locked y axis range", async () => { const store = getStore(); - const wrapper = getWrapper({ - graphConfig: { - settings: { - ...defaultGraphSettings(), - lockYAxis: true, - yAxisRange: [10, 20] + const wrapper = getWrapper( + { + graphConfig: { + settings: { + ...defaultGraphSettings(), + lockYAxis: true, + yAxisRange: [10, 20] + } } - } - }, store); + }, + store + ); const { relayout } = wrapper.vm as any; const relayoutEvent = { "xaxis.autorange": false, @@ -413,11 +413,14 @@ describe("WodinPlot", () => { it("renders y axis log scale if graph setting is set", async () => { const store = getStore(); - const wrapper = getWrapper({ - graphConfig: { - settings: {...defaultGraphSettings(), logScaleYAxis: true} - } - }, store); + const wrapper = getWrapper( + { + graphConfig: { + settings: { ...defaultGraphSettings(), logScaleYAxis: true } + } + }, + store + ); mockPlotElementOn(wrapper); wrapper.setProps({ redrawWatches: [{} as any] }); @@ -430,7 +433,7 @@ describe("WodinPlot", () => { }); it("renders y axis log scale if fit graph setting is set, and fitPlot is true", async () => { - const store = getStore({...defaultGraphSettings(), logScaleYAxis: true}); + const store = getStore({ ...defaultGraphSettings(), logScaleYAxis: true }); const wrapper = getWrapper({ fitPlot: true }, store); mockPlotElementOn(wrapper); @@ -445,15 +448,18 @@ describe("WodinPlot", () => { it("locks axes if graph setting is set", async () => { const store = getStore(); - const wrapper = getWrapper({ - graphConfig: { - settings: { - ...defaultGraphSettings(), - lockYAxis: true, - yAxisRange: [10, 20] + const wrapper = getWrapper( + { + graphConfig: { + settings: { + ...defaultGraphSettings(), + lockYAxis: true, + yAxisRange: [10, 20] + } } - } - }, store); + }, + store + ); mockPlotElementOn(wrapper); wrapper.setProps({ redrawWatches: [{} as any] }); @@ -495,13 +501,13 @@ describe("WodinPlot", () => { await nextTick(); await wrapper.setProps({ redrawWatches: [{} as any] }); await nextTick(); - expect(mockSetYAxisRange.mock.calls[0][1]).toStrictEqual({graphIndex: 0, value: [1, 2]}); + expect(mockSetYAxisRange.mock.calls[0][1]).toStrictEqual({ graphIndex: 0, value: [1, 2] }); expect(mockSetFitYAxisRange).not.toHaveBeenCalled(); }); it("commits SetFitYAxisRange on drawPlot, when fitPlot is true", async () => { const store = getStore(); - const wrapper = getWrapper({fitPlot: true}, store); + const wrapper = getWrapper({ fitPlot: true }, store); mockPlotElementOn(wrapper); (wrapper.vm as any).plot = { @@ -517,11 +523,14 @@ describe("WodinPlot", () => { it("relayout uses graph settings log scale y axis value", async () => { const store = getStore(); - const wrapper = getWrapper({ - graphConfig: { - settings: {...defaultGraphSettings(), logScaleYAxis: true} - } - }, store); + const wrapper = getWrapper( + { + graphConfig: { + settings: { ...defaultGraphSettings(), logScaleYAxis: true } + } + }, + store + ); mockPlotElementOn(wrapper); wrapper.setProps({ redrawWatches: [{} as any] }); @@ -570,7 +579,7 @@ describe("WodinPlot", () => { await wrapper.setProps({ graphConfig: { - settings: {...defaultGraphSettings(), logScaleYAxis: true} + settings: { ...defaultGraphSettings(), logScaleYAxis: true } } }); await nextTick(); @@ -592,7 +601,7 @@ describe("WodinPlot", () => { await wrapper.setProps({ fadePlot: true, graphConfig: { - settings: {...defaultGraphSettings(), logScaleYAxis: true} + settings: { ...defaultGraphSettings(), logScaleYAxis: true } } }); await nextTick(); diff --git a/app/static/tests/unit/serialiser.test.ts b/app/static/tests/unit/serialiser.test.ts index 8ac8ce84..0a8d255b 100644 --- a/app/static/tests/unit/serialiser.test.ts +++ b/app/static/tests/unit/serialiser.test.ts @@ -25,7 +25,7 @@ import { Language } from "../../src/app/types/languageTypes"; import { AdvancedOptions } from "../../src/app/types/responseTypes"; import { AdvancedComponentType } from "../../src/app/store/run/state"; import { noSensitivityUpdateRequired } from "../../src/app/store/sensitivity/sensitivity"; -import {defaultGraphSettings} from "../../src/app/store/graphs/state"; +import { defaultGraphSettings } from "../../src/app/store/graphs/state"; jest.mock("../../src/app/utils", () => { return { @@ -827,6 +827,5 @@ describe("serialise", () => { deserialiseState(target, serialised); expect(target.graphs.fitGraphSettings.logScaleYAxis).toBe(false); expect(target.graphs.config[0].settings.logScaleYAxis).toBe(false); - }); }); diff --git a/app/static/tests/unit/store/graphs/actions.test.ts b/app/static/tests/unit/store/graphs/actions.test.ts index 22a0f92b..6beebecf 100644 --- a/app/static/tests/unit/store/graphs/actions.test.ts +++ b/app/static/tests/unit/store/graphs/actions.test.ts @@ -3,7 +3,7 @@ import { actions, GraphsAction } from "../../../../src/app/store/graphs/actions" import { GraphsMutation } from "../../../../src/app/store/graphs/mutations"; import { AppType } from "../../../../src/app/store/appState/state"; import { FitDataAction } from "../../../../src/app/store/fitData/actions"; -import {defaultGraphSettings} from "../../../../src/app/store/graphs/state"; +import { defaultGraphSettings } from "../../../../src/app/store/graphs/state"; describe("Graphs actions", () => { const modelState = { diff --git a/app/static/tests/unit/store/graphs/getters.test.ts b/app/static/tests/unit/store/graphs/getters.test.ts index 45ac6904..e1a11c9c 100644 --- a/app/static/tests/unit/store/graphs/getters.test.ts +++ b/app/static/tests/unit/store/graphs/getters.test.ts @@ -1,12 +1,15 @@ import { mockGraphsState } from "../../../mocks"; import { getters, GraphsGetter } from "../../../../src/app/store/graphs/getters"; +import { defaultGraphSettings } from "../../../../src/app/store/graphs/state"; describe("GraphsGetters", () => { + const settings = defaultGraphSettings(); + it("gets allSelectedVariables", () => { const state = mockGraphsState({ config: [ - { id: "123", selectedVariables: ["a", "b"], unselectedVariables: ["c", "d"] }, - { id: "456", selectedVariables: ["d"], unselectedVariables: ["a", "b", "c"] } + { id: "123", selectedVariables: ["a", "b"], unselectedVariables: ["c", "d"], settings }, + { id: "456", selectedVariables: ["d"], unselectedVariables: ["a", "b", "c"], settings } ] }); expect((getters[GraphsGetter.allSelectedVariables] as any)(state)).toStrictEqual(["a", "b", "d"]); diff --git a/app/static/tests/unit/store/graphs/mutations.test.ts b/app/static/tests/unit/store/graphs/mutations.test.ts index 0d969823..7d364722 100644 --- a/app/static/tests/unit/store/graphs/mutations.test.ts +++ b/app/static/tests/unit/store/graphs/mutations.test.ts @@ -1,5 +1,5 @@ import { mutations } from "../../../../src/app/store/graphs/mutations"; -import {defaultGraphSettings, GraphsState} from "../../../../src/app/store/graphs/state"; +import { defaultGraphSettings, GraphsState } from "../../../../src/app/store/graphs/state"; import { mockGraphsState, mockModelState } from "../../../mocks"; describe("Graphs mutations", () => { @@ -21,12 +21,12 @@ describe("Graphs mutations", () => { }); it("sets logScaleYAxis", () => { - mutations.SetLogScaleYAxis(state, {graphIndex: 1, value: true}); + mutations.SetLogScaleYAxis(state, { graphIndex: 1, value: true }); expect(state.config[1].settings.logScaleYAxis).toBe(true); }); it("sets lockYAxis", () => { - mutations.SetLockYAxis(state, {graphIndex: 1, value: true}); + mutations.SetLockYAxis(state, { graphIndex: 1, value: true }); expect(state.config[1].settings.lockYAxis).toBe(true); }); @@ -72,7 +72,12 @@ describe("Graphs mutations", () => { unselectedVariables: ["y"], settings }); - expect(testState.config[0]).toStrictEqual({ id: "123", selectedVariables: [], unselectedVariables: [], settings }); + expect(testState.config[0]).toStrictEqual({ + id: "123", + selectedVariables: [], + unselectedVariables: [], + settings + }); }); it("AddGraph pushes new graph to config", () => { @@ -80,7 +85,12 @@ describe("Graphs mutations", () => { const testState = mockGraphsState({ config: [{ id: "123", selectedVariables: ["a"], unselectedVariables: ["b", "c"], settings }] }); - mutations.AddGraph(testState, { id: "456", selectedVariables: [], unselectedVariables: ["b", "a", "c"], settings }); + mutations.AddGraph(testState, { + id: "456", + selectedVariables: [], + unselectedVariables: ["b", "a", "c"], + settings + }); expect(testState.config).toStrictEqual([ { id: "123", selectedVariables: ["a"], unselectedVariables: ["b", "c"], settings }, { id: "456", selectedVariables: [], unselectedVariables: ["b", "a", "c"], settings } From 3aa1ae2590c971a695c8d10f4291acebde148cd1 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 25 Jul 2024 10:04:04 +0100 Subject: [PATCH 52/80] formatting --- app/static/src/app/components/run/RunPlot.vue | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/static/src/app/components/run/RunPlot.vue b/app/static/src/app/components/run/RunPlot.vue index 0b787410..b2a69c18 100644 --- a/app/static/src/app/components/run/RunPlot.vue +++ b/app/static/src/app/components/run/RunPlot.vue @@ -30,11 +30,10 @@ import { ParameterSet } from "../../store/run/state"; import { GraphConfig } from "../../store/graphs/state"; const props = defineProps({ - fadePlot: Boolean, - graphIndex: {type: Number, default: 0}, - graphConfig: {type: Object as PropType, required: true}, - linkedXAxis: {type: Object as PropType | null>, required: true} - + fadePlot: Boolean, + graphIndex: { type: Number, default: 0 }, + graphConfig: { type: Object as PropType, required: true }, + linkedXAxis: { type: Object as PropType | null>, required: true } }); const emit = defineEmits<{ From 79a700a1e6e2240a185e8cafa6c202ef0aab3f5e Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 25 Jul 2024 15:52:06 +0100 Subject: [PATCH 53/80] changes following review --- app/static/src/app/components/GraphSettings.vue | 1 + app/static/src/app/components/WodinPlot.vue | 2 ++ app/static/src/app/components/run/RunPlot.vue | 2 +- app/static/src/app/components/run/RunStochasticPlot.vue | 2 +- app/static/src/app/store/graphs/state.ts | 4 ++++ 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/static/src/app/components/GraphSettings.vue b/app/static/src/app/components/GraphSettings.vue index c2dcc77e..29fe9aa4 100644 --- a/app/static/src/app/components/GraphSettings.vue +++ b/app/static/src/app/components/GraphSettings.vue @@ -24,6 +24,7 @@ export default defineComponent({ required: false, default: -1 }, + // fitPlot should be true if graphIndex is not provided - in this case we update the single fit plot settings fitPlot: { type: Boolean, required: true diff --git a/app/static/src/app/components/WodinPlot.vue b/app/static/src/app/components/WodinPlot.vue index a95dc3de..86bd9fc3 100644 --- a/app/static/src/app/components/WodinPlot.vue +++ b/app/static/src/app/components/WodinPlot.vue @@ -65,6 +65,8 @@ export default defineComponent({ required: false, default: -1 }, + // We could look up graphConfig from graphIndex - however this causes a specific reactivity bug + // when the last plot is deleted and the plot tries to observe the old maximum index before it is updated. graphConfig: { type: Object as PropType, required: true diff --git a/app/static/src/app/components/run/RunPlot.vue b/app/static/src/app/components/run/RunPlot.vue index b2a69c18..658731ce 100644 --- a/app/static/src/app/components/run/RunPlot.vue +++ b/app/static/src/app/components/run/RunPlot.vue @@ -31,7 +31,7 @@ import { GraphConfig } from "../../store/graphs/state"; const props = defineProps({ fadePlot: Boolean, - graphIndex: { type: Number, default: 0 }, + graphIndex: { type: Number, required: true }, graphConfig: { type: Object as PropType, required: true }, linkedXAxis: { type: Object as PropType | null>, required: true } }); diff --git a/app/static/src/app/components/run/RunStochasticPlot.vue b/app/static/src/app/components/run/RunStochasticPlot.vue index fe253757..b7fb36d5 100644 --- a/app/static/src/app/components/run/RunStochasticPlot.vue +++ b/app/static/src/app/components/run/RunStochasticPlot.vue @@ -31,7 +31,7 @@ export default defineComponent({ fadePlot: Boolean, graphIndex: { type: Number, - default: 0 + required: true }, graphConfig: { type: Object as PropType, diff --git a/app/static/src/app/store/graphs/state.ts b/app/static/src/app/store/graphs/state.ts index f10e528b..86a341a7 100644 --- a/app/static/src/app/store/graphs/state.ts +++ b/app/static/src/app/store/graphs/state.ts @@ -1,11 +1,15 @@ export type YAxisRange = [number, number]; +// User-adjustable settings for a given graph - log/linear y axis scale and lock y axis are selected via checkboxes, +// yAxisRange is saved on data/variable update in order to implement lock y axis. export interface GraphSettings { logScaleYAxis: boolean; lockYAxis: boolean; yAxisRange: YAxisRange; } +// GraphConfig holds all the configuration for the user-configurable array of graphs which will be shown on the Run +// and Sensitivity tabs, both the variable selections and the graph settings. export interface GraphConfig { id: string; // We need to keep a persistent id to identify configs in vue when a graph is deleted from the array selectedVariables: string[]; From 80d7375756f3f8fecfacc136a4d9510dc42a552d Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 25 Jul 2024 16:02:43 +0100 Subject: [PATCH 54/80] fix unit tests --- app/static/tests/unit/components/run/runPlot.test.ts | 1 + app/static/tests/unit/components/run/runStochasticPlot.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/app/static/tests/unit/components/run/runPlot.test.ts b/app/static/tests/unit/components/run/runPlot.test.ts index a2c51c6c..31445288 100644 --- a/app/static/tests/unit/components/run/runPlot.test.ts +++ b/app/static/tests/unit/components/run/runPlot.test.ts @@ -273,6 +273,7 @@ describe("RunPlot", () => { const wrapper = shallowMount(RunPlot, { props: { fadePlot: false, + graphIndex: 0, graphConfig }, global: { diff --git a/app/static/tests/unit/components/run/runStochasticPlot.test.ts b/app/static/tests/unit/components/run/runStochasticPlot.test.ts index 1d515bf0..5add1a83 100644 --- a/app/static/tests/unit/components/run/runStochasticPlot.test.ts +++ b/app/static/tests/unit/components/run/runStochasticPlot.test.ts @@ -172,6 +172,7 @@ describe("RunPlot for stochastic", () => { const wrapper = shallowMount(RunPlot, { props: { fadePlot: false, + graphIndex: 0, graphConfig }, global: { From c598d715a30bce3e2fb1a716d058636bd1c6c670 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 25 Jul 2024 16:22:17 +0100 Subject: [PATCH 55/80] Show Time label on final graph only --- app/static/src/app/components/WodinPlot.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/static/src/app/components/WodinPlot.vue b/app/static/src/app/components/WodinPlot.vue index 86bd9fc3..65917d6e 100644 --- a/app/static/src/app/components/WodinPlot.vue +++ b/app/static/src/app/components/WodinPlot.vue @@ -119,9 +119,11 @@ export default defineComponent({ const defaultLayout = (): Partial => { // Get generic layout, which will be modifed dynamically as required + // Show Time label on final graph only + const xAxisTitle = props.fitPlot || props.graphIndex === store.state.graphs.config.length - 1 ? "Time" : ""; const result = { margin: { ...margin }, - xaxis: { title: "Time" }, + xaxis: { title: xAxisTitle }, yaxis: { type: yAxisType.value } }; if (legendWidth.value) { From 263f598006b0ff9803ad0b3a7b77040050200b8b Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Thu, 25 Jul 2024 16:42:02 +0100 Subject: [PATCH 56/80] add tests --- .../tests/unit/components/wodinPlot.test.ts | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/app/static/tests/unit/components/wodinPlot.test.ts b/app/static/tests/unit/components/wodinPlot.test.ts index 6c837fde..4227f6a0 100644 --- a/app/static/tests/unit/components/wodinPlot.test.ts +++ b/app/static/tests/unit/components/wodinPlot.test.ts @@ -51,6 +51,7 @@ describe("WodinPlot", () => { } ]; const mockPlotDataFn = jest.fn().mockReturnValue(mockPlotData); + const settings = defaultGraphSettings(); const defaultProps = { fadePlot: false, endTime: 99, @@ -58,8 +59,8 @@ describe("WodinPlot", () => { plotData: mockPlotDataFn, placeholderMessage: "No data available", fitPlot: false, - graphIndex: 0, - graphConfig: { settings: defaultGraphSettings() } + graphIndex: 1, + graphConfig: { settings } }; const mockSetYAxisRange = jest.fn(); @@ -71,7 +72,8 @@ describe("WodinPlot", () => { graphs: { namespaced: true, state: { - fitGraphSettings + fitGraphSettings, + config: [{ settings }, { settings }] }, mutations: { [GraphsMutation.SetYAxisRange]: mockSetYAxisRange, @@ -501,7 +503,7 @@ describe("WodinPlot", () => { await nextTick(); await wrapper.setProps({ redrawWatches: [{} as any] }); await nextTick(); - expect(mockSetYAxisRange.mock.calls[0][1]).toStrictEqual({ graphIndex: 0, value: [1, 2] }); + expect(mockSetYAxisRange.mock.calls[0][1]).toStrictEqual({ graphIndex: 1, value: [1, 2] }); expect(mockSetFitYAxisRange).not.toHaveBeenCalled(); }); @@ -665,4 +667,43 @@ describe("WodinPlot", () => { expect(mockPlotDataFn.mock.calls[1][1]).toBe(5); expect(mockPlotDataFn.mock.calls[1][2]).toBe(1000); }); + + it("does not display Time xAxis label if not final config in array", async () => { + const store = getStore(); + const wrapper = getWrapper( + { + graphIndex: 0 + }, + store + ); + mockPlotElementOn(wrapper); + + wrapper.setProps({ redrawWatches: [{} as any] }); + await nextTick(); + expect(mockPlotlyNewPlot.mock.calls[0][2]).toStrictEqual({ + margin: { t: 25 }, + xaxis: { title: "" }, + yaxis: { type: "linear" } + }); + }); + + it("does display Time xAxis label if fitPlot is true", async () => { + const store = getStore(); + const wrapper = getWrapper( + { + graphIndex: 0, + fitPlot: true + }, + store + ); + mockPlotElementOn(wrapper); + + wrapper.setProps({ redrawWatches: [{} as any] }); + await nextTick(); + expect(mockPlotlyNewPlot.mock.calls[0][2]).toStrictEqual({ + margin: { t: 25 }, + xaxis: { title: "Time" }, + yaxis: { type: "linear" } + }); + }); }); From 68ba6b1ddcfeff064e01692524260dfc28694cb1 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Fri, 26 Jul 2024 13:26:34 +0100 Subject: [PATCH 57/80] e2e tests --- app/static/tests/e2e/fit.etest.ts | 2 ++ app/static/tests/e2e/run.etest.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/app/static/tests/e2e/fit.etest.ts b/app/static/tests/e2e/fit.etest.ts index 2664ddea..58b242eb 100644 --- a/app/static/tests/e2e/fit.etest.ts +++ b/app/static/tests/e2e/fit.etest.ts @@ -145,6 +145,8 @@ test.describe("Wodin App model fit tests", () => { const plotSelector = ".wodin-right .wodin-content div.mt-4 .js-plotly-plot"; + await expect(await page.locator(".plotly .xtitle").textContent()).toBe("Time"); + // Test line is plotted for fit trace and data points const linesSelector = `${plotSelector} .scatterlayer .trace .lines path`; await expect((await page.locator(linesSelector).getAttribute("d"))!.startsWith("M")).toBe(true); diff --git a/app/static/tests/e2e/run.etest.ts b/app/static/tests/e2e/run.etest.ts index 18cd38d8..96e3f695 100644 --- a/app/static/tests/e2e/run.etest.ts +++ b/app/static/tests/e2e/run.etest.ts @@ -47,4 +47,18 @@ test.describe("Run Tab", () => { // 3. Check - using x axis ticks - that the expected x axis values are shown on all three graphs. await expectXTicks(page, 3, [15, 20, 25, 30, 35, 40]); }); + + test("x axis Time label is shown for final plot only", async ({ page }) => { + await page.goto("/apps/day1"); + await addGraphWithVariable(page, 1); + const firstGraph = page.locator(":nth-match(.plotly, 1)"); + const secondGraph = page.locator(":nth-match(.plotly, 2)"); + + await expect(await firstGraph.locator(".xtitle")).not.toBeVisible(); + await expect(await secondGraph.locator(".xtitle").textContent()).toBe("Time"); + + // Delete second config - the Time label should be shown on the first graph + await page.locator(":nth-match(button.delete-graph, 2)").click(); + await expect(await firstGraph.locator(".xtitle").textContent()).toBe("Time"); + }); }); From a9f3e0bb89a3a9d5a14c5467838cb5094df98a28 Mon Sep 17 00:00:00 2001 From: EmmaLRussell Date: Fri, 26 Jul 2024 14:02:14 +0100 Subject: [PATCH 58/80] add graphConfig prop to sensitivity components --- .../sensitivity/SensitivitySummaryPlot.vue | 10 ++++++---- .../app/components/sensitivity/SensitivityTab.vue | 9 +++++++-- .../components/sensitivity/SensitivityTracesPlot.vue | 12 ++++++------ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/static/src/app/components/sensitivity/SensitivitySummaryPlot.vue b/app/static/src/app/components/sensitivity/SensitivitySummaryPlot.vue index b44747d7..f1efaeeb 100644 --- a/app/static/src/app/components/sensitivity/SensitivitySummaryPlot.vue +++ b/app/static/src/app/components/sensitivity/SensitivitySummaryPlot.vue @@ -10,7 +10,7 @@