diff --git a/client/src/components/Form/Elements/FormOptionalText.test.js b/client/src/components/Form/Elements/FormOptionalText.test.js new file mode 100644 index 00000000000..ab859e305bb --- /dev/null +++ b/client/src/components/Form/Elements/FormOptionalText.test.js @@ -0,0 +1,35 @@ +import { mount } from "@vue/test-utils"; +import { getLocalVue } from "tests/jest/helpers"; +import FormOptionalText from "./FormOptionalText"; + +const localVue = getLocalVue(); + +describe("FormOptionalText", () => { + const mountFormOptionalText = async (props) => + await mount(FormOptionalText, { + propsData: props, + localVue, + }); + + it("should display existing values", async () => { + const v = "somevalue"; + const wrapper = await mountFormOptionalText({ value: v }); + const el = wrapper.find("input"); + expect(el.element.checked).toEqual(true); + const elText = wrapper.find("input[type='text']"); + expect(elText.element.value).toEqual(v); + await el.setChecked(false); + expect(wrapper.emitted().input[0][0]).toEqual(null); + await el.setChecked(true); + expect(wrapper.emitted().input[1][0]).toEqual(""); + }); + + it("should initialize with null if value does not exist", async () => { + const v = null; + const wrapper = await mountFormOptionalText({ value: v }); + const el = wrapper.find("input"); + expect(el.element.checked).toEqual(false); + await wrapper.setProps({ value: "" }); + expect(el.element.checked).toEqual(true); + }); +}); diff --git a/client/src/components/Form/Elements/FormOptionalText.vue b/client/src/components/Form/Elements/FormOptionalText.vue new file mode 100644 index 00000000000..d399d48af58 --- /dev/null +++ b/client/src/components/Form/Elements/FormOptionalText.vue @@ -0,0 +1,91 @@ + + + diff --git a/client/src/components/Form/Elements/FormText.test.js b/client/src/components/Form/Elements/FormText.test.js new file mode 100644 index 00000000000..0a4c2953b06 --- /dev/null +++ b/client/src/components/Form/Elements/FormText.test.js @@ -0,0 +1,78 @@ +import { mount } from "@vue/test-utils"; +import { getLocalVue } from "tests/jest/helpers"; +import FormText from "./FormText"; + +const localVue = getLocalVue(); + +describe("FormText", () => { + const mountFormText = async (props) => + await mount(FormText, { + propsData: props, + localVue, + }); + + it("should render the appropriate input type", async () => { + const wrapper = await mountFormText({}); + const el = wrapper.find("input[type='text']"); + expect(el.exists()).toBe(true); + await wrapper.setProps({ type: "password" }); + const elPassword = wrapper.find("input[type='password']"); + expect(elPassword.exists()).toBe(true); + await wrapper.setProps({ type: "anyothertype" }); + const elOtherType = wrapper.find("input[type='text']"); + expect(elOtherType.exists()).toBe(true); + }); + + it("should render the appropriate component", async () => { + const wrapper = await mountFormText({ area: true }); + const el = wrapper.find("textarea"); + expect(el.exists()).toBe(true); + await wrapper.setProps({ area: false, multiple: true }); + const elMultiple = wrapper.find("textarea"); + expect(elMultiple.exists()).toBe(true); + }); + + it("should be able to render a datalist", async () => { + const wrapper = await mountFormText({ id: "text-input", datalist: ["one", "two", "three"] }); + let el = wrapper.find("datalist"); + expect(el.exists()).toBe(true); + el = wrapper.find("option"); + expect(el.exists()).toBe(true); + el = wrapper.find("[list='text-input-datalist']"); + expect(el.exists()).toBe(true); + }); + + it("should be able to render border and text color from props", async () => { + const wrapper = await mountFormText({}); + const el = wrapper.find("input"); + expect(el.element.style).toMatchObject({}); + await wrapper.setProps({ color: "green" }); + expect(el.element.style).toMatchObject({ color: "green", "border-color": "green" }); + await wrapper.setProps({ cls: "my-custom-class" }); + expect(el.element.classList).toContain("my-custom-class"); + }); + + it("should be able to accept a default value", async () => { + const v = "something"; + const wrapper = await mountFormText({ value: v }); + const el = wrapper.find("input"); + expect(el.props("value")).toEqual(v); + }); + + it("should be able to accept an array as value", async () => { + const v = ["field_1", "field_2", "field_3"]; + const wrapper = await mountFormText({ value: v }); + const el = wrapper.find("input"); + expect(el.props("value")).toEqual("field_1"); + await wrapper.setProps({ multiple: true }); + const elMultiple = wrapper.find("textarea"); + expect(elMultiple.props("value")).toEqual("field_1\nfield_2\nfield_3\n"); + }); + + it("should be able to accept an empty array as value", async () => { + const v = []; + const wrapper = await mountFormText({ value: v }); + const el = wrapper.find("input"); + expect(el.props("value")).toEqual(""); + }); +}); diff --git a/client/src/components/Form/Elements/FormText.vue b/client/src/components/Form/Elements/FormText.vue new file mode 100644 index 00000000000..8a07155ae4f --- /dev/null +++ b/client/src/components/Form/Elements/FormText.vue @@ -0,0 +1,118 @@ + + + + diff --git a/client/src/components/Form/FormElement.test.js b/client/src/components/Form/FormElement.test.js index ef20e91452d..79a7770442f 100644 --- a/client/src/components/Form/FormElement.test.js +++ b/client/src/components/Form/FormElement.test.js @@ -2,7 +2,7 @@ import { mount } from "@vue/test-utils"; import { getLocalVue } from "tests/jest/helpers"; import FormElement from "./FormElement"; import FormHidden from "./Elements/FormHidden"; -import FormInput from "./Elements/FormInput"; +import FormText from "./Elements/FormText"; const localVue = getLocalVue(); @@ -70,12 +70,17 @@ describe("FormElement", () => { it("check type matching", async () => { await wrapper.setProps({ type: "text" }); - expect(wrapper.findComponent(FormInput).exists()).toBe(true); + expect(wrapper.findComponent(FormText).exists()).toBe(true); expect(wrapper.findComponent(FormHidden).exists()).toBe(false); await wrapper.setProps({ attributes: { titleonly: true } }); expect(wrapper.findComponent(FormHidden).exists()).toBe(true); - expect(wrapper.findComponent(FormInput).exists()).toBe(false); + expect(wrapper.findComponent(FormText).exists()).toBe(false); + }); + + it("displays as the correct type if is_workflow is true", async () => { + await wrapper.setProps({ type: "data_column", attributes: { is_workflow: true } }); + expect(wrapper.findComponent(FormText).exists()).toBe(true); }); it("marks required values", async () => { diff --git a/client/src/components/Form/FormElement.vue b/client/src/components/Form/FormElement.vue index 3906594e94a..9df07595047 100644 --- a/client/src/components/Form/FormElement.vue +++ b/client/src/components/Form/FormElement.vue @@ -7,6 +7,8 @@ import FormSelection from "./Elements/FormSelection.vue"; import FormColor from "./Elements/FormColor.vue"; import FormDirectory from "./Elements/FormDirectory.vue"; import FormNumber from "./Elements/FormNumber.vue"; +import FormText from "./Elements/FormText.vue"; +import FormOptionalText from "./Elements/FormOptionalText.vue"; import FormRulesEdit from "./Elements/FormRulesEdit.vue"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { ref, computed, useAttrs } from "vue"; @@ -214,7 +216,6 @@ const isOptional = computed(() => !isRequired.value && attrs.value["optional"] ! - optional -
@@ -226,6 +227,33 @@ const isOptional = computed(() => !isRequired.value && attrs.value["optional"] ! :min="attrs.min" :type="props.type ?? 'float'" :workflow-building-mode="workflowBuildingMode" /> + + !isRequired.value && attrs.value["optional"] ! :multiple="attrs.multiple" /> - + { diff --git a/client/src/utils/navigation/navigation.yml b/client/src/utils/navigation/navigation.yml index 1167759fe3a..869da96d6ae 100644 --- a/client/src/utils/navigation/navigation.yml +++ b/client/src/utils/navigation/navigation.yml @@ -486,7 +486,7 @@ tool_form: options: '.tool-dropdown' execute: 'button#execute' parameter_div: 'div.ui-form-element[id="form-element-${parameter}"]' - parameter_checkbox: 'div.ui-form-element[id="form-element-${parameter}"] .ui-switch div' + parameter_checkbox: 'div.ui-form-element[id="form-element-${parameter}"] .ui-switch' parameter_input: 'div.ui-form-element[id="form-element-${parameter}"] .ui-input' parameter_textarea: 'div.ui-form-element[id="form-element-${parameter}"] textarea' reference: '.formatted-reference' diff --git a/lib/galaxy_test/selenium/test_personal_information.py b/lib/galaxy_test/selenium/test_personal_information.py index 4e5985814fc..74fdcb7e99b 100644 --- a/lib/galaxy_test/selenium/test_personal_information.py +++ b/lib/galaxy_test/selenium/test_personal_information.py @@ -135,7 +135,9 @@ class TestManageInformation(SeleniumTestCase): element.send_keys(new_input_text) def get_address_input_field(self, address_form, input_field_label): - return address_form.find_element(self.by.CSS_SELECTOR, f"[data-label='{input_field_label}'] > input") + return address_form.find_element( + self.by.CSS_SELECTOR, f"[data-label='{input_field_label}'] > div > div > input" + ) class TestDeleteCurrentAccount(SeleniumTestCase): diff --git a/lib/galaxy_test/selenium/test_workflow_editor.py b/lib/galaxy_test/selenium/test_workflow_editor.py index f7de0383af0..88ef6c9c9ea 100644 --- a/lib/galaxy_test/selenium/test_workflow_editor.py +++ b/lib/galaxy_test/selenium/test_workflow_editor.py @@ -101,11 +101,8 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows): node = editor.node._(label="select_from_dataset_optional") node.title.wait_for_and_click() self.components.tool_form.parameter_checkbox(parameter="select_single").wait_for_and_click() - # External (selenium-side) debounce hack for old backbone input - # TODO: remove when form elements are all converted. - self.components.tool_form.parameter_input(parameter="select_single").wait_for_and_send_keys("parameter valu") - self.sleep_for(self.wait_types.UX_RENDER) - self.components.tool_form.parameter_input(parameter="select_single").wait_for_and_send_keys("e") + self.components.tool_form.parameter_input(parameter="select_single").wait_for_and_send_keys("parameter value") + # onSetData does an extra POST to build_modules, so we need to wait for that ... self.sleep_for(self.wait_types.UX_RENDER) self.assert_workflow_has_changes_and_save() workflow = self.workflow_populator.download_workflow(workflow_id)