mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #13375 from neoformit/tools-ui-refactor
Refactor _FieldText to vue.js
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-form-checkbox v-model="currentStatus" class="ui-switch" switch>
|
||||
Set value for this optional select field?
|
||||
</b-form-checkbox>
|
||||
<FormText
|
||||
v-if="currentStatus"
|
||||
:id="id"
|
||||
v-model="currentValue"
|
||||
:readonly="readonly"
|
||||
:value="value"
|
||||
:area="area"
|
||||
:placeholder="placeholder"
|
||||
:multiple="multiple"
|
||||
:datalist="datalist"
|
||||
:type="type" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormText from "./FormText";
|
||||
export default {
|
||||
components: {
|
||||
FormText,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
default: "",
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "text",
|
||||
},
|
||||
area: {
|
||||
// <textarea> instead of <input> element
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
// Allow multiple entries to be created
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
datalist: {
|
||||
// Display list of suggestions in autocomplete dialog
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
status: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentValue: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit("input", val);
|
||||
},
|
||||
},
|
||||
currentStatus: {
|
||||
get() {
|
||||
return this.status || this.value !== null;
|
||||
},
|
||||
set(val) {
|
||||
this.status = Boolean(val);
|
||||
if (this.status) {
|
||||
this.currentValue = "";
|
||||
} else {
|
||||
this.currentValue = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -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("");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<b-row align-v="center">
|
||||
<b-col>
|
||||
<b-form-textarea
|
||||
v-if="inputArea"
|
||||
:id="id"
|
||||
v-model="currentValue"
|
||||
:class="['ui-text-area', cls]"
|
||||
:readonly="readonly"
|
||||
:placeholder="placeholder"
|
||||
:style="style" />
|
||||
<b-form-input
|
||||
v-else
|
||||
:id="id"
|
||||
v-model="currentValue"
|
||||
:class="['ui-input', cls]"
|
||||
:readonly="readonly"
|
||||
:placeholder="placeholder"
|
||||
:style="style"
|
||||
:type="acceptedTypes"
|
||||
:list="`${id}-datalist`" />
|
||||
<datalist v-if="datalist && !inputArea" :id="`${id}-datalist`">
|
||||
<option v-for="data in datalist" :key="data.value" :label="data.label" :value="data.value" />
|
||||
</datalist>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
// String; Array for multiple
|
||||
default: "",
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "text",
|
||||
},
|
||||
area: {
|
||||
// <textarea> instead of <input> element
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
// Allow multiple entries to be created
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
cls: {
|
||||
// Refers to an optional custom css class name
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
datalist: {
|
||||
// Display list of suggestions in autocomplete dialog
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
acceptedTypes() {
|
||||
return ["text", "password"].includes(this.type) ? this.type : "text";
|
||||
},
|
||||
currentValue: {
|
||||
get() {
|
||||
const v = this.value ?? "";
|
||||
if (Array.isArray(v)) {
|
||||
if (v.length === 0) {
|
||||
return "";
|
||||
}
|
||||
return this.multiple
|
||||
? this.value.reduce((str_value, v) => str_value + String(v) + "\n", "")
|
||||
: String(this.value[0]);
|
||||
}
|
||||
return String(v);
|
||||
},
|
||||
set(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.$emit("input", newVal);
|
||||
}
|
||||
},
|
||||
},
|
||||
inputArea() {
|
||||
return this.area || this.multiple;
|
||||
},
|
||||
style() {
|
||||
return this.color
|
||||
? {
|
||||
color: this.color,
|
||||
"border-color": this.color,
|
||||
}
|
||||
: null;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.ui-input-linked {
|
||||
border-left-width: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="showField" class="ui-form-field" :data-label="props.title">
|
||||
<FormBoolean v-if="props.type === 'boolean'" :id="props.id" v-model="currentValue" />
|
||||
<FormHidden v-else-if="isHiddenType" :id="props.id" v-model="currentValue" :info="attrs['info']" />
|
||||
@@ -226,6 +227,33 @@ const isOptional = computed(() => !isRequired.value && attrs.value["optional"] !
|
||||
:min="attrs.min"
|
||||
:type="props.type ?? 'float'"
|
||||
:workflow-building-mode="workflowBuildingMode" />
|
||||
<FormOptionalText
|
||||
v-else-if="props.type === 'select' && attrs.is_workflow && attrs.optional"
|
||||
:id="id"
|
||||
v-model="currentValue"
|
||||
:readonly="attrs.readonly"
|
||||
:value="attrs.value"
|
||||
:area="attrs.area"
|
||||
:placeholder="attrs.placeholder"
|
||||
:multiple="attrs.multiple"
|
||||
:datalist="attrs.datalist"
|
||||
:type="props.type" />
|
||||
<FormText
|
||||
v-else-if="
|
||||
['text', 'password'].includes(props.type) ||
|
||||
(attrs.is_workflow && ['select', 'genomebuild', 'data_column', 'group_tag'].includes(props.type))
|
||||
"
|
||||
:id="id"
|
||||
v-model="currentValue"
|
||||
:readonly="attrs.readonly"
|
||||
:value="attrs.value"
|
||||
:area="attrs.area"
|
||||
:placeholder="attrs.placeholder"
|
||||
:color="attrs.color"
|
||||
:multiple="attrs.multiple"
|
||||
:cls="attrs.cls"
|
||||
:datalist="attrs.datalist"
|
||||
:type="props.type" />
|
||||
<FormSelection
|
||||
v-else-if="props.type === 'select' && ['radio', 'checkboxes'].includes(attrs.display)"
|
||||
:id="id"
|
||||
@@ -237,7 +265,7 @@ const isOptional = computed(() => !isRequired.value && attrs.value["optional"] !
|
||||
:multiple="attrs.multiple" />
|
||||
<FormColor v-else-if="props.type === 'color'" :id="props.id" v-model="currentValue" />
|
||||
<FormDirectory v-else-if="props.type === 'directory_uri'" v-model="currentValue" />
|
||||
<FormRulesEdit v-else-if="type == 'rules'" v-model="currentValue" :target="attrs.target" />
|
||||
<FormRulesEdit v-else-if="props.type == 'rules'" v-model="currentValue" :target="attrs.target" />
|
||||
<FormParameter
|
||||
v-else-if="backbonejs"
|
||||
:id="props.id"
|
||||
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
},
|
||||
nodeContentId: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
nodeAnnotation: {
|
||||
type: String,
|
||||
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
},
|
||||
nodeLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
nodeInputs: {
|
||||
type: Array,
|
||||
|
||||
@@ -100,7 +100,7 @@ export class WorkflowRunModel {
|
||||
name: wp_name,
|
||||
type: "text",
|
||||
color: `hsl( ${++wp_count * 100}, 70%, 30% )`,
|
||||
style: "ui-form-wp-source",
|
||||
cls: "ui-input-linked",
|
||||
links: [],
|
||||
optional: true,
|
||||
});
|
||||
@@ -122,8 +122,7 @@ export class WorkflowRunModel {
|
||||
wp_input.links.push(step);
|
||||
input.wp_linked = true;
|
||||
input.type = "text";
|
||||
input.backdrop = true;
|
||||
input.style = "ui-form-wp-target";
|
||||
input.cls = "ui-input-linked";
|
||||
});
|
||||
});
|
||||
_.each(step.replacement_parameters, (wp_name) => {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user