mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 06:53:45 +08:00
DEV: Show form templates in the composer (#21190)
This commit is contained in:
parent
5abe98afb5
commit
c74c90bae5
|
@ -32,10 +32,11 @@ export default class FormTemplateForm extends Component {
|
|||
type: "dropdown",
|
||||
icon: "chevron-circle-down",
|
||||
},
|
||||
{
|
||||
type: "upload",
|
||||
icon: "cloud-upload-alt",
|
||||
},
|
||||
// TODO(@keegan): add support for uploads
|
||||
// {
|
||||
// type: "upload",
|
||||
// icon: "cloud-upload-alt",
|
||||
// },
|
||||
{
|
||||
type: "multiselect",
|
||||
icon: "bullseye",
|
||||
|
|
|
@ -30,16 +30,14 @@ export default class FormTemplate extends RestModel {
|
|||
});
|
||||
}
|
||||
|
||||
static findAll() {
|
||||
return ajax(`/admin/customize/form-templates.json`).then((model) => {
|
||||
return model.form_templates.sort((a, b) => a.id - b.id);
|
||||
});
|
||||
static async findAll() {
|
||||
const result = await ajax("/admin/customize/form-templates.json");
|
||||
return result.form_templates;
|
||||
}
|
||||
|
||||
static findById(id) {
|
||||
return ajax(`/admin/customize/form-templates/${id}.json`).then((model) => {
|
||||
return model.form_template;
|
||||
});
|
||||
static async findById(id) {
|
||||
const result = await ajax(`/admin/customize/form-templates/${id}.json`);
|
||||
return result.form_template;
|
||||
}
|
||||
|
||||
static validateTemplate(data) {
|
||||
|
|
|
@ -121,6 +121,7 @@
|
|||
@afterRefresh={{this.composer.afterRefresh}}
|
||||
@focusTarget={{this.composer.focusTarget}}
|
||||
@disableTextarea={{this.composer.disableTextarea}}
|
||||
@formTemplateIds={{this.composer.formTemplateIds}}
|
||||
>
|
||||
<div class="composer-fields">
|
||||
<PluginOutlet
|
||||
|
@ -167,9 +168,7 @@
|
|||
<div class="category-input">
|
||||
<CategoryChooser
|
||||
@value={{this.composer.model.categoryId}}
|
||||
@onChange={{action
|
||||
(mut this.composer.model.categoryId)
|
||||
}}
|
||||
@onChange={{this.composer.updateCategory}}
|
||||
@options={{hash
|
||||
disabled=this.composer.disableCategoryChooser
|
||||
scopedCategoryId=this.composer.scopedCategoryId
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
@onExpandPopupMenuOptions={{action "onExpandPopupMenuOptions"}}
|
||||
@onPopupMenuAction={{this.onPopupMenuAction}}
|
||||
@popupMenuOptions={{this.popupMenuOptions}}
|
||||
@formTemplateIds={{this.formTemplateIds}}
|
||||
@disabled={{this.disableTextarea}}
|
||||
@outletArgs={{hash composer=this.composer editorType="composer"}}
|
||||
>
|
||||
|
|
|
@ -1,63 +1,75 @@
|
|||
<div class="d-editor-container">
|
||||
<div class="d-editor-textarea-column">
|
||||
{{yield}}
|
||||
|
||||
<div
|
||||
class="d-editor-textarea-wrapper
|
||||
{{if this.disabled 'disabled'}}
|
||||
{{if this.isEditorFocused 'in-focus'}}"
|
||||
>
|
||||
<div class="d-editor-button-bar" role="toolbar">
|
||||
{{#each this.toolbar.groups as |group|}}
|
||||
{{#each group.buttons as |b|}}
|
||||
{{#if b.popupMenu}}
|
||||
<ToolbarPopupMenuOptions
|
||||
@content={{this.popupMenuOptions}}
|
||||
@onChange={{this.onPopupMenuAction}}
|
||||
@onOpen={{action b.action b}}
|
||||
@class={{b.className}}
|
||||
@tabindex={{-1}}
|
||||
@onKeydown={{this.rovingButtonBar}}
|
||||
@options={{hash icon=b.icon focusAfterOnChange=false}}
|
||||
/>
|
||||
{{else}}
|
||||
<DButton
|
||||
@action={{b.action}}
|
||||
@type="button"
|
||||
@actionParam={{b}}
|
||||
@translatedTitle={{b.title}}
|
||||
@label={{b.label}}
|
||||
@icon={{b.icon}}
|
||||
@class={{b.className}}
|
||||
@preventFocus={{b.preventFocus}}
|
||||
@tabindex={{b.tabindex}}
|
||||
@onKeyDown={{this.rovingButtonBar}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @formTemplateIds}}
|
||||
{{#if (gt @formTemplateIds.length 1)}}
|
||||
<FormTemplateChooser
|
||||
@class="composer-select-form-template"
|
||||
@filteredIds={{@formTemplateIds}}
|
||||
@value={{this.selectedFormTemplateId}}
|
||||
@onChange={{this.updateSelectedFormTemplateId}}
|
||||
@options={{hash maximum=1}}
|
||||
/>
|
||||
{{/if}}
|
||||
<FormTemplateField::Wrapper @id={{this.selectedFormTemplateId}} />
|
||||
{{else}}
|
||||
<div
|
||||
class="d-editor-textarea-wrapper
|
||||
{{if this.disabled 'disabled'}}
|
||||
{{if this.isEditorFocused 'in-focus'}}"
|
||||
>
|
||||
<div class="d-editor-button-bar" role="toolbar">
|
||||
{{#each this.toolbar.groups as |group|}}
|
||||
{{#each group.buttons as |b|}}
|
||||
{{#if b.popupMenu}}
|
||||
<ToolbarPopupMenuOptions
|
||||
@content={{this.popupMenuOptions}}
|
||||
@onChange={{this.onPopupMenuAction}}
|
||||
@onOpen={{action b.action b}}
|
||||
@class={{b.className}}
|
||||
@tabindex={{-1}}
|
||||
@onKeydown={{this.rovingButtonBar}}
|
||||
@options={{hash icon=b.icon focusAfterOnChange=false}}
|
||||
/>
|
||||
{{else}}
|
||||
<DButton
|
||||
@action={{b.action}}
|
||||
@type="button"
|
||||
@actionParam={{b}}
|
||||
@translatedTitle={{b.title}}
|
||||
@label={{b.label}}
|
||||
@icon={{b.icon}}
|
||||
@class={{b.className}}
|
||||
@preventFocus={{b.preventFocus}}
|
||||
@tabindex={{b.tabindex}}
|
||||
@onKeyDown={{this.rovingButtonBar}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}} />
|
||||
<DTextarea
|
||||
@autocomplete="off"
|
||||
@tabindex={{this.tabindex}}
|
||||
@value={{this.value}}
|
||||
@class="d-editor-input"
|
||||
@placeholder={{this.placeholderTranslated}}
|
||||
@aria-label={{this.placeholderTranslated}}
|
||||
@disabled={{this.disabled}}
|
||||
@input={{this.change}}
|
||||
@focusIn={{action "focusIn"}}
|
||||
@focusOut={{action "focusOut"}}
|
||||
/>
|
||||
<PopupInputTip @validation={{this.validation}} />
|
||||
<PluginOutlet
|
||||
@name="after-d-editor"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{this.outletArgs}}
|
||||
/>
|
||||
</div>
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}} />
|
||||
<DTextarea
|
||||
@autocomplete="off"
|
||||
@tabindex={{this.tabindex}}
|
||||
@value={{this.value}}
|
||||
@class="d-editor-input"
|
||||
@placeholder={{this.placeholderTranslated}}
|
||||
@aria-label={{this.placeholderTranslated}}
|
||||
@disabled={{this.disabled}}
|
||||
@input={{this.change}}
|
||||
@focusIn={{action "focusIn"}}
|
||||
@focusOut={{action "focusOut"}}
|
||||
/>
|
||||
<PopupInputTip @validation={{this.validation}} />
|
||||
<PluginOutlet
|
||||
@name="after-d-editor"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{this.outletArgs}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
|
@ -33,7 +33,7 @@ import showModal from "discourse/lib/show-modal";
|
|||
import { siteDir } from "discourse/lib/text-direction";
|
||||
import { translations } from "pretty-text/emoji/data";
|
||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||
import { action } from "@ember/object";
|
||||
import { action, computed } from "@ember/object";
|
||||
import TextareaTextManipulation, {
|
||||
getHead,
|
||||
} from "discourse/mixins/textarea-text-manipulation";
|
||||
|
@ -228,6 +228,25 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
processPreview: true,
|
||||
composerFocusSelector: "#reply-control .d-editor-input",
|
||||
|
||||
selectedFormTemplateId: computed("formTemplateIds", {
|
||||
get() {
|
||||
if (this._selectedFormTemplateId) {
|
||||
return this._selectedFormTemplateId;
|
||||
}
|
||||
|
||||
return this.formTemplateIds?.[0];
|
||||
},
|
||||
|
||||
set(key, value) {
|
||||
return (this._selectedFormTemplateId = value);
|
||||
},
|
||||
}),
|
||||
|
||||
@action
|
||||
updateSelectedFormTemplateId(formTemplateId) {
|
||||
this.selectedFormTemplateId = formTemplateId;
|
||||
},
|
||||
|
||||
@discourseComputed("placeholder")
|
||||
placeholderTranslated(placeholder) {
|
||||
if (placeholder) {
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
{{#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}}
|
||||
{{#if this.parsedTemplate}}
|
||||
<div
|
||||
class="form-template-form__wrapper"
|
||||
{{did-update this.refreshTemplate @id}}
|
||||
>
|
||||
{{#each this.parsedTemplate as |content|}}
|
||||
{{component
|
||||
(concat "form-template-field/" content.type)
|
||||
attributes=content.attributes
|
||||
choices=content.choices
|
||||
validations=content.validations
|
||||
}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="alert alert-error">
|
||||
{{this.error}}
|
||||
|
|
|
@ -1,17 +1,45 @@
|
|||
import Component from "@glimmer/component";
|
||||
import Yaml from "js-yaml";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import FormTemplate from "discourse/models/form-template";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class FormTemplateFieldWrapper extends Component {
|
||||
@tracked error = null;
|
||||
@tracked parsedTemplate = null;
|
||||
|
||||
get canShowContent() {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
if (this.args.content) {
|
||||
// Content used when no id exists yet
|
||||
// (i.e. previewing while creating a new template)
|
||||
this._loadTemplate(this.args.content);
|
||||
} else if (this.args.id) {
|
||||
this._fetchTemplate(this.args.id);
|
||||
}
|
||||
}
|
||||
|
||||
_loadTemplate(templateContent) {
|
||||
try {
|
||||
const parsedContent = Yaml.load(this.args.content);
|
||||
this.parsedContent = parsedContent;
|
||||
return true;
|
||||
this.parsedTemplate = Yaml.load(templateContent);
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
refreshTemplate() {
|
||||
if (Array.isArray(this.args?.id) && this.args?.id.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this._fetchTemplate(this.args.id);
|
||||
}
|
||||
|
||||
async _fetchTemplate(id) {
|
||||
const response = await FormTemplate.findById(id);
|
||||
const templateContent = await response.form_template.template;
|
||||
return this._loadTemplate(templateContent);
|
||||
}
|
||||
}
|
||||
|
|
13
app/assets/javascripts/discourse/app/models/form-template.js
Normal file
13
app/assets/javascripts/discourse/app/models/form-template.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { ajax } from "discourse/lib/ajax";
|
||||
import RestModel from "discourse/models/rest";
|
||||
|
||||
export default class FormTemplate extends RestModel {
|
||||
static async findAll() {
|
||||
const result = await ajax("/form-templates.json");
|
||||
return result.form_templates;
|
||||
}
|
||||
|
||||
static async findById(id) {
|
||||
return await ajax(`/form-templates/${id}.json`);
|
||||
}
|
||||
}
|
|
@ -158,6 +158,14 @@ export default class ComposerController extends Controller {
|
|||
return this.set("_disableSubmit", value);
|
||||
}
|
||||
|
||||
get formTemplateIds() {
|
||||
if (!this.siteSettings.experimental_form_templates) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.model.category?.get("form_template_ids");
|
||||
}
|
||||
|
||||
@discourseComputed("showPreview")
|
||||
toggleText(showPreview) {
|
||||
return showPreview
|
||||
|
@ -498,6 +506,11 @@ export default class ComposerController extends Controller {
|
|||
});
|
||||
}
|
||||
|
||||
@action
|
||||
updateCategory(categoryId) {
|
||||
this.model.categoryId = categoryId;
|
||||
}
|
||||
|
||||
@action
|
||||
openIfDraft(event) {
|
||||
if (!this.get("model.viewDraft")) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
/>
|
||||
</div>
|
||||
{{#if this.showPreview}}
|
||||
<FormTemplateField::Wrapper @content={{this.model.template}} />
|
||||
<FormTemplateField::Wrapper @id={{this.model.id}} />
|
||||
{{else}}
|
||||
<HighlightedCode @lang="yaml" @code={{this.model.template}} />
|
||||
{{/if}}
|
||||
|
|
|
@ -3,6 +3,7 @@ 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";
|
||||
import pretender, { response } from "discourse/tests/helpers/create-pretender";
|
||||
|
||||
module(
|
||||
"Integration | Component | form-template-field | wrapper",
|
||||
|
@ -22,7 +23,7 @@ module(
|
|||
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) {
|
||||
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 componentTypes = [
|
||||
"checkbox",
|
||||
|
@ -45,5 +46,28 @@ module(
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
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", () => {
|
||||
return response({
|
||||
form_template: {
|
||||
id: 1,
|
||||
name: "Bug Reports",
|
||||
template:
|
||||
'- type: checkbox\n choices:\n - "Option 1"\n - "Option 2"\n - "Option 3"\n attributes:\n label: "Enter question here"\n description: "Enter description here"\n validations:\n required: true',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
this.set("formTemplateId", [1]);
|
||||
await render(
|
||||
hbs`<FormTemplateField::Wrapper @id={{this.formTemplateId}} />`
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(`.form-template-field[data-field-type='checkbox']`),
|
||||
`Checkbox component renders`
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -12,7 +12,7 @@ module(
|
|||
|
||||
hooks.beforeEach(function () {
|
||||
this.set("subject", selectKit());
|
||||
pretender.get("/admin/customize/form-templates.json", () => {
|
||||
pretender.get("/form-templates.json", () => {
|
||||
return response({
|
||||
form_templates: [
|
||||
{ id: 1, name: "template 1", template: "test: true" },
|
||||
|
@ -40,7 +40,7 @@ module(
|
|||
});
|
||||
|
||||
test("when no templates are available, the select is disabled", async function (assert) {
|
||||
pretender.get("/admin/customize/form-templates.json", () => {
|
||||
pretender.get("/form-templates.json", () => {
|
||||
return response({ form_templates: [] });
|
||||
});
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import MultiSelectComponent from "select-kit/components/multi-select";
|
||||
import FormTemplate from "admin/models/form-template";
|
||||
import FormTemplate from "discourse/models/form-template";
|
||||
import { computed } from "@ember/object";
|
||||
|
||||
export default MultiSelectComponent.extend({
|
||||
pluginApiIdentifiers: ["form-template-chooser"],
|
||||
classNames: ["form-template-chooser"],
|
||||
selectKitOptions: {
|
||||
none: "admin.form_templates.edit_category.select_template",
|
||||
none: "form_template_chooser.select_template",
|
||||
},
|
||||
|
||||
init() {
|
||||
|
@ -17,6 +17,11 @@ export default MultiSelectComponent.extend({
|
|||
}
|
||||
},
|
||||
|
||||
didUpdateAttrs() {
|
||||
this._super(...arguments);
|
||||
this._fetchTemplates();
|
||||
},
|
||||
|
||||
@computed("templates")
|
||||
get content() {
|
||||
if (!this.templates) {
|
||||
|
@ -28,7 +33,14 @@ export default MultiSelectComponent.extend({
|
|||
|
||||
_fetchTemplates() {
|
||||
FormTemplate.findAll().then((result) => {
|
||||
const sortedTemplates = this._sortTemplatesByName(result);
|
||||
let sortedTemplates = this._sortTemplatesByName(result);
|
||||
|
||||
if (this.filteredIds) {
|
||||
sortedTemplates = sortedTemplates.filter((t) =>
|
||||
this.filteredIds.includes(t.id)
|
||||
);
|
||||
}
|
||||
|
||||
if (sortedTemplates.length > 0) {
|
||||
return this.set("templates", sortedTemplates);
|
||||
} else {
|
||||
|
|
|
@ -394,6 +394,17 @@ html.composer-open {
|
|||
#file-uploader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.composer-select-form-template {
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
|
||||
.name,
|
||||
.formatted-selection,
|
||||
.d-icon {
|
||||
color: var(--primary-high);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.autocomplete {
|
||||
|
|
|
@ -344,3 +344,10 @@
|
|||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.d-editor .form-template-form__wrapper {
|
||||
overflow: auto;
|
||||
background: var(--primary-very-low);
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--primary-medium);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ class Admin::FormTemplatesController < Admin::StaffController
|
|||
before_action :ensure_form_templates_enabled
|
||||
|
||||
def index
|
||||
form_templates = FormTemplate.all
|
||||
form_templates = FormTemplate.all.order(:id)
|
||||
render_serialized(form_templates, AdminFormTemplateSerializer, root: "form_templates")
|
||||
end
|
||||
|
||||
|
|
26
app/controllers/form_templates_controller.rb
Normal file
26
app/controllers/form_templates_controller.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormTemplatesController < ApplicationController
|
||||
requires_login
|
||||
before_action :ensure_form_templates_enabled
|
||||
|
||||
def index
|
||||
form_templates = FormTemplate.all.order(:id)
|
||||
render_serialized(form_templates, FormTemplateSerializer, root: "form_templates")
|
||||
end
|
||||
|
||||
def show
|
||||
params.require(:id)
|
||||
|
||||
template = FormTemplate.find_by(id: params[:id])
|
||||
raise Discourse::NotFound if template.nil?
|
||||
|
||||
render_serialized(template, FormTemplateSerializer, root: "form_template")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_form_templates_enabled
|
||||
raise Discourse::InvalidAccess.new unless SiteSetting.experimental_form_templates
|
||||
end
|
||||
end
|
5
app/serializers/form_template_serializer.rb
Normal file
5
app/serializers/form_template_serializer.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormTemplateSerializer < ApplicationSerializer
|
||||
attributes :id, :name, :template
|
||||
end
|
|
@ -4525,6 +4525,9 @@ en:
|
|||
char_counter:
|
||||
exceeded: "The maximum number of characters allowed has been exceeded."
|
||||
|
||||
form_template_chooser:
|
||||
select_template: "Select form templates"
|
||||
|
||||
# This section is exported to the javascript for i18n in the admin section
|
||||
admin_js:
|
||||
type_to_filter: "type to filter..."
|
||||
|
|
|
@ -1597,5 +1597,8 @@ Discourse::Application.routes.draw do
|
|||
put "/sidebar_sections/reset/:id" => "sidebar_sections#reset"
|
||||
|
||||
get "*url", to: "permalinks#show", constraints: PermalinkConstraint.new
|
||||
|
||||
get "/form-templates/:id" => "form_templates#show"
|
||||
get "/form-templates" => "form_templates#index"
|
||||
end
|
||||
end
|
||||
|
|
86
spec/requests/form_templates_controller_spec.rb
Normal file
86
spec/requests/form_templates_controller_spec.rb
Normal file
|
@ -0,0 +1,86 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe FormTemplatesController do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
|
||||
before { SiteSetting.experimental_form_templates = true }
|
||||
|
||||
describe "#index" do
|
||||
fab!(:form_template) { Fabricate(:form_template, id: 2) }
|
||||
fab!(:form_template_2) { Fabricate(:form_template, id: 1) }
|
||||
fab!(:form_template_3) { Fabricate(:form_template, id: 3) }
|
||||
|
||||
context "when logged in as a user" do
|
||||
before { sign_in(user) }
|
||||
|
||||
it "should return all form templates ordered by its ids" do
|
||||
get "/form-templates.json"
|
||||
expect(response.status).to eq(200)
|
||||
json = response.parsed_body
|
||||
expect(json["form_templates"]).to be_present
|
||||
expect(json["form_templates"].length).to eq(3)
|
||||
|
||||
templates = json["form_templates"]
|
||||
expect(templates[0]["id"]).to eq(form_template_2.id)
|
||||
expect(templates[1]["id"]).to eq(form_template.id)
|
||||
expect(templates[2]["id"]).to eq(form_template_3.id)
|
||||
end
|
||||
end
|
||||
|
||||
context "when you are not logged in" do
|
||||
it "should deny access" do
|
||||
get "/form-templates.json"
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
|
||||
context "when experimental form templates is disabled" do
|
||||
before do
|
||||
sign_in(user)
|
||||
SiteSetting.experimental_form_templates = false
|
||||
end
|
||||
|
||||
it "should not work if you are a logged in user" do
|
||||
get "/form-templates.json"
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#show" do
|
||||
fab!(:form_template) { Fabricate(:form_template) }
|
||||
|
||||
context "when logged in as a user" do
|
||||
before { sign_in(user) }
|
||||
|
||||
it "should return a single template" do
|
||||
get "/form-templates/#{form_template.id}.json"
|
||||
expect(response.status).to eq(200)
|
||||
json = response.parsed_body
|
||||
current_template = json["form_template"]
|
||||
expect(current_template["id"]).to eq(form_template.id)
|
||||
expect(current_template["name"]).to eq(form_template.name)
|
||||
expect(current_template["template"]).to eq(form_template.template)
|
||||
end
|
||||
end
|
||||
|
||||
context "when you are not logged in" do
|
||||
it "should deny access" do
|
||||
get "/form-templates/#{form_template.id}.json"
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
|
||||
context "when experimental form templates is disabled" do
|
||||
before do
|
||||
sign_in(user)
|
||||
SiteSetting.experimental_form_templates = false
|
||||
end
|
||||
|
||||
it "should not work if you are a logged in user" do
|
||||
get "/form-templates/#{form_template.id}.json"
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -119,7 +119,8 @@ describe "Admin Customize Form Templates", type: :system, js: true do
|
|||
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")
|
||||
# TODO(@keegan): Add this back when upload functionality is added
|
||||
# expect(form_template_page).to have_input_field("upload")
|
||||
expect(form_template_page).to have_input_field("multi-select")
|
||||
end
|
||||
|
||||
|
@ -175,7 +176,8 @@ describe "Admin Customize Form Templates", type: :system, js: true do
|
|||
)
|
||||
end
|
||||
|
||||
it "should allow quick insertion of upload field" do
|
||||
# TODO(@keegan): Unskip this test when Upload functionality is added
|
||||
xit "should allow quick insertion of upload field" do
|
||||
quick_insertion_test(
|
||||
"upload",
|
||||
'- type: upload
|
||||
|
|
155
spec/system/composer/category_templates_spec.rb
Normal file
155
spec/system/composer/category_templates_spec.rb
Normal file
|
@ -0,0 +1,155 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe "Composer Form Templates", type: :system, js: true do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
fab!(:form_template_1) do
|
||||
Fabricate(:form_template, name: "Bug Reports", template: "- type: checkbox")
|
||||
end
|
||||
fab!(:form_template_2) do
|
||||
Fabricate(:form_template, name: "Feature Request", template: "- type: input")
|
||||
end
|
||||
fab!(:form_template_3) do
|
||||
Fabricate(:form_template, name: "Awesome Possum", template: "- type: dropdown")
|
||||
end
|
||||
fab!(:form_template_4) do
|
||||
Fabricate(:form_template, name: "Biography", template: "- type: textarea")
|
||||
end
|
||||
fab!(:category_with_template_1) do
|
||||
Fabricate(
|
||||
:category,
|
||||
name: "Reports",
|
||||
slug: "reports",
|
||||
topic_count: 2,
|
||||
form_template_ids: [form_template_1.id],
|
||||
)
|
||||
end
|
||||
fab!(:category_with_template_2) do
|
||||
Fabricate(
|
||||
:category,
|
||||
name: "Features",
|
||||
slug: "features",
|
||||
topic_count: 3,
|
||||
form_template_ids: [form_template_2.id],
|
||||
)
|
||||
end
|
||||
fab!(:category_with_multiple_templates_1) do
|
||||
Fabricate(
|
||||
:category,
|
||||
name: "Multiple",
|
||||
slug: "mulitple",
|
||||
topic_count: 10,
|
||||
form_template_ids: [form_template_1.id, form_template_2.id],
|
||||
)
|
||||
end
|
||||
fab!(:category_with_multiple_templates_2) do
|
||||
Fabricate(
|
||||
:category,
|
||||
name: "More Stuff",
|
||||
slug: "more-stuff",
|
||||
topic_count: 10,
|
||||
form_template_ids: [form_template_3.id, form_template_4.id],
|
||||
)
|
||||
end
|
||||
fab!(:category_no_template) do
|
||||
Fabricate(:category, name: "Staff", slug: "staff", topic_count: 2, form_template_ids: [])
|
||||
end
|
||||
fab!(:category_topic_template) do
|
||||
Fabricate(
|
||||
:category,
|
||||
name: "Random",
|
||||
slug: "random",
|
||||
topic_count: 5,
|
||||
form_template_ids: [],
|
||||
topic_template: "Testing",
|
||||
)
|
||||
end
|
||||
let(:category_page) { PageObjects::Pages::Category.new }
|
||||
let(:composer) { PageObjects::Components::Composer.new }
|
||||
let(:form_template_chooser) { PageObjects::Components::SelectKit.new(".form-template-chooser") }
|
||||
|
||||
before do
|
||||
SiteSetting.experimental_form_templates = true
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "shows a textarea when no form template is assigned to the category" do
|
||||
category_page.visit(category_no_template)
|
||||
category_page.new_topic_button.click
|
||||
expect(composer).to have_composer_input
|
||||
end
|
||||
|
||||
it "shows a textarea filled in with topic template when a topic template is assigned to the category" do
|
||||
category_page.visit(category_topic_template)
|
||||
category_page.new_topic_button.click
|
||||
expect(composer).to have_composer_input
|
||||
expect(composer).to have_content(category_topic_template.topic_template)
|
||||
end
|
||||
|
||||
it "shows a form when a form template is assigned to the category" do
|
||||
category_page.visit(category_with_template_1)
|
||||
category_page.new_topic_button.click
|
||||
expect(composer).not_to have_composer_input
|
||||
expect(composer).to have_form_template
|
||||
expect(composer).to have_form_template_field("checkbox")
|
||||
end
|
||||
|
||||
it "shows the correct template when switching categories" do
|
||||
category_page.visit(category_no_template)
|
||||
category_page.new_topic_button.click
|
||||
# first category has no template
|
||||
expect(composer).to have_composer_input
|
||||
# switch to category with topic template
|
||||
composer.switch_category(category_topic_template.name)
|
||||
expect(composer).to have_composer_input
|
||||
expect(composer).to have_content(category_topic_template.topic_template)
|
||||
# switch to category with form template
|
||||
composer.switch_category(category_with_template_1.name)
|
||||
expect(composer).to have_form_template
|
||||
expect(composer).to have_form_template_field("checkbox")
|
||||
# switch to category with a different form template
|
||||
composer.switch_category(category_with_template_2.name)
|
||||
expect(composer).to have_form_template
|
||||
expect(composer).to have_form_template_field("input")
|
||||
end
|
||||
|
||||
it "does not show form template chooser when a category only has form template" do
|
||||
category_page.visit(category_with_template_1)
|
||||
category_page.new_topic_button.click
|
||||
expect(composer).not_to have_form_template_chooser
|
||||
end
|
||||
|
||||
it "shows form template chooser when a category has multiple form templates" do
|
||||
category_page.visit(category_with_multiple_templates_1)
|
||||
category_page.new_topic_button.click
|
||||
expect(composer).to have_form_template_chooser
|
||||
end
|
||||
|
||||
it "updates the form template when a different template is selected" do
|
||||
category_page.visit(category_with_multiple_templates_1)
|
||||
category_page.new_topic_button.click
|
||||
expect(composer).to have_form_template_field("checkbox")
|
||||
form_template_chooser.select_row_by_name(form_template_2.name)
|
||||
expect(composer).to have_form_template_field("input")
|
||||
end
|
||||
|
||||
it "shows the correct template options when switching categories" do
|
||||
category_page.visit(category_with_multiple_templates_1)
|
||||
category_page.new_topic_button.click
|
||||
expect(composer).to have_form_template_chooser
|
||||
form_template_chooser.expand
|
||||
expect(form_template_chooser).to have_selected_choice_name(form_template_1.name)
|
||||
expect(form_template_chooser).to have_option_name(form_template_2.name)
|
||||
composer.switch_category(category_with_multiple_templates_2.name)
|
||||
form_template_chooser.expand
|
||||
expect(form_template_chooser).to have_selected_choice_name(form_template_3.name)
|
||||
expect(form_template_chooser).to have_option_name(form_template_4.name)
|
||||
end
|
||||
|
||||
it "shows the correct template name in the dropdown header after switching templates" do
|
||||
category_page.visit(category_with_multiple_templates_1)
|
||||
category_page.new_topic_button.click
|
||||
expect(form_template_chooser).to have_selected_name(form_template_1.name)
|
||||
form_template_chooser.select_row_by_name(form_template_2.name)
|
||||
expect(form_template_chooser).to have_selected_name(form_template_2.name)
|
||||
end
|
||||
end
|
|
@ -72,6 +72,11 @@ module PageObjects
|
|||
find(AUTOCOMPLETE_MENU)
|
||||
end
|
||||
|
||||
def switch_category(category_name)
|
||||
find(".category-chooser").click
|
||||
find(".category-row[data-name='#{category_name}']").click
|
||||
end
|
||||
|
||||
def has_emoji_autocomplete?
|
||||
has_css?(AUTOCOMPLETE_MENU)
|
||||
end
|
||||
|
@ -98,6 +103,22 @@ module PageObjects
|
|||
page.has_no_css?(emoji_preview_selector(emoji))
|
||||
end
|
||||
|
||||
def has_composer_input?
|
||||
page.has_css?("#{COMPOSER_ID} .d-editor .d-editor-input")
|
||||
end
|
||||
|
||||
def has_form_template?
|
||||
page.has_css?(".form-template-form__wrapper")
|
||||
end
|
||||
|
||||
def has_form_template_field?(field)
|
||||
page.has_css?(".form-template-field[data-field-type='#{field}']")
|
||||
end
|
||||
|
||||
def has_form_template_chooser?
|
||||
page.has_css?(".composer-select-form-template")
|
||||
end
|
||||
|
||||
def composer_input
|
||||
find("#{COMPOSER_ID} .d-editor .d-editor-input")
|
||||
end
|
||||
|
|
|
@ -34,8 +34,16 @@ module PageObjects
|
|||
component.find(".select-kit-header[data-value='#{value}']")
|
||||
end
|
||||
|
||||
def has_selected_name?(value)
|
||||
component.find(".select-kit-header[data-name='#{value}']")
|
||||
def has_selected_name?(name)
|
||||
component.find(".select-kit-header[data-name='#{name}']")
|
||||
end
|
||||
|
||||
def has_selected_choice_name?(name)
|
||||
component.find(".selected-choice[data-name='#{name}']")
|
||||
end
|
||||
|
||||
def has_option_name?(name)
|
||||
component.find(".select-kit-collection li[data-name='#{name}']")
|
||||
end
|
||||
|
||||
def expand
|
||||
|
|
Loading…
Reference in New Issue
Block a user