mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 02:40:53 -06:00
DEV: Define form template field inputs (#20430)
This commit is contained in:
parent
8b67a534a0
commit
666b4a7e6b
@ -1,4 +1,4 @@
|
||||
<div class="form-templates--form">
|
||||
<div class="form-templates__form">
|
||||
<div class="control-group">
|
||||
<label for="template-name">
|
||||
{{i18n "admin.form_templates.new_template_form.name.label"}}
|
||||
@ -6,24 +6,38 @@
|
||||
<TextField
|
||||
@value={{this.templateName}}
|
||||
@name="template-name"
|
||||
@class="form-templates--form-name-input"
|
||||
@class="form-templates__form-name-input"
|
||||
@placeholderKey="admin.form_templates.new_template_form.name.placeholder"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="control-group form-templates--quick-insert-field-buttons">
|
||||
<span>
|
||||
{{I18n "admin.form_templates.quick_insert_fields.add_new_field"}}
|
||||
</span>
|
||||
{{#each this.quickInsertFields as |field|}}
|
||||
<div class="control-group form-templates__editor">
|
||||
<div class="form-templates__quick-insert-field-buttons">
|
||||
<span>
|
||||
{{I18n "admin.form_templates.quick_insert_fields.add_new_field"}}
|
||||
</span>
|
||||
{{#each this.quickInsertFields as |field|}}
|
||||
<DButton
|
||||
@class="btn-flat btn-icon-text quick-insert-{{field.type}}"
|
||||
@icon={{field.icon}}
|
||||
@label="admin.form_templates.quick_insert_fields.{{field.type}}"
|
||||
@action={{this.onInsertField}}
|
||||
@actionParam={{field.type}}
|
||||
/>
|
||||
{{/each}}
|
||||
<DButton
|
||||
@class="btn-flat btn-icon-text quick-insert-{{field.type}}"
|
||||
@icon={{field.icon}}
|
||||
@label="admin.form_templates.quick_insert_fields.{{field.type}}"
|
||||
@action={{this.onInsertField}}
|
||||
@actionParam={{field.type}}
|
||||
class="btn-flat btn-icon-text form-templates__validations-modal-button"
|
||||
@label="admin.form_templates.validations_modal.button_title"
|
||||
@icon="check-circle"
|
||||
@action={{this.showValidationOptionsModal}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
<DButton
|
||||
@class="form-templates__preview-button"
|
||||
@icon="eye"
|
||||
@label="admin.form_templates.new_template_form.preview"
|
||||
@action={{this.showPreview}}
|
||||
@disabled={{this.disablePreviewButton}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
@ -36,7 +50,7 @@
|
||||
@label="admin.form_templates.new_template_form.submit"
|
||||
@icon="check"
|
||||
@action={{this.onSubmit}}
|
||||
@disabled={{this.formSubmitted}}
|
||||
@disabled={{this.disableSubmitButton}}
|
||||
/>
|
||||
|
||||
<DButton
|
||||
|
@ -6,14 +6,15 @@ import I18n from "I18n";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { templateFormFields } from "admin/lib/template-form-fields";
|
||||
import FormTemplate from "admin/models/form-template";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
export default class FormTemplateForm extends Component {
|
||||
@service router;
|
||||
@service dialog;
|
||||
@tracked formSubmitted = false;
|
||||
@tracked templateContent = this.args.model?.template || "";
|
||||
@tracked templateName = this.args.model?.name || "";
|
||||
isEditing = this.args.model?.id ? true : false;
|
||||
templateName = this.args.model?.name;
|
||||
quickInsertFields = [
|
||||
{
|
||||
type: "checkbox",
|
||||
@ -41,6 +42,17 @@ export default class FormTemplateForm extends Component {
|
||||
},
|
||||
];
|
||||
|
||||
get disablePreviewButton() {
|
||||
return Boolean(!this.templateName.length || !this.templateContent.length);
|
||||
}
|
||||
|
||||
get disableSubmitButton() {
|
||||
return (
|
||||
Boolean(!this.templateName.length || !this.templateContent.length) ||
|
||||
this.formSubmitted
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
onSubmit() {
|
||||
if (!this.formSubmitted) {
|
||||
@ -54,27 +66,17 @@ export default class FormTemplateForm extends Component {
|
||||
|
||||
if (this.isEditing) {
|
||||
postData["id"] = this.args.model.id;
|
||||
|
||||
FormTemplate.updateTemplate(this.args.model.id, postData)
|
||||
.then(() => {
|
||||
this.formSubmitted = false;
|
||||
this.router.transitionTo("adminCustomizeFormTemplates.index");
|
||||
})
|
||||
.catch((e) => {
|
||||
popupAjaxError(e);
|
||||
this.formSubmitted = false;
|
||||
});
|
||||
} else {
|
||||
FormTemplate.createTemplate(postData)
|
||||
.then(() => {
|
||||
this.formSubmitted = false;
|
||||
this.router.transitionTo("adminCustomizeFormTemplates.index");
|
||||
})
|
||||
.catch((e) => {
|
||||
popupAjaxError(e);
|
||||
this.formSubmitted = false;
|
||||
});
|
||||
}
|
||||
|
||||
FormTemplate.createOrUpdateTemplate(postData)
|
||||
.then(() => {
|
||||
this.formSubmitted = false;
|
||||
this.router.transitionTo("adminCustomizeFormTemplates.index");
|
||||
})
|
||||
.catch((e) => {
|
||||
popupAjaxError(e);
|
||||
this.formSubmitted = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
@ -106,4 +108,33 @@ export default class FormTemplateForm extends Component {
|
||||
this.templateContent += `\n${structure}`;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
showValidationOptionsModal() {
|
||||
return showModal("admin-form-template-validation-options", {
|
||||
admin: true,
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
showPreview() {
|
||||
const data = {
|
||||
name: this.templateName,
|
||||
template: this.templateContent,
|
||||
};
|
||||
|
||||
if (this.isEditing) {
|
||||
data["id"] = this.args.model.id;
|
||||
}
|
||||
|
||||
FormTemplate.validateTemplate(data)
|
||||
.then(() => {
|
||||
return showModal("form-template-form-preview", {
|
||||
model: {
|
||||
content: this.templateContent,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,7 @@ export default class FormTemplateRowItem extends Component {
|
||||
|
||||
@action
|
||||
viewTemplate() {
|
||||
showModal("admin-customize-form-template-view", {
|
||||
admin: true,
|
||||
showModal("customize-form-template-view", {
|
||||
model: this.args.template,
|
||||
refreshModel: this.args.refreshModel,
|
||||
});
|
||||
|
@ -0,0 +1,35 @@
|
||||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class AdminFormTemplateValidationOptions extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
TABLE_HEADER_KEYS = ["key", "type", "description"];
|
||||
VALIDATION_KEYS = ["required", "minimum", "maximum", "pattern"];
|
||||
|
||||
get tableHeaders() {
|
||||
const translatedHeaders = [];
|
||||
this.TABLE_HEADER_KEYS.forEach((header) => {
|
||||
translatedHeaders.push(
|
||||
I18n.t(`admin.form_templates.validations_modal.table_headers.${header}`)
|
||||
);
|
||||
});
|
||||
|
||||
return translatedHeaders;
|
||||
}
|
||||
|
||||
get validations() {
|
||||
const translatedValidations = [];
|
||||
const prefix = "admin.form_templates.validations_modal.validations";
|
||||
this.VALIDATION_KEYS.forEach((validation) => {
|
||||
translatedValidations.push({
|
||||
key: I18n.t(`${prefix}.${validation}.key`),
|
||||
type: I18n.t(`${prefix}.${validation}.type`),
|
||||
description: I18n.t(`${prefix}.${validation}.description`),
|
||||
});
|
||||
});
|
||||
|
||||
return translatedValidations;
|
||||
}
|
||||
}
|
@ -1,70 +1,75 @@
|
||||
// TODO(@keegan): Add translations for template strings
|
||||
import I18n from "I18n";
|
||||
|
||||
export const templateFormFields = [
|
||||
{
|
||||
type: "checkbox",
|
||||
structure: `- type: checkbox
|
||||
choices:
|
||||
- "Option 1"
|
||||
- "Option 2"
|
||||
- "Option 3"
|
||||
attributes:
|
||||
label: "Enter question here"
|
||||
description: "Enter description here"
|
||||
validations:
|
||||
required: true`,
|
||||
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
||||
validations:
|
||||
# ${I18n.t("admin.form_templates.field_placeholders.validations")}`,
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
structure: `- type: input
|
||||
attributes:
|
||||
label: "Enter input label here"
|
||||
description: "Enter input description here"
|
||||
placeholder: "Enter input placeholder here"
|
||||
validations:
|
||||
required: true`,
|
||||
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
||||
placeholder: "${I18n.t(
|
||||
"admin.form_templates.field_placeholders.placeholder"
|
||||
)}"
|
||||
validations:
|
||||
# ${I18n.t("admin.form_templates.field_placeholders.validations")}`,
|
||||
},
|
||||
{
|
||||
type: "textarea",
|
||||
structure: `- type: textarea
|
||||
attributes:
|
||||
label: "Enter textarea label here"
|
||||
description: "Enter textarea description here"
|
||||
placeholder: "Enter textarea placeholder here"
|
||||
validations:
|
||||
required: true`,
|
||||
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
||||
placeholder: "${I18n.t(
|
||||
"admin.form_templates.field_placeholders.placeholder"
|
||||
)}"
|
||||
validations:
|
||||
# ${I18n.t("admin.form_templates.field_placeholders.validations")}`,
|
||||
},
|
||||
{
|
||||
type: "dropdown",
|
||||
structure: `- type: dropdown
|
||||
choices:
|
||||
- "Option 1"
|
||||
- "Option 2"
|
||||
- "Option 3"
|
||||
- "${I18n.t("admin.form_templates.field_placeholders.choices.first")}"
|
||||
- "${I18n.t("admin.form_templates.field_placeholders.choices.second")}"
|
||||
- "${I18n.t("admin.form_templates.field_placeholders.choices.third")}"
|
||||
attributes:
|
||||
label: "Enter dropdown label here"
|
||||
description: "Enter dropdown description here"
|
||||
validations:
|
||||
required: true`,
|
||||
none_label: "${I18n.t(
|
||||
"admin.form_templates.field_placeholders.none_label"
|
||||
)}"
|
||||
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
||||
filterable: false
|
||||
validations:
|
||||
# ${I18n.t("admin.form_templates.field_placeholders.validations")}`,
|
||||
},
|
||||
{
|
||||
type: "upload",
|
||||
structure: `- type: upload
|
||||
attributes:
|
||||
file_types: "jpg, png, gif"
|
||||
label: "Enter upload label here"
|
||||
description: "Enter upload description here"`,
|
||||
allow_multiple: false
|
||||
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
||||
validations:
|
||||
# ${I18n.t("admin.form_templates.field_placeholders.validations")}`,
|
||||
},
|
||||
{
|
||||
type: "multiselect",
|
||||
structure: `- type: multiple_choice
|
||||
structure: `- type: multi-select
|
||||
choices:
|
||||
- "Option 1"
|
||||
- "Option 2"
|
||||
- "Option 3"
|
||||
- "${I18n.t("admin.form_templates.field_placeholders.choices.first")}"
|
||||
- "${I18n.t("admin.form_templates.field_placeholders.choices.second")}"
|
||||
- "${I18n.t("admin.form_templates.field_placeholders.choices.third")}"
|
||||
attributes:
|
||||
label: "Enter multiple choice label here"
|
||||
description: "Enter multiple choice description here"
|
||||
validations:
|
||||
required: true`,
|
||||
none_label: "${I18n.t(
|
||||
"admin.form_templates.field_placeholders.none_label"
|
||||
)}"
|
||||
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
||||
validations:
|
||||
# ${I18n.t("admin.form_templates.field_placeholders.validations")}`,
|
||||
},
|
||||
];
|
||||
|
@ -18,6 +18,14 @@ FormTemplate.reopenClass({
|
||||
});
|
||||
},
|
||||
|
||||
createOrUpdateTemplate(data) {
|
||||
if (data.id) {
|
||||
return this.updateTemplate(data.id, data);
|
||||
} else {
|
||||
return this.createTemplate(data);
|
||||
}
|
||||
},
|
||||
|
||||
deleteTemplate(id) {
|
||||
return ajax(`/admin/customize/form-templates/${id}.json`, {
|
||||
type: "DELETE",
|
||||
@ -35,4 +43,11 @@ FormTemplate.reopenClass({
|
||||
return model.form_template;
|
||||
});
|
||||
},
|
||||
|
||||
validateTemplate(data) {
|
||||
return ajax(`/admin/customize/form-templates/preview.json`, {
|
||||
type: "GET",
|
||||
data,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -2,7 +2,7 @@
|
||||
<FormTemplate::InfoHeader />
|
||||
|
||||
{{#if this.model}}
|
||||
<table class="form-templates--table grid">
|
||||
<table class="form-templates__table grid">
|
||||
<thead>
|
||||
<th class="col heading">
|
||||
{{i18n "admin.form_templates.list_table.headings.name"}}
|
||||
|
@ -0,0 +1,23 @@
|
||||
<DModalBody
|
||||
@class="form-templates__validation-options"
|
||||
@title="admin.form_templates.validations_modal.modal_title"
|
||||
>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{{#each this.tableHeaders as |header|}}
|
||||
<th>{{header}}</th>
|
||||
{{/each}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each this.validations as |item|}}
|
||||
<tr>
|
||||
<td><pre>{{item.key}}</pre></td>
|
||||
<td>{{item.type}}</td>
|
||||
<td>{{item.description}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</DModalBody>
|
@ -0,0 +1,6 @@
|
||||
<div class="control-group form-template-field" data-field-type="checkbox">
|
||||
<label class="form-template-field__label">
|
||||
<Input class="form-template-field__checkbox" @type="checkbox" />
|
||||
{{@attributes.label}}
|
||||
</label>
|
||||
</div>
|
@ -0,0 +1,15 @@
|
||||
<div class="control-group form-template-field" data-field-type="dropdown">
|
||||
{{#if @attributes.label}}
|
||||
<label class="form-template-field__label">{{@attributes.label}}</label>
|
||||
{{/if}}
|
||||
<ComboBox
|
||||
@class="form-template-field__dropdown"
|
||||
@content={{@choices}}
|
||||
@nameProperty={{null}}
|
||||
@valueProperty={{null}}
|
||||
@options={{hash
|
||||
translatedNone=@attributes.none_label
|
||||
filterable=@attributes.filterable
|
||||
}}
|
||||
/>
|
||||
</div>
|
@ -0,0 +1,10 @@
|
||||
<div class="control-group form-template-field" data-field-type="input">
|
||||
{{#if @attributes.label}}
|
||||
<label class="form-template-field__label">{{@attributes.label}}</label>
|
||||
{{/if}}
|
||||
<Input
|
||||
class="form-template-field__input"
|
||||
@type="text"
|
||||
placeholder={{@attributes.placeholder}}
|
||||
/>
|
||||
</div>
|
@ -0,0 +1,15 @@
|
||||
<div class="control-group form-template-field" data-field-type="multi-select">
|
||||
{{#if @attributes.label}}
|
||||
<label class="form-template-field__label">{{@attributes.label}}</label>
|
||||
{{/if}}
|
||||
<MultiSelect
|
||||
@class="form-template-field__multi-select"
|
||||
@content={{@choices}}
|
||||
@nameProperty={{null}}
|
||||
@valueProperty={{null}}
|
||||
@options={{hash
|
||||
translatedNone=@attributes.none_label
|
||||
maximum=@validations.maximum
|
||||
}}
|
||||
/>
|
||||
</div>
|
@ -0,0 +1,9 @@
|
||||
<div class="control-group form-template-field" data-field-type="textarea">
|
||||
{{#if @attributes.label}}
|
||||
<label class="form-template-field__label">{{@attributes.label}}</label>
|
||||
{{/if}}
|
||||
<Textarea
|
||||
class="form-template-field__textarea"
|
||||
placeholder={{@attributes.placeholder}}
|
||||
/>
|
||||
</div>
|
@ -0,0 +1,11 @@
|
||||
<div class="control-group form-template-field" data-field-type="upload">
|
||||
{{#if @attributes.label}}
|
||||
<label class="form-template-field__label">{{@attributes.label}}</label>
|
||||
{{/if}}
|
||||
<input
|
||||
type="file"
|
||||
accept={{@attributes.file_types}}
|
||||
class="form-template-field__upload"
|
||||
multiple={{@attributes.allow_multiple}}
|
||||
/>
|
||||
</div>
|
@ -0,0 +1,14 @@
|
||||
{{#if this.canShowContent}}
|
||||
{{#each this.parsedContent as |content|}}
|
||||
{{component
|
||||
(concat "form-template-field/" content.type)
|
||||
attributes=content.attributes
|
||||
choices=content.choices
|
||||
validations=content.validations
|
||||
}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<div class="alert alert-error">
|
||||
{{this.error}}
|
||||
</div>
|
||||
{{/if}}
|
@ -0,0 +1,17 @@
|
||||
import Component from "@glimmer/component";
|
||||
import Yaml from "js-yaml";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
|
||||
export default class FormTemplateFieldWrapper extends Component {
|
||||
@tracked error = null;
|
||||
|
||||
get canShowContent() {
|
||||
try {
|
||||
const parsedContent = Yaml.load(this.args.content);
|
||||
this.parsedContent = parsedContent;
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,12 +5,19 @@ import { inject as service } from "@ember/service";
|
||||
import I18n from "I18n";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
|
||||
export default class AdminCustomizeFormTemplateView extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
@service router;
|
||||
@service dialog;
|
||||
@tracked showPreview = false;
|
||||
|
||||
@action
|
||||
togglePreview() {
|
||||
this.showPreview = !this.showPreview;
|
||||
}
|
||||
|
||||
@action
|
||||
editTemplate() {
|
@ -0,0 +1,6 @@
|
||||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
|
||||
export default class AdminFormTemplateValidationOptions extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {}
|
@ -1,6 +1,17 @@
|
||||
<DModalBody @rawTitle={{this.model.name}}>
|
||||
<HighlightedCode @lang="yaml" @code={{this.model.template}} />
|
||||
{{! ? TODO(@keegan): Perhaps add what places (ex. categories) the templates are active in }}
|
||||
<div class="control-group">
|
||||
<DToggleSwitch
|
||||
class="form-templates__preview-toggle"
|
||||
@state={{this.showPreview}}
|
||||
@label="admin.form_templates.view_template.toggle_preview"
|
||||
{{on "click" this.togglePreview}}
|
||||
/>
|
||||
</div>
|
||||
{{#if this.showPreview}}
|
||||
<FormTemplateField::Wrapper @content={{this.model.template}} />
|
||||
{{else}}
|
||||
<HighlightedCode @lang="yaml" @code={{this.model.template}} />
|
||||
{{/if}}
|
||||
</DModalBody>
|
||||
<div class="modal-footer">
|
||||
<DButton
|
@ -0,0 +1,6 @@
|
||||
<DModalBody
|
||||
@class="form-templates__validation-options"
|
||||
@title="admin.form_templates.preview_modal.title"
|
||||
>
|
||||
<FormTemplateField::Wrapper @content={{this.model.content}} />
|
||||
</DModalBody>
|
@ -0,0 +1,43 @@
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
module(
|
||||
"Integration | Component | form-template-field | checkbox",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("renders a checkbox input", async function (assert) {
|
||||
await render(hbs`<FormTemplateField::Checkbox />`);
|
||||
|
||||
assert.ok(
|
||||
exists(
|
||||
".form-template-field[data-field-type='checkbox'] input[type='checkbox']"
|
||||
),
|
||||
"A checkbox component exists"
|
||||
);
|
||||
});
|
||||
|
||||
test("renders a checkbox with a label", async function (assert) {
|
||||
const attributes = {
|
||||
label: "Click this box",
|
||||
};
|
||||
this.set("attributes", attributes);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::Checkbox @attributes={{this.attributes}} />`
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(
|
||||
".form-template-field[data-field-type='checkbox'] input[type='checkbox']"
|
||||
),
|
||||
"A checkbox component exists"
|
||||
);
|
||||
|
||||
assert.dom(".form-template-field__label").hasText("Click this box");
|
||||
});
|
||||
}
|
||||
);
|
@ -0,0 +1,88 @@
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import { exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
module(
|
||||
"Integration | Component | form-template-field | dropdown",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.set("subject", selectKit());
|
||||
});
|
||||
|
||||
test("renders a dropdown with choices", async function (assert) {
|
||||
const choices = ["Choice 1", "Choice 2", "Choice 3"];
|
||||
|
||||
this.set("choices", choices);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::Dropdown @choices={{this.choices}}/>`
|
||||
);
|
||||
assert.ok(
|
||||
exists(".form-template-field__dropdown"),
|
||||
"A dropdown component exists"
|
||||
);
|
||||
|
||||
await this.subject.expand();
|
||||
|
||||
const dropdown = this.subject.displayedContent();
|
||||
assert.strictEqual(dropdown.length, 3, "it has 3 choices");
|
||||
assert.strictEqual(
|
||||
dropdown[0].name,
|
||||
"Choice 1",
|
||||
"it has the correct name for choice 1"
|
||||
);
|
||||
assert.strictEqual(
|
||||
dropdown[1].name,
|
||||
"Choice 2",
|
||||
"it has the correct name for choice 2"
|
||||
);
|
||||
assert.strictEqual(
|
||||
dropdown[2].name,
|
||||
"Choice 3",
|
||||
"it has the correct name for choice 3"
|
||||
);
|
||||
});
|
||||
|
||||
test("renders a dropdown with choices and attributes", async function (assert) {
|
||||
const choices = ["Choice 1", "Choice 2", "Choice 3"];
|
||||
const attributes = {
|
||||
none_label: "Select a choice",
|
||||
filterable: true,
|
||||
};
|
||||
|
||||
this.set("choices", choices);
|
||||
this.set("attributes", attributes);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::Dropdown @choices={{this.choices}} @attributes={{this.attributes}} />`
|
||||
);
|
||||
assert.ok(
|
||||
exists(".form-template-field__dropdown"),
|
||||
"A dropdown component exists"
|
||||
);
|
||||
|
||||
await this.subject.expand();
|
||||
assert.strictEqual(
|
||||
this.subject.header().label(),
|
||||
attributes.none_label,
|
||||
"None label is correct"
|
||||
);
|
||||
});
|
||||
|
||||
test("doesn't render a label when attribute is missing", async function (assert) {
|
||||
const choices = ["Choice 1", "Choice 2", "Choice 3"];
|
||||
this.set("choices", choices);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::Dropdown @choices={{this.choices}} />`
|
||||
);
|
||||
|
||||
assert.dom(".form-template-field__label").doesNotExist();
|
||||
});
|
||||
}
|
||||
);
|
@ -0,0 +1,61 @@
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
module(
|
||||
"Integration | Component | form-template-field | input",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("renders a text input", async function (assert) {
|
||||
await render(hbs`<FormTemplateField::Input />`);
|
||||
|
||||
assert.ok(
|
||||
exists(
|
||||
".form-template-field[data-field-type='input'] input[type='text']"
|
||||
),
|
||||
"A text input component exists"
|
||||
);
|
||||
});
|
||||
|
||||
test("renders a text input with attributes", async function (assert) {
|
||||
const attributes = {
|
||||
label: "My text label",
|
||||
placeholder: "Enter text here",
|
||||
};
|
||||
this.set("attributes", attributes);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::Input @attributes={{this.attributes}} />`
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(
|
||||
".form-template-field[data-field-type='input'] input[type='text']"
|
||||
),
|
||||
"A text input component exists"
|
||||
);
|
||||
|
||||
assert.dom(".form-template-field__label").hasText("My text label");
|
||||
assert.strictEqual(
|
||||
query(".form-template-field__input").placeholder,
|
||||
"Enter text here"
|
||||
);
|
||||
});
|
||||
|
||||
test("doesn't render a label when attribute is missing", async function (assert) {
|
||||
const attributes = {
|
||||
placeholder: "Enter text here",
|
||||
};
|
||||
this.set("attributes", attributes);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::Input @attributes={{this.attributes}} />`
|
||||
);
|
||||
|
||||
assert.dom(".form-template-field__label").doesNotExist();
|
||||
});
|
||||
}
|
||||
);
|
@ -0,0 +1,88 @@
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import { exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
module(
|
||||
"Integration | Component | form-template-field | multi-select",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.set("subject", selectKit());
|
||||
});
|
||||
|
||||
test("renders a multi-select dropdown with choices", async function (assert) {
|
||||
const choices = ["Choice 1", "Choice 2", "Choice 3"];
|
||||
|
||||
this.set("choices", choices);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::MultiSelect @choices={{this.choices}}/>`
|
||||
);
|
||||
assert.ok(
|
||||
exists(".form-template-field__multi-select"),
|
||||
"A multiselect component exists"
|
||||
);
|
||||
|
||||
await this.subject.expand();
|
||||
|
||||
const dropdown = this.subject.displayedContent();
|
||||
assert.strictEqual(dropdown.length, 3, "it has 3 choices");
|
||||
assert.strictEqual(
|
||||
dropdown[0].name,
|
||||
"Choice 1",
|
||||
"it has the correct name for choice 1"
|
||||
);
|
||||
assert.strictEqual(
|
||||
dropdown[1].name,
|
||||
"Choice 2",
|
||||
"it has the correct name for choice 2"
|
||||
);
|
||||
assert.strictEqual(
|
||||
dropdown[2].name,
|
||||
"Choice 3",
|
||||
"it has the correct name for choice 3"
|
||||
);
|
||||
});
|
||||
|
||||
test("renders a multi-select with choices and attributes", async function (assert) {
|
||||
const choices = ["Choice 1", "Choice 2", "Choice 3"];
|
||||
const attributes = {
|
||||
none_label: "Select a choice",
|
||||
filterable: true,
|
||||
};
|
||||
|
||||
this.set("choices", choices);
|
||||
this.set("attributes", attributes);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::MultiSelect @choices={{this.choices}} @attributes={{this.attributes}} />`
|
||||
);
|
||||
assert.ok(
|
||||
exists(".form-template-field__multi-select"),
|
||||
"A multiselect dropdown component exists"
|
||||
);
|
||||
|
||||
await this.subject.expand();
|
||||
assert.strictEqual(
|
||||
this.subject.header().label(),
|
||||
attributes.none_label,
|
||||
"None label is correct"
|
||||
);
|
||||
});
|
||||
|
||||
test("doesn't render a label when attribute is missing", async function (assert) {
|
||||
const choices = ["Choice 1", "Choice 2", "Choice 3"];
|
||||
this.set("choices", choices);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::MultiSelect @choices={{this.choices}} />`
|
||||
);
|
||||
|
||||
assert.dom(".form-template-field__label").doesNotExist();
|
||||
});
|
||||
}
|
||||
);
|
@ -0,0 +1,57 @@
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
module(
|
||||
"Integration | Component | form-template-field | textarea",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("renders a textarea input", async function (assert) {
|
||||
await render(hbs`<FormTemplateField::Textarea />`);
|
||||
|
||||
assert.ok(
|
||||
exists(".form-template-field__textarea"),
|
||||
"A textarea input component exists"
|
||||
);
|
||||
});
|
||||
|
||||
test("renders a text input with attributes", async function (assert) {
|
||||
const attributes = {
|
||||
label: "My text label",
|
||||
placeholder: "Enter text here",
|
||||
};
|
||||
this.set("attributes", attributes);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::Textarea @attributes={{this.attributes}} />`
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(".form-template-field__textarea"),
|
||||
"A textarea input component exists"
|
||||
);
|
||||
|
||||
assert.dom(".form-template-field__label").hasText("My text label");
|
||||
assert.strictEqual(
|
||||
query(".form-template-field__textarea").placeholder,
|
||||
"Enter text here"
|
||||
);
|
||||
});
|
||||
|
||||
test("doesn't render a label when attribute is missing", async function (assert) {
|
||||
const attributes = {
|
||||
placeholder: "Enter text here",
|
||||
};
|
||||
this.set("attributes", attributes);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::Textarea @attributes={{this.attributes}} />`
|
||||
);
|
||||
|
||||
assert.dom(".form-template-field__label").doesNotExist();
|
||||
});
|
||||
}
|
||||
);
|
@ -0,0 +1,49 @@
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
module(
|
||||
"Integration | Component | form-template-field | wrapper",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("does not render a component when template content has invalid YAML", async function (assert) {
|
||||
this.set("content", `- type: checkbox\n attributes;invalid`);
|
||||
await render(
|
||||
hbs`<FormTemplateField::Wrapper @content={{this.content}} />`
|
||||
);
|
||||
|
||||
assert.notOk(
|
||||
exists(".form-template-field"),
|
||||
"A form template field should not exist"
|
||||
);
|
||||
assert.ok(exists(".alert"), "An alert message should exist");
|
||||
});
|
||||
|
||||
test("renders a component based on the component type in the template content", async function (assert) {
|
||||
const content = `- type: checkbox\n- type: input\n- type: textarea\n- type: dropdown\n- type: upload\n- type: multi-select`;
|
||||
const componentTypes = [
|
||||
"checkbox",
|
||||
"input",
|
||||
"textarea",
|
||||
"dropdown",
|
||||
"upload",
|
||||
"multi-select",
|
||||
];
|
||||
this.set("content", content);
|
||||
|
||||
await render(
|
||||
hbs`<FormTemplateField::Wrapper @content={{this.content}} />`
|
||||
);
|
||||
|
||||
componentTypes.forEach((componentType) => {
|
||||
assert.ok(
|
||||
exists(`.form-template-field[data-field-type='${componentType}']`),
|
||||
`${componentType} component exists`
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -925,11 +925,11 @@ table.permalinks {
|
||||
}
|
||||
|
||||
.form-templates {
|
||||
&--info {
|
||||
&__info {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
&--table {
|
||||
&__table {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.admin-list-item .action {
|
||||
@ -937,7 +937,7 @@ table.permalinks {
|
||||
}
|
||||
}
|
||||
|
||||
&--form {
|
||||
&__form {
|
||||
input {
|
||||
width: 300px;
|
||||
}
|
||||
@ -975,7 +975,13 @@ table.permalinks {
|
||||
}
|
||||
}
|
||||
|
||||
&--quick-insert-field-buttons {
|
||||
&__editor {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__quick-insert-field-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
@ -986,14 +992,22 @@ table.permalinks {
|
||||
}
|
||||
|
||||
.btn {
|
||||
&:not(:last-child) {
|
||||
&:not(:last-of-type) {
|
||||
border-right: 1px solid var(--primary-low);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__validation-options td {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
&__preview-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.admin-customize-form-template-view-modal {
|
||||
.customize-form-template-view-modal {
|
||||
.modal-footer {
|
||||
.btn:last-child {
|
||||
margin-left: auto;
|
||||
|
@ -11,6 +11,25 @@ class Admin::FormTemplatesController < Admin::StaffController
|
||||
def new
|
||||
end
|
||||
|
||||
def preview
|
||||
params.require(:name)
|
||||
params.require(:template)
|
||||
|
||||
if params[:id].present?
|
||||
template = FormTemplate.find(params[:id])
|
||||
template.assign_attributes(name: params[:name], template: params[:template])
|
||||
else
|
||||
template = FormTemplate.new(name: params[:name], template: params[:template])
|
||||
end
|
||||
|
||||
begin
|
||||
template.validate!
|
||||
render json: success_json
|
||||
rescue FormTemplate::NotAllowed => err
|
||||
render_json_error(err.message)
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
params.require(:name)
|
||||
params.require(:template)
|
||||
|
@ -5547,6 +5547,7 @@ en:
|
||||
close: "Close"
|
||||
edit: "Edit"
|
||||
delete: "Delete"
|
||||
toggle_preview: "Toggle Preview"
|
||||
new_template_form:
|
||||
submit: "Save"
|
||||
cancel: "Cancel"
|
||||
@ -5556,6 +5557,7 @@ en:
|
||||
template:
|
||||
label: "Template"
|
||||
placeholder: "Create a YAML template here..."
|
||||
preview: "Preview"
|
||||
delete_confirm: "Are you sure you would like to delete this template?"
|
||||
quick_insert_fields:
|
||||
add_new_field: "Add"
|
||||
@ -5565,6 +5567,41 @@ en:
|
||||
dropdown: "Dropdown"
|
||||
upload: "Upload a file"
|
||||
multiselect: "Multiple choice"
|
||||
validations_modal:
|
||||
button_title: "Validations"
|
||||
modal_title: "Validation Options"
|
||||
table_headers:
|
||||
key: "Key"
|
||||
type: "Type"
|
||||
description: "Description"
|
||||
validations:
|
||||
required:
|
||||
key: "required"
|
||||
type: "boolean"
|
||||
description: "Requires the field to be completed to submit the form."
|
||||
minimum:
|
||||
key: "minimum"
|
||||
type: "integer"
|
||||
description: "In text fields, specifies the minimum number of characters allowed."
|
||||
maximum:
|
||||
key: "maximum"
|
||||
type: "integer"
|
||||
description: "In text fields, specifies the maximum number of characters allowed. In multi select dropdowns, specifies the maximum number of options that can be selected."
|
||||
pattern:
|
||||
key: "pattern"
|
||||
type: "regex string"
|
||||
description: "In text fields, a regular expression specifying the allowed input."
|
||||
preview_modal:
|
||||
title: "Preview Template"
|
||||
field_placeholders:
|
||||
validations: "enter validations here"
|
||||
label: "Enter label here"
|
||||
placeholder: "Enter placeholder here"
|
||||
none_label: "Select an item"
|
||||
choices:
|
||||
first: "Option 1"
|
||||
second: "Option 2"
|
||||
third: "Option 3"
|
||||
edit_category:
|
||||
toggle_freeform: "form template disabled"
|
||||
toggle_form_template: "form template enabled"
|
||||
|
@ -5253,3 +5253,5 @@ en:
|
||||
form_templates:
|
||||
errors:
|
||||
invalid_yaml: "is not a valid YAML string"
|
||||
invalid_type: "contains an invalid template type: %{type} (valid types are: %{valid_types})"
|
||||
missing_type: "is missing a field type"
|
||||
|
@ -233,7 +233,9 @@ Discourse::Application.routes.draw do
|
||||
scope "/customize", constraints: AdminConstraint.new do
|
||||
resources :user_fields, constraints: AdminConstraint.new
|
||||
resources :emojis, constraints: AdminConstraint.new
|
||||
resources :form_templates, constraints: AdminConstraint.new, path: "/form-templates"
|
||||
resources :form_templates, constraints: AdminConstraint.new, path: "/form-templates" do
|
||||
collection { get "preview" => "form_templates#preview" }
|
||||
end
|
||||
|
||||
get "themes/:id/:target/:field_name/edit" => "themes#index"
|
||||
get "themes/:id" => "themes#index"
|
||||
|
@ -4,8 +4,36 @@ class FormTemplateYamlValidator < ActiveModel::Validator
|
||||
def validate(record)
|
||||
begin
|
||||
yaml = Psych.safe_load(record.template)
|
||||
check_missing_type(record, yaml)
|
||||
check_allowed_types(record, yaml)
|
||||
rescue Psych::SyntaxError
|
||||
record.errors.add(:template, I18n.t("form_templates.errors.invalid_yaml"))
|
||||
end
|
||||
end
|
||||
|
||||
def check_allowed_types(record, yaml)
|
||||
allowed_types = %w[checkbox dropdown input multi-select textarea upload]
|
||||
yaml.each do |field|
|
||||
if !allowed_types.include?(field["type"])
|
||||
return(
|
||||
record.errors.add(
|
||||
:template,
|
||||
I18n.t(
|
||||
"form_templates.errors.invalid_type",
|
||||
type: field["type"],
|
||||
valid_types: allowed_types.join(", "),
|
||||
),
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_missing_type(record, yaml)
|
||||
yaml.each do |field|
|
||||
if field["type"].blank?
|
||||
return record.errors.add(:template, I18n.t("form_templates.errors.missing_type"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,5 +2,5 @@
|
||||
|
||||
Fabricator(:form_template) do
|
||||
name { sequence(:name) { |i| "template_#{i}" } }
|
||||
template "some yaml template: value"
|
||||
template "- type: input"
|
||||
end
|
||||
|
@ -4,17 +4,29 @@ require "rails_helper"
|
||||
|
||||
RSpec.describe FormTemplate, type: :model do
|
||||
it "can't have duplicate names" do
|
||||
Fabricate(:form_template, name: "Bug Report", template: "some yaml: true")
|
||||
t = Fabricate.build(:form_template, name: "Bug Report", template: "some yaml: true")
|
||||
Fabricate(:form_template, name: "Bug Report", template: "- type: input")
|
||||
t = Fabricate.build(:form_template, name: "Bug Report", template: "- type: input")
|
||||
expect(t.save).to eq(false)
|
||||
t = Fabricate.build(:form_template, name: "Bug Report", template: "some yaml: true")
|
||||
t = Fabricate.build(:form_template, name: "Bug Report", template: "- type: input")
|
||||
expect(t.save).to eq(false)
|
||||
expect(described_class.count).to eq(1)
|
||||
end
|
||||
|
||||
it "can't have an invalid yaml template" do
|
||||
template = "first: good\nsecond; bad"
|
||||
template = "- type: checkbox\nattributes; bad"
|
||||
t = Fabricate.build(:form_template, name: "Feature Request", template: template)
|
||||
expect(t.save).to eq(false)
|
||||
end
|
||||
|
||||
it "must have a supported type" do
|
||||
template = "- type: fancy"
|
||||
t = Fabricate.build(:form_template, name: "Fancy Template", template: template)
|
||||
expect(t.save).to eq(false)
|
||||
end
|
||||
|
||||
it "must have a type propety" do
|
||||
template = "- hello: world"
|
||||
t = Fabricate.build(:form_template, name: "Basic Template", template: template)
|
||||
expect(t.save).to eq(false)
|
||||
end
|
||||
end
|
||||
|
@ -74,7 +74,7 @@ RSpec.describe Admin::FormTemplatesController do
|
||||
params: {
|
||||
name: "Bug Reports",
|
||||
template:
|
||||
"body:\n- type: input\n attributes:\n label: Website or apps\n description: |\n Which website or app were you using when the bug happened?\n placeholder: |\n e.g. website URL, name of the app\n validations:\n required: true",
|
||||
"- type: input\n attributes:\n label: Website or apps\n description: |\n Which website or app were you using when the bug happened?\n placeholder: |\n e.g. website URL, name of the app\n validations:\n required: true",
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
@ -112,13 +112,13 @@ RSpec.describe Admin::FormTemplatesController do
|
||||
params: {
|
||||
id: form_template.id,
|
||||
name: "Updated Template",
|
||||
template: "New yaml: true",
|
||||
template: "- type: checkbox",
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
form_template.reload
|
||||
expect(form_template.name).to eq("Updated Template")
|
||||
expect(form_template.template).to eq("New yaml: true")
|
||||
expect(form_template.template).to eq("- type: checkbox")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -21,7 +21,14 @@ describe "Admin Customize Form Templates", type: :system, js: true do
|
||||
it "should show the form template structure in a modal" do
|
||||
visit("/admin/customize/form-templates")
|
||||
form_template_page.click_view_form_template
|
||||
expect(form_template_page).to have_template_structure("some yaml template: value")
|
||||
expect(form_template_page).to have_template_structure("- type: input")
|
||||
end
|
||||
|
||||
it "should show a preview of the template in a modal when toggling the preview" do
|
||||
visit("/admin/customize/form-templates")
|
||||
form_template_page.click_view_form_template
|
||||
form_template_page.click_toggle_preview
|
||||
expect(form_template_page).to have_input_field("input")
|
||||
end
|
||||
end
|
||||
|
||||
@ -45,7 +52,7 @@ describe "Admin Customize Form Templates", type: :system, js: true do
|
||||
visit("/admin/customize/form-templates/new")
|
||||
|
||||
sample_name = "My First Template"
|
||||
sample_template = "test: true"
|
||||
sample_template = "- type: input"
|
||||
|
||||
form_template_page.type_in_template_name(sample_name)
|
||||
ace_editor.type_input(sample_template)
|
||||
@ -53,19 +60,62 @@ describe "Admin Customize Form Templates", type: :system, js: true do
|
||||
expect(form_template_page).to have_form_template(sample_name)
|
||||
end
|
||||
|
||||
it "should disable the save button until form is filled out" do
|
||||
visit("/admin/customize/form-templates/new")
|
||||
expect(form_template_page).to have_save_button_with_state(true)
|
||||
form_template_page.type_in_template_name("New Template")
|
||||
expect(form_template_page).to have_save_button_with_state(true)
|
||||
ace_editor.type_input("- type: input")
|
||||
expect(form_template_page).to have_save_button_with_state(false)
|
||||
end
|
||||
|
||||
it "should disable the preview button until form is filled out" do
|
||||
visit("/admin/customize/form-templates/new")
|
||||
expect(form_template_page).to have_preview_button_with_state(true)
|
||||
form_template_page.type_in_template_name("New Template")
|
||||
expect(form_template_page).to have_preview_button_with_state(true)
|
||||
ace_editor.type_input("- type: input")
|
||||
expect(form_template_page).to have_preview_button_with_state(false)
|
||||
end
|
||||
|
||||
it "should show validation options in a modal when clicking the validations button" do
|
||||
visit("/admin/customize/form-templates/new")
|
||||
form_template_page.click_validations_button
|
||||
expect(form_template_page).to have_validations_modal
|
||||
end
|
||||
|
||||
it "should show a preview of the template in a modal when clicking the preview button" do
|
||||
visit("/admin/customize/form-templates/new")
|
||||
form_template_page.type_in_template_name("New Template")
|
||||
ace_editor.type_input("- type: input")
|
||||
form_template_page.click_preview_button
|
||||
expect(form_template_page).to have_preview_modal
|
||||
expect(form_template_page).to have_input_field("input")
|
||||
end
|
||||
|
||||
it "should render all the input field types in the preview" do
|
||||
visit("/admin/customize/form-templates/new")
|
||||
form_template_page.type_in_template_name("New Template")
|
||||
ace_editor.type_input(
|
||||
"- type: input\n- type: textarea\n- type: checkbox\n- type: dropdown\n- type: upload\n- type: multi-select",
|
||||
)
|
||||
form_template_page.click_preview_button
|
||||
expect(form_template_page).to have_input_field("input")
|
||||
expect(form_template_page).to have_input_field("textarea")
|
||||
expect(form_template_page).to have_input_field("checkbox")
|
||||
expect(form_template_page).to have_input_field("dropdown")
|
||||
expect(form_template_page).to have_input_field("upload")
|
||||
expect(form_template_page).to have_input_field("multi-select")
|
||||
end
|
||||
|
||||
it "should allow quick insertion of checkbox field" do
|
||||
quick_insertion_test(
|
||||
"checkbox",
|
||||
'- type: checkbox
|
||||
choices:
|
||||
- "Option 1"
|
||||
- "Option 2"
|
||||
- "Option 3"
|
||||
attributes:
|
||||
label: "Enter question here"
|
||||
description: "Enter description here"
|
||||
validations:
|
||||
required: true',
|
||||
label: "Enter label here"
|
||||
validations:
|
||||
# enter validations here',
|
||||
)
|
||||
end
|
||||
|
||||
@ -74,11 +124,10 @@ describe "Admin Customize Form Templates", type: :system, js: true do
|
||||
"input",
|
||||
'- type: input
|
||||
attributes:
|
||||
label: "Enter input label here"
|
||||
description: "Enter input description here"
|
||||
placeholder: "Enter input placeholder here"
|
||||
validations:
|
||||
required: true',
|
||||
label: "Enter label here"
|
||||
placeholder: "Enter placeholder here"
|
||||
validations:
|
||||
# enter validations here',
|
||||
)
|
||||
end
|
||||
|
||||
@ -87,11 +136,10 @@ describe "Admin Customize Form Templates", type: :system, js: true do
|
||||
"textarea",
|
||||
'- type: textarea
|
||||
attributes:
|
||||
label: "Enter textarea label here"
|
||||
description: "Enter textarea description here"
|
||||
placeholder: "Enter textarea placeholder here"
|
||||
validations:
|
||||
required: true',
|
||||
label: "Enter label here"
|
||||
placeholder: "Enter placeholder here"
|
||||
validations:
|
||||
# enter validations here',
|
||||
)
|
||||
end
|
||||
|
||||
@ -104,10 +152,11 @@ describe "Admin Customize Form Templates", type: :system, js: true do
|
||||
- "Option 2"
|
||||
- "Option 3"
|
||||
attributes:
|
||||
label: "Enter dropdown label here"
|
||||
description: "Enter dropdown description here"
|
||||
validations:
|
||||
required: true',
|
||||
none_label: "Select an item"
|
||||
label: "Enter label here"
|
||||
filterable: false
|
||||
validations:
|
||||
# enter validations here',
|
||||
)
|
||||
end
|
||||
|
||||
@ -117,24 +166,26 @@ describe "Admin Customize Form Templates", type: :system, js: true do
|
||||
'- type: upload
|
||||
attributes:
|
||||
file_types: "jpg, png, gif"
|
||||
label: "Enter upload label here"
|
||||
description: "Enter upload description here"',
|
||||
allow_multiple: false
|
||||
label: "Enter label here"
|
||||
validations:
|
||||
# enter validations here',
|
||||
)
|
||||
end
|
||||
|
||||
it "should allow quick insertion of multiple choice field" do
|
||||
quick_insertion_test(
|
||||
"multiselect",
|
||||
'- type: multiple_choice
|
||||
'- type: multi-select
|
||||
choices:
|
||||
- "Option 1"
|
||||
- "Option 2"
|
||||
- "Option 3"
|
||||
attributes:
|
||||
label: "Enter multiple choice label here"
|
||||
description: "Enter multiple choice description here"
|
||||
validations:
|
||||
required: true',
|
||||
none_label: "Select an item"
|
||||
label: "Enter label here"
|
||||
validations:
|
||||
# enter validations here',
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -5,15 +5,20 @@ module PageObjects
|
||||
class FormTemplate < PageObjects::Pages::Base
|
||||
# Form Template Index
|
||||
def has_form_template_table?
|
||||
page.has_selector?("table.form-templates--table")
|
||||
page.has_selector?("table.form-templates__table")
|
||||
end
|
||||
|
||||
def click_view_form_template
|
||||
find(".form-templates--table tr:first-child .btn-view-template").click
|
||||
find(".form-templates__table tr:first-child .btn-view-template").click
|
||||
end
|
||||
|
||||
def click_toggle_preview
|
||||
find(".d-toggle-switch .d-toggle-switch__checkbox-slider", visible: false).click
|
||||
self
|
||||
end
|
||||
|
||||
def has_form_template?(name)
|
||||
find(".form-templates--table tbody tr td", text: name).present?
|
||||
find(".form-templates__table tbody tr td", text: name).present?
|
||||
end
|
||||
|
||||
def has_template_structure?(structure)
|
||||
@ -22,20 +27,48 @@ module PageObjects
|
||||
|
||||
# Form Template new/edit form related
|
||||
def type_in_template_name(input)
|
||||
find(".form-templates--form-name-input").send_keys(input)
|
||||
find(".form-templates__form-name-input").send_keys(input)
|
||||
self
|
||||
end
|
||||
|
||||
def click_save_button
|
||||
find(".form-templates--form .footer-buttons .btn-primary").click
|
||||
find(".form-templates__form .footer-buttons .btn-primary").click
|
||||
end
|
||||
|
||||
def click_quick_insert(field_type)
|
||||
find(".form-templates--form .quick-insert-#{field_type}").click
|
||||
find(".form-templates__form .quick-insert-#{field_type}").click
|
||||
end
|
||||
|
||||
def click_validations_button
|
||||
find(".form-templates__validations-modal-button").click
|
||||
end
|
||||
|
||||
def click_preview_button
|
||||
find(".form-templates__preview-button").click
|
||||
end
|
||||
|
||||
def has_input_field?(type)
|
||||
find(".form-template-field__#{type}").present?
|
||||
end
|
||||
|
||||
def has_preview_modal?
|
||||
find(".form-template-form-preview-modal").present?
|
||||
end
|
||||
|
||||
def has_validations_modal?
|
||||
find(".admin-form-template-validation-options-modal").present?
|
||||
end
|
||||
|
||||
def has_name_value?(name)
|
||||
find(".form-templates--form-name-input").value == name
|
||||
find(".form-templates__form-name-input").value == name
|
||||
end
|
||||
|
||||
def has_save_button_with_state?(state)
|
||||
find_button("Save", disabled: state)
|
||||
end
|
||||
|
||||
def has_preview_button_with_state?(state)
|
||||
find_button("Preview", disabled: state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user