mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 21:52:43 +08:00
FEATURE: support to initial values for form templates through /new-topic (#23313)
* FEATURE: adds support for initial values through /new-topic to form templates
This commit is contained in:
@ -4,6 +4,7 @@ export const templateFormFields = [
|
|||||||
{
|
{
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
structure: `- type: checkbox
|
structure: `- type: checkbox
|
||||||
|
id: ${I18n.t("admin.form_templates.field_placeholders.id")}
|
||||||
attributes:
|
attributes:
|
||||||
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
||||||
validations:
|
validations:
|
||||||
@ -12,6 +13,7 @@ export const templateFormFields = [
|
|||||||
{
|
{
|
||||||
type: "input",
|
type: "input",
|
||||||
structure: `- type: input
|
structure: `- type: input
|
||||||
|
id: ${I18n.t("admin.form_templates.field_placeholders.id")}
|
||||||
attributes:
|
attributes:
|
||||||
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
||||||
placeholder: "${I18n.t(
|
placeholder: "${I18n.t(
|
||||||
@ -23,6 +25,7 @@ export const templateFormFields = [
|
|||||||
{
|
{
|
||||||
type: "textarea",
|
type: "textarea",
|
||||||
structure: `- type: textarea
|
structure: `- type: textarea
|
||||||
|
id: ${I18n.t("admin.form_templates.field_placeholders.id")}
|
||||||
attributes:
|
attributes:
|
||||||
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
label: "${I18n.t("admin.form_templates.field_placeholders.label")}"
|
||||||
placeholder: "${I18n.t(
|
placeholder: "${I18n.t(
|
||||||
@ -34,6 +37,7 @@ export const templateFormFields = [
|
|||||||
{
|
{
|
||||||
type: "dropdown",
|
type: "dropdown",
|
||||||
structure: `- type: dropdown
|
structure: `- type: dropdown
|
||||||
|
id: ${I18n.t("admin.form_templates.field_placeholders.id")}
|
||||||
choices:
|
choices:
|
||||||
- "${I18n.t("admin.form_templates.field_placeholders.choices.first")}"
|
- "${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.second")}"
|
||||||
@ -50,6 +54,7 @@ export const templateFormFields = [
|
|||||||
{
|
{
|
||||||
type: "upload",
|
type: "upload",
|
||||||
structure: `- type: upload
|
structure: `- type: upload
|
||||||
|
id: ${I18n.t("admin.form_templates.field_placeholders.id")}
|
||||||
attributes:
|
attributes:
|
||||||
file_types: ".jpg, .png, .gif"
|
file_types: ".jpg, .png, .gif"
|
||||||
allow_multiple: false
|
allow_multiple: false
|
||||||
@ -60,6 +65,7 @@ export const templateFormFields = [
|
|||||||
{
|
{
|
||||||
type: "multiselect",
|
type: "multiselect",
|
||||||
structure: `- type: multi-select
|
structure: `- type: multi-select
|
||||||
|
id: ${I18n.t("admin.form_templates.field_placeholders.id")}
|
||||||
choices:
|
choices:
|
||||||
- "${I18n.t("admin.form_templates.field_placeholders.choices.first")}"
|
- "${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.second")}"
|
||||||
|
@ -125,6 +125,7 @@
|
|||||||
@focusTarget={{this.composer.focusTarget}}
|
@focusTarget={{this.composer.focusTarget}}
|
||||||
@disableTextarea={{this.composer.disableTextarea}}
|
@disableTextarea={{this.composer.disableTextarea}}
|
||||||
@formTemplateIds={{this.composer.formTemplateIds}}
|
@formTemplateIds={{this.composer.formTemplateIds}}
|
||||||
|
@formTemplateInitialValues={{this.composer.formTemplateInitialValues}}
|
||||||
>
|
>
|
||||||
<div class="composer-fields">
|
<div class="composer-fields">
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
@onPopupMenuAction={{this.onPopupMenuAction}}
|
@onPopupMenuAction={{this.onPopupMenuAction}}
|
||||||
@popupMenuOptions={{this.popupMenuOptions}}
|
@popupMenuOptions={{this.popupMenuOptions}}
|
||||||
@formTemplateIds={{this.formTemplateIds}}
|
@formTemplateIds={{this.formTemplateIds}}
|
||||||
|
@formTemplateInitialValues={{@formTemplateInitialValues}}
|
||||||
@replyingToTopic={{this.composer.replyingToTopic}}
|
@replyingToTopic={{this.composer.replyingToTopic}}
|
||||||
@editingPost={{this.composer.editingPost}}
|
@editingPost={{this.composer.editingPost}}
|
||||||
@disabled={{this.disableTextarea}}
|
@disabled={{this.disableTextarea}}
|
||||||
|
@ -15,7 +15,10 @@
|
|||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<form id="form-template-form">
|
<form id="form-template-form">
|
||||||
<FormTemplateField::Wrapper @id={{this.selectedFormTemplateId}} />
|
<FormTemplateField::Wrapper
|
||||||
|
@id={{this.selectedFormTemplateId}}
|
||||||
|
@initialValues={{@formTemplateInitialValues}}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div
|
<div
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<div class="control-group form-template-field" data-field-type="checkbox">
|
<div class="control-group form-template-field" data-field-type="checkbox">
|
||||||
<label class="form-template-field__label">
|
<label class="form-template-field__label">
|
||||||
<Input
|
<Input
|
||||||
name={{@attributes.label}}
|
name={{@id}}
|
||||||
class="form-template-field__checkbox"
|
class="form-template-field__checkbox"
|
||||||
|
@checked={{@value}}
|
||||||
@type="checkbox"
|
@type="checkbox"
|
||||||
required={{if @validations.required "required" ""}}
|
required={{if @validations.required "required" ""}}
|
||||||
/>
|
/>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
{{! TODO(@keegan): Update implementation to use <ComboBox/> instead }}
|
{{! TODO(@keegan): Update implementation to use <ComboBox/> instead }}
|
||||||
{{! Current using <select> as it integrates easily with FormData (will update in v2) }}
|
{{! Current using <select> as it integrates easily with FormData (will update in v2) }}
|
||||||
<select
|
<select
|
||||||
name={{@attributes.label}}
|
name={{@id}}
|
||||||
class="form-template-field__dropdown"
|
class="form-template-field__dropdown"
|
||||||
required={{if @validations.required "required" ""}}
|
required={{if @validations.required "required" ""}}
|
||||||
>
|
>
|
||||||
@ -25,7 +25,7 @@
|
|||||||
>{{@attributes.none_label}}</option>
|
>{{@attributes.none_label}}</option>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#each @choices as |choice|}}
|
{{#each @choices as |choice|}}
|
||||||
<option value={{choice}}>{{choice}}</option>
|
<option value={{choice}} selected={{eq @value choice}}>{{choice}}</option>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
@ -9,8 +9,9 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
name={{@attributes.label}}
|
name={{@id}}
|
||||||
class="form-template-field__input"
|
class="form-template-field__input"
|
||||||
|
@value={{@value}}
|
||||||
@type={{if @validations.type @validations.type "text"}}
|
@type={{if @validations.type @validations.type "text"}}
|
||||||
placeholder={{@attributes.placeholder}}
|
placeholder={{@attributes.placeholder}}
|
||||||
required={{if @validations.required "required" ""}}
|
required={{if @validations.required "required" ""}}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
{{! TODO(@keegan): Update implementation to use <MultiSelect/> instead }}
|
{{! TODO(@keegan): Update implementation to use <MultiSelect/> instead }}
|
||||||
{{! Current using <select multiple> as it integrates easily with FormData (will update in v2) }}
|
{{! Current using <select multiple> as it integrates easily with FormData (will update in v2) }}
|
||||||
<select
|
<select
|
||||||
name={{@attributes.label}}
|
name={{@id}}
|
||||||
class="form-template-field__multi-select"
|
class="form-template-field__multi-select"
|
||||||
required={{if @validations.required "required" ""}}
|
required={{if @validations.required "required" ""}}
|
||||||
multiple="multiple"
|
multiple="multiple"
|
||||||
@ -25,7 +25,10 @@
|
|||||||
>{{@attributes.none_label}}</option>
|
>{{@attributes.none_label}}</option>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#each @choices as |choice|}}
|
{{#each @choices as |choice|}}
|
||||||
<option value={{choice}}>{{choice}}</option>
|
<option
|
||||||
|
value={{choice}}
|
||||||
|
selected={{this.isSelected choice}}
|
||||||
|
>{{choice}}</option>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
@ -0,0 +1,9 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
|
||||||
|
export default class FormTemplateFieldMultiSelect extends Component {
|
||||||
|
@action
|
||||||
|
isSelected(option) {
|
||||||
|
return this.args.value?.includes(option);
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,8 @@
|
|||||||
</label>
|
</label>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<Textarea
|
<Textarea
|
||||||
name={{@attributes.label}}
|
name={{@id}}
|
||||||
|
@value={{@value}}
|
||||||
class="form-template-field__textarea"
|
class="form-template-field__textarea"
|
||||||
placeholder={{@attributes.placeholder}}
|
placeholder={{@attributes.placeholder}}
|
||||||
pattern={{@validations.pattern}}
|
pattern={{@validations.pattern}}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</label>
|
</label>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<input type="hidden" name={{@attributes.label}} value={{this.uploadValue}} />
|
<input type="hidden" name={{@id}} value={{this.uploadValue}} />
|
||||||
|
|
||||||
<PickFilesButton
|
<PickFilesButton
|
||||||
@fileInputClass="form-template-field__upload"
|
@fileInputClass="form-template-field__upload"
|
||||||
|
@ -12,12 +12,8 @@ export default class FormTemplateFieldUpload extends Component.extend(
|
|||||||
@tracked uploadComplete = false;
|
@tracked uploadComplete = false;
|
||||||
@tracked uploadedFiles = [];
|
@tracked uploadedFiles = [];
|
||||||
@tracked disabled = this.uploading;
|
@tracked disabled = this.uploading;
|
||||||
@tracked
|
@tracked fileUploadElementId = `${dasherize(this.id)}-uploader`;
|
||||||
fileUploadElementId = this.attributes?.label
|
|
||||||
? `${dasherize(this.attributes.label)}-uploader`
|
|
||||||
: `${this.elementId}-uploader`;
|
|
||||||
@tracked fileInputSelector = `#${this.fileUploadElementId}`;
|
@tracked fileInputSelector = `#${this.fileUploadElementId}`;
|
||||||
@tracked id = this.fileUploadElementId;
|
|
||||||
|
|
||||||
@computed("uploading", "uploadValue")
|
@computed("uploading", "uploadValue")
|
||||||
get uploadStatus() {
|
get uploadStatus() {
|
||||||
|
@ -6,9 +6,11 @@
|
|||||||
{{#each this.parsedTemplate as |content|}}
|
{{#each this.parsedTemplate as |content|}}
|
||||||
{{component
|
{{component
|
||||||
(concat "form-template-field/" content.type)
|
(concat "form-template-field/" content.type)
|
||||||
|
id=content.id
|
||||||
attributes=content.attributes
|
attributes=content.attributes
|
||||||
choices=content.choices
|
choices=content.choices
|
||||||
validations=content.validations
|
validations=content.validations
|
||||||
|
value=(get @initialValues content.id)
|
||||||
}}
|
}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,6 +54,8 @@ export default class extends DiscourseRoute {
|
|||||||
category,
|
category,
|
||||||
tags: transition.to.queryParams.tags,
|
tags: transition.to.queryParams.tags,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.composer.set("formTemplateInitialValues", transition.to.queryParams);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +174,14 @@ export default class ComposerService extends Service {
|
|||||||
return this.model.category?.get("form_template_ids");
|
return this.model.category?.get("form_template_ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get formTemplateInitialValues() {
|
||||||
|
return this._formTemplateInitialValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
set formTemplateInitialValues(values) {
|
||||||
|
return this.set("_formTemplateInitialValues", values);
|
||||||
|
}
|
||||||
|
|
||||||
@discourseComputed("showPreview")
|
@discourseComputed("showPreview")
|
||||||
toggleText(showPreview) {
|
toggleText(showPreview) {
|
||||||
return showPreview
|
return showPreview
|
||||||
@ -246,7 +254,6 @@ export default class ComposerService extends Service {
|
|||||||
canEditTags(canEditTitle, creatingPrivateMessage) {
|
canEditTags(canEditTitle, creatingPrivateMessage) {
|
||||||
const isPrivateMessage =
|
const isPrivateMessage =
|
||||||
creatingPrivateMessage || this.get("model.topic.isPrivateMessage");
|
creatingPrivateMessage || this.get("model.topic.isPrivateMessage");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
canEditTitle &&
|
canEditTitle &&
|
||||||
this.site.can_tag_topics &&
|
this.site.can_tag_topics &&
|
||||||
|
@ -2,7 +2,7 @@ import { module, test } from "qunit";
|
|||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
import { render } from "@ember/test-helpers";
|
import { render } from "@ember/test-helpers";
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import { exists } from "discourse/tests/helpers/qunit-helpers";
|
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
|
||||||
import pretender, { response } from "discourse/tests/helpers/create-pretender";
|
import pretender, { response } from "discourse/tests/helpers/create-pretender";
|
||||||
|
|
||||||
module(
|
module(
|
||||||
@ -24,7 +24,12 @@ module(
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("renders a component based on the component type found in the content YAML", async function (assert) {
|
test("renders a component based on the component type found in the content YAML", async function (assert) {
|
||||||
const content = `- type: checkbox\n- type: input\n- type: textarea\n- type: dropdown\n- type: upload\n- type: multi-select`;
|
const content = `- type: checkbox\n id: checkbox\n
|
||||||
|
- type: input\n id: name
|
||||||
|
- type: textarea\n id: notes
|
||||||
|
- type: dropdown\n id: dropdown
|
||||||
|
- type: upload\n id: upload
|
||||||
|
- type: multi-select\n id: multi`;
|
||||||
const componentTypes = [
|
const componentTypes = [
|
||||||
"checkbox",
|
"checkbox",
|
||||||
"input",
|
"input",
|
||||||
@ -47,6 +52,36 @@ module(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("renders a component based on the component type found in the content YAML, with initial values", async function (assert) {
|
||||||
|
const content = `- type: checkbox\n id: checkbox\n
|
||||||
|
- type: input\n id: name
|
||||||
|
- type: textarea\n id: notes
|
||||||
|
- type: dropdown\n id: dropdown\n choices:\n - "Option 1"\n - "Option 2"\n - "Option 3"
|
||||||
|
- type: multi-select\n id: multi\n choices:\n - "Option 1"\n - "Option 2"\n - "Option 3"`;
|
||||||
|
this.set("content", content);
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
checkbox: "on",
|
||||||
|
name: "Test Name",
|
||||||
|
notes: "Test Notes",
|
||||||
|
dropdown: "Option 1",
|
||||||
|
multi: ["Option 1"],
|
||||||
|
};
|
||||||
|
this.set("initialValues", initialValues);
|
||||||
|
|
||||||
|
await render(
|
||||||
|
hbs`<FormTemplateField::Wrapper @content={{this.content}} @initialValues={{this.initialValues}} />`
|
||||||
|
);
|
||||||
|
|
||||||
|
Object.keys(initialValues).forEach((componentId) => {
|
||||||
|
assert.equal(
|
||||||
|
query(`[name='${componentId}']`).value,
|
||||||
|
initialValues[componentId],
|
||||||
|
`${componentId} component has initial value`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("renders a component based on the component type found in the content YAML when passed ids", async function (assert) {
|
test("renders a component based on the component type found in the content YAML when passed ids", async function (assert) {
|
||||||
pretender.get("/form-templates/1.json", () => {
|
pretender.get("/form-templates/1.json", () => {
|
||||||
return response({
|
return response({
|
||||||
|
@ -16,6 +16,9 @@ class FormTemplate < ActiveRecord::Base
|
|||||||
|
|
||||||
has_many :category_form_templates, dependent: :destroy
|
has_many :category_form_templates, dependent: :destroy
|
||||||
has_many :categories, through: :category_form_templates
|
has_many :categories, through: :category_form_templates
|
||||||
|
|
||||||
|
class NotAllowed < StandardError
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
|
@ -5750,6 +5750,7 @@ en:
|
|||||||
title: "Preview Template"
|
title: "Preview Template"
|
||||||
field_placeholders:
|
field_placeholders:
|
||||||
validations: "enter validations here"
|
validations: "enter validations here"
|
||||||
|
id: "enter-id-here"
|
||||||
label: "Enter label here"
|
label: "Enter label here"
|
||||||
placeholder: "Enter placeholder here"
|
placeholder: "Enter placeholder here"
|
||||||
none_label: "Select an item"
|
none_label: "Select an item"
|
||||||
|
@ -5291,3 +5291,6 @@ en:
|
|||||||
invalid_yaml: "is not a valid YAML string"
|
invalid_yaml: "is not a valid YAML string"
|
||||||
invalid_type: "contains an invalid template type: %{type} (valid types are: %{valid_types})"
|
invalid_type: "contains an invalid template type: %{type} (valid types are: %{valid_types})"
|
||||||
missing_type: "is missing a field type"
|
missing_type: "is missing a field type"
|
||||||
|
missing_id: "is missing a field id"
|
||||||
|
duplicate_ids: "has duplicate ids"
|
||||||
|
reserved_id: "has a reserved keyword as id: %{id}"
|
||||||
|
@ -1123,7 +1123,7 @@ posting:
|
|||||||
min: 5
|
min: 5
|
||||||
max: 255
|
max: 255
|
||||||
max_form_template_content_length:
|
max_form_template_content_length:
|
||||||
default: 2000
|
default: 5000
|
||||||
max: 150000
|
max: 150000
|
||||||
|
|
||||||
email:
|
email:
|
||||||
|
@ -1,27 +1,30 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class FormTemplateYamlValidator < ActiveModel::Validator
|
class FormTemplateYamlValidator < ActiveModel::Validator
|
||||||
|
RESERVED_KEYWORDS = %w[title body category category_id tags]
|
||||||
|
ALLOWED_TYPES = %w[checkbox dropdown input multi-select textarea upload]
|
||||||
|
|
||||||
def validate(record)
|
def validate(record)
|
||||||
begin
|
begin
|
||||||
yaml = Psych.safe_load(record.template)
|
yaml = Psych.safe_load(record.template)
|
||||||
check_missing_type(record, yaml)
|
check_missing_fields(record, yaml)
|
||||||
check_allowed_types(record, yaml)
|
check_allowed_types(record, yaml)
|
||||||
|
check_ids(record, yaml)
|
||||||
rescue Psych::SyntaxError
|
rescue Psych::SyntaxError
|
||||||
record.errors.add(:template, I18n.t("form_templates.errors.invalid_yaml"))
|
record.errors.add(:template, I18n.t("form_templates.errors.invalid_yaml"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_allowed_types(record, yaml)
|
def check_allowed_types(record, yaml)
|
||||||
allowed_types = %w[checkbox dropdown input multi-select textarea upload]
|
|
||||||
yaml.each do |field|
|
yaml.each do |field|
|
||||||
if !allowed_types.include?(field["type"])
|
if !ALLOWED_TYPES.include?(field["type"])
|
||||||
return(
|
return(
|
||||||
record.errors.add(
|
record.errors.add(
|
||||||
:template,
|
:template,
|
||||||
I18n.t(
|
I18n.t(
|
||||||
"form_templates.errors.invalid_type",
|
"form_templates.errors.invalid_type",
|
||||||
type: field["type"],
|
type: field["type"],
|
||||||
valid_types: allowed_types.join(", "),
|
valid_types: ALLOWED_TYPES.join(", "),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -29,11 +32,33 @@ class FormTemplateYamlValidator < ActiveModel::Validator
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_missing_type(record, yaml)
|
def check_missing_fields(record, yaml)
|
||||||
yaml.each do |field|
|
yaml.each do |field|
|
||||||
if field["type"].blank?
|
if field["type"].blank?
|
||||||
return record.errors.add(:template, I18n.t("form_templates.errors.missing_type"))
|
return(record.errors.add(:template, I18n.t("form_templates.errors.missing_type")))
|
||||||
|
end
|
||||||
|
if field["id"].blank?
|
||||||
|
return(record.errors.add(:template, I18n.t("form_templates.errors.missing_id")))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_ids(record, yaml)
|
||||||
|
ids = []
|
||||||
|
yaml.each do |field|
|
||||||
|
next if field["id"].blank?
|
||||||
|
|
||||||
|
if RESERVED_KEYWORDS.include?(field["id"])
|
||||||
|
return(
|
||||||
|
record.errors.add(:template, I18n.t("form_templates.errors.reserved_id", id: field["id"]))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
if ids.include?(field["id"])
|
||||||
|
return(record.errors.add(:template, I18n.t("form_templates.errors.duplicate_ids")))
|
||||||
|
end
|
||||||
|
|
||||||
|
ids << field["id"]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,5 +2,6 @@
|
|||||||
|
|
||||||
Fabricator(:form_template) do
|
Fabricator(:form_template) do
|
||||||
name { sequence(:name) { |i| "template_#{i}" } }
|
name { sequence(:name) { |i| "template_#{i}" } }
|
||||||
template "- type: input"
|
template "- type: input
|
||||||
|
id: name"
|
||||||
end
|
end
|
||||||
|
@ -4,11 +4,12 @@ require "rails_helper"
|
|||||||
|
|
||||||
RSpec.describe FormTemplate, type: :model do
|
RSpec.describe FormTemplate, type: :model do
|
||||||
it "can't have duplicate names" do
|
it "can't have duplicate names" do
|
||||||
Fabricate(:form_template, name: "Bug Report", template: "- type: input")
|
Fabricate(:form_template, name: "Bug Report", template: "- type: input\n id: name")
|
||||||
t = Fabricate.build(:form_template, name: "Bug Report", template: "- type: input")
|
t = Fabricate.build(:form_template, name: "Bug Report", template: "- type: input\n id: name")
|
||||||
expect(t.save).to eq(false)
|
expect(t.save).to eq(false)
|
||||||
t = Fabricate.build(:form_template, name: "Bug Report", template: "- type: input")
|
t = Fabricate.build(:form_template, name: "Bug Report", template: "- type: input\n id: name")
|
||||||
expect(t.save).to eq(false)
|
expect(t.save).to eq(false)
|
||||||
|
expect(t.errors.full_messages.first).to include(I18n.t("errors.messages.taken"))
|
||||||
expect(described_class.count).to eq(1)
|
expect(described_class.count).to eq(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -16,17 +17,33 @@ RSpec.describe FormTemplate, type: :model do
|
|||||||
template = "- type: checkbox\nattributes; bad"
|
template = "- type: checkbox\nattributes; bad"
|
||||||
t = Fabricate.build(:form_template, name: "Feature Request", template: template)
|
t = Fabricate.build(:form_template, name: "Feature Request", template: template)
|
||||||
expect(t.save).to eq(false)
|
expect(t.save).to eq(false)
|
||||||
|
expect(t.errors.full_messages.first).to include(I18n.t("form_templates.errors.invalid_yaml"))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "must have a supported type" do
|
it "must have a supported type" do
|
||||||
template = "- type: fancy"
|
template = "- type: fancy\n id: something"
|
||||||
t = Fabricate.build(:form_template, name: "Fancy Template", template: template)
|
t = Fabricate.build(:form_template, name: "Fancy Template", template: template)
|
||||||
expect(t.save).to eq(false)
|
expect(t.save).to eq(false)
|
||||||
|
expect(t.errors.full_messages.first).to include(
|
||||||
|
I18n.t(
|
||||||
|
"form_templates.errors.invalid_type",
|
||||||
|
type: "fancy",
|
||||||
|
valid_types: FormTemplateYamlValidator::ALLOWED_TYPES.join(", "),
|
||||||
|
),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "must have a type property" do
|
it "must have a type property" do
|
||||||
template = "- hello: world"
|
template = "- hello: world\n id: something"
|
||||||
t = Fabricate.build(:form_template, name: "Basic Template", template: template)
|
t = Fabricate.build(:form_template, name: "Basic Template", template: template)
|
||||||
expect(t.save).to eq(false)
|
expect(t.save).to eq(false)
|
||||||
|
expect(t.errors.full_messages.first).to include(I18n.t("form_templates.errors.missing_type"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "must have a id property" do
|
||||||
|
template = "- type: checkbox"
|
||||||
|
t = Fabricate.build(:form_template, name: "Basic Template", template: template)
|
||||||
|
expect(t.save).to eq(false)
|
||||||
|
expect(t.errors.full_messages.first).to include(I18n.t("form_templates.errors.missing_id"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -74,7 +74,7 @@ RSpec.describe Admin::FormTemplatesController do
|
|||||||
params: {
|
params: {
|
||||||
name: "Bug Reports",
|
name: "Bug Reports",
|
||||||
template:
|
template:
|
||||||
"- 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 id: website\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)
|
expect(response.status).to eq(200)
|
||||||
@ -112,13 +112,13 @@ RSpec.describe Admin::FormTemplatesController do
|
|||||||
params: {
|
params: {
|
||||||
id: form_template.id,
|
id: form_template.id,
|
||||||
name: "Updated Template",
|
name: "Updated Template",
|
||||||
template: "- type: checkbox",
|
template: "- type: checkbox\n id: checkbox",
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
form_template.reload
|
form_template.reload
|
||||||
expect(form_template.name).to eq("Updated Template")
|
expect(form_template.name).to eq("Updated Template")
|
||||||
expect(form_template.template).to eq("- type: checkbox")
|
expect(form_template.template).to eq("- type: checkbox\n id: checkbox")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ describe "Admin Customize Form Templates", type: :system do
|
|||||||
form_template_page.visit_new
|
form_template_page.visit_new
|
||||||
|
|
||||||
sample_name = "My First Template"
|
sample_name = "My First Template"
|
||||||
sample_template = "- type: input"
|
sample_template = "- type: input\n id: name"
|
||||||
|
|
||||||
form_template_page.type_in_template_name(sample_name)
|
form_template_page.type_in_template_name(sample_name)
|
||||||
ace_editor.type_input(sample_template)
|
ace_editor.type_input(sample_template)
|
||||||
@ -101,7 +101,7 @@ describe "Admin Customize Form Templates", type: :system do
|
|||||||
it "should show a preview of the template in a modal when clicking the preview button" do
|
it "should show a preview of the template in a modal when clicking the preview button" do
|
||||||
form_template_page.visit_new
|
form_template_page.visit_new
|
||||||
form_template_page.type_in_template_name("New Template")
|
form_template_page.type_in_template_name("New Template")
|
||||||
ace_editor.type_input("- type: input")
|
ace_editor.type_input("- type: input\n id: name")
|
||||||
form_template_page.click_preview_button
|
form_template_page.click_preview_button
|
||||||
|
|
||||||
expect(form_template_page).to have_preview_modal
|
expect(form_template_page).to have_preview_modal
|
||||||
@ -112,7 +112,12 @@ describe "Admin Customize Form Templates", type: :system do
|
|||||||
form_template_page.visit_new
|
form_template_page.visit_new
|
||||||
form_template_page.type_in_template_name("New Template")
|
form_template_page.type_in_template_name("New Template")
|
||||||
ace_editor.type_input(
|
ace_editor.type_input(
|
||||||
"- type: input\n- type: textarea\n- type: checkbox\n- type: dropdown\n- type: upload\n- type: multi-select",
|
"- type: input\n id: name
|
||||||
|
\b\b- type: textarea\n id: description
|
||||||
|
\b\b- type: checkbox\n id: checkbox
|
||||||
|
\b\b- type: dropdown\n id: dropdown
|
||||||
|
\b\b- type: upload\n id: upload
|
||||||
|
\b\b- type: multi-select\n id: multi-select",
|
||||||
)
|
)
|
||||||
form_template_page.click_preview_button
|
form_template_page.click_preview_button
|
||||||
expect(form_template_page).to have_input_field("input")
|
expect(form_template_page).to have_input_field("input")
|
||||||
@ -127,6 +132,7 @@ describe "Admin Customize Form Templates", type: :system do
|
|||||||
quick_insertion_test(
|
quick_insertion_test(
|
||||||
"checkbox",
|
"checkbox",
|
||||||
'- type: checkbox
|
'- type: checkbox
|
||||||
|
id: enter-id-here
|
||||||
attributes:
|
attributes:
|
||||||
label: "Enter label here"
|
label: "Enter label here"
|
||||||
validations:
|
validations:
|
||||||
@ -138,6 +144,7 @@ describe "Admin Customize Form Templates", type: :system do
|
|||||||
quick_insertion_test(
|
quick_insertion_test(
|
||||||
"input",
|
"input",
|
||||||
'- type: input
|
'- type: input
|
||||||
|
id: enter-id-here
|
||||||
attributes:
|
attributes:
|
||||||
label: "Enter label here"
|
label: "Enter label here"
|
||||||
placeholder: "Enter placeholder here"
|
placeholder: "Enter placeholder here"
|
||||||
@ -150,6 +157,7 @@ describe "Admin Customize Form Templates", type: :system do
|
|||||||
quick_insertion_test(
|
quick_insertion_test(
|
||||||
"textarea",
|
"textarea",
|
||||||
'- type: textarea
|
'- type: textarea
|
||||||
|
id: enter-id-here
|
||||||
attributes:
|
attributes:
|
||||||
label: "Enter label here"
|
label: "Enter label here"
|
||||||
placeholder: "Enter placeholder here"
|
placeholder: "Enter placeholder here"
|
||||||
@ -162,6 +170,7 @@ describe "Admin Customize Form Templates", type: :system do
|
|||||||
quick_insertion_test(
|
quick_insertion_test(
|
||||||
"dropdown",
|
"dropdown",
|
||||||
'- type: dropdown
|
'- type: dropdown
|
||||||
|
id: enter-id-here
|
||||||
choices:
|
choices:
|
||||||
- "Option 1"
|
- "Option 1"
|
||||||
- "Option 2"
|
- "Option 2"
|
||||||
@ -179,6 +188,7 @@ describe "Admin Customize Form Templates", type: :system do
|
|||||||
quick_insertion_test(
|
quick_insertion_test(
|
||||||
"upload",
|
"upload",
|
||||||
'- type: upload
|
'- type: upload
|
||||||
|
id: enter-id-here
|
||||||
attributes:
|
attributes:
|
||||||
file_types: ".jpg, .png, .gif"
|
file_types: ".jpg, .png, .gif"
|
||||||
allow_multiple: false
|
allow_multiple: false
|
||||||
@ -192,6 +202,7 @@ describe "Admin Customize Form Templates", type: :system do
|
|||||||
quick_insertion_test(
|
quick_insertion_test(
|
||||||
"multiselect",
|
"multiselect",
|
||||||
'- type: multi-select
|
'- type: multi-select
|
||||||
|
id: enter-id-here
|
||||||
choices:
|
choices:
|
||||||
- "Option 1"
|
- "Option 1"
|
||||||
- "Option 2"
|
- "Option 2"
|
||||||
|
@ -8,6 +8,7 @@ describe "Composer Form Templates", type: :system do
|
|||||||
name: "Bug Reports",
|
name: "Bug Reports",
|
||||||
template:
|
template:
|
||||||
"- type: input
|
"- type: input
|
||||||
|
id: full-name
|
||||||
attributes:
|
attributes:
|
||||||
label: What is your full name?
|
label: What is your full name?
|
||||||
placeholder: John Doe
|
placeholder: John Doe
|
||||||
@ -16,13 +17,13 @@ describe "Composer Form Templates", type: :system do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
fab!(:form_template_2) do
|
fab!(:form_template_2) do
|
||||||
Fabricate(:form_template, name: "Feature Request", template: "- type: checkbox")
|
Fabricate(:form_template, name: "Feature Request", template: "- type: checkbox\n id: check")
|
||||||
end
|
end
|
||||||
fab!(:form_template_3) do
|
fab!(:form_template_3) do
|
||||||
Fabricate(:form_template, name: "Awesome Possum", template: "- type: dropdown")
|
Fabricate(:form_template, name: "Awesome Possum", template: "- type: dropdown\n id: dropdown")
|
||||||
end
|
end
|
||||||
fab!(:form_template_4) do
|
fab!(:form_template_4) do
|
||||||
Fabricate(:form_template, name: "Biography", template: "- type: textarea")
|
Fabricate(:form_template, name: "Biography", template: "- type: textarea\n id: bio")
|
||||||
end
|
end
|
||||||
fab!(:form_template_5) do
|
fab!(:form_template_5) do
|
||||||
Fabricate(
|
Fabricate(
|
||||||
@ -31,12 +32,14 @@ describe "Composer Form Templates", type: :system do
|
|||||||
template:
|
template:
|
||||||
%Q(
|
%Q(
|
||||||
- type: input
|
- type: input
|
||||||
|
id: full-name
|
||||||
attributes:
|
attributes:
|
||||||
label: "What is your name?"
|
label: "What is your name?"
|
||||||
placeholder: "John Smith"
|
placeholder: "John Smith"
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: upload
|
- type: upload
|
||||||
|
id: prescription
|
||||||
attributes:
|
attributes:
|
||||||
file_types: ".jpg, .png"
|
file_types: ".jpg, .png"
|
||||||
allow_multiple: false
|
allow_multiple: false
|
||||||
@ -44,6 +47,7 @@ describe "Composer Form Templates", type: :system do
|
|||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: upload
|
- type: upload
|
||||||
|
id: additional-docs
|
||||||
attributes:
|
attributes:
|
||||||
file_types: ".jpg, .png, .pdf, .mp3, .mp4"
|
file_types: ".jpg, .png, .pdf, .mp3, .mp4"
|
||||||
allow_multiple: true
|
allow_multiple: true
|
||||||
@ -240,7 +244,7 @@ describe "Composer Form Templates", type: :system do
|
|||||||
|
|
||||||
category_page.visit(category_with_upload_template)
|
category_page.visit(category_with_upload_template)
|
||||||
category_page.new_topic_button.click
|
category_page.new_topic_button.click
|
||||||
attach_file "upload-your-prescription-uploader",
|
attach_file "prescription-uploader",
|
||||||
"#{Rails.root}/spec/fixtures/images/logo.png",
|
"#{Rails.root}/spec/fixtures/images/logo.png",
|
||||||
make_visible: true
|
make_visible: true
|
||||||
composer.fill_title(topic_title)
|
composer.fill_title(topic_title)
|
||||||
@ -253,11 +257,9 @@ describe "Composer Form Templates", type: :system do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't allow uploading an invalid file type" do
|
it "doesn't allow uploading an invalid file type" do
|
||||||
topic_title = "Bruce Wayne's Medication"
|
|
||||||
|
|
||||||
category_page.visit(category_with_upload_template)
|
category_page.visit(category_with_upload_template)
|
||||||
category_page.new_topic_button.click
|
category_page.new_topic_button.click
|
||||||
attach_file "upload-your-prescription-uploader",
|
attach_file "prescription-uploader",
|
||||||
"#{Rails.root}/spec/fixtures/images/animated.gif",
|
"#{Rails.root}/spec/fixtures/images/animated.gif",
|
||||||
make_visible: true
|
make_visible: true
|
||||||
expect(find("#dialog-holder .dialog-body p", visible: :all)).to have_content(
|
expect(find("#dialog-holder .dialog-body p", visible: :all)).to have_content(
|
||||||
@ -270,10 +272,10 @@ describe "Composer Form Templates", type: :system do
|
|||||||
|
|
||||||
category_page.visit(category_with_upload_template)
|
category_page.visit(category_with_upload_template)
|
||||||
category_page.new_topic_button.click
|
category_page.new_topic_button.click
|
||||||
attach_file "upload-your-prescription-uploader",
|
attach_file "prescription-uploader",
|
||||||
"#{Rails.root}/spec/fixtures/images/logo.png",
|
"#{Rails.root}/spec/fixtures/images/logo.png",
|
||||||
make_visible: true
|
make_visible: true
|
||||||
attach_file "any-additional-docs-uploader",
|
attach_file "additional-docs-uploader",
|
||||||
[
|
[
|
||||||
"#{Rails.root}/spec/fixtures/media/small.mp3",
|
"#{Rails.root}/spec/fixtures/media/small.mp3",
|
||||||
"#{Rails.root}/spec/fixtures/media/small.mp4",
|
"#{Rails.root}/spec/fixtures/media/small.mp4",
|
||||||
|
@ -8,6 +8,7 @@ describe "Composer Form Template Validations", type: :system, js: true do
|
|||||||
name: "Bug Reports",
|
name: "Bug Reports",
|
||||||
template:
|
template:
|
||||||
"- type: input
|
"- type: input
|
||||||
|
id: full-name
|
||||||
attributes:
|
attributes:
|
||||||
label: What is your full name?
|
label: What is your full name?
|
||||||
placeholder: John Doe
|
placeholder: John Doe
|
||||||
@ -24,6 +25,7 @@ describe "Composer Form Template Validations", type: :system, js: true do
|
|||||||
name: "Websites",
|
name: "Websites",
|
||||||
template:
|
template:
|
||||||
"- type: input
|
"- type: input
|
||||||
|
id: website-name
|
||||||
attributes:
|
attributes:
|
||||||
label: What is your website name?
|
label: What is your website name?
|
||||||
placeholder: https://www.example.com
|
placeholder: https://www.example.com
|
||||||
|
Reference in New Issue
Block a user