Merge pull request #13246 from guerler/fix_highlighting

Initialize error attribute in input model for workflows
This commit is contained in:
Dannon
2022-01-28 07:33:53 -05:00
committed by GitHub
6 changed files with 140 additions and 12 deletions
@@ -0,0 +1,108 @@
import { mount } from "@vue/test-utils";
import { getLocalVue } from "jest/helpers";
import FormDisplay from "./FormDisplay";
const localVue = getLocalVue();
describe("FormDisplay", () => {
let wrapper;
let propsData;
beforeEach(() => {
propsData = {
id: "input",
inputs: [
{
name: "text_name",
value: "text_value",
help: "text_help",
type: "text",
},
{
type: "conditional",
name: "conditional_section",
test_param: {
name: "conditional_bool",
label: "conditional_bool_label",
type: "boolean",
value: "true",
help: "",
},
cases: [
{
value: "true",
inputs: [
{
name: "conditional_leaf",
value: "conditional_leaf_value",
type: "text",
},
],
},
{
value: "false",
inputs: [],
},
],
},
],
errors: {},
validationScrollTo: [],
replaceParams: {},
prefix: "",
sustainRepeats: false,
sustainConditionals: false,
collapsedEnableText: "Enable",
collapsedDisableText: "Disable",
collapsedEnableIcon: "collapsedEnableIcon",
collapsedDisableIcon: "collapsedDisableIcon",
};
wrapper = mount(FormDisplay, {
propsData,
localVue,
stubs: {},
});
});
it("error highlighting", async () => {
await wrapper.setProps({
validationScrollTo: ["text_name", "error_message"],
});
const error = wrapper.find(".ui-form-error-text");
expect(error.text()).toEqual("error_message");
await wrapper.setProps({
errors: { text_name: "error_message_2" },
});
expect(error.text()).toEqual("error_message_2");
});
it("parameter replacement", async () => {
const textInput = wrapper.find("[id='field-text_name']");
const conditionalInput = wrapper.find("[id='field-conditional_section|conditional_leaf']");
expect(textInput.element.value).toEqual("text_value");
expect(conditionalInput.element.value).toEqual("conditional_leaf_value");
await wrapper.setProps({
replaceParams: {
text_name: "replaced",
"conditional_section|conditional_leaf": "conditional_leaf_value_new",
},
});
expect(textInput.element.value).toEqual("replaced");
expect(conditionalInput.element.value).toEqual("conditional_leaf_value_new");
});
it("conditional switch", async () => {
const conditionalBool = wrapper.find("[type='checkbox']");
await conditionalBool.setChecked(false);
const conditionalInputUnchecked = wrapper.findAll("[id='field-conditional_section|conditional_leaf']");
expect(conditionalInputUnchecked.length).toEqual(0);
await conditionalBool.setChecked(true);
const conditionalInputChecked = wrapper.findAll("[id='field-conditional_section|conditional_leaf']");
expect(conditionalInputChecked.length).toEqual(1);
await wrapper.setProps({
sustainConditionals: true,
});
const conditionalBoolDisabled = wrapper.findAll("[type='checkbox']");
expect(conditionalBoolDisabled.length).toEqual(0);
});
});
+9 -3
View File
@@ -9,12 +9,12 @@
:collapsed-enable-icon="collapsedEnableIcon"
:collapsed-disable-text="collapsedDisableText"
:collapsed-disable-icon="collapsedDisableIcon"
:errors="errors"
:on-change="onChange"
:on-change-form="onChangeForm" />
</template>
<script>
import Vue from "vue";
import FormInputs from "./FormInputs";
import { visitInputs, validateInputs, matchErrors, getElementId } from "./utilities";
export default {
@@ -108,7 +108,7 @@ export default {
errors() {
this.resetError();
if (this.errors) {
const errorMessages = matchErrors(this.errors, this.formIndex);
const errorMessages = matchErrors(this.formIndex, this.errors);
for (const inputId in errorMessages) {
this.setError(inputId, errorMessages[inputId]);
}
@@ -147,6 +147,9 @@ export default {
},
onCloneInputs() {
this.formInputs = JSON.parse(JSON.stringify(this.inputs));
visitInputs(this.formInputs, (input) => {
Vue.set(input, "error", null);
});
this.onCreateIndex();
},
onChange(refreshOnChange) {
@@ -178,7 +181,10 @@ export default {
const elementId = getElementId(inputId);
const element = this.$el.querySelector(`#${elementId}`);
if (element) {
document.querySelector(".center-panel").scrollTo(0, this.getOffsetTop(element));
const centerPanel = document.querySelector(".center-panel");
if (centerPanel) {
centerPanel.scrollTo(0, this.getOffsetTop(element));
}
}
}
}
+4 -5
View File
@@ -72,16 +72,15 @@ export function matchCase(input, value) {
}
/** Match server validation response to highlight errors
* @param{dict} response - Nested dictionary with error messages
* @param{dict} index - Index of input elements
* @param{dict} response - Nested dictionary with error messages
*/
export function matchErrors(response, index) {
export function matchErrors(index, response) {
var result = {};
function search(id, head) {
if (typeof head === "string") {
var input_id = index[id];
if (input_id) {
result[input_id] = head;
if (index[id]) {
result[id] = head;
}
} else {
for (var i in head) {
+18 -1
View File
@@ -1,4 +1,4 @@
import { visitInputs, validateInputs, matchCase } from "./utilities";
import { visitInputs, validateInputs, matchCase, matchErrors } from "./utilities";
import toolModel from "./test-data/tool";
function visitInputsString(inputs) {
@@ -117,4 +117,21 @@ describe("form component utilities", () => {
result = validateInputs(index, values);
expect(JSON.stringify(result)).toEqual('["input_c","Please provide data for this input."]');
});
it("test error matching", () => {
const index = {
input_a: {},
input_b_0: {},
"input_c|input_d": {},
};
const values = {
input_a: "error_a",
input_b: ["error_b"],
input_c: { input_d: "error_d" },
};
const result = matchErrors(index, values);
expect(result["input_a"]).toEqual("error_a");
expect(result["input_b_0"]).toEqual("error_b");
expect(result["input_c|input_d"]).toEqual("error_d");
});
});
-2
View File
@@ -142,7 +142,6 @@ export default {
showSuccess: false,
showError: false,
showExecuting: false,
error: null,
formConfig: {},
formData: {},
remapAllowed: false,
@@ -159,7 +158,6 @@ export default {
jobResponse: {},
validationInternal: null,
validationScrollTo: null,
validationErrors: null,
currentVersion: this.version,
};
},
+1 -1
View File
@@ -135,7 +135,7 @@ export class WorkflowRunModel {
// select fields are shown for dynamic fields if all putative data inputs are available,
// or if an explicit reference is specified as data_ref and available
_.each(this.steps, (step, i) => {
_.each(this.steps, (step) => {
if (step.step_type == "tool") {
var data_resolved = true;
visitInputs(step.inputs, (input, name, context) => {