From 3da4d546b2f7baf4384155403e09d442e8024349 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sun, 8 Nov 2020 14:24:20 +1100 Subject: [PATCH] Add custom field spec and improve custom field structure --- .../admin-wizards-custom-fields.js.es6 | 33 ++-- .../models/custom-wizard-custom-field.js.es6 | 29 +++ .../routes/admin-wizards-custom-fields.js.es6 | 7 +- .../templates/admin-wizards-custom-fields.hbs | 7 +- assets/stylesheets/common/wizard-admin.scss | 4 + config/locales/client.en.yml | 3 + config/locales/server.en.yml | 12 ++ .../custom_wizard/admin/custom_fields.rb | 50 ++--- extensions/extra_locales_controller.rb | 2 +- lib/custom_wizard/custom_field.rb | 149 +++++++++++---- plugin.rb | 41 ++-- .../custom_wizard/custom_field_serializer.rb | 2 +- .../custom_wizard/custom_field_spec.rb | 178 ++++++++++++++++++ .../custom_field_extensions_spec.rb | 117 ++++++++++++ .../extra_locales_controller_spec.rb | 3 + spec/fixtures/custom_field/custom_fields.json | 37 ++++ .../admin/custom_fields_controller_spec.rb | 32 ++++ .../custom_field_serializer_spec.rb | 29 +++ 18 files changed, 639 insertions(+), 96 deletions(-) create mode 100644 assets/javascripts/discourse/models/custom-wizard-custom-field.js.es6 create mode 100644 spec/components/custom_wizard/custom_field_spec.rb create mode 100644 spec/extensions/custom_field_extensions_spec.rb create mode 100644 spec/fixtures/custom_field/custom_fields.json create mode 100644 spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb create mode 100644 spec/serializers/custom_wizard/custom_field_serializer_spec.rb diff --git a/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 index 9ed7762c..0a7803b3 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 @@ -2,16 +2,16 @@ import Controller from "@ember/controller"; import EmberObject from '@ember/object'; import { ajax } from 'discourse/lib/ajax'; import { popupAjaxError } from 'discourse/lib/ajax-error'; +import CustomWizardCustomField from "../models/custom-wizard-custom-field"; export default Controller.extend({ fieldKeys: ['klass', 'type', 'serializers', 'name'], + documentationUrl: "https://thepavilion.io/t/3572", actions: { addField() { this.get('customFields').pushObject( - EmberObject.create({ - new: true - }) + CustomWizardCustomField.create() ); }, @@ -21,22 +21,17 @@ export default Controller.extend({ saveFields() { this.set('saving', true); - ajax(`/admin/wizards/custom-fields`, { - type: 'PUT', - dataType: 'json', - contentType: 'application/json', - data: JSON.stringify({ - custom_fields: this.customFields - }) - }).then(result => { - if (result.success) { - this.set('saveIcon', 'check'); - } else { - this.set('saveIcon', 'times'); - } - setTimeout(() => this.set('saveIcon', ''), 5000); - }).finally(() => this.set('saving', false)) - .catch(popupAjaxError); + CustomWizardCustomField.saveFields(this.customFields) + .then(result => { + if (result.success) { + this.set('saveIcon', 'check'); + } else { + this.set('saveIcon', 'times'); + } + setTimeout(() => this.set('saveIcon', ''), 5000); + }).finally(() => { + this.set('saving', false); + }); } } }); \ No newline at end of file diff --git a/assets/javascripts/discourse/models/custom-wizard-custom-field.js.es6 b/assets/javascripts/discourse/models/custom-wizard-custom-field.js.es6 new file mode 100644 index 00000000..01b88f44 --- /dev/null +++ b/assets/javascripts/discourse/models/custom-wizard-custom-field.js.es6 @@ -0,0 +1,29 @@ +import { ajax } from 'discourse/lib/ajax'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; +import EmberObject from "@ember/object"; +import { isEmpty } from "@ember/utils"; + +const CustomWizardCustomField = EmberObject.extend({ + isNew: isEmpty('id') +}); + +const basePath = '/admin/wizards/custom-fields'; + +CustomWizardCustomField.reopenClass({ + listFields() { + return ajax(basePath).catch(popupAjaxError); + }, + + saveFields(customFields) { + return ajax(basePath, { + type: 'PUT', + dataType: 'json', + contentType: 'application/json', + data: JSON.stringify({ + custom_fields: customFields + }) + }).catch(popupAjaxError); + } +}); + +export default CustomWizardCustomField; \ No newline at end of file diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 index c6775477..b4fd1c4e 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 @@ -1,13 +1,14 @@ import DiscourseRoute from "discourse/routes/discourse"; -import { ajax } from 'discourse/lib/ajax'; +import CustomWizardCustomField from "../models/custom-wizard-custom-field"; import { A } from "@ember/array"; export default DiscourseRoute.extend({ model() { - return ajax('/admin/wizards/custom-fields'); + return CustomWizardCustomField.listFields(); }, setupController(controller, model) { - controller.set('customFields', A(model || [])); + const customFields = A(model || []); + controller.set('customFields', customFields); } }); \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs index a79ff3a9..3e451294 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs @@ -1,6 +1,6 @@

{{i18n 'admin.wizard.custom_field.nav_label'}}

- +
{{#if saving}} {{loading-spinner size="small"}} @@ -19,6 +19,11 @@
+{{wizard-message + key='create' + url=documentationUrl + component='custom_fields'}} +
{{#if customFields}} diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 0b07475d..b92e6e33 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -537,6 +537,10 @@ } .admin-wizards-custom-fields { + h3 { + margin-bottom: 0; + } + .select-kit { width: 200px; } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 39137f57..906d6f77 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -72,6 +72,9 @@ en: type: "Select an action type" edit: "You're editing an action" documentation: "Check out the action documentation" + custom_fields: + create: "Create or edit a custom field record" + documentation: Check out the custom field documentation editor: show: "Show" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 4f9ef820..b06be5f8 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -6,6 +6,18 @@ en: wizard: custom_title: "Wizard" + + custom_field: + error: + required_attribute: "'%{attr}' is a required attribute" + unsupported_class: "'%{class}' is not a supported class" + unsupported_serializers: "'%{serializers}' are not supported serializers for '%{class}'" + unsupported_type: "%{type} is not a supported custom field type" + name_invalid: "'%{name}' is not a valid custom field name" + name_too_short: "'%{name}' is too short for a custom field name (min length is #{min_length})" + name_already_taken: "'%{value}' is already taken as a custom field name" + save_default: "Failed to save custom field '%{name}'" + field: too_short: "%{label} must be at least %{min} characters" required: "%{label} is required." diff --git a/controllers/custom_wizard/admin/custom_fields.rb b/controllers/custom_wizard/admin/custom_fields.rb index 6359d08f..add147da 100644 --- a/controllers/custom_wizard/admin/custom_fields.rb +++ b/controllers/custom_wizard/admin/custom_fields.rb @@ -3,33 +3,35 @@ class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController render_json_dump(custom_field_list) end - def update - custom_fields = custom_field_params[:custom_fields].map do |data| - CustomWizard::CustomField.new(data.to_h) - end + def update + fields_to_save = [] - custom_fields.each do |custom_field| - custom_field.validate + custom_field_params[:custom_fields].each do |field_param| + field_id = nil + field_data = {} - unless custom_field.valid? - raise Discourse::InvalidParameters, - custom_field.errors.full_messages.join("\n\n") + if saved_field = CustomWizard::CustomField.find(field_param[:name]) + CustomWizard::CustomField::ATTRS.each do |attr| + field_data[attr] = field_param[attr] || saved_field.send(attr) + end + field_id = saved_field.id + end + + fields_to_save.push(CustomWizard::CustomField.new(field_id, field_data)) + end + + PluginStoreRow.transaction do + fields_to_save.each do |field| + unless field.save + raise ActiveRecord::Rollback.new, + field.errors.any? ? + field.errors.full_messages.join("\n\n") : + I18n.t("wizard.custom_field.error.save_default", name: field.name) + end end end - - all_fields_saved = true - - custom_fields.each do |field| - unless field.save - all_fields_saved = false - end - end - - if all_fields_saved - render json: success_json - else - render json: error_json - end + + render json: success_json end private @@ -37,8 +39,8 @@ class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController def custom_field_params params.permit( custom_fields: [ - :klass, :name, + :klass, :type, serializers: [] ] diff --git a/extensions/extra_locales_controller.rb b/extensions/extra_locales_controller.rb index 2e3f8a78..31a9f488 100644 --- a/extensions/extra_locales_controller.rb +++ b/extensions/extra_locales_controller.rb @@ -7,7 +7,7 @@ module ExtraLocalesControllerCustomWizard hash = ::ExtraLocalesController.bundle_js_hash(bundle) immutable_for(1.year) if hash == params[:v] end - + render plain: ::ExtraLocalesController.bundle_js(bundle), content_type: "application/javascript" else super diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb index b658a958..afce1c9e 100644 --- a/lib/custom_wizard/custom_field.rb +++ b/lib/custom_wizard/custom_field.rb @@ -1,19 +1,40 @@ +# frozen_string_literal: true + class ::CustomWizard::CustomField include HasErrors include ActiveModel::Serialization - CLASSES ||= ["topic", "group", "category", "post"] - SERIALIZERS ||= ["topic_view", "topic_list_item", "post", "basic_category"] - TYPES ||= ["string", "boolean", "integer", "json"] - ATTRS ||= ["name", "klass", "type", "serializers"] - KEY ||= "custom_wizard_custom_fields" + attr_reader :id - def initialize(data) + ATTRS ||= ["name", "klass", "type", "serializers"] + REQUIRED ||= ["name", "klass"] + NAMESPACE ||= "custom_wizard_custom_fields" + NAME_MIN_LENGTH ||= 3 + + CLASSES ||= { + topic: ["topic_view", "topic_list_item"], + group: ["basic_group"], + category: ["basic_category"], + post: ["post"] + } + TYPES ||= ["string", "boolean", "integer", "json"] + + def self.serializers + CLASSES.values.flatten.uniq + end + + def initialize(id, data) + @id = id data = data.with_indifferent_access ATTRS.each do |attr| - self.class.class_eval { attr_accessor attr } - send("#{attr}=", data[attr]) if data[attr].present? + self.class.class_eval { attr_accessor attr } + + value = data[attr] + + if value.present? + send("#{attr}=", value) + end end end @@ -22,19 +43,18 @@ class ::CustomWizard::CustomField if valid? data = {} - name = nil + key = name - ATTRS.each do |attr| - value = send(attr) - - if attr == 'name' - name = value.parameterize(separator: '_') - else - data[attr] = value - end + (ATTRS - ['name']).each do |attr| + data[attr] = send(attr) end - PluginStore.set(KEY, name, data) + if self.class.save_to_store(id, key, data) + self.class.reset + true + else + false + end else false end @@ -43,50 +63,111 @@ class ::CustomWizard::CustomField def validate ATTRS.each do |attr| value = send(attr) + i18n_key = "wizard.custom_field.error" - if value.blank? - add_error("Attribute required: #{attr}") + if REQUIRED.include?(attr) && value.blank? + I18n.t("#{i18n_key}.required_attribute", attr: attr) next end - if attr == 'klass' && CLASSES.exclude?(value) - add_error("Unsupported class: #{value}") + if (attr == 'klass' && CLASSES.keys.exclude?(value.to_sym)) || + (attr == 'serializers' && CLASSES[klass.to_sym].blank?) + add_error(I18n.t("#{i18n_key}.unsupported_class", class: value)) + next end - if attr == 'serializers' && value.present? && (SERIALIZERS & value).empty? - add_error("Unsupported serializer: #{value}") + if attr == 'serializers' && (unsupported = value - CLASSES[klass.to_sym]).length > 0 + add_error(I18n.t("#{i18n_key}.unsupported_serializers", + class: klass, + serializers: unsupported.join(", ") + )) end if attr == 'type' && TYPES.exclude?(value) - add_error("Unsupported type: #{value}") + add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) end - if attr == 'name' && value.length < 3 - add_error("Field name is too short") + + if attr == 'name' + unless value.is_a?(String) + add_error(I18n.t("#{i18n_key}.name_invalid", name: value)) + end + + if value.length < NAME_MIN_LENGTH + add_error(I18n.t("#{i18n_key}.name_too_short", name: value, min_length: NAME_MIN_LENGTH)) + end + + if new? && self.class.exists?(name) + add_error(I18n.t("#{i18n_key}.name_already_taken", name: value)) + end + + begin + @name = value.parameterize(separator: '_') + rescue + add_error(I18n.t("#{i18n_key}.name_invalid", name: value)) + end end end end + def new? + id.blank? + end + def valid? errors.blank? end + def self.reset + @list = nil + end + def self.list - PluginStoreRow.where(plugin_name: KEY) - .map do |record| - data = JSON.parse(record.value) - data[:name] = record.key - self.new(data) - end + @list ||= PluginStoreRow.where(plugin_name: NAMESPACE) + .map { |record| create_from_store(record) } end def self.list_by(attr, value) self.list.select do |cf| - if attr == 'serializers' + if attr == :serializers cf.send(attr).include?(value) else cf.send(attr) == value end end end + + def self.exists?(name) + PluginStoreRow.where(plugin_name: NAMESPACE, key: name).exists? + end + + def self.find(name) + records = PluginStoreRow.where(plugin_name: NAMESPACE, key: name) + + if records.exists? + create_from_store(records.first) + else + false + end + end + + def self.create_from_store(record) + data = JSON.parse(record.value) + data[:name] = record.key + new(record.id, data) + end + + def self.save_to_store(id = nil, key, data) + if id + record = PluginStoreRow.find_by(id: id, plugin_name: NAMESPACE, key: key) + return false if !record + record.value = data.to_json + record.save + else + record = PluginStoreRow.new(plugin_name: NAMESPACE, key: key) + record.type_name = "JSON" + record.value = data.to_json + record.save + end + end end \ No newline at end of file diff --git a/plugin.rb b/plugin.rb index 3392353d..20df5a12 100644 --- a/plugin.rb +++ b/plugin.rb @@ -167,29 +167,44 @@ after_initialize do import_files(DiscoursePluginRegistry.stylesheets["wizard_custom"]) end - CustomWizard::CustomField::CLASSES.each do |klass| - add_model_callback(klass.to_sym, :after_initialize) do - CustomWizard::CustomField.list_by('klass', klass).each do |field| - klass.classify + CustomWizard::CustomField::CLASSES.keys.each do |klass| + add_model_callback(klass, :after_initialize) do + CustomWizard::CustomField.list_by(:klass, klass.to_s).each do |field| + klass.to_s + .classify .constantize .register_custom_field_type(field.name, field.type.to_sym) end end end - CustomWizard::CustomField::SERIALIZERS.each do |serializer_klass| - "#{serializer_klass}_serializer".classify.constantize.class_eval do - CustomWizard::CustomField.list_by('serializers', serializer_klass).each do |field| - attributes(field.name.to_sym) - class_eval %{def #{field.name} - if "#{serializer_klass}" == "topic_view" - object.topic.custom_fields["#{field.name}"] + module CustomWizardCustomFieldSerialization + def attributes(*args) + hash = super + @cw_klass = self.class.name.underscore.gsub("_serializer", "") + + if cw_fields.any? + cw_fields.each do |field| + if @cw_klass == "topic_view" + hash[field.name.to_sym] = object.topic.custom_fields["#{field.name}"] else - object.custom_fields["#{field.name}"] + hash[field.name.to_sym] = object.custom_fields["#{field.name}"] end - end} + end end + + hash end + + private + + def cw_fields + @cw_fields ||= CustomWizard::CustomField.list_by(:serializers, @cw_klass) + end + end + + CustomWizard::CustomField.serializers.each do |serializer_klass| + "#{serializer_klass}_serializer".classify.constantize.prepend CustomWizardCustomFieldSerialization end DiscourseEvent.trigger(:custom_wizard_ready) diff --git a/serializers/custom_wizard/custom_field_serializer.rb b/serializers/custom_wizard/custom_field_serializer.rb index 14e3fb32..9d684be3 100644 --- a/serializers/custom_wizard/custom_field_serializer.rb +++ b/serializers/custom_wizard/custom_field_serializer.rb @@ -1,3 +1,3 @@ class CustomWizard::CustomFieldSerializer < ApplicationSerializer - attributes :klass, :name, :type, :serializers + attributes :id, :klass, :name, :type, :serializers end \ No newline at end of file diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb new file mode 100644 index 00000000..6d7f8708 --- /dev/null +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::CustomField do + + let(:custom_field_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json" + ).read) + } + + it "saves custom field records" do + custom_field_json['custom_fields'].each do |field_json| + custom_field = CustomWizard::CustomField.new(nil, field_json) + expect(custom_field.save).to eq(true) + expect( + PluginStoreRow.where(" + plugin_name = '#{CustomWizard::CustomField::NAMESPACE}' AND + key = '#{custom_field.name}' AND + value::jsonb = '#{field_json.except('name').to_json}'::jsonb + ", ).exists? + ).to eq(true) + end + end + + it "updates existing custom field records" do + custom_field_json['custom_fields'].each do |field_json| + CustomWizard::CustomField.new(nil, field_json).save + end + + updated_field_json = custom_field_json['custom_fields'][0] + updated_field_json['serializers'] = ["topic_view"] + existing_field = CustomWizard::CustomField.find(updated_field_json["name"]) + updated_field = CustomWizard::CustomField.new(existing_field.id, updated_field_json) + + expect(updated_field.save).to eq(true) + expect( + PluginStoreRow.where(" + plugin_name = '#{CustomWizard::CustomField::NAMESPACE}' AND + key = '#{updated_field.name}' AND + value::jsonb = '#{updated_field_json.except('name').to_json}'::jsonb + ", ).exists? + ).to eq(true) + end + + context "validation" do + it "does not save with an unsupported class" do + invalid_field_json = custom_field_json['custom_fields'].first + invalid_field_json['klass'] = 'user' + + custom_field = CustomWizard::CustomField.new(nil, invalid_field_json) + + expect(custom_field.save).to eq(false) + expect(custom_field.valid?).to eq(false) + expect(custom_field.errors.full_messages.first).to eq( + I18n.t("wizard.custom_field.error.unsupported_class", class: "user") + ) + expect( + PluginStoreRow.where( + plugin_name: CustomWizard::CustomField::NAMESPACE, + key: custom_field.name + ).exists? + ).to eq(false) + end + + it "does not save with an unsupported serializer" do + invalid_field_json = custom_field_json['custom_fields'].first + invalid_field_json['klass'] = 'category' + invalid_field_json['serializers'] = ['category', 'site_category'] + + custom_field = CustomWizard::CustomField.new(nil, invalid_field_json) + + expect(custom_field.save).to eq(false) + expect(custom_field.valid?).to eq(false) + expect(custom_field.errors.full_messages.first).to eq( + I18n.t("wizard.custom_field.error.unsupported_serializers", + class: "category", + serializers: "category, site_category" + ) + ) + expect( + PluginStoreRow.where( + plugin_name: CustomWizard::CustomField::NAMESPACE, + key: custom_field.name + ).exists? + ).to eq(false) + end + + it "does not save with an unsupported type" do + invalid_field_json = custom_field_json['custom_fields'].first + invalid_field_json['type'] = 'bigint' + + custom_field = CustomWizard::CustomField.new(nil, invalid_field_json) + + expect(custom_field.save).to eq(false) + expect(custom_field.valid?).to eq(false) + expect(custom_field.errors.full_messages.first).to eq( + I18n.t("wizard.custom_field.error.unsupported_type", type: "bigint") + ) + expect( + PluginStoreRow.where( + plugin_name: CustomWizard::CustomField::NAMESPACE, + key: custom_field.name + ).exists? + ).to eq(false) + end + + it "does not save with a short field name" do + invalid_field_json = custom_field_json['custom_fields'].first + invalid_field_json['name'] = 'cf' + + custom_field = CustomWizard::CustomField.new(nil, invalid_field_json) + + expect(custom_field.save).to eq(false) + expect(custom_field.valid?).to eq(false) + expect(custom_field.errors.full_messages.first).to eq( + I18n.t("wizard.custom_field.error.name_too_short", name: "cf") + ) + expect( + PluginStoreRow.where( + plugin_name: CustomWizard::CustomField::NAMESPACE, + key: custom_field.name + ).exists? + ).to eq(false) + end + + it "does not save with an existing name if new" do + custom_field_json['custom_fields'].each do |field_json| + CustomWizard::CustomField.new(nil, field_json).save + end + + first_field_json = custom_field_json['custom_fields'][0] + custom_field = CustomWizard::CustomField.new(nil, first_field_json) + + expect(custom_field.save).to eq(false) + expect(custom_field.valid?).to eq(false) + expect(custom_field.errors.full_messages.first).to eq( + I18n.t("wizard.custom_field.error.name_already_taken", name: "topic_field_1") + ) + end + + it "does not save with an invalid name" do + invalid_field_json = custom_field_json['custom_fields'].first + invalid_field_json['name'] = ["invalid_name"] + + custom_field = CustomWizard::CustomField.new(nil, invalid_field_json) + + expect(custom_field.save).to eq(false) + expect(custom_field.valid?).to eq(false) + expect(custom_field.errors.full_messages.first).to eq( + I18n.t("wizard.custom_field.error.name_invalid", name: ["invalid_name"]) + ) + expect( + PluginStoreRow.where( + plugin_name: CustomWizard::CustomField::NAMESPACE, + key: custom_field.name + ).exists? + ).to eq(false) + end + end + + context "lists" do + before do + custom_field_json['custom_fields'].each do |field_json| + CustomWizard::CustomField.new(nil, field_json).save + end + end + + it "lists saved custom field records" do + expect(CustomWizard::CustomField.list.length).to eq(4) + end + + it "lists saved custom field records by attribute value" do + expect(CustomWizard::CustomField.list_by(:klass, 'topic').length).to eq(1) + end + end +end \ No newline at end of file diff --git a/spec/extensions/custom_field_extensions_spec.rb b/spec/extensions/custom_field_extensions_spec.rb new file mode 100644 index 00000000..de588d95 --- /dev/null +++ b/spec/extensions/custom_field_extensions_spec.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe "custom field extensions" do + fab!(:topic) { Fabricate(:topic) } + fab!(:post) { Fabricate(:post) } + fab!(:category) { Fabricate(:category) } + fab!(:group) { Fabricate(:group) } + fab!(:user) { Fabricate(:user) } + + let(:custom_field_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json" + ).read) + } + + before do + custom_field_json['custom_fields'].each do |field_json| + custom_field = CustomWizard::CustomField.new(nil, field_json) + custom_field.save + end + end + + context "topic" do + it "registers topic custom fields" do + topic + expect(Topic.get_custom_field_type("topic_field_1")).to eq(:boolean) + end + + it "adds topic custom fields to the topic_view serializer" do + topic.custom_fields["topic_field_1"] = true + topic.save_custom_fields(true) + + serializer = TopicViewSerializer.new( + TopicView.new(topic.id, user), + scope: Guardian.new(user), + root: false + ).as_json + + expect(serializer[:topic_field_1]).to eq(true) + end + + it "adds topic custom fields to the topic_list_item serializer" do + topic.custom_fields["topic_field_1"] = true + topic.save_custom_fields(true) + + serializer = TopicListItemSerializer.new( + topic, + scope: Guardian.new(user), + root: false + ).as_json + + expect(serializer[:topic_field_1]).to eq(true) + end + end + + context "post" do + it "registers post custom fields" do + post + expect(Post.get_custom_field_type("post_field_1")).to eq(:integer) + end + + it "adds post custom fields to the post serializer" do + post.custom_fields["post_field_1"] = 7 + post.save_custom_fields(true) + + serializer = PostSerializer.new( + post, + scope: Guardian.new(user), + root: false + ).as_json + + expect(serializer[:post_field_1]).to eq(7) + end + end + + context "category" do + it "registers category custom fields" do + category + expect(Category.get_custom_field_type("category_field_1")).to eq(:json) + end + + it "adds category custom fields to the basic category serializer" do + category.custom_fields["category_field_1"] = { a: 1, b: 2 }.to_json + category.save_custom_fields(true) + + serializer = BasicCategorySerializer.new( + category, + scope: Guardian.new(user), + root: false + ).as_json + + expect(serializer[:category_field_1]).to eq({ a: 1, b: 2 }.to_json) + end + end + + context "group" do + it "registers group custom fields" do + group + expect(Group.get_custom_field_type("group_field_1")).to eq(:string) + end + + it "adds group custom fields to the basic group serializer" do + group.custom_fields["group_field_1"] = "Hello" + group.save_custom_fields(true) + + serializer = BasicGroupSerializer.new( + group, + scope: Guardian.new(user), + root: false + ).as_json + + expect(serializer[:group_field_1]).to eq("Hello") + end + end +end \ No newline at end of file diff --git a/spec/extensions/extra_locales_controller_spec.rb b/spec/extensions/extra_locales_controller_spec.rb index bac0f2c6..e12b5f04 100644 --- a/spec/extensions/extra_locales_controller_spec.rb +++ b/spec/extensions/extra_locales_controller_spec.rb @@ -14,6 +14,9 @@ describe ExtraLocalesControllerCustomWizard, type: :request do end it "returns locales when requested by wizard" do + @controller.request = ActionController::TestRequest.create(@controller.class) + @controller.request.env['HTTP_REFERER'] = "/w/super-mega-fun-wizard" + expect( ExtraLocalesController.url("wizard") ).to eq( diff --git a/spec/fixtures/custom_field/custom_fields.json b/spec/fixtures/custom_field/custom_fields.json new file mode 100644 index 00000000..0e7741ce --- /dev/null +++ b/spec/fixtures/custom_field/custom_fields.json @@ -0,0 +1,37 @@ +{ + "custom_fields": [ + { + "klass": "topic", + "name": "topic_field_1", + "type": "boolean", + "serializers": [ + "topic_list_item", + "topic_view" + ] + }, + { + "klass": "post", + "name": "post_field_1", + "type": "integer", + "serializers": [ + "post" + ] + }, + { + "klass": "category", + "name": "category_field_1", + "type": "json", + "serializers": [ + "basic_category" + ] + }, + { + "klass": "group", + "name": "group_field_1", + "type": "string", + "serializers": [ + "basic_group" + ] + } + ] +} \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb new file mode 100644 index 00000000..90e7e618 --- /dev/null +++ b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb @@ -0,0 +1,32 @@ +require 'rails_helper' + +describe CustomWizard::AdminCustomFieldsController do + fab!(:admin_user) { Fabricate(:user, admin: true) } + + let(:custom_field_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json" + ).read) + } + + before do + custom_field_json['custom_fields'].each do |field_json| + CustomWizard::CustomField.new(nil, field_json).save + end + sign_in(admin_user) + end + + it "returns the list of custom fields" do + get "/admin/wizards/custom-fields.json" + expect(response.parsed_body.length).to eq(4) + end + + it "updates the list of custom fields" do + custom_field_json['custom_fields'][0]['type'] = 'string' + put "/admin/wizards/custom-fields.json", params: custom_field_json + expect(response.status).to eq(200) + expect( + CustomWizard::CustomField.find('topic_field_1').type + ).to eq('string') + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/custom_field_serializer_spec.rb b/spec/serializers/custom_wizard/custom_field_serializer_spec.rb new file mode 100644 index 00000000..525b6fd8 --- /dev/null +++ b/spec/serializers/custom_wizard/custom_field_serializer_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::CustomFieldSerializer do + fab!(:user) { Fabricate(:user) } + + let(:custom_field_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json" + ).read) + } + + it 'should return custom field attributes' do + custom_field_json['custom_fields'].each do |field_json| + CustomWizard::CustomField.new(nil, field_json).save + end + + json = CustomWizard::CustomFieldSerializer.new( + CustomWizard::CustomField.find("topic_field_1"), + scope: Guardian.new(user), + root: false + ).as_json + expect(json[:name]).to eq("topic_field_1") + expect(json[:klass]).to eq("topic") + expect(json[:type]).to eq("boolean") + expect(json[:serializers]).to match_array(["topic_list_item","topic_view"]) + end +end \ No newline at end of file