DEV: Show form templates in the composer (#21190)

This commit is contained in:
Keegan George 2023-05-29 14:47:18 -07:00 committed by GitHub
parent 5abe98afb5
commit c74c90bae5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 548 additions and 96 deletions

View File

@ -32,10 +32,11 @@ export default class FormTemplateForm extends Component {
type: "dropdown", type: "dropdown",
icon: "chevron-circle-down", icon: "chevron-circle-down",
}, },
{ // TODO(@keegan): add support for uploads
type: "upload", // {
icon: "cloud-upload-alt", // type: "upload",
}, // icon: "cloud-upload-alt",
// },
{ {
type: "multiselect", type: "multiselect",
icon: "bullseye", icon: "bullseye",

View File

@ -30,16 +30,14 @@ export default class FormTemplate extends RestModel {
}); });
} }
static findAll() { static async findAll() {
return ajax(`/admin/customize/form-templates.json`).then((model) => { const result = await ajax("/admin/customize/form-templates.json");
return model.form_templates.sort((a, b) => a.id - b.id); return result.form_templates;
});
} }
static findById(id) { static async findById(id) {
return ajax(`/admin/customize/form-templates/${id}.json`).then((model) => { const result = await ajax(`/admin/customize/form-templates/${id}.json`);
return model.form_template; return result.form_template;
});
} }
static validateTemplate(data) { static validateTemplate(data) {

View File

@ -121,6 +121,7 @@
@afterRefresh={{this.composer.afterRefresh}} @afterRefresh={{this.composer.afterRefresh}}
@focusTarget={{this.composer.focusTarget}} @focusTarget={{this.composer.focusTarget}}
@disableTextarea={{this.composer.disableTextarea}} @disableTextarea={{this.composer.disableTextarea}}
@formTemplateIds={{this.composer.formTemplateIds}}
> >
<div class="composer-fields"> <div class="composer-fields">
<PluginOutlet <PluginOutlet
@ -167,9 +168,7 @@
<div class="category-input"> <div class="category-input">
<CategoryChooser <CategoryChooser
@value={{this.composer.model.categoryId}} @value={{this.composer.model.categoryId}}
@onChange={{action @onChange={{this.composer.updateCategory}}
(mut this.composer.model.categoryId)
}}
@options={{hash @options={{hash
disabled=this.composer.disableCategoryChooser disabled=this.composer.disableCategoryChooser
scopedCategoryId=this.composer.scopedCategoryId scopedCategoryId=this.composer.scopedCategoryId

View File

@ -16,6 +16,7 @@
@onExpandPopupMenuOptions={{action "onExpandPopupMenuOptions"}} @onExpandPopupMenuOptions={{action "onExpandPopupMenuOptions"}}
@onPopupMenuAction={{this.onPopupMenuAction}} @onPopupMenuAction={{this.onPopupMenuAction}}
@popupMenuOptions={{this.popupMenuOptions}} @popupMenuOptions={{this.popupMenuOptions}}
@formTemplateIds={{this.formTemplateIds}}
@disabled={{this.disableTextarea}} @disabled={{this.disableTextarea}}
@outletArgs={{hash composer=this.composer editorType="composer"}} @outletArgs={{hash composer=this.composer editorType="composer"}}
> >

View File

@ -1,63 +1,75 @@
<div class="d-editor-container"> <div class="d-editor-container">
<div class="d-editor-textarea-column"> <div class="d-editor-textarea-column">
{{yield}} {{yield}}
{{#if @formTemplateIds}}
<div {{#if (gt @formTemplateIds.length 1)}}
class="d-editor-textarea-wrapper <FormTemplateChooser
{{if this.disabled 'disabled'}} @class="composer-select-form-template"
{{if this.isEditorFocused 'in-focus'}}" @filteredIds={{@formTemplateIds}}
> @value={{this.selectedFormTemplateId}}
<div class="d-editor-button-bar" role="toolbar"> @onChange={{this.updateSelectedFormTemplateId}}
{{#each this.toolbar.groups as |group|}} @options={{hash maximum=1}}
{{#each group.buttons as |b|}} />
{{#if b.popupMenu}} {{/if}}
<ToolbarPopupMenuOptions <FormTemplateField::Wrapper @id={{this.selectedFormTemplateId}} />
@content={{this.popupMenuOptions}} {{else}}
@onChange={{this.onPopupMenuAction}} <div
@onOpen={{action b.action b}} class="d-editor-textarea-wrapper
@class={{b.className}} {{if this.disabled 'disabled'}}
@tabindex={{-1}} {{if this.isEditorFocused 'in-focus'}}"
@onKeydown={{this.rovingButtonBar}} >
@options={{hash icon=b.icon focusAfterOnChange=false}} <div class="d-editor-button-bar" role="toolbar">
/> {{#each this.toolbar.groups as |group|}}
{{else}} {{#each group.buttons as |b|}}
<DButton {{#if b.popupMenu}}
@action={{b.action}} <ToolbarPopupMenuOptions
@type="button" @content={{this.popupMenuOptions}}
@actionParam={{b}} @onChange={{this.onPopupMenuAction}}
@translatedTitle={{b.title}} @onOpen={{action b.action b}}
@label={{b.label}} @class={{b.className}}
@icon={{b.icon}} @tabindex={{-1}}
@class={{b.className}} @onKeydown={{this.rovingButtonBar}}
@preventFocus={{b.preventFocus}} @options={{hash icon=b.icon focusAfterOnChange=false}}
@tabindex={{b.tabindex}} />
@onKeyDown={{this.rovingButtonBar}} {{else}}
/> <DButton
{{/if}} @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}}
{{/each}} </div>
</div>
<ConditionalLoadingSpinner @condition={{this.loading}} /> <ConditionalLoadingSpinner @condition={{this.loading}} />
<DTextarea <DTextarea
@autocomplete="off" @autocomplete="off"
@tabindex={{this.tabindex}} @tabindex={{this.tabindex}}
@value={{this.value}} @value={{this.value}}
@class="d-editor-input" @class="d-editor-input"
@placeholder={{this.placeholderTranslated}} @placeholder={{this.placeholderTranslated}}
@aria-label={{this.placeholderTranslated}} @aria-label={{this.placeholderTranslated}}
@disabled={{this.disabled}} @disabled={{this.disabled}}
@input={{this.change}} @input={{this.change}}
@focusIn={{action "focusIn"}} @focusIn={{action "focusIn"}}
@focusOut={{action "focusOut"}} @focusOut={{action "focusOut"}}
/> />
<PopupInputTip @validation={{this.validation}} /> <PopupInputTip @validation={{this.validation}} />
<PluginOutlet <PluginOutlet
@name="after-d-editor" @name="after-d-editor"
@connectorTagName="div" @connectorTagName="div"
@outletArgs={{this.outletArgs}} @outletArgs={{this.outletArgs}}
/> />
</div> </div>
{{/if}}
</div> </div>
<div <div

View File

@ -33,7 +33,7 @@ import showModal from "discourse/lib/show-modal";
import { siteDir } from "discourse/lib/text-direction"; import { siteDir } from "discourse/lib/text-direction";
import { translations } from "pretty-text/emoji/data"; import { translations } from "pretty-text/emoji/data";
import { wantsNewWindow } from "discourse/lib/intercept-click"; import { wantsNewWindow } from "discourse/lib/intercept-click";
import { action } from "@ember/object"; import { action, computed } from "@ember/object";
import TextareaTextManipulation, { import TextareaTextManipulation, {
getHead, getHead,
} from "discourse/mixins/textarea-text-manipulation"; } from "discourse/mixins/textarea-text-manipulation";
@ -228,6 +228,25 @@ export default Component.extend(TextareaTextManipulation, {
processPreview: true, processPreview: true,
composerFocusSelector: "#reply-control .d-editor-input", 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") @discourseComputed("placeholder")
placeholderTranslated(placeholder) { placeholderTranslated(placeholder) {
if (placeholder) { if (placeholder) {

View File

@ -1,12 +1,17 @@
{{#if this.canShowContent}} {{#if this.parsedTemplate}}
{{#each this.parsedContent as |content|}} <div
{{component class="form-template-form__wrapper"
(concat "form-template-field/" content.type) {{did-update this.refreshTemplate @id}}
attributes=content.attributes >
choices=content.choices {{#each this.parsedTemplate as |content|}}
validations=content.validations {{component
}} (concat "form-template-field/" content.type)
{{/each}} attributes=content.attributes
choices=content.choices
validations=content.validations
}}
{{/each}}
</div>
{{else}} {{else}}
<div class="alert alert-error"> <div class="alert alert-error">
{{this.error}} {{this.error}}

View File

@ -1,17 +1,45 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import Yaml from "js-yaml"; import Yaml from "js-yaml";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import FormTemplate from "discourse/models/form-template";
import { action } from "@ember/object";
export default class FormTemplateFieldWrapper extends Component { export default class FormTemplateFieldWrapper extends Component {
@tracked error = null; @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 { try {
const parsedContent = Yaml.load(this.args.content); this.parsedTemplate = Yaml.load(templateContent);
this.parsedContent = parsedContent;
return true;
} catch (e) { } catch (e) {
this.error = 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);
}
} }

View 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`);
}
}

View File

@ -158,6 +158,14 @@ export default class ComposerController extends Controller {
return this.set("_disableSubmit", value); 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") @discourseComputed("showPreview")
toggleText(showPreview) { toggleText(showPreview) {
return showPreview return showPreview
@ -498,6 +506,11 @@ export default class ComposerController extends Controller {
}); });
} }
@action
updateCategory(categoryId) {
this.model.categoryId = categoryId;
}
@action @action
openIfDraft(event) { openIfDraft(event) {
if (!this.get("model.viewDraft")) { if (!this.get("model.viewDraft")) {

View File

@ -8,7 +8,7 @@
/> />
</div> </div>
{{#if this.showPreview}} {{#if this.showPreview}}
<FormTemplateField::Wrapper @content={{this.model.template}} /> <FormTemplateField::Wrapper @id={{this.model.id}} />
{{else}} {{else}}
<HighlightedCode @lang="yaml" @code={{this.model.template}} /> <HighlightedCode @lang="yaml" @code={{this.model.template}} />
{{/if}} {{/if}}

View File

@ -3,6 +3,7 @@ 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 } from "discourse/tests/helpers/qunit-helpers";
import pretender, { response } from "discourse/tests/helpers/create-pretender";
module( module(
"Integration | Component | form-template-field | wrapper", "Integration | Component | form-template-field | wrapper",
@ -22,7 +23,7 @@ module(
assert.ok(exists(".alert"), "An alert message should 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) { 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- type: input\n- type: textarea\n- type: dropdown\n- type: upload\n- type: multi-select`;
const componentTypes = [ const componentTypes = [
"checkbox", "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`
);
});
} }
); );

View File

@ -12,7 +12,7 @@ module(
hooks.beforeEach(function () { hooks.beforeEach(function () {
this.set("subject", selectKit()); this.set("subject", selectKit());
pretender.get("/admin/customize/form-templates.json", () => { pretender.get("/form-templates.json", () => {
return response({ return response({
form_templates: [ form_templates: [
{ id: 1, name: "template 1", template: "test: true" }, { 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) { 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: [] }); return response({ form_templates: [] });
}); });

View File

@ -1,12 +1,12 @@
import MultiSelectComponent from "select-kit/components/multi-select"; 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"; import { computed } from "@ember/object";
export default MultiSelectComponent.extend({ export default MultiSelectComponent.extend({
pluginApiIdentifiers: ["form-template-chooser"], pluginApiIdentifiers: ["form-template-chooser"],
classNames: ["form-template-chooser"], classNames: ["form-template-chooser"],
selectKitOptions: { selectKitOptions: {
none: "admin.form_templates.edit_category.select_template", none: "form_template_chooser.select_template",
}, },
init() { init() {
@ -17,6 +17,11 @@ export default MultiSelectComponent.extend({
} }
}, },
didUpdateAttrs() {
this._super(...arguments);
this._fetchTemplates();
},
@computed("templates") @computed("templates")
get content() { get content() {
if (!this.templates) { if (!this.templates) {
@ -28,7 +33,14 @@ export default MultiSelectComponent.extend({
_fetchTemplates() { _fetchTemplates() {
FormTemplate.findAll().then((result) => { 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) { if (sortedTemplates.length > 0) {
return this.set("templates", sortedTemplates); return this.set("templates", sortedTemplates);
} else { } else {

View File

@ -394,6 +394,17 @@ html.composer-open {
#file-uploader { #file-uploader {
display: none; display: none;
} }
.composer-select-form-template {
margin-bottom: 8px;
width: 100%;
.name,
.formatted-selection,
.d-icon {
color: var(--primary-high);
}
}
} }
.autocomplete { .autocomplete {

View File

@ -344,3 +344,10 @@
justify-content: center; justify-content: center;
} }
} }
.d-editor .form-template-form__wrapper {
overflow: auto;
background: var(--primary-very-low);
padding: 1rem;
border: 1px solid var(--primary-medium);
}

View File

@ -4,7 +4,7 @@ class Admin::FormTemplatesController < Admin::StaffController
before_action :ensure_form_templates_enabled before_action :ensure_form_templates_enabled
def index def index
form_templates = FormTemplate.all form_templates = FormTemplate.all.order(:id)
render_serialized(form_templates, AdminFormTemplateSerializer, root: "form_templates") render_serialized(form_templates, AdminFormTemplateSerializer, root: "form_templates")
end end

View 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

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
class FormTemplateSerializer < ApplicationSerializer
attributes :id, :name, :template
end

View File

@ -4525,6 +4525,9 @@ en:
char_counter: char_counter:
exceeded: "The maximum number of characters allowed has been exceeded." 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 # This section is exported to the javascript for i18n in the admin section
admin_js: admin_js:
type_to_filter: "type to filter..." type_to_filter: "type to filter..."

View File

@ -1597,5 +1597,8 @@ Discourse::Application.routes.draw do
put "/sidebar_sections/reset/:id" => "sidebar_sections#reset" put "/sidebar_sections/reset/:id" => "sidebar_sections#reset"
get "*url", to: "permalinks#show", constraints: PermalinkConstraint.new get "*url", to: "permalinks#show", constraints: PermalinkConstraint.new
get "/form-templates/:id" => "form_templates#show"
get "/form-templates" => "form_templates#index"
end end
end end

View 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

View File

@ -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("textarea")
expect(form_template_page).to have_input_field("checkbox") 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("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") expect(form_template_page).to have_input_field("multi-select")
end end
@ -175,7 +176,8 @@ describe "Admin Customize Form Templates", type: :system, js: true do
) )
end 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( quick_insertion_test(
"upload", "upload",
'- type: upload '- type: upload

View 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

View File

@ -72,6 +72,11 @@ module PageObjects
find(AUTOCOMPLETE_MENU) find(AUTOCOMPLETE_MENU)
end end
def switch_category(category_name)
find(".category-chooser").click
find(".category-row[data-name='#{category_name}']").click
end
def has_emoji_autocomplete? def has_emoji_autocomplete?
has_css?(AUTOCOMPLETE_MENU) has_css?(AUTOCOMPLETE_MENU)
end end
@ -98,6 +103,22 @@ module PageObjects
page.has_no_css?(emoji_preview_selector(emoji)) page.has_no_css?(emoji_preview_selector(emoji))
end 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 def composer_input
find("#{COMPOSER_ID} .d-editor .d-editor-input") find("#{COMPOSER_ID} .d-editor .d-editor-input")
end end

View File

@ -34,8 +34,16 @@ module PageObjects
component.find(".select-kit-header[data-value='#{value}']") component.find(".select-kit-header[data-value='#{value}']")
end end
def has_selected_name?(value) def has_selected_name?(name)
component.find(".select-kit-header[data-name='#{value}']") 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 end
def expand def expand