From 8fdd263d8e0b5734b93cfd04fd2b2383048eaa9e Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 20 Oct 2020 10:15:03 +1100 Subject: [PATCH 1/4] wip --- .../components/wizard-custom-action.hbs | 3 + controllers/custom_wizard/steps.rb | 4 +- lib/custom_wizard/action.rb | 126 +++-- lib/custom_wizard/builder.rb | 10 +- lib/custom_wizard/mapper.rb | 2 +- spec/components/custom_wizard/action_spec.rb | 149 +++-- spec/components/custom_wizard/builder_spec.rb | 200 ++++--- spec/components/custom_wizard/mapper_spec.rb | 14 - spec/fixtures/wizard.json | 530 +++++++++++++++++- .../custom_wizard/wizard_serializer_spec.rb | 49 -- .../components/custom_wizard/api_spec.rb | 0 .../components/custom_wizard/mapper_spec.rb | 13 + .../custom_wizard/admin_controller_spec.rb | 0 .../application_controller_spec.rb | 0 .../custom_wizard/wizard_controller_spec.rb | 0 .../custom_wizard/wizard_serializer_spec.rb | 45 ++ 16 files changed, 859 insertions(+), 286 deletions(-) delete mode 100644 spec/components/custom_wizard/mapper_spec.rb delete mode 100644 spec/serializers/custom_wizard/wizard_serializer_spec.rb rename {spec => spec_offload}/components/custom_wizard/api_spec.rb (100%) create mode 100644 spec_offload/components/custom_wizard/mapper_spec.rb rename {spec => spec_offload}/requests/custom_wizard/admin_controller_spec.rb (100%) rename {spec => spec_offload}/requests/custom_wizard/application_controller_spec.rb (100%) rename {spec => spec_offload}/requests/custom_wizard/wizard_controller_spec.rb (100%) create mode 100644 spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index 272c3155..03565ab8 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -113,6 +113,7 @@ wizardFieldSelection=true userFieldSelection='key,value' categorySelection='output' + wizardActionSelection='output' outputDefaultSelection='category' context='action' )}} @@ -198,6 +199,7 @@ textSelection='value' userFieldSelection='key' wizardFieldSelection='value' + wizardActionSelection='value' keyDefaultSelection='userField' context='action' )}} @@ -270,6 +272,7 @@ textSelection='value,output' wizardFieldSelection='key,value,assignment' userFieldSelection='key,value,assignment' + wizardActionSelection=true groupSelection='value,output' outputDefaultSelection='group' context='action' diff --git a/controllers/custom_wizard/steps.rb b/controllers/custom_wizard/steps.rb index e370a1dd..9318d16b 100644 --- a/controllers/custom_wizard/steps.rb +++ b/controllers/custom_wizard/steps.rb @@ -11,11 +11,11 @@ class CustomWizard::StepsController < ::ApplicationController permitted[:fields] = params[:fields].select { |k, v| field_ids.include? k } permitted.permit! end - + wizard = CustomWizard::Builder.new(permitted[:wizard_id].underscore, current_user).build updater = wizard.create_updater(permitted[:step_id], permitted[:fields]) updater.update - + if updater.success? result = success_json result.merge!(updater.result) if updater.result diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index e37a9a4c..a28498e6 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -77,6 +77,11 @@ class CustomWizard::Action multiple: true ).perform + if targets.blank? + log_error("no recipients", "send_message has no recipients") + return + end + targets.each do |target| if Group.find_by(name: target) params[:target_group_names] = target @@ -119,12 +124,18 @@ class CustomWizard::Action def update_profile params = {} - + if (profile_updates = action['profile_updates']) profile_updates.first[:pairs].each do |pair| if allowed_profile_field?(pair['key']) key = cast_profile_key(pair['key']) - value = cast_profile_value(mapper.map_field(pair['value'], pair['value_type']), pair['key']) + value = cast_profile_value( + mapper.map_field( + pair['value'], + pair['value_type'] + ), + pair['key'] + ) if user_field?(pair['key']) params[:custom_fields] ||= {} @@ -137,10 +148,10 @@ class CustomWizard::Action end params = add_custom_fields(params) - + if params.present? result = UserUpdater.new(Discourse.system_user, user).update(params) - + if params[:avatar].present? result = update_avatar(params[:avatar]) end @@ -250,11 +261,11 @@ class CustomWizard::Action def open_composer params = basic_topic_params - + if params[:title].present? && params[:raw].present? url = "/new-topic?title=#{params[:title]}" url += "&body=#{params[:raw]}" - + if category_id = action_category if category_id && category = Category.find(category_id) url += "&category=#{category.full_slug('/')}" @@ -266,7 +277,7 @@ class CustomWizard::Action end route_to = Discourse.base_uri + URI.encode(url) - data['redirect_on_complete'] = route_to + data['route_to'] = route_to log_info("route: #{route_to}") else @@ -283,8 +294,15 @@ class CustomWizard::Action multiple: true } ).perform + + group_map = group_map.flatten.compact + + unless group_map.present? + log_error("invalid group map") + return + end - groups = group_map.flatten.reduce([]) do |groups, g| + groups = group_map.reduce([]) do |groups, g| begin groups.push(Integer(g)) rescue ArgumentError @@ -324,7 +342,7 @@ class CustomWizard::Action user: user ).perform end - + if action['code'] data[action['code']] = SecureRandom.hex(8) url += "&#{action['code']}=#{data[action['code']]}" @@ -336,20 +354,36 @@ class CustomWizard::Action log_info("route: #{route_to}") end - def create_group + def create_group group = begin - Group.new(new_group_params) + Group.new(new_group_params.except(:usernames, :owner_usernames)) rescue ArgumentError => e raise Discourse::InvalidParameters, "Invalid group params" end if group.save - GroupActionLogger.new(user, group).log_change_group_settings + def get_user_ids(username_string) + User.where(username: username_string.split(",")).pluck(:id) + end + + if new_group_params[:owner_usernames].present? + owner_ids = get_user_ids(new_group_params[:owner_usernames]) + owner_ids.each { |user_id| group.group_users.build(user_id: user_id, owner: true) } + end + + if new_group_params[:usernames].present? + user_ids = get_user_ids(new_group_params[:usernames]) + user_ids -= owner_ids if owner_ids + user_ids.each { |user_id| group.group_users.build(user_id: user_id) } + end + + GroupActionLogger.new(user, group, skip_guardian: true).log_change_group_settings log_success("Group created", group.name) + result.output = group.name else - log_error("Group creation failed") + log_error("Group creation failed", group.errors.messages) end end @@ -366,7 +400,7 @@ class CustomWizard::Action log_success("Category created", category.name) result.output = category.id else - log_error("Category creation failed") + log_error("Category creation failed", category.errors.messages) end end @@ -378,7 +412,7 @@ class CustomWizard::Action data: data, user: user ).perform - + if output.is_a?(Array) output.first elsif output.is_a?(Integer) @@ -505,10 +539,12 @@ class CustomWizard::Action user: user ).perform - value = value.parameterize(separator: '_') if attr === "name" - value = value.to_i if attr.include?("_level") - - params[attr.to_sym] = value + if value + value = value.parameterize(separator: '_') if attr === "name" + value = value.to_i if attr.include?("_level") + + params[attr.to_sym] = value + end end end @@ -533,30 +569,36 @@ class CustomWizard::Action user: user ).perform - if attr === "parent_category_id" && value.is_a?(Array) - value = value[0] - end - - if attr === "permissions" && value.is_a?(Array) - permissions = value - value = {} - - permissions.each do |p| - k = p[:key] - v = p[:value].to_i - - if k.is_a?(Array) - group = Group.find_by(id: k[0]) - k = group.name - else - k = k.parameterize(separator: '_') - end - - value[k] = v + if value + if attr === "parent_category_id" && value.is_a?(Array) + value = value[0] end + + if attr === "permissions" && value.is_a?(Array) + permissions = value + value = {} + + permissions.each do |p| + k = p[:key] + v = p[:value].to_i + + if k.is_a?(Array) + group = Group.find_by(id: k[0]) + k = group.name + else + k = k.parameterize(separator: '_') + end + + value[k] = v + end + end + + if attr === 'slug' + value = value.parameterize(separator: '-') + end + + params[attr.to_sym] = value end - - params[attr.to_sym] = value end end @@ -584,6 +626,8 @@ class CustomWizard::Action end def cast_profile_value(value, key) + return value if value.nil? + if profile_url_fields.include?(key) value['url'] elsif key === 'avatar' diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 0af53245..728ed847 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -133,7 +133,7 @@ class CustomWizard::Builder validate_field(field, updater, step_template) if field['type'] != 'text_only' end end - + next if updater.errors.any? CustomWizard::Builder.step_handlers.each do |handler| @@ -156,10 +156,10 @@ class CustomWizard::Builder if @actions.present? @actions.each do |action| - + if (action['run_after'] === updater.step.id) || (final_step && (!action['run_after'] || (action['run_after'] === 'wizard_completion'))) - + CustomWizard::Action.new( wizard: @wizard, action: action, @@ -217,7 +217,7 @@ class CustomWizard::Builder submission = @submissions.last params[:value] = submission[field_template['id']] if submission[field_template['id']] end - + params[:value] = prefill_field(field_template, step_template) || params[:value] if field_template['type'] === 'group' && params[:value].present? @@ -339,7 +339,7 @@ class CustomWizard::Builder if type === 'time' && value.present? && !validate_time(value) updater.errors.add(id, I18n.t('wizard.field.invalid_time')) end - + CustomWizard::Builder.field_validators.each do |validator| if type === validator[:type] validator[:block].call(field, updater, step_template) diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index 72bf8396..97791703 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -48,7 +48,7 @@ class CustomWizard::Mapper inputs.each do |input| input_type = input['type'] pairs = input['pairs'] - + if (input_type === 'conditional' && validate_pairs(pairs)) || input_type === 'assignment' output = input['output'] output_type = input['output_type'] diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 6437c8c4..9ebfe779 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -1,17 +1,27 @@ +require 'rails_helper' + describe CustomWizard::Action do - let(:create_topic_action) {{"id":"create_topic","type":"create_topic","title":"text","post":"textarea"}} - let(:send_message_action) {{"id":"send_message","type":"send_message","title":"text","post":"textarea","username":"angus"}} - let(:route_to_action) {{"id":"route_to","type":"route_to","url":"https://google.com"}} - let(:open_composer_action) {{"id":"open_composer","type":"open_composer","title":"text","post":"textarea"}} - let(:add_to_group_action) {{"id":"add_to_group","type":"add_to_group","group_id":"dropdown_groups"}} + fab!(:user) { Fabricate(:user, name: "Angus", username: 'angus', email: "angus@email.com", trust_level: TrustLevel[2]) } + fab!(:category) { Fabricate(:category, name: 'cat1', slug: 'cat-slug') } + fab!(:group) { Fabricate(:group) } + + before do + Group.refresh_automatic_group!(:trust_level_2) + template = JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + CustomWizard::Wizard.add_wizard(template) + @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) + end it 'creates a topic' do - template['steps'][0]['fields'] = [text_field, textarea_field] - template['steps'][0]["actions"] = [create_topic_action] - updater = run_update(template, nil, - text: "Topic Title", - textarea: "topic body" - ) + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + updater = built_wizard.create_updater(built_wizard.steps[0].id, + step_1_field_1: "Topic Title", + step_1_field_2: "topic body" + ).update + updater2 = built_wizard.create_updater(built_wizard.steps[1].id, {}).update + topic = Topic.where(title: "Topic Title") expect(topic.exists?).to eq(true) @@ -22,71 +32,102 @@ describe CustomWizard::Action do end it 'sends a message' do - fields = [text_field, textarea_field] + User.create(username: 'angus1', email: "angus1@email.com") - if extra_field - fields.push(extra_field) - end - - template['steps'][0]['fields'] = fields - template['steps'][0]["actions"] = [send_message_action.merge(extra_action_opts)] - - run_update(template, nil, - text: "Message Title", - textarea: "message body" - ) + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + built_wizard.create_updater(built_wizard.steps[0].id, {}).update + built_wizard.create_updater(built_wizard.steps[1].id, {}).update topic = Topic.where( archetype: Archetype.private_message, - title: "Message Title" + title: "Message title" ) - expect(topic.exists?).to eq(true) - expect( - topic.first.topic_allowed_users.first.user.username - ).to eq('angus') - expect(Post.where( + post = Post.where( topic_id: topic.pluck(:id), - raw: "message body" - ).exists?).to eq(true) + raw: "I will interpolate some wizard fields" + ) + + expect(topic.exists?).to eq(true) + expect(topic.first.topic_allowed_users.first.user.username).to eq('angus1') + expect(post.exists?).to eq(true) end it 'updates a profile' do - run_update(template, template['steps'][1]['id'], name: "Sally") - expect(user.name).to eq('Sally') + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + upload = Upload.create!( + url: '/images/image.png', + original_filename: 'image.png', + filesize: 100, + user_id: -1, + ) + steps = built_wizard.steps + built_wizard.create_updater(steps[0].id, {}).update + built_wizard.create_updater(steps[1].id, + step_2_field_7: upload.as_json, + ).update + expect(user.profile_background_upload.id).to eq(upload.id) end it 'opens a composer' do - template['steps'][0]['fields'] = [text_field, textarea_field] - template['steps'][0]["actions"] = [open_composer_action] + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + built_wizard.create_updater(built_wizard.steps[0].id, step_1_field_1: "Text input").update - updater = run_update(template, nil, - text: "Topic Title", - textarea: "topic body" - ) + updater = built_wizard.create_updater(built_wizard.steps[1].id, {}) + updater.update - expect(updater.result.blank?).to eq(true) + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + category = Category.find_by(id: submissions.first['action_8']) - updater = run_update(template, template['steps'][1]['id']) - - expect(updater.result[:redirect_on_complete]).to eq( - "/new-topic?title=Topic%20Title&body=topic%20body" + expect(updater.result[:redirect_on_next]).to eq( + "/new-topic?title=Title%20of%20the%20composer%20topic&body=I%20am%20interpolating%20some%20user%20fields%20Angus%20angus%20angus@email.com&category=#{category.slug}/#{category.id}&tags=tag1" ) end - it 'adds a user to a group' do - template['steps'][0]['fields'] = [dropdown_groups_field] - template['steps'][0]["actions"] = [add_to_group_action] - - updater = run_update(template, nil, dropdown_groups: group.id) + it 'creates a category' do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + built_wizard.create_updater(built_wizard.steps[0].id, step_1_field_1: "Text input").update + built_wizard.create_updater(built_wizard.steps[1].id, {}).update + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + expect(Category.where(id: submissions.first['action_8']).exists?).to eq(true) + end + + it 'creates a group' do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + step_id = built_wizard.steps[0].id + updater = built_wizard.create_updater(step_id, step_1_field_1: "Text input").update + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + expect(Group.where(name: submissions.first['action_9']).exists?).to eq(true) + end + + it 'adds a user to a group' do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + step_id = built_wizard.steps[0].id + updater = built_wizard.create_updater(step_id, step_1_field_1: "Text input").update + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + group = Group.find_by(name: submissions.first['action_9']) expect(group.users.first.username).to eq('angus') end + it 'watches categories' do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + built_wizard.create_updater(built_wizard.steps[0].id, step_1_field_1: "Text input").update + built_wizard.create_updater(built_wizard.steps[1].id, {}).update + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + expect(CategoryUser.where( + category_id: submissions.first['action_8'], + user_id: user.id + ).first.notification_level).to eq(2) + expect(CategoryUser.where( + category_id: category.id, + user_id: user.id + ).first.notification_level).to eq(0) + end + it 're-routes a user' do - template['steps'][0]["actions"] = [route_to_action] - updater = run_update(template, nil, {}) - expect(updater.result[:redirect_on_next]).to eq( - "https://google.com" - ) + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + updater = built_wizard.create_updater(built_wizard.steps.last.id, {}) + updater.update + expect(updater.result[:redirect_on_complete]).to eq("https://google.com") end end diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index ba8da001..9a7c8b60 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -3,39 +3,19 @@ require 'rails_helper' describe CustomWizard::Builder do - fab!(:user) { Fabricate(:user, username: 'angus') } - fab!(:trusted_user) { Fabricate(:user, trust_level: 3) } + fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[2]) } + fab!(:new_user) { Fabricate(:user, trust_level: 0) } fab!(:category1) { Fabricate(:category, name: 'cat1') } fab!(:category2) { Fabricate(:category, name: 'cat2') } fab!(:group) { Fabricate(:group) } - let!(:template) do - JSON.parse(File.open( + before do + Group.refresh_automatic_group!(:trust_level_2) + template = JSON.parse(File.open( "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" ).read) - end - - def build_wizard(t = template, u = user, build_opts = {}, params = {}) - CustomWizard::Wizard.add_wizard(t) - CustomWizard::Builder.new('welcome', u).build(build_opts, params) - end - - def add_submission_data(data = {}) - PluginStore.set("welcome_submissions", user.id, { - name: 'Angus', - website: 'https://thepavilion.io' - }.merge(data)) - end - - def get_submission_data - PluginStore.get("welcome_submissions", user.id) - end - - def run_update(t = template, step_id = nil, data = {}) - wizard = build_wizard(t) - updater = wizard.create_updater(step_id || t['steps'][0]['id'], data) - updater.update - updater + CustomWizard::Wizard.add_wizard(template) + @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) end context 'disabled' do @@ -43,15 +23,8 @@ describe CustomWizard::Builder do SiteSetting.custom_wizard_enabled = false end - it "returns no steps" do - wizard = build_wizard - expect(wizard.steps.length).to eq(0) - expect(wizard.name).to eq('Welcome') - end - - it "doesn't save submissions" do - run_update(template, nil, name: 'Angus') - expect(get_submission_data.blank?).to eq(true) + it "returns nil" do + expect(CustomWizard::Builder.new(@wizard.id, user).build).to eq(nil) end end @@ -61,123 +34,164 @@ describe CustomWizard::Builder do end it "returns steps" do - expect(build_wizard.steps.length).to eq(2) + expect( + CustomWizard::Builder.new(@wizard.id, user).build.steps.length + ).to eq(2) end it 'returns no steps if multiple submissions are disabled and user has completed' do + wizard_template = CustomWizard::Wizard.find(@wizard.id) + wizard_template[:multiple_submissions] = false + CustomWizard::Wizard.save(wizard_template) + history_params = { action: UserHistory.actions[:custom_wizard_step], acting_user_id: user.id, - context: template['id'] + context: @wizard.id } - UserHistory.create!(history_params.merge(subject: template['steps'][0]['id'])) - UserHistory.create!(history_params.merge(subject: template['steps'][1]['id'])) + @wizard.steps.each do |step| + UserHistory.create!(history_params.merge(subject: step.id)) + end - template["multiple_submissions"] = false - expect(build_wizard(template).steps.length).to eq(0) + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + expect( + CustomWizard::Builder.new(@wizard.id, user).build.steps.length + ).to eq(0) end it 'returns no steps if user is not permitted' do - template["min_trust"] = 3 - expect(build_wizard(template).steps.length).to eq(0) + expect( + CustomWizard::Builder.new(@wizard.id, new_user).build.steps.length + ).to eq(0) end it 'returns steps if user is permitted' do - template["min_trust"] = 3 - expect(build_wizard(template, trusted_user).steps.length).to eq(2) + expect( + CustomWizard::Builder.new(@wizard.id, user).build.steps.length + ).to eq(3) end it 'returns a wizard with prefilled data if user has partially completed it' do - add_submission_data - wizard = build_wizard - expect(wizard.steps[0].fields.first.value).to eq('Angus') - expect(wizard.steps[1].fields.first.value).to eq('https://thepavilion.io') + expect( + CustomWizard::Builder.new(@wizard.id, user) + .build + .steps[0].fields[0].value + ).to eq('I am prefilled') end it 'returns a wizard with no prefilled data if options include reset' do - add_submission_data - wizard = build_wizard(template, user, reset: true) - expect(wizard.steps[0].fields.first.value).to eq(nil) - expect(wizard.steps[1].fields.first.value).to eq(nil) + PluginStore.set("super_mega_fun_wizard_submissions", user.id, { + text: 'Input into text', + }) + expect( + CustomWizard::Builder.new(@wizard.id, user) + .build(reset: true) + .steps[0].fields[0].value + ).to eq(nil) end context 'building steps' do it 'returns step metadata' do - expect(build_wizard.steps[0].title).to eq('Welcome to Pavilion') - expect(build_wizard.steps[1].title).to eq('Tell us about you') + expect( + CustomWizard::Builder.new(@wizard.id, user) + .build(reset: true) + .steps[0] + ).to eq('Super Mega Fun Wizard') end it 'saves permitted params' do - template['steps'][0]['permitted_params'] = permitted_params - wizard = build_wizard(template, user, {}, param_key: 'param_value') - submissions = get_submission_data - expect(submissions.first['submission_param_key']).to eq('param_value') + @wizard.steps[0].permitted_params = permitted_params + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build({}, param_key: 'param_value') + submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) + expect(submissions[0]['submission_param_key']).to eq('param_value') end it 'is not permitted if required data is not present' do - template['steps'][0]['required_data'] = required_data - expect(build_wizard(template, user).steps[0].permitted).to eq(false) + @wizard.steps[0].required_data = required_data + expect( + CustomWizard::Builder.new(@wizard.id, user).build.steps[0].permitted + ).to eq(false) end it 'it shows required data message if required data has message' do - template['steps'][0]['required_data'] = required_data - template['steps'][0]['required_data_message'] = required_data_message - add_submission_data(nickname: "John") - wizard = build_wizard(template, user) - expect(wizard.steps[0].permitted).to eq(false) - expect(wizard.steps[0].permitted_message).to eq(required_data_message) + @wizard.steps[0].required_data = required_data + @wizard.steps[0].required_data_message = "Data is required" + PluginStore.set("super_mega_fun_wizard_submissions", user.id, + text: 'Input into text', + ) + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + expect(built_wizard.steps[0].permitted).to eq(false) + expect(built_wizard.steps[0].permitted_message).to eq("Data is required") end it 'is permitted if required data is present' do - template['steps'][0]['required_data'] = required_data - PluginStore.set('welcome_submissions', user.id, nickname: "Angus", name: "Angus") - expect(build_wizard(template, user).steps[0].permitted).to eq(true) + @wizard.steps[0].required_data = required_data + PluginStore.set('super_mega_fun_wizard_submissions', user.id, + text: "Input into text" + ) + expect( + CustomWizard::Builder.new(@wizard.id, user).build.steps[0].permitted + ).to eq(true) end it 'returns field metadata' do - expect(build_wizard(template, user).steps[0].fields[0].label).to eq("

Name

") - expect(build_wizard(template, user).steps[0].fields[0].type).to eq("text") + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + expect(built_wizard.steps[0].fields[0].label).to eq("

Name

") + expect(built_wizard.steps[0].fields[0].type).to eq("text") end it 'returns fields' do - template['steps'][0]['fields'][1] = checkbox_field - expect(build_wizard(template, user).steps[0].fields.length).to eq(2) + @wizard.steps[0].fields[1] = checkbox_field + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + expect(built_wizard.steps[0].fields.length).to eq(2) end end context 'on update' do it 'saves submissions' do - run_update(template, nil, name: 'Angus') - expect(get_submission_data.first['name']).to eq('Angus') + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + built_wizard.create_updater(built_wizard.steps[0].id, + step_1_field_1: 'Text input' + ).update + expect( + PluginStore.get("super_mega_fun_wizard_submissions", user.id) + .first['step_1_field_1'] + ).to eq('Text input') end context 'validation' do it 'applies min length' do - template['steps'][0]['fields'][0]['min_length'] = 10 - updater = run_update(template, nil, name: 'short') - expect(updater.errors.messages[:name].first).to eq( - I18n.t('wizard.field.too_short', label: 'Name', min: 10) + @wizard.steps[0].fields[0].min_length = 10 + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + updater = built_wizard.create_updater(built_wizard.steps[0].id, + step_1_field_1: 'Te' + ).update + expect(updater.errors.messages[:text].first).to eq( + I18n.t('wizard.field.too_short', label: 'Text', min: 3) ) end it 'standardises boolean entries' do - template['steps'][0]['fields'][0] = checkbox_field - run_update(template, nil, checkbox: 'false') - expect(get_submission_data.first['checkbox']).to eq(false) + @wizard.steps[0].fields[0] = checkbox_field + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + updater = built_wizard.create_updater(built_wizard.steps[1].id, + step_2_field_5: 'false' + ).update + expect( + PluginStore.get("super_mega_fun_wizard_submissions", user.id) + .first['step_2_field_5'] + ).to eq(false) end it 'requires required fields' do - template['steps'][0]['fields'][0]['required'] = true - expect(run_update(template).errors.messages[:name].first).to eq( - I18n.t('wizard.field.required', label: 'Name') - ) + @wizard.steps[0].fields[0]['required'] = true + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + updater = built_wizard.create_updater(built_wizard.steps.second.id).update + expect( + updater.errors.messages[:step_1_field_1].first + ).to eq(I18n.t('wizard.field.required', label: 'Text')) end end - - it 'runs actions attached to a step' do - run_update(template, template['steps'][1]['id'], name: "Gus") - expect(user.name).to eq('Gus') - end end end end \ No newline at end of file diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb deleted file mode 100644 index 4fb8a10e..00000000 --- a/spec/components/custom_wizard/mapper_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -describe CustomWizard::Mapper do - -it 'interpolates user data' do - user.name = "Angus" - user.save! - - expect( - CustomWizard::Builder.fill_placeholders( - "My name is u{name}", - user, - {} - ) - ).to eq('My name is Angus') -end \ No newline at end of file diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json index a9251f65..09ffc95e 100644 --- a/spec/fixtures/wizard.json +++ b/spec/fixtures/wizard.json @@ -1,49 +1,525 @@ { - "id": "welcome", - "name": "Welcome", - "background": "#006da3", + "id": "super_mega_fun_wizard", + "name": "Super Mega Fun Wizard", + "background": "#333333", "save_submissions": true, "multiple_submissions": true, - "after_signup": true, - "min_trust": 1, - "theme_id": 4, + "after_signup": false, + "prompt_completion": true, + "theme_id": 2, + "permitted": [ + { + "type": "assignment", + "output_type": "group", + "output_connector": "set", + "output": [ + 12 + ] + } + ], "steps": [ { - "id": "welcome", - "title": "Welcome to Pavilion", - "raw_description": "Hey there, thanks for signing up.\n\nWe're Pavilion, an international freelancer cooperative that specialises in online communities.\n\nThis site is our own community, where we work with our clients, users of our open source work and our broader community.\n\n", - "description": "

Hey there, thanks for signing up.

\n

We’re Pavilion, an international freelancer cooperative that specialises in online communities.

\n

This site is our own community, where we work with our clients, users of our open source work and our broader community.

", + "id": "step_1", + "title": "Text", + "raw_description": "Text inputs!", "fields": [ { - "id": "name", + "id": "step_1_field_1", + "label": "Text", "type": "text", - "label": "Name" + "min_length": "3", + "prefill": [ + { + "type": "assignment", + "output": "I am prefilled", + "output_type": "text", + "output_connector": "set" + } + ] + }, + { + "id": "step_1_field_2", + "label": "Textarea", + "type": "textarea", + "min_length": "" + }, + { + "id": "step_1_field_3", + "label": "Composer", + "type": "composer" + }, + { + "id": "step_1_field_4", + "label": "I'm only text", + "description": "", + "type": "text_only" + } + ], + "description": "

Text inputs!

" + }, + { + "id": "step_2", + "title": "Values", + "raw_description": "Because I couldn't think of another name for this step :)", + "fields": [ + { + "id": "step_2_field_1", + "label": "Date", + "type": "date", + "format": "YYYY-MM-DD" + }, + { + "id": "step_2_field_2", + "label": "Time", + "type": "time", + "format": "HH:mm" + }, + { + "id": "step_2_field_3", + "label": "Date & Time", + "type": "date_time", + "format": "" + }, + { + "id": "step_2_field_4", + "label": "Number", + "type": "number" + }, + { + "id": "step_2_field_5", + "label": "Checkbox", + "type": "checkbox" + }, + { + "id": "step_2_field_7", + "label": "Upload", + "type": "upload", + "file_types": ".jpg,.png" + } + ], + "description": "

Because I couldn’t think of another name for this step \":slight_smile:\"

" + }, + { + "id": "step_3", + "title": "Combo-boxes", + "raw_description": "Unfortunately not the edible type :sushi: ", + "fields": [ + { + "id": "step_3_field_1", + "label": "Custom Dropdown", + "type": "dropdown", + "content": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "choice1", + "key_type": "text", + "value": "Choice 1", + "value_type": "text", + "connector": "equal" + }, + { + "index": 1, + "key": "choice2", + "key_type": "text", + "value": "Choice 2", + "value_type": "text", + "connector": "association" + }, + { + "index": 2, + "key": "choice3", + "key_type": "text", + "value": "Choice 3", + "value_type": "text", + "connector": "association" + } + ] + } + ] + }, + { + "id": "step_3_field_2", + "label": "Tag", + "type": "tag" + }, + { + "id": "step_3_field_3", + "label": "Category", + "type": "category", + "limit": 1, + "property": "id" + }, + { + "id": "step_3_field_4", + "label": "Group", + "type": "group" + }, + { + "id": "step_3_field_5", + "label": "User Selector", + "description": "", + "type": "user_selector" + } + ], + "description": "

Unfortunately not the edible type \":sushi:\"

" + } + ], + "actions": [ + { + "id": "action_9", + "run_after": "step_1", + "type": "create_group", + "title": [ + { + "type": "assignment", + "output": "New Group Member", + "output_type": "text", + "output_connector": "set" + } + ], + "custom_fields": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "group_custom_field", + "key_type": "text", + "value": "step_3_field_1", + "value_type": "wizard_field", + "connector": "association" + } + ] + } + ], + "name": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "full_name": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "usernames": [ + { + "type": "assignment", + "output_type": "user", + "output_connector": "set", + "output": [ + "angus1" + ] + } + ], + "owner_usernames": [ + { + "type": "assignment", + "output_type": "user", + "output_connector": "set", + "output": [ + "angus" + ] + } + ], + "grant_trust_level": [ + { + "type": "assignment", + "output": "3", + "output_type": "text", + "output_connector": "set" + } + ], + "mentionable_level": [ + { + "type": "assignment", + "output": "1", + "output_type": "text", + "output_connector": "set" + } + ], + "messageable_level": [ + { + "type": "assignment", + "output": "2", + "output_type": "text", + "output_connector": "set" + } + ], + "visibility_level": [ + { + "type": "assignment", + "output": "3", + "output_type": "text", + "output_connector": "set" + } + ], + "members_visibility_level": [ + { + "type": "assignment", + "output": "99", + "output_type": "text", + "output_connector": "set" } ] }, { - "id": "about_you", - "title": "Tell us about you", - "raw_description": "We'd like to know a little more about you. Add a your name and your website below. This will update your user profile.", - "description": "

We’d like to know a little more about you. Add a your name and your website below. This will update your user profile.

", - "fields": [ + "id": "action_6", + "run_after": "step_1", + "type": "add_to_group", + "group": [ { - "id": "website", - "label": "Website", - "type": "text" + "type": "assignment", + "output": "action_9", + "output_type": "wizard_action", + "output_connector": "set" } - ], - "actions": [ + ] + }, + { + "id": "action_8", + "run_after": "step_1", + "type": "create_category", + "custom_fields": [ { - "id": "update_profile", - "type": "update_profile", - "profile_updates": [ + "type": "association", + "pairs": [ { - "key": "name", - "value": "name" + "index": 0, + "key": "category_custom_field", + "key_type": "text", + "value": "CC Val", + "value_type": "text", + "connector": "association" } ] } + ], + "name": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "slug": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "permissions": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "action_9", + "key_type": "wizard_action", + "value": "2", + "value_type": "text", + "connector": "association" + } + ] + } + ] + }, + { + "id": "action_5", + "run_after": "step_1", + "type": "watch_categories", + "notification_level": "tracking", + "wizard_user": true, + "categories": [ + { + "type": "assignment", + "output": "action_8", + "output_type": "wizard_action", + "output_connector": "set" + } + ], + "mute_remainder": [ + { + "type": "assignment", + "output": "true", + "output_type": "text", + "output_connector": "set" + } + ] + }, + { + "id": "action_1", + "run_after": "step_2", + "type": "create_topic", + "skip_redirect": true, + "post": "step_1_field_2", + "title": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "category": [ + { + "type": "assignment", + "output": "step_3_field_3", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "tags": [ + { + "type": "assignment", + "output": "step_3_field_2", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "custom_fields": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "custom_field_1", + "key_type": "text", + "value": "title", + "value_type": "user_field", + "connector": "association" + } + ] + } + ], + "visible": [ + { + "type": "conditional", + "output": "true", + "output_type": "text", + "output_connector": "then", + "pairs": [ + { + "index": 0, + "key": "name", + "key_type": "user_field", + "value": "Angus", + "value_type": "text", + "connector": "equal" + } + ] + } + ] + }, + { + "id": "action_4", + "run_after": "step_2", + "type": "update_profile", + "profile_updates": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "profile_background", + "key_type": "user_field", + "value": "step_2_field_7", + "value_type": "wizard_field", + "connector": "association" + } + ] + } + ] + }, + { + "id": "action_2", + "run_after": "step_2", + "type": "send_message", + "post_builder": true, + "post_template": "I will interpolate some wizard fields w{step_1_field_1} w{step_1_field_2}", + "title": [ + { + "type": "assignment", + "output": "Message title", + "output_type": "text", + "output_connector": "set" + } + ], + "recipient": [ + { + "type": "assignment", + "output_type": "user", + "output_connector": "set", + "output": [ + "angus1" + ] + } + ] + }, + { + "id": "action_3", + "run_after": "step_2", + "type": "open_composer", + "post_builder": true, + "post_template": "I am interpolating some user fields u{name} u{username} u{email}", + "title": [ + { + "type": "assignment", + "output": "Title of the composer topic", + "output_type": "text", + "output_connector": "set" + } + ], + "category": [ + { + "type": "assignment", + "output": "action_8", + "output_type": "wizard_action", + "output_connector": "set", + "pairs": [ + { + "index": 0, + "key": "step_2_field_5", + "key_type": "wizard_field", + "value": "true", + "value_type": "text", + "connector": "is" + } + ] + } + ], + "tags": [ + { + "type": "assignment", + "output": "tag1", + "output_type": "text", + "output_connector": "set" + } + ] + }, + { + "id": "action_10", + "run_after": "wizard_completion", + "type": "route_to", + "url": [ + { + "type": "assignment", + "output": "https://google.com", + "output_type": "text", + "output_connector": "set" + } ] } ] diff --git a/spec/serializers/custom_wizard/wizard_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_serializer_spec.rb deleted file mode 100644 index a470bccd..00000000 --- a/spec/serializers/custom_wizard/wizard_serializer_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe CustomWizardSerializer do - fab!(:user) { Fabricate(:user) } - fab!(:category) { Fabricate(:category) } - - let!(:template) do - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - end - - let(:category_field) {{"id": "category","type": "category","limit": "1","label": "Category"}} - - def build_wizard(t = template, u = user, build_opts = {}, params = {}) - CustomWizard::Wizard.add_wizard(t) - CustomWizard::Builder.new('welcome', u).build(build_opts, params) - end - - it 'should return the wizard attributes' do - json = CustomWizardSerializer.new(build_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:id]).to eq("welcome") - expect(json[:custom_wizard][:name]).to eq("Welcome") - expect(json[:custom_wizard][:background]).to eq("#006da3") - expect(json[:custom_wizard][:required]).to eq(false) - expect(json[:custom_wizard][:min_trust]).to eq(1) - end - - it "should return the wizard user attributes" do - json = CustomWizardSerializer.new(build_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:permitted]).to eq(true) - expect(json[:custom_wizard][:user]).to eq(BasicUserSerializer.new(user, root: false).as_json) - end - - it "should not return category attributes if there are no category fields" do - json = CustomWizardSerializer.new(build_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:categories].present?).to eq(false) - expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(false) - end - - it "should return category attributes if there is a category selector field" do - template['steps'][0]['fields'][0] = category_field - json = CustomWizardSerializer.new(build_wizard(template), scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:categories].present?).to eq(true) - expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(true) - end -end \ No newline at end of file diff --git a/spec/components/custom_wizard/api_spec.rb b/spec_offload/components/custom_wizard/api_spec.rb similarity index 100% rename from spec/components/custom_wizard/api_spec.rb rename to spec_offload/components/custom_wizard/api_spec.rb diff --git a/spec_offload/components/custom_wizard/mapper_spec.rb b/spec_offload/components/custom_wizard/mapper_spec.rb new file mode 100644 index 00000000..b186ddb0 --- /dev/null +++ b/spec_offload/components/custom_wizard/mapper_spec.rb @@ -0,0 +1,13 @@ +describe CustomWizard::Mapper do + fab!(:user) { Fabricate(:user, name: 'Angus', username: 'angus', email: "angus@email.com") } + + it 'interpolates user data' do + expect( + CustomWizard::Mapper.fill_placeholders( + "My name is u{name}", + user, + {} + ) + ).to eq('My name is Angus') + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin_controller_spec.rb b/spec_offload/requests/custom_wizard/admin_controller_spec.rb similarity index 100% rename from spec/requests/custom_wizard/admin_controller_spec.rb rename to spec_offload/requests/custom_wizard/admin_controller_spec.rb diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec_offload/requests/custom_wizard/application_controller_spec.rb similarity index 100% rename from spec/requests/custom_wizard/application_controller_spec.rb rename to spec_offload/requests/custom_wizard/application_controller_spec.rb diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec_offload/requests/custom_wizard/wizard_controller_spec.rb similarity index 100% rename from spec/requests/custom_wizard/wizard_controller_spec.rb rename to spec_offload/requests/custom_wizard/wizard_controller_spec.rb diff --git a/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb b/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb new file mode 100644 index 00000000..c036e9fa --- /dev/null +++ b/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::WizardSerializer do + fab!(:user) { Fabricate(:user) } + fab!(:category) { Fabricate(:category) } + + before do + template = JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + CustomWizard::Wizard.add_wizard(template) + @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) + end + + it 'should return the wizard attributes' do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json + expect(json[:custom_wizard][:id]).to eq("super_mega_fun_wizard") + expect(json[:custom_wizard][:name]).to eq("Super Mega Fun Wizard") + expect(json[:custom_wizard][:background]).to eq("#333333") + expect(json[:custom_wizard][:required]).to eq(false) + end + + it "should return the wizard user attributes" do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json + expect(json[:custom_wizard][:user]).to eq(BasicUserSerializer.new(user, root: false).as_json) + end + + it "should not return category attributes if there are no category fields" do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json + expect(json[:custom_wizard][:categories].present?).to eq(false) + expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(false) + end + + it "should return category attributes if there is a category selector field" do + built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json + expect(json[:custom_wizard][:categories].present?).to eq(true) + expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(true) + end +end \ No newline at end of file From b726d40a0c716175b4d8a83d41668306258a634d Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 31 Oct 2020 18:05:50 +1100 Subject: [PATCH 2/4] working builder and action specs --- controllers/custom_wizard/admin/wizard.rb | 13 +- controllers/custom_wizard/steps.rb | 46 +- controllers/custom_wizard/wizard.rb | 2 +- lib/custom_wizard/builder.rb | 124 +++--- lib/custom_wizard/template.rb | 81 ++++ lib/custom_wizard/validator.rb | 2 +- lib/custom_wizard/wizard.rb | 196 ++------- plugin.rb | 7 +- spec/components/custom_wizard/action_spec.rb | 69 +-- spec/components/custom_wizard/builder_spec.rb | 397 ++++++++++++------ spec/fixtures/step/permitted_params.json | 17 + spec/fixtures/wizard.json | 10 - spec/fixtures/wizard/permitted.json | 12 + spec/fixtures/wizard/required_data.json | 18 + .../custom_wizard/wizard_serializer_spec.rb | 2 +- 15 files changed, 603 insertions(+), 393 deletions(-) create mode 100644 lib/custom_wizard/template.rb create mode 100644 spec/fixtures/step/permitted_params.json create mode 100644 spec/fixtures/wizard/permitted.json create mode 100644 spec/fixtures/wizard/required_data.json diff --git a/controllers/custom_wizard/admin/wizard.rb b/controllers/custom_wizard/admin/wizard.rb index ac5eeb8a..48beea67 100644 --- a/controllers/custom_wizard/admin/wizard.rb +++ b/controllers/custom_wizard/admin/wizard.rb @@ -4,7 +4,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController def index render_json_dump( wizard_list: ActiveModel::ArraySerializer.new( - CustomWizard::Wizard.list, + CustomWizard::Template.list, each_serializer: CustomWizard::BasicWizardSerializer ), field_types: CustomWizard::Field.types @@ -14,7 +14,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController def show params.require(:wizard_id) - if data = CustomWizard::Wizard.find(params[:wizard_id].underscore) + if data = CustomWizard::Template.find(params[:wizard_id].underscore) render json: data.as_json else render json: { none: true } @@ -22,8 +22,11 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController end def remove - CustomWizard::Wizard.remove(@wizard.id) - render json: success_json + if CustomWizard::Template.remove(@wizard.id) + render json: success_json + else + render json: failed_json + end end def save @@ -36,7 +39,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController if validation[:error] render json: { error: validation[:error] } else - if wizard_id = CustomWizard::Wizard.save(validation[:wizard]) + if wizard_id = CustomWizard::Template.save(validation[:wizard]) render json: success_json.merge(wizard_id: wizard_id) else render json: failed_json diff --git a/controllers/custom_wizard/steps.rb b/controllers/custom_wizard/steps.rb index 9318d16b..4d338ab8 100644 --- a/controllers/custom_wizard/steps.rb +++ b/controllers/custom_wizard/steps.rb @@ -1,19 +1,30 @@ class CustomWizard::StepsController < ::ApplicationController before_action :ensure_logged_in + before_action :ensure_can_update def update params.require(:step_id) params.require(:wizard_id) - field_ids = CustomWizard::Wizard.field_ids(params[:wizard_id], params[:step_id]) + + wizard = @builder.build + step = wizard.steps.select { |s| s.id == update_params[:step_id] }.first - permitted = params.permit(:wizard_id, :step_id) - if params[:fields] - permitted[:fields] = params[:fields].select { |k, v| field_ids.include? k } - permitted.permit! + if !step || step.fields.blank? + raise Discourse::InvalidParameters.new(:step_id) end - wizard = CustomWizard::Builder.new(permitted[:wizard_id].underscore, current_user).build - updater = wizard.create_updater(permitted[:step_id], permitted[:fields]) + field_ids = step.fields.map(&:id) + + if params[:fields] + permitted_fields = params[:fields].select { |k, v| field_ids.include? k } + update_params[:fields] = permitted_fields + update_params.permit! + end + + updater = wizard.create_updater( + update_params[:step_id], + update_params[:fields] + ) updater.update if updater.success? @@ -29,4 +40,25 @@ class CustomWizard::StepsController < ::ApplicationController render json: { errors: errors }, status: 422 end end + + private + + def ensure_can_update + @builder = CustomWizard::Builder.new( + update_params[:wizard_id].underscore, + current_user + ) + + if @builder.nil? + raise Discourse::InvalidParameters.new(:wizard_id) + end + + if !@builder.wizard || !@builder.wizard.can_access? + raise Discourse::InvalidAccess.new + end + end + + def update_params + params.permit(:wizard_id, :step_id) + end end diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index 914d3cea..bf2b3ed4 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -24,7 +24,7 @@ class CustomWizard::WizardController < ::ApplicationController if builder.wizard.present? builder_opts = {} - builder_opts[:reset] = params[:reset] || builder.wizard.restart_on_revisit + builder_opts[:reset] = params[:reset] built_wizard = builder.build(builder_opts, params) render_serialized(built_wizard, ::CustomWizard::WizardSerializer, root: false) diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 728ed847..4e8ec8b5 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -2,13 +2,13 @@ class CustomWizard::Builder attr_accessor :wizard, :updater, :submissions def initialize(wizard_id, user=nil) - params = CustomWizard::Wizard.find(wizard_id) - return nil if params.blank? + template = CustomWizard::Template.find(wizard_id) + return nil if template.blank? - @wizard = CustomWizard::Wizard.new(params, user) - @steps = params['steps'] || [] - @actions = params['actions'] || [] - @submissions = @wizard.submissions if user && @wizard + @wizard = CustomWizard::Wizard.new(template, user) + @steps = template['steps'] || [] + @actions = template['actions'] || [] + @submissions = @wizard.submissions end def self.sorted_handlers @@ -48,12 +48,27 @@ class CustomWizard::Builder return nil if !SiteSetting.custom_wizard_enabled || !@wizard return @wizard if !@wizard.can_access? + build_opts[:reset] = build_opts[:reset] || @wizard.restart_on_revisit reset_submissions if build_opts[:reset] @steps.each do |step_template| @wizard.append_step(step_template['id']) do |step| + step.permitted = true + + if step_template['required_data'] + step = ensure_required_data(step, step_template) + end + + if !step.permitted + if step_template['required_data_message'] + step.permitted_message = step_template['required_data_message'] + end + next + end + step.title = step_template['title'] if step_template['title'] step.banner = step_template['banner'] if step_template['banner'] + step.key = step_template['key'] if step_template['key'] if step_template['description'] step.description = mapper.interpolate( @@ -63,59 +78,8 @@ class CustomWizard::Builder ) end - step.key = step_template['key'] if step_template['key'] - step.permitted = true - if permitted_params = step_template['permitted_params'] - permitted_data = {} - - permitted_params.each do |p| - pair = p['pairs'].first - params_key = pair['key'].to_sym - submission_key = pair['value'].to_sym - permitted_data[submission_key] = params[params_key] if params[params_key] - end - - if permitted_data.present? - current_data = @submissions.last || {} - save_submissions(current_data.merge(permitted_data), false) - end - end - - if (required_data = step_template['required_data']).present? - has_required_data = true - - required_data.each do |required| - required['pairs'].each do |pair| - if pair['key'].blank? || pair['value'].blank? - has_required_data = false - end - end - end - - if has_required_data - if !@submissions.last - step.permitted = false - else - required_data.each do |required| - pairs = required['pairs'].map do |p| - p['key'] = @submissions.last[p['key']] - end - - unless mapper.validate_pairs(pairs) - step.permitted = false - end - end - end - - if !step.permitted - if step_template['required_data_message'] - step.permitted_message = step_template['required_data_message'] - end - - next - end - end + save_permitted_params(permitted_params, params) end if step_template['fields'] && step_template['fields'].length @@ -211,14 +175,14 @@ class CustomWizard::Builder params[:description] = field_template['description'] if field_template['description'] params[:image] = field_template['image'] if field_template['image'] params[:key] = field_template['key'] if field_template['key'] + params[:min_length] = field_template['min_length'] if field_template['min_length'] + params[:value] = prefill_field(field_template, step_template) ## Load previously submitted values if !build_opts[:reset] && @submissions.last && !@submissions.last.key?("submitted_at") submission = @submissions.last params[:value] = submission[field_template['id']] if submission[field_template['id']] end - - params[:value] = prefill_field(field_template, step_template) || params[:value] if field_template['type'] === 'group' && params[:value].present? params[:value] = params[:value].first @@ -404,4 +368,44 @@ class CustomWizard::Builder PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, @submissions) @wizard.reset end + + def save_permitted_params(permitted_params, params) + permitted_data = {} + + permitted_params.each do |pp| + pair = pp['pairs'].first + params_key = pair['key'].to_sym + submission_key = pair['value'].to_sym + permitted_data[submission_key] = params[params_key] if params[params_key] + end + + if permitted_data.present? + current_data = @submissions.last || {} + save_submissions(current_data.merge(permitted_data), false) + end + end + + def ensure_required_data(step, step_template) + step_template['required_data'].each do |required| + pairs = required['pairs'].select do |pair| + pair['key'].present? && pair['value'].present? + end + + if pairs.any? && !@submissions.last + step.permitted = false + break + end + + pairs.each do |pair| + pair['key'] = @submissions.last[pair['key']] + end + + if !mapper.validate_pairs(pairs) + step.permitted = false + break + end + end + + step + end end diff --git a/lib/custom_wizard/template.rb b/lib/custom_wizard/template.rb new file mode 100644 index 00000000..39e4b3e5 --- /dev/null +++ b/lib/custom_wizard/template.rb @@ -0,0 +1,81 @@ +class CustomWizard::Template + def self.add(obj) + wizard = obj.is_a?(String) ? ::JSON.parse(json) : obj + PluginStore.set('custom_wizard', wizard["id"], wizard) + end + + def self.find(wizard_id) + PluginStore.get('custom_wizard', wizard_id) + end + + def self.save(data) + data = data.with_indifferent_access + existing = self.find(data[:id]) + + data[:steps].each do |step| + if step[:raw_description] + step[:description] = PrettyText.cook(step[:raw_description]) + end + end + + data = data.slice!(:create) + + ActiveRecord::Base.transaction do + PluginStore.set('custom_wizard', data[:id], data) + + if data[:after_time] + Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: data[:id]) + enqueue_at = Time.parse(data[:after_time_scheduled]).utc + Jobs.enqueue_at(enqueue_at, :set_after_time_wizard, wizard_id: data[:id]) + end + + if existing && existing[:after_time] && !data[:after_time] + Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: data[:id]) + Jobs.enqueue(:clear_after_time_wizard, wizard_id: data[:id]) + end + end + + data[:id] + end + + def self.remove(wizard_id) + wizard = self.create(wizard_id) + + ActiveRecord::Base.transaction do + if wizard.after_time + Jobs.cancel_scheduled_job(:set_after_time_wizard) + Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard.id) + end + + PluginStore.remove('custom_wizard', wizard.id) + end + end + + def self.exists?(wizard_id) + PluginStoreRow.exists?(plugin_name: 'custom_wizard', key: wizard_id) + end + + def self.list(user=nil) + PluginStoreRow.where(plugin_name: 'custom_wizard').order(:id) + .reduce([]) do |result, record| + attrs = JSON.parse(record.value) + + if attrs.present? && + attrs.is_a?(Hash) && + attrs['id'].present? && + attrs['name'].present? + + result.push(attrs) + end + + result + end + end + + def self.setting_enabled(attr) + PluginStoreRow.where(" + plugin_name = 'custom_wizard' AND + (value::json ->> '#{attr}')::boolean IS TRUE + ") + end +end \ No newline at end of file diff --git a/lib/custom_wizard/validator.rb b/lib/custom_wizard/validator.rb index 5b377584..b75b295d 100644 --- a/lib/custom_wizard/validator.rb +++ b/lib/custom_wizard/validator.rb @@ -95,7 +95,7 @@ class CustomWizard::Validator end def check_id(object, type) - if type === :wizard && @opts[:create] && CustomWizard::Wizard.exists?(object[:id]) + if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(object[:id]) @error = { type: 'conflict', params: { type: type, property: 'id', value: object[:id] } diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index 04308a7f..b4f41089 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -24,6 +24,7 @@ class CustomWizard::Wizard :needs_categories, :needs_groups, :steps, + :step_ids, :actions, :user @@ -52,6 +53,7 @@ class CustomWizard::Wizard end @first_step = nil + @step_ids = attrs['steps'].map { |s| s['id'] } @steps = [] @actions = [] end @@ -65,13 +67,11 @@ class CustomWizard::Wizard yield step if block_given? - last_step = @steps.last - - @steps << step + last_step = steps.last + steps << step - # If it's the first step - if @steps.size == 1 - @first_step = step + if steps.size == 1 + first_step = step step.index = 0 elsif last_step.present? last_step.next = step @@ -81,67 +81,62 @@ class CustomWizard::Wizard end def start - return nil if !@user + return nil if !user if unfinished? && last_completed_step = ::UserHistory.where( - acting_user_id: @user.id, + acting_user_id: user.id, action: ::UserHistory.actions[:custom_wizard_step], - context: @id, - subject: @steps.map(&:id) + context: id, + subject: steps.map(&:id) ).order("created_at").last step_id = last_completed_step.subject - last_index = @steps.index { |s| s.id == step_id } - @steps[last_index + 1] + last_index = steps.index { |s| s.id == step_id } + steps[last_index + 1] else - @first_step + first_step end end def create_updater(step_id, fields) step = @steps.find { |s| s.id == step_id } wizard = self - CustomWizard::StepUpdater.new(@user, wizard, step, fields) + CustomWizard::StepUpdater.new(user, wizard, step, fields) end def unfinished? - return nil if !@user + return nil if !user most_recent = ::UserHistory.where( - acting_user_id: @user.id, + acting_user_id: user.id, action: ::UserHistory.actions[:custom_wizard_step], - context: @id, + context: id, ).distinct.order('updated_at DESC').first if most_recent && most_recent.subject == "reset" false elsif most_recent - last_finished_step = most_recent.subject - last_step = CustomWizard::Wizard.step_ids(@id).last - last_finished_step != last_step + most_recent.subject != steps.last.id else true end end def completed? - return nil if !@user + return nil if !user - steps = CustomWizard::Wizard.step_ids(@id) - history = ::UserHistory.where( - acting_user_id: @user.id, + acting_user_id: user.id, action: ::UserHistory.actions[:custom_wizard_step], - context: @id + context: id ) - if @after_time - history = history.where("updated_at > ?", @after_time_scheduled) + if after_time + history = history.where("updated_at > ?", after_time_scheduled) end completed = history.distinct.order(:subject).pluck(:subject) - - (steps - completed).empty? + (step_ids - completed).empty? end def permitted? @@ -178,33 +173,38 @@ class CustomWizard::Wizard def reset ::UserHistory.create( action: ::UserHistory.actions[:custom_wizard_step], - acting_user_id: @user.id, - context: @id, + acting_user_id: user.id, + context: id, subject: "reset" ) end def categories - @categories ||= ::Site.new(Guardian.new(@user)).categories + @categories ||= ::Site.new(Guardian.new(user)).categories end def groups - @groups ||= ::Site.new(Guardian.new(@user)).groups + @groups ||= ::Site.new(Guardian.new(user)).groups end def submissions - Array.wrap(PluginStore.get("#{id}_submissions", @user.id)) + Array.wrap(PluginStore.get("#{id}_submissions", user.id)) end - def self.filter_records(filter) - PluginStoreRow.where(" - plugin_name = 'custom_wizard' AND - (value::json ->> '#{filter}')::boolean IS TRUE - ") + def self.create(wizard_id, user = nil) + if template = CustomWizard::Template.find(wizard_id) + self.new(template.to_h, user) + else + false + end + end + + def self.list(user=nil) + CustomWizard::Template.list.map { |template| self.new(template.to_h, user) } end def self.after_signup(user) - if (records = filter_records('after_signup')).any? + if (records = CustomWizard::Template.setting_enabled('after_signup')).any? result = false records @@ -225,9 +225,9 @@ class CustomWizard::Wizard end def self.prompt_completion(user) - if (records = filter_records('prompt_completion')).any? + if (records = CustomWizard::Template.setting_enabled('prompt_completion')).any? records.reduce([]) do |result, record| - wizard = CustomWizard::Wizard.new(::JSON.parse(record.value), user) + wizard = self.new(::JSON.parse(record.value), user) if wizard.permitted? && !wizard.completed? result.push(id: wizard.id, name: wizard.name) @@ -241,115 +241,13 @@ class CustomWizard::Wizard end def self.restart_on_revisit - if (records = filter_records('restart_on_revisit')).any? + if (records = CustomWizard::Template.setting_enabled('restart_on_revisit')).any? records.first.key else false end end - def self.steps(wizard_id) - wizard = PluginStore.get('custom_wizard', wizard_id) - wizard ? wizard['steps'] : nil - end - - def self.step_ids(wizard_id) - steps = self.steps(wizard_id) - return [] if !steps - steps.map { |s| s['id'] }.flatten.uniq - end - - def self.field_ids(wizard_id, step_id) - steps = self.steps(wizard_id) - return [] if !steps - step = steps.select { |s| s['id'] === step_id }.first - if step && fields = step['fields'] - fields.map { |f| f['id'] } - else - [] - end - end - - def self.add_wizard(obj) - wizard = obj.is_a?(String) ? ::JSON.parse(json) : obj - PluginStore.set('custom_wizard', wizard["id"], wizard) - end - - def self.find(wizard_id) - PluginStore.get('custom_wizard', wizard_id) - end - - def self.list(user=nil) - PluginStoreRow.where(plugin_name: 'custom_wizard').order(:id) - .reduce([]) do |result, record| - attrs = JSON.parse(record.value) - - if attrs.present? && - attrs.is_a?(Hash) && - attrs['id'].present? && - attrs['name'].present? - - result.push(self.new(attrs, user)) - end - - result - end - end - - def self.save(wizard) - existing_wizard = self.create(wizard[:id]) - - wizard[:steps].each do |step| - if step[:raw_description] - step[:description] = PrettyText.cook(step[:raw_description]) - end - end - - wizard = wizard.slice!(:create) - - ActiveRecord::Base.transaction do - PluginStore.set('custom_wizard', wizard[:id], wizard) - - if wizard[:after_time] - Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard[:id]) - enqueue_at = Time.parse(wizard[:after_time_scheduled]).utc - Jobs.enqueue_at(enqueue_at, :set_after_time_wizard, wizard_id: wizard[:id]) - end - - if existing_wizard && existing_wizard.after_time && !wizard[:after_time] - Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard[:id]) - Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard[:id]) - end - end - - wizard[:id] - end - - def self.remove(wizard_id) - wizard = self.create(wizard_id) - - ActiveRecord::Base.transaction do - if wizard.after_time - Jobs.cancel_scheduled_job(:set_after_time_wizard) - Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard.id) - end - - PluginStore.remove('custom_wizard', wizard.id) - end - end - - def self.exists?(wizard_id) - PluginStoreRow.exists?(plugin_name: 'custom_wizard', key: wizard_id) - end - - def self.create(wizard_id, user = nil) - if wizard = self.find(wizard_id) - self.new(wizard.to_h, user) - else - false - end - end - def self.set_submission_redirect(user, wizard_id, url) PluginStore.set("#{wizard_id.underscore}_submissions", user.id, [{ redirect_to: url }]) end @@ -364,12 +262,4 @@ class CustomWizard::Wizard false end end - - def self.register_styles - full_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets/stylesheets/wizard/wizard_custom.scss" - DiscoursePluginRegistry.register_asset(full_path, {}, "wizard_custom") - Stylesheet::Importer.register_import("wizard_custom") do - import_files(DiscoursePluginRegistry.stylesheets["wizard_custom"]) - end - end end diff --git a/plugin.rb b/plugin.rb index dfe3d063..3a8d48e0 100644 --- a/plugin.rb +++ b/plugin.rb @@ -55,6 +55,7 @@ after_initialize do ../lib/custom_wizard/mapper.rb ../lib/custom_wizard/log.rb ../lib/custom_wizard/step_updater.rb + ../lib/custom_wizard/template.rb ../lib/custom_wizard/validator.rb ../lib/custom_wizard/wizard.rb ../lib/custom_wizard/api/api.rb @@ -158,7 +159,11 @@ after_initialize do ::Wizard::Field.prepend CustomWizardFieldExtension ::Wizard::Step.prepend CustomWizardStepExtension - CustomWizard::Wizard.register_styles + full_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets/stylesheets/wizard/wizard_custom.scss" + DiscoursePluginRegistry.register_asset(full_path, {}, "wizard_custom") + Stylesheet::Importer.register_import("wizard_custom") do + import_files(DiscoursePluginRegistry.stylesheets["wizard_custom"]) + end DiscourseEvent.trigger(:custom_wizard_ready) end diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 9ebfe779..12308ef2 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -7,20 +7,23 @@ describe CustomWizard::Action do before do Group.refresh_automatic_group!(:trust_level_2) - template = JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - CustomWizard::Wizard.add_wizard(template) - @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) + CustomWizard::Template.add( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + ) + @template = CustomWizard::Template.find('super_mega_fun_wizard') end it 'creates a topic' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - updater = built_wizard.create_updater(built_wizard.steps[0].id, + wizard = CustomWizard::Builder.new(@template[:id], user).build + + updater = wizard.create_updater( + wizard.steps[0].id, step_1_field_1: "Topic Title", step_1_field_2: "topic body" ).update - updater2 = built_wizard.create_updater(built_wizard.steps[1].id, {}).update + updater2 = wizard.create_updater(wizard.steps[1].id, {}).update topic = Topic.where(title: "Topic Title") @@ -34,9 +37,9 @@ describe CustomWizard::Action do it 'sends a message' do User.create(username: 'angus1', email: "angus1@email.com") - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - built_wizard.create_updater(built_wizard.steps[0].id, {}).update - built_wizard.create_updater(built_wizard.steps[1].id, {}).update + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater(wizard.steps[0].id, {}).update + wizard.create_updater(wizard.steps[1].id, {}).update topic = Topic.where( archetype: Archetype.private_message, @@ -54,26 +57,26 @@ describe CustomWizard::Action do end it 'updates a profile' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build + wizard = CustomWizard::Builder.new(@template[:id], user).build upload = Upload.create!( url: '/images/image.png', original_filename: 'image.png', filesize: 100, user_id: -1, ) - steps = built_wizard.steps - built_wizard.create_updater(steps[0].id, {}).update - built_wizard.create_updater(steps[1].id, - step_2_field_7: upload.as_json, + steps = wizard.steps + wizard.create_updater(steps[0].id, {}).update + wizard.create_updater(steps[1].id, + step_2_field_7: upload.as_json ).update expect(user.profile_background_upload.id).to eq(upload.id) end it 'opens a composer' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - built_wizard.create_updater(built_wizard.steps[0].id, step_1_field_1: "Text input").update + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update - updater = built_wizard.create_updater(built_wizard.steps[1].id, {}) + updater = wizard.create_updater(wizard.steps[1].id, {}) updater.update submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) @@ -85,34 +88,34 @@ describe CustomWizard::Action do end it 'creates a category' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - built_wizard.create_updater(built_wizard.steps[0].id, step_1_field_1: "Text input").update - built_wizard.create_updater(built_wizard.steps[1].id, {}).update + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update + wizard.create_updater(wizard.steps[1].id, {}).update submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) expect(Category.where(id: submissions.first['action_8']).exists?).to eq(true) end it 'creates a group' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - step_id = built_wizard.steps[0].id - updater = built_wizard.create_updater(step_id, step_1_field_1: "Text input").update + wizard = CustomWizard::Builder.new(@template[:id], user).build + step_id = wizard.steps[0].id + updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) expect(Group.where(name: submissions.first['action_9']).exists?).to eq(true) end it 'adds a user to a group' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - step_id = built_wizard.steps[0].id - updater = built_wizard.create_updater(step_id, step_1_field_1: "Text input").update + wizard = CustomWizard::Builder.new(@template[:id], user).build + step_id = wizard.steps[0].id + updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) group = Group.find_by(name: submissions.first['action_9']) expect(group.users.first.username).to eq('angus') end it 'watches categories' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - built_wizard.create_updater(built_wizard.steps[0].id, step_1_field_1: "Text input").update - built_wizard.create_updater(built_wizard.steps[1].id, {}).update + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update + wizard.create_updater(wizard.steps[1].id, {}).update submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) expect(CategoryUser.where( category_id: submissions.first['action_8'], @@ -125,8 +128,8 @@ describe CustomWizard::Action do end it 're-routes a user' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - updater = built_wizard.create_updater(built_wizard.steps.last.id, {}) + wizard = CustomWizard::Builder.new(@template[:id], user).build + updater = wizard.create_updater(wizard.steps.last.id, {}) updater.update expect(updater.result[:redirect_on_complete]).to eq("https://google.com") end diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 9a7c8b60..341cf045 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -3,19 +3,45 @@ require 'rails_helper' describe CustomWizard::Builder do - fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[2]) } - fab!(:new_user) { Fabricate(:user, trust_level: 0) } + fab!(:trusted_user) { + Fabricate( + :user, + username: 'angus', + email: "angus@email.com", + trust_level: TrustLevel[3] + ) + } + fab!(:user) { Fabricate(:user) } fab!(:category1) { Fabricate(:category, name: 'cat1') } fab!(:category2) { Fabricate(:category, name: 'cat2') } fab!(:group) { Fabricate(:group) } - before do - Group.refresh_automatic_group!(:trust_level_2) - template = JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + let(:required_data_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/required_data.json" ).read) - CustomWizard::Wizard.add_wizard(template) - @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) + } + + let(:permitted_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" + ).read) + } + + let(:permitted_param_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/permitted_params.json" + ).read) + } + + before do + Group.refresh_automatic_group!(:trust_level_3) + CustomWizard::Template.add( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + ) + @template = CustomWizard::Template.find('super_mega_fun_wizard') end context 'disabled' do @@ -24,7 +50,9 @@ describe CustomWizard::Builder do end it "returns nil" do - expect(CustomWizard::Builder.new(@wizard.id, user).build).to eq(nil) + expect( + CustomWizard::Builder.new(@template[:id], user).build + ).to eq(nil) end end @@ -33,150 +61,276 @@ describe CustomWizard::Builder do SiteSetting.custom_wizard_enabled = true end + it "returns wizard metadata" do + wizard = CustomWizard::Builder.new(@template[:id], user).build + expect(wizard.id).to eq("super_mega_fun_wizard") + expect(wizard.name).to eq("Super Mega Fun Wizard") + expect(wizard.background).to eq("#333333") + end + it "returns steps" do expect( - CustomWizard::Builder.new(@wizard.id, user).build.steps.length - ).to eq(2) - end - - it 'returns no steps if multiple submissions are disabled and user has completed' do - wizard_template = CustomWizard::Wizard.find(@wizard.id) - wizard_template[:multiple_submissions] = false - CustomWizard::Wizard.save(wizard_template) - - history_params = { - action: UserHistory.actions[:custom_wizard_step], - acting_user_id: user.id, - context: @wizard.id - } - @wizard.steps.each do |step| - UserHistory.create!(history_params.merge(subject: step.id)) - end - - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - expect( - CustomWizard::Builder.new(@wizard.id, user).build.steps.length - ).to eq(0) - end - - it 'returns no steps if user is not permitted' do - expect( - CustomWizard::Builder.new(@wizard.id, new_user).build.steps.length - ).to eq(0) - end - - it 'returns steps if user is permitted' do - expect( - CustomWizard::Builder.new(@wizard.id, user).build.steps.length + CustomWizard::Builder.new(@template[:id], user).build + .steps.length ).to eq(3) end - it 'returns a wizard with prefilled data if user has partially completed it' do - expect( - CustomWizard::Builder.new(@wizard.id, user) - .build - .steps[0].fields[0].value - ).to eq('I am prefilled') - end - - it 'returns a wizard with no prefilled data if options include reset' do - PluginStore.set("super_mega_fun_wizard_submissions", user.id, { - text: 'Input into text', - }) - expect( - CustomWizard::Builder.new(@wizard.id, user) - .build(reset: true) - .steps[0].fields[0].value - ).to eq(nil) - end - - context 'building steps' do - it 'returns step metadata' do - expect( - CustomWizard::Builder.new(@wizard.id, user) - .build(reset: true) - .steps[0] - ).to eq('Super Mega Fun Wizard') + context "with multiple submissions disabled" do + before do + @template[:multiple_submissions] = false + CustomWizard::Template.save(@template.as_json) end - it 'saves permitted params' do - @wizard.steps[0].permitted_params = permitted_params - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build({}, param_key: 'param_value') - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) - expect(submissions[0]['submission_param_key']).to eq('param_value') + it 'returns steps if user has not completed it' do + expect( + CustomWizard::Builder.new(@template[:id], user).build + .steps.length + ).to eq(3) end - it 'is not permitted if required data is not present' do - @wizard.steps[0].required_data = required_data + it 'returns no steps if user has completed it' do + @template[:steps].each do |step| + UserHistory.create!( + { + action: UserHistory.actions[:custom_wizard_step], + acting_user_id: user.id, + context: @template[:id] + }.merge( + subject: step[:id] + ) + ) + end + expect( - CustomWizard::Builder.new(@wizard.id, user).build.steps[0].permitted + CustomWizard::Builder.new(@template[:id], user).build + .steps.length + ).to eq(0) + end + end + + context "with restricted permissions" do + before do + @template[:permitted] = permitted_json["permitted"] + CustomWizard::Template.save(@template.as_json) + end + + it 'is not permitted if user is not in permitted group' do + expect( + CustomWizard::Builder.new(@template[:id], user).build + .permitted? ).to eq(false) end - it 'it shows required data message if required data has message' do - @wizard.steps[0].required_data = required_data - @wizard.steps[0].required_data_message = "Data is required" - PluginStore.set("super_mega_fun_wizard_submissions", user.id, - text: 'Input into text', - ) - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - expect(built_wizard.steps[0].permitted).to eq(false) - expect(built_wizard.steps[0].permitted_message).to eq("Data is required") + it 'user cannot access if not permitted' do + expect( + CustomWizard::Builder.new(@template[:id], user).build + .can_access? + ).to eq(false) end - it 'is permitted if required data is present' do - @wizard.steps[0].required_data = required_data - PluginStore.set('super_mega_fun_wizard_submissions', user.id, - text: "Input into text" - ) + it 'returns wizard metadata if user is not permitted' do expect( - CustomWizard::Builder.new(@wizard.id, user).build.steps[0].permitted + CustomWizard::Builder.new(@template[:id], user).build + .name + ).to eq("Super Mega Fun Wizard") + end + + it 'returns no steps if user is not permitted' do + expect( + CustomWizard::Builder.new(@template[:id], user).build + .steps.length + ).to eq(0) + end + + it 'is permitted if user is in permitted group' do + expect( + CustomWizard::Builder.new(@template[:id], trusted_user).build + .permitted? ).to eq(true) end - it 'returns field metadata' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - expect(built_wizard.steps[0].fields[0].label).to eq("

Name

") - expect(built_wizard.steps[0].fields[0].type).to eq("text") + it 'user can access if permitted' do + expect( + CustomWizard::Builder.new(@template[:id], trusted_user).build + .can_access? + ).to eq(true) end - it 'returns fields' do - @wizard.steps[0].fields[1] = checkbox_field - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - expect(built_wizard.steps[0].fields.length).to eq(2) + it 'returns steps if user is permitted' do + expect( + CustomWizard::Builder.new(@template[:id], trusted_user).build + .steps.length + ).to eq(3) + end + end + + it 'returns prefilled data' do + expect( + CustomWizard::Builder.new(@template[:id], user).build + .steps.first + .fields.first + .value + ).to eq('I am prefilled') + end + + context "user has partially completed" do + before do + PluginStore.set("super_mega_fun_wizard_submissions", user.id, + step_1_field_1: 'I am a user submission' + ) + end + + it 'returns saved submissions' do + expect( + CustomWizard::Builder.new(@template[:id], user).build + .steps.first + .fields.first + .value + ).to eq('I am a user submission') + end + + context "restart is enabled" do + before do + @template[:restart_on_revisit] = true + CustomWizard::Template.save(@template.as_json) + end + + it 'does not return saved submissions' do + expect( + CustomWizard::Builder.new(@template[:id], user).build + .steps.first + .fields.first + .value + ).to eq('I am prefilled') + end + end + end + + context 'building step' do + it 'returns step metadata' do + first_step = CustomWizard::Builder.new(@template[:id], user) + .build(reset: true) + .steps.first + + expect(first_step.id).to eq("step_1") + expect(first_step.title).to eq("Text") + expect(first_step.description).to eq("

Text inputs!

") + end + + context 'with required data' do + before do + @template[:steps][0][:required_data] = required_data_json['required_data'] + @template[:steps][0][:required_data_message] = required_data_json['required_data_message'] + CustomWizard::Template.save(@template.as_json) + end + + it 'is not permitted if required data is not present' do + expect( + CustomWizard::Builder.new(@template[:id], user).build + .steps.first + .permitted + ).to eq(false) + end + + it 'it shows required data message' do + expect( + CustomWizard::Builder.new(@template[:id], user).build + .steps.first + .permitted_message + ).to eq("Missing required data") + end + + it 'is permitted if required data is present' do + PluginStore.set('super_mega_fun_wizard_submissions', user.id, + required_data: "required_value" + ) + expect( + CustomWizard::Builder.new(@template[:id], user).build + .steps.first + .permitted + ).to eq(true) + end + end + + context "with permitted params" do + before do + @template[:steps][0][:permitted_params] = permitted_param_json['permitted_params'] + CustomWizard::Template.save(@template.as_json) + end + + it 'saves permitted params' do + CustomWizard::Builder.new(@template[:id], user).build({}, + param: 'param_value' + ) + expect( + PluginStore.get("super_mega_fun_wizard_submissions", user.id) + .first['saved_param'] + ).to eq('param_value') + end + end + end + + context 'building field' do + it 'returns field metadata' do + wizard = CustomWizard::Builder.new(@template[:id], user).build + field = wizard.steps.first.fields.first + + expect(field.label).to eq("

Text

") + expect(field.type).to eq("text") + expect(field.id).to eq("step_1_field_1") + expect(field.min_length).to eq("3") + end + + it 'returns all step fields' do + expect( + CustomWizard::Builder.new(@template[:id], user) + .build + .steps.first + .fields.length + ).to eq(4) end end context 'on update' do + def perform_update(step_id, submission) + wizard = CustomWizard::Builder.new(@template[:id], user).build + updater = wizard.create_updater(step_id, submission) + updater.update + updater + end + it 'saves submissions' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - built_wizard.create_updater(built_wizard.steps[0].id, - step_1_field_1: 'Text input' - ).update + perform_update('step_1', step_1_field_1: 'Text input') expect( PluginStore.get("super_mega_fun_wizard_submissions", user.id) .first['step_1_field_1'] ).to eq('Text input') end + context 'save submissions disabled' do + before do + @template[:save_submissions] = false + CustomWizard::Template.save(@template.as_json) + end + + it "does not save submissions" do + perform_update('step_1', step_1_field_1: 'Text input') + expect( + PluginStore.get("super_mega_fun_wizard_submissions", user.id) + ).to eq(nil) + end + end + context 'validation' do it 'applies min length' do - @wizard.steps[0].fields[0].min_length = 10 - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - updater = built_wizard.create_updater(built_wizard.steps[0].id, - step_1_field_1: 'Te' - ).update - expect(updater.errors.messages[:text].first).to eq( - I18n.t('wizard.field.too_short', label: 'Text', min: 3) - ) + expect( + perform_update('step_1', step_1_field_1: 'Te') + .errors.messages[:step_1_field_1].first + ).to eq(I18n.t('wizard.field.too_short', label: 'Text', min: 3)) end it 'standardises boolean entries' do - @wizard.steps[0].fields[0] = checkbox_field - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - updater = built_wizard.create_updater(built_wizard.steps[1].id, - step_2_field_5: 'false' - ).update + perform_update('step_2', step_2_field_5: 'false') expect( PluginStore.get("super_mega_fun_wizard_submissions", user.id) .first['step_2_field_5'] @@ -184,12 +338,13 @@ describe CustomWizard::Builder do end it 'requires required fields' do - @wizard.steps[0].fields[0]['required'] = true - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - updater = built_wizard.create_updater(built_wizard.steps.second.id).update + @template[:steps][0][:fields][1][:required] = true + CustomWizard::Template.save(@template.as_json) + expect( - updater.errors.messages[:step_1_field_1].first - ).to eq(I18n.t('wizard.field.required', label: 'Text')) + perform_update('step_1', step_1_field_2: nil) + .errors.messages[:step_1_field_2].first + ).to eq(I18n.t('wizard.field.required', label: 'Textarea')) end end end diff --git a/spec/fixtures/step/permitted_params.json b/spec/fixtures/step/permitted_params.json new file mode 100644 index 00000000..d6b7e729 --- /dev/null +++ b/spec/fixtures/step/permitted_params.json @@ -0,0 +1,17 @@ +{ + "permitted_params": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "param", + "key_type": "text", + "value": "saved_param", + "value_type": "text", + "connector": "association" + } + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json index 09ffc95e..1d5e1bc7 100644 --- a/spec/fixtures/wizard.json +++ b/spec/fixtures/wizard.json @@ -7,16 +7,6 @@ "after_signup": false, "prompt_completion": true, "theme_id": 2, - "permitted": [ - { - "type": "assignment", - "output_type": "group", - "output_connector": "set", - "output": [ - 12 - ] - } - ], "steps": [ { "id": "step_1", diff --git a/spec/fixtures/wizard/permitted.json b/spec/fixtures/wizard/permitted.json new file mode 100644 index 00000000..91d8edf1 --- /dev/null +++ b/spec/fixtures/wizard/permitted.json @@ -0,0 +1,12 @@ +{ + "permitted": [ + { + "type": "assignment", + "output_type": "group", + "output_connector": "set", + "output": [ + 13 + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/wizard/required_data.json b/spec/fixtures/wizard/required_data.json new file mode 100644 index 00000000..9f65d516 --- /dev/null +++ b/spec/fixtures/wizard/required_data.json @@ -0,0 +1,18 @@ +{ + "required_data_message": "Missing required data", + "required_data": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "required_data", + "key_type": "text", + "value": "required_value", + "value_type": "text", + "connector": "equal" + } + ] + } + ] +} \ No newline at end of file diff --git a/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb b/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb index c036e9fa..1498ede9 100644 --- a/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb +++ b/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb @@ -10,7 +10,7 @@ describe CustomWizard::WizardSerializer do template = JSON.parse(File.open( "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" ).read) - CustomWizard::Wizard.add_wizard(template) + CustomWizard::Template.add(template) @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) end From fc7c5b9b34f69ceab90956326c7abade90d28973 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 3 Nov 2020 11:24:20 +1100 Subject: [PATCH 3/4] Complete tests --- config/locales/server.en.yml | 5 + config/routes.rb | 6 +- controllers/custom_wizard/admin/admin.rb | 1 + controllers/custom_wizard/admin/logs.rb | 2 +- .../custom_wizard/admin/submissions.rb | 54 ++-- .../custom_wizard/{ => admin}/transfer.rb | 56 ++-- controllers/custom_wizard/admin/wizard.rb | 19 +- controllers/custom_wizard/steps.rb | 18 +- controllers/custom_wizard/wizard.rb | 20 +- jobs/set_after_time_wizard.rb | 2 +- lib/custom_wizard/action.rb | 10 +- lib/custom_wizard/builder.rb | 53 ++-- lib/custom_wizard/field.rb | 6 +- lib/custom_wizard/log.rb | 9 +- lib/custom_wizard/mapper.rb | 2 +- lib/custom_wizard/template.rb | 126 ++++++--- lib/custom_wizard/validator.rb | 100 ++----- lib/custom_wizard/wizard.rb | 131 +++++---- plugin.rb | 5 +- spec/components/custom_wizard/action_spec.rb | 86 ++++-- spec/components/custom_wizard/builder_spec.rb | 30 +- spec/components/custom_wizard/field_spec.rb | 30 ++ spec/components/custom_wizard/log_spec.rb | 27 ++ spec/components/custom_wizard/mapper_spec.rb | 250 +++++++++++++++++ .../components/custom_wizard/template_spec.rb | 111 ++++++++ .../custom_wizard/validator_spec.rb | 53 ++++ spec/components/custom_wizard/wizard_spec.rb | 230 +++++++++++++++ .../extra_locales_controller_spec.rb | 23 ++ spec/extensions/invites_controller_spec.rb | 24 ++ spec/extensions/users_controller_spec.rb | 21 ++ spec/extensions/wizard_field_spec.rb | 21 ++ spec/extensions/wizard_step_spec.rb | 23 ++ spec/fixtures/field/field.json | 10 + spec/fixtures/mapper/data.json | 4 + spec/fixtures/mapper/inputs.json | 264 ++++++++++++++++++ .../{wizard => step}/required_data.json | 0 spec/fixtures/step/step.json | 10 + spec/fixtures/wizard.json | 3 +- spec/fixtures/wizard/after_time.json | 4 + spec/jobs/clear_after_time_wizard_spec.rb | 37 +++ spec/jobs/set_after_time_wizard_spec.rb | 42 +++ spec/plugin_helper.rb | 4 +- .../admin/logs_controller_spec.rb | 22 ++ .../admin/submissions_controller_spec.rb | 45 +++ .../admin/transfer_controller_spec.rb | 52 ++++ .../admin/wizard_controller_spec.rb | 66 +++++ .../application_controller_spec.rb | 61 ++++ .../custom_wizard/steps_controller_spec.rb | 34 +++ .../custom_wizard/wizard_controller_spec.rb | 63 +++++ .../basic_wizard_serializer_spec.rb | 21 ++ .../custom_wizard/log_serializer_spec.rb | 19 ++ .../wizard_field_serializer_spec.rb | 37 +++ .../custom_wizard/wizard_serializer_spec.rb | 86 ++++++ .../custom_wizard/wizard_step_serializer.rb | 59 ++++ .../components/custom_wizard/api_spec.rb | 21 -- .../components/custom_wizard/mapper_spec.rb | 13 - .../custom_wizard/admin_controller_spec.rb | 5 - .../application_controller_spec.rb | 5 - .../custom_wizard/wizard_controller_spec.rb | 31 -- .../custom_wizard/wizard_serializer_spec.rb | 45 --- 60 files changed, 2138 insertions(+), 479 deletions(-) rename controllers/custom_wizard/{ => admin}/transfer.rb (50%) create mode 100644 spec/components/custom_wizard/field_spec.rb create mode 100644 spec/components/custom_wizard/log_spec.rb create mode 100644 spec/components/custom_wizard/mapper_spec.rb create mode 100644 spec/components/custom_wizard/template_spec.rb create mode 100644 spec/components/custom_wizard/validator_spec.rb create mode 100644 spec/components/custom_wizard/wizard_spec.rb create mode 100644 spec/extensions/extra_locales_controller_spec.rb create mode 100644 spec/extensions/invites_controller_spec.rb create mode 100644 spec/extensions/users_controller_spec.rb create mode 100644 spec/extensions/wizard_field_spec.rb create mode 100644 spec/extensions/wizard_step_spec.rb create mode 100644 spec/fixtures/field/field.json create mode 100644 spec/fixtures/mapper/data.json create mode 100644 spec/fixtures/mapper/inputs.json rename spec/fixtures/{wizard => step}/required_data.json (100%) create mode 100644 spec/fixtures/step/step.json create mode 100644 spec/fixtures/wizard/after_time.json create mode 100644 spec/jobs/clear_after_time_wizard_spec.rb create mode 100644 spec/jobs/set_after_time_wizard_spec.rb create mode 100644 spec/requests/custom_wizard/admin/logs_controller_spec.rb create mode 100644 spec/requests/custom_wizard/admin/submissions_controller_spec.rb create mode 100644 spec/requests/custom_wizard/admin/transfer_controller_spec.rb create mode 100644 spec/requests/custom_wizard/admin/wizard_controller_spec.rb create mode 100644 spec/requests/custom_wizard/application_controller_spec.rb create mode 100644 spec/requests/custom_wizard/steps_controller_spec.rb create mode 100644 spec/requests/custom_wizard/wizard_controller_spec.rb create mode 100644 spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb create mode 100644 spec/serializers/custom_wizard/log_serializer_spec.rb create mode 100644 spec/serializers/custom_wizard/wizard_field_serializer_spec.rb create mode 100644 spec/serializers/custom_wizard/wizard_serializer_spec.rb create mode 100644 spec/serializers/custom_wizard/wizard_step_serializer.rb delete mode 100644 spec_offload/components/custom_wizard/api_spec.rb delete mode 100644 spec_offload/components/custom_wizard/mapper_spec.rb delete mode 100644 spec_offload/requests/custom_wizard/admin_controller_spec.rb delete mode 100644 spec_offload/requests/custom_wizard/application_controller_spec.rb delete mode 100644 spec_offload/requests/custom_wizard/wizard_controller_spec.rb delete mode 100644 spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 2fcfe687..4f9ef820 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -26,6 +26,11 @@ en: file_large: "File too large" invalid_json: "File is not a valid json file" no_valid_wizards: "File doesn't contain any valid wizards" + + validation: + required: "%{property} is required" + conflict: "Wizard with %{wizard_id} already exists" + after_time: "After time setting is invalid" site_settings: custom_wizard_enabled: "Enable custom wizards." diff --git a/config/routes.rb b/config/routes.rb index 908e9656..8f754d08 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,8 +33,8 @@ Discourse::Application.routes.append do get 'admin/wizards/logs' => 'admin_logs#index' - get 'admin/wizards/transfer' => 'transfer#index' - get 'admin/wizards/transfer/export' => 'transfer#export' - post 'admin/wizards/transfer/import' => 'transfer#import' + get 'admin/wizards/transfer' => 'admin_transfer#index' + get 'admin/wizards/transfer/export' => 'admin_transfer#export' + post 'admin/wizards/transfer/import' => 'admin_transfer#import' end end \ No newline at end of file diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index 2dd87e86..fe438e5d 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -9,5 +9,6 @@ class CustomWizard::AdminController < ::Admin::AdminController def find_wizard params.require(:wizard_id) @wizard = CustomWizard::Wizard.create(params[:wizard_id].underscore) + raise Discourse::InvalidParameters.new(:wizard_id) unless @wizard end end \ No newline at end of file diff --git a/controllers/custom_wizard/admin/logs.rb b/controllers/custom_wizard/admin/logs.rb index 60303af7..005de8dc 100644 --- a/controllers/custom_wizard/admin/logs.rb +++ b/controllers/custom_wizard/admin/logs.rb @@ -1,7 +1,7 @@ class CustomWizard::AdminLogsController < CustomWizard::AdminController def index render_serialized( - CustomWizard::Log.list(params[:page].to_i), + CustomWizard::Log.list(params[:page].to_i, params[:limit].to_i), CustomWizard::LogSerializer ) end diff --git a/controllers/custom_wizard/admin/submissions.rb b/controllers/custom_wizard/admin/submissions.rb index b7d45e44..69088492 100644 --- a/controllers/custom_wizard/admin/submissions.rb +++ b/controllers/custom_wizard/admin/submissions.rb @@ -1,29 +1,23 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController skip_before_action :preload_json, :check_xhr, only: [:download] - - before_action :find_wizard + before_action :find_wizard, except: [:index] def index render json: ActiveModel::ArraySerializer.new( - CustomWizard::Wizard.list, + CustomWizard::Wizard.list(current_user), each_serializer: CustomWizard::BasicWizardSerializer ) end def show - result = {} - - if wizard = @wizard - submissions = build_submissions(wizard.id) - result[:wizard] = CustomWizard::BasicWizardSerializer.new(wizard, root: false) - result[:submissions] = submissions.as_json - end - - render_json_dump(result) + render_json_dump( + wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false), + submissions: build_submissions.as_json + ) end def download - send_data build_submissions(@wizard.id).to_json, + send_data build_submissions.to_json, filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json", content_type: "application/json", disposition: "attachment" @@ -31,23 +25,21 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController private - def build_submissions(wizard_id) - rows = PluginStoreRow.where(plugin_name: "#{wizard_id}_submissions").order('id DESC') - - submissions = [*rows].map do |row| - value = ::JSON.parse(row.value) - - if user = User.find_by(id: row.key) - username = user.username - else - username = I18n.t('admin.wizard.submissions.no_user', id: row.key) - end - - value.map do |submission| - { - username: username - }.merge!(submission.except("redirect_to")) - end - end.flatten + def build_submissions + PluginStoreRow.where(plugin_name: "#{@wizard.id}_submissions") + .order('id DESC') + .map do |row| + value = ::JSON.parse(row.value) + + if user = User.find_by(id: row.key) + username = user.username + else + username = I18n.t('admin.wizard.submissions.no_user', id: row.key) + end + + value.map do |v| + { username: username }.merge!(v.except("redirect_to")) + end + end.flatten end end \ No newline at end of file diff --git a/controllers/custom_wizard/transfer.rb b/controllers/custom_wizard/admin/transfer.rb similarity index 50% rename from controllers/custom_wizard/transfer.rb rename to controllers/custom_wizard/admin/transfer.rb index f1bf0bba..b55fe872 100644 --- a/controllers/custom_wizard/transfer.rb +++ b/controllers/custom_wizard/admin/transfer.rb @@ -1,25 +1,22 @@ -class CustomWizard::TransferController < ::ApplicationController - before_action :ensure_logged_in - before_action :ensure_admin +class CustomWizard::AdminTransferController < CustomWizard::AdminController skip_before_action :check_xhr, :only => [:export] - def index - end - def export - wizards = params['wizards'] - wizard_objects = [] + wizard_ids = params['wizards'] + templates = [] - if wizards.nil? + if wizard_ids.nil? render json: { error: I18n.t('wizard.export.error.select_one') } return end - wizards.each do |w| - wizard_objects.push(PluginStore.get('custom_wizard', w.tr('-', '_'))) + wizard_ids.each do |wizard_id| + if template = CustomWizard::Template.find(wizard_id) + templates.push(template) + end end - send_data wizard_objects.to_json, + send_data templates.to_json, type: "application/json", disposition: 'attachment', filename: 'wizards.json' @@ -27,45 +24,42 @@ class CustomWizard::TransferController < ::ApplicationController def import file = File.read(params['file'].tempfile) - + if file.nil? render json: { error: I18n.t('wizard.import.error.no_file') } return end - fileSize = file.size - maxFileSize = 512 * 1024 - - if maxFileSize < fileSize + file_size = file.size + max_file_size = 512 * 1024 + + if max_file_size < file_size render json: { error: I18n.t('wizard.import.error.file_large') } return end - + begin - jsonObject = JSON.parse file + template_json = JSON.parse file rescue JSON::ParserError render json: { error: I18n.t('wizard.import.error.invalid_json') } return end - countValid = 0 success_ids = [] failed_ids = [] - jsonObject.each do |o| - if !CustomWizard::Wizard.new(o) - failed_ids.push o['id'] - next + template_json.each do |t_json| + template = CustomWizard::Template.new(t_json) + template.save(skip_jobs: true) + + if template.errors.any? + failed_ids.push t_json['id'] + else + success_ids.push t_json['id'] end - - countValid += 1 - pluginStoreEntry = PluginStore.new 'custom_wizard' - saved = pluginStoreEntry.set(o['id'], o) unless pluginStoreEntry.get(o['id']) - success_ids.push o['id'] if !!saved - failed_ids.push o['id'] if !saved end - if countValid == 0 + if success_ids.length == 0 render json: { error: I18n.t('wizard.import.error.no_valid_wizards') } else render json: { success: success_ids, failed: failed_ids } diff --git a/controllers/custom_wizard/admin/wizard.rb b/controllers/custom_wizard/admin/wizard.rb index 48beea67..0594de5e 100644 --- a/controllers/custom_wizard/admin/wizard.rb +++ b/controllers/custom_wizard/admin/wizard.rb @@ -4,7 +4,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController def index render_json_dump( wizard_list: ActiveModel::ArraySerializer.new( - CustomWizard::Template.list, + CustomWizard::Wizard.list(current_user), each_serializer: CustomWizard::BasicWizardSerializer ), field_types: CustomWizard::Field.types @@ -30,20 +30,13 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController end def save - opts = {} - opts[:create] = params[:create] if params[:create] + template = CustomWizard::Template.new(save_wizard_params.to_h) + wizard_id = template.save(create: params[:create]) - validator = CustomWizard::Validator.new(save_wizard_params.to_h, opts) - validation = validator.perform - - if validation[:error] - render json: { error: validation[:error] } + if template.errors.any? + render json: failed_json.merge(errors: result.errors.full_messages) else - if wizard_id = CustomWizard::Template.save(validation[:wizard]) - render json: success_json.merge(wizard_id: wizard_id) - else - render json: failed_json - end + render json: success_json.merge(wizard_id: wizard_id) end end diff --git a/controllers/custom_wizard/steps.rb b/controllers/custom_wizard/steps.rb index 4d338ab8..778707a6 100644 --- a/controllers/custom_wizard/steps.rb +++ b/controllers/custom_wizard/steps.rb @@ -15,22 +15,24 @@ class CustomWizard::StepsController < ::ApplicationController field_ids = step.fields.map(&:id) + update = update_params.to_h + if params[:fields] - permitted_fields = params[:fields].select { |k, v| field_ids.include? k } - update_params[:fields] = permitted_fields - update_params.permit! + update[:fields] = {} + + params[:fields].each do |k, v| + update[:fields][k] = v if field_ids.include? k + end end - updater = wizard.create_updater( - update_params[:step_id], - update_params[:fields] - ) + updater = wizard.create_updater(update[:step_id], update[:fields]) updater.update - + if updater.success? result = success_json result.merge!(updater.result) if updater.result result[:refresh_required] = true if updater.refresh_required? + render json: result else errors = [] diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index bf2b3ed4..aa786510 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -1,7 +1,8 @@ class CustomWizard::WizardController < ::ApplicationController prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'views')) layout 'wizard' - + + before_action :ensure_plugin_enabled helper_method :wizard_page_title helper_method :theme_ids @@ -38,7 +39,7 @@ class CustomWizard::WizardController < ::ApplicationController def skip params.require(:wizard_id) - + if wizard.required && !wizard.completed? && wizard.permitted? return render json: { error: I18n.t('wizard.no_skip') } end @@ -47,16 +48,11 @@ class CustomWizard::WizardController < ::ApplicationController user = current_user if user - submission = wizard.submissions.last - + submission = wizard.current_submission if submission && submission['redirect_to'] result.merge!(redirect_to: submission['redirect_to']) end - if submission && !wizard.save_submissions - PluginStore.remove("#{wizard.id}_submissions", user.id) - end - if user.custom_fields['redirect_to_wizard'] === wizard.id user.custom_fields.delete('redirect_to_wizard') user.save_custom_fields(true) @@ -65,4 +61,12 @@ class CustomWizard::WizardController < ::ApplicationController render json: result end + + private + + def ensure_plugin_enabled + unless SiteSetting.custom_wizard_enabled + redirect_to path("/") + end + end end diff --git a/jobs/set_after_time_wizard.rb b/jobs/set_after_time_wizard.rb index 80f8f5e5..38d0eebe 100644 --- a/jobs/set_after_time_wizard.rb +++ b/jobs/set_after_time_wizard.rb @@ -3,7 +3,7 @@ module Jobs def execute(args) if SiteSetting.custom_wizard_enabled wizard = CustomWizard::Wizard.create(args[:wizard_id]) - + if wizard && wizard.after_time user_ids = [] diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index a28498e6..18252f85 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -267,7 +267,7 @@ class CustomWizard::Action url += "&body=#{params[:raw]}" if category_id = action_category - if category_id && category = Category.find(category_id) + if category = Category.find_by(id: category_id) url += "&category=#{category.full_slug('/')}" end end @@ -412,6 +412,8 @@ class CustomWizard::Action data: data, user: user ).perform + + return false unless output.present? if output.is_a?(Array) output.first @@ -428,6 +430,8 @@ class CustomWizard::Action data: data, user: user, ).perform + + return false unless output.present? if output.is_a?(Array) output.flatten @@ -483,11 +487,11 @@ class CustomWizard::Action def public_topic_params params = {} - if (category = action_category) + if category = action_category params[:category] = category end - if (tags = action_tags) + if tags = action_tags params[:tags] = tags end diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 4e8ec8b5..66d2f5df 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -49,7 +49,6 @@ class CustomWizard::Builder return @wizard if !@wizard.can_access? build_opts[:reset] = build_opts[:reset] || @wizard.restart_on_revisit - reset_submissions if build_opts[:reset] @steps.each do |step_template| @wizard.append_step(step_template['id']) do |step| @@ -110,9 +109,7 @@ class CustomWizard::Builder data = updater.fields - ## if the wizard has data from the previous steps make that accessible to the actions. - if @submissions && @submissions.last && !@submissions.last.key?("submitted_at") - submission = @submissions.last + if submission = @wizard.current_submission data = submission.merge(data) end @@ -133,29 +130,31 @@ class CustomWizard::Builder end end end - - if route_to = data['route_to'] - data.delete('route_to') - end - - if @wizard.save_submissions && updater.errors.empty? - save_submissions(data, final_step) - elsif final_step - PluginStore.remove("#{@wizard.id}_submissions", @wizard.user.id) - end - - if final_step && @wizard.id === @wizard.user.custom_fields['redirect_to_wizard'] - @wizard.user.custom_fields.delete('redirect_to_wizard'); - @wizard.user.save_custom_fields(true) - end - + if updater.errors.empty? + if route_to = data['route_to'] + data.delete('route_to') + end + + if @wizard.save_submissions + save_submissions(data, final_step) + end + if final_step + if @wizard.id == @wizard.user.custom_fields['redirect_to_wizard'] + @wizard.user.custom_fields.delete('redirect_to_wizard'); + @wizard.user.save_custom_fields(true) + end + redirect_url = route_to || data['redirect_on_complete'] || data["redirect_to"] updater.result[:redirect_on_complete] = redirect_url elsif route_to updater.result[:redirect_on_next] = route_to end + + true + else + false end end end @@ -177,10 +176,8 @@ class CustomWizard::Builder params[:key] = field_template['key'] if field_template['key'] params[:min_length] = field_template['min_length'] if field_template['min_length'] params[:value] = prefill_field(field_template, step_template) - - ## Load previously submitted values - if !build_opts[:reset] && @submissions.last && !@submissions.last.key?("submitted_at") - submission = @submissions.last + + if !build_opts[:reset] && (submission = @wizard.current_submission) params[:value] = submission[field_template['id']] if submission[field_template['id']] end @@ -359,15 +356,9 @@ class CustomWizard::Builder if data.present? @submissions.pop(1) if @wizard.unfinished? @submissions.push(data) - PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, @submissions) + @wizard.set_submissions(@submissions) end end - - def reset_submissions - @submissions.pop(1) if @wizard.unfinished? - PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, @submissions) - @wizard.reset - end def save_permitted_params(permitted_params, params) permitted_data = {} diff --git a/lib/custom_wizard/field.rb b/lib/custom_wizard/field.rb index 534e4824..cbb08e9c 100644 --- a/lib/custom_wizard/field.rb +++ b/lib/custom_wizard/field.rb @@ -57,10 +57,10 @@ class CustomWizard::Field @require_assets ||= {} end - def self.add_assets(type, plugin = nil, asset_paths = [], opts={}) + def self.register(type, plugin = nil, asset_paths = [], opts={}) if type - types[type] ||= {} - types[type] = opts[:type_opts] if opts[:type_opts].present? + types[type.to_sym] ||= {} + types[type.to_sym] = opts[:type_opts] if opts[:type_opts].present? end if plugin && asset_paths diff --git a/lib/custom_wizard/log.rb b/lib/custom_wizard/log.rb index 56430949..7dca2093 100644 --- a/lib/custom_wizard/log.rb +++ b/lib/custom_wizard/log.rb @@ -29,9 +29,12 @@ class CustomWizard::Log ").order("value::json->>'date' DESC") end - def self.list(page = 0) - self.list_query.limit(PAGE_LIMIT) - .offset(page * PAGE_LIMIT) + def self.list(page = 0, limit = nil) + limit = limit.to_i > 0 ? limit.to_i : PAGE_LIMIT + page = page.to_i + + self.list_query.limit(limit) + .offset(page * limit) .map { |r| self.new(JSON.parse(r.value)) } end end \ No newline at end of file diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index 97791703..d28daed9 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -144,7 +144,7 @@ class CustomWizard::Mapper end if operator == '=~' - result == 0 ? true : false + result.nil? ? false : true else result end diff --git a/lib/custom_wizard/template.rb b/lib/custom_wizard/template.rb index 39e4b3e5..fc79e91d 100644 --- a/lib/custom_wizard/template.rb +++ b/lib/custom_wizard/template.rb @@ -1,53 +1,48 @@ class CustomWizard::Template - def self.add(obj) - wizard = obj.is_a?(String) ? ::JSON.parse(json) : obj - PluginStore.set('custom_wizard', wizard["id"], wizard) + include HasErrors + + attr_reader :data, + :opts + + def initialize(data) + @data = data end - + + def save(opts={}) + @opts = opts + + normalize_data + validate_data + prepare_data + + return false if errors.any? + + ActiveRecord::Base.transaction do + schedule_save_jobs unless opts[:skip_jobs] + PluginStore.set('custom_wizard', @data[:id], @data) + end + + @data[:id] + end + + def self.save(data, opts={}) + new(data).save(opts) + end + def self.find(wizard_id) PluginStore.get('custom_wizard', wizard_id) end - def self.save(data) - data = data.with_indifferent_access - existing = self.find(data[:id]) - - data[:steps].each do |step| - if step[:raw_description] - step[:description] = PrettyText.cook(step[:raw_description]) - end - end - - data = data.slice!(:create) - - ActiveRecord::Base.transaction do - PluginStore.set('custom_wizard', data[:id], data) - - if data[:after_time] - Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: data[:id]) - enqueue_at = Time.parse(data[:after_time_scheduled]).utc - Jobs.enqueue_at(enqueue_at, :set_after_time_wizard, wizard_id: data[:id]) - end - - if existing && existing[:after_time] && !data[:after_time] - Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: data[:id]) - Jobs.enqueue(:clear_after_time_wizard, wizard_id: data[:id]) - end - end - - data[:id] - end - def self.remove(wizard_id) - wizard = self.create(wizard_id) + wizard = CustomWizard::Wizard.create(wizard_id) - ActiveRecord::Base.transaction do + ActiveRecord::Base.transaction do + PluginStore.remove('custom_wizard', wizard.id) + if wizard.after_time Jobs.cancel_scheduled_job(:set_after_time_wizard) - Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard.id) + Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id) end - - PluginStore.remove('custom_wizard', wizard.id) end end @@ -55,8 +50,11 @@ class CustomWizard::Template PluginStoreRow.exists?(plugin_name: 'custom_wizard', key: wizard_id) end - def self.list(user=nil) - PluginStoreRow.where(plugin_name: 'custom_wizard').order(:id) + def self.list(setting: nil, order: :id) + query = "plugin_name = 'custom_wizard'" + query += "AND (value::json ->> '#{setting}')::boolean IS TRUE" if setting + + PluginStoreRow.where(query).order(order) .reduce([]) do |result, record| attrs = JSON.parse(record.value) @@ -72,10 +70,46 @@ class CustomWizard::Template end end - def self.setting_enabled(attr) - PluginStoreRow.where(" - plugin_name = 'custom_wizard' AND - (value::json ->> '#{attr}')::boolean IS TRUE - ") + private + + def normalize_data + @data = ::JSON.parse(@data) if @data.is_a?(String) + @data = @data.with_indifferent_access + end + + def prepare_data + @data[:steps].each do |step| + if step[:raw_description] + step[:description] = PrettyText.cook(step[:raw_description]) + end + end + end + + def validate_data + validator = CustomWizard::Validator.new(@data, @opts) + validator.perform + add_errors_from(validator) + end + + def schedule_save_jobs + if @data[:after_time] && @data[:after_time_scheduled] + wizard_id = @data[:id] + old_data = CustomWizard::Template.find(data[:id]) + + begin + enqueue_wizard_at = Time.parse(@data[:after_time_scheduled]).utc + rescue ArgumentError + errors.add :validation, I18n.t("wizard.validation.after_time") + raise ActiveRecord::Rollback.new + end + + if enqueue_wizard_at + Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard_id) + Jobs.enqueue_at(enqueue_wizard_at, :set_after_time_wizard, wizard_id: wizard_id) + elsif old_data && old_data[:after_time] + Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard_id) + Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id) + end + end end end \ No newline at end of file diff --git a/lib/custom_wizard/validator.rb b/lib/custom_wizard/validator.rb index b75b295d..e98023d0 100644 --- a/lib/custom_wizard/validator.rb +++ b/lib/custom_wizard/validator.rb @@ -1,52 +1,38 @@ class CustomWizard::Validator + include HasErrors - def initialize(params, opts={}) - @params = params + def initialize(data, opts={}) + @data = data @opts = opts - @error = nil end def perform - params = @params + data = @data - check_id(params, :wizard) - check_required(params, :wizard) - check_depdendent(params, :wizard) - - after_time = nil - - if !@error && @params[:after_time] - validate_after_time - end + check_id(data, :wizard) + check_required(data, :wizard) + validate_after_time - if !@error - params[:steps].each do |step| - check_required(step, :step) - check_depdendent(step, :step) - break if @error.present? - - if params[:fields].present? - params[:fields].each do |field| - check_required(field, :field) - check_depdendent(field, :field) - break if @error.present? - end - end - end + data[:steps].each do |step| + check_required(step, :step) - if params[:actions].present? - params[:actions].each do |action| - check_required(action, :action) - check_depdendent(action, :action) - break if @error.present? + if data[:fields].present? + data[:fields].each do |field| + check_required(field, :field) end end end + + if data[:actions].present? + data[:actions].each do |action| + check_required(action, :action) + end + end - if @error - { error: @error } + if errors.any? + false else - { wizard: params } + true end end @@ -59,54 +45,28 @@ class CustomWizard::Validator } end - def self.dependent - { - wizard: { - after_time: 'after_time_scheduled' - }, - step: {}, - field: {}, - action: {} - } - end - private def check_required(object, type) CustomWizard::Validator.required[type].each do |property| if object[property].blank? - @error = { - type: 'required', - params: { type: type, property: property } - } + errors.add :validation, I18n.t("wizard.validation.required", property: property) end end end - - def check_depdendent(object, type) - CustomWizard::Validator.dependent[type].each do |property, dependent| - if object[property] && object[dependent].blank? - @error = { - type: 'dependent', - params: { property: property, dependent: dependent } - } - end - end - end - + def check_id(object, type) if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(object[:id]) - @error = { - type: 'conflict', - params: { type: type, property: 'id', value: object[:id] } - } + errors.add :validation, I18n.t("wizard.validation.conflict", id: object[:id]) end end - def validate_after_time - wizard = CustomWizard::Wizard.create(@params[:id]) if !@opts[:create] + def validate_after_time + return unless @data[:after_time] + + wizard = CustomWizard::Wizard.create(@data[:id]) if !@opts[:create] current_time = wizard.present? ? wizard.after_time_scheduled : nil - new_time = @params[:after_time_scheduled] + new_time = @data[:after_time_scheduled] begin active_time = Time.parse(new_time.present? ? new_time : current_time).utc @@ -115,7 +75,7 @@ class CustomWizard::Validator end if invalid_time || active_time.blank? || active_time < Time.now.utc - @error = { type: 'after_time' } + errors.add :validation, I18n.t("wizard.validation.after_time") end end end \ No newline at end of file diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index b4f41089..96ae4e0e 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -26,22 +26,24 @@ class CustomWizard::Wizard :steps, :step_ids, :actions, - :user + :user, + :first_step def initialize(attrs = {}, user=nil) @user = user + attrs = attrs.with_indifferent_access @id = attrs['id'] @name = attrs['name'] @background = attrs['background'] - @save_submissions = attrs['save_submissions'] || false - @multiple_submissions = attrs['multiple_submissions'] || false - @prompt_completion = attrs['prompt_completion'] || false - @restart_on_revisit = attrs['restart_on_revisit'] || false - @after_signup = attrs['after_signup'] - @after_time = attrs['after_time'] + @save_submissions = cast_bool(attrs['save_submissions']) + @multiple_submissions = cast_bool(attrs['multiple_submissions']) + @prompt_completion = cast_bool(attrs['prompt_completion']) + @restart_on_revisit = cast_bool(attrs['restart_on_revisit']) + @after_signup = cast_bool(attrs['after_signup']) + @after_time = cast_bool(attrs['after_time']) @after_time_scheduled = attrs['after_time_scheduled'] - @required = attrs['required'] || false + @required = cast_bool(attrs['required']) @permitted = attrs['permitted'] || nil @needs_categories = false @needs_groups = false @@ -53,10 +55,17 @@ class CustomWizard::Wizard end @first_step = nil - @step_ids = attrs['steps'].map { |s| s['id'] } @steps = [] + if attrs['steps'].present? + @step_ids = attrs['steps'].map { |s| s['id'] } + end + @actions = [] end + + def cast_bool(val) + val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val) + end def create_step(step_name) ::Wizard::Step.new(step_name) @@ -69,9 +78,9 @@ class CustomWizard::Wizard last_step = steps.last steps << step - + if steps.size == 1 - first_step = step + @first_step = step step.index = 0 elsif last_step.present? last_step.next = step @@ -94,14 +103,14 @@ class CustomWizard::Wizard last_index = steps.index { |s| s.id == step_id } steps[last_index + 1] else - first_step + @first_step end end - def create_updater(step_id, fields) + def create_updater(step_id, inputs) step = @steps.find { |s| s.id == step_id } wizard = self - CustomWizard::StepUpdater.new(user, wizard, step, fields) + CustomWizard::StepUpdater.new(user, wizard, step, inputs) end def unfinished? @@ -116,7 +125,7 @@ class CustomWizard::Wizard if most_recent && most_recent.subject == "reset" false elsif most_recent - most_recent.subject != steps.last.id + most_recent.subject != steps.last.id else true end @@ -134,7 +143,7 @@ class CustomWizard::Wizard if after_time history = history.where("updated_at > ?", after_time_scheduled) end - + completed = history.distinct.order(:subject).pluck(:subject) (step_ids - completed).empty? end @@ -166,6 +175,7 @@ class CustomWizard::Wizard end def can_access? + return false unless user return true if user.admin return permitted? && (multiple_submissions || !completed?) end @@ -191,63 +201,76 @@ class CustomWizard::Wizard Array.wrap(PluginStore.get("#{id}_submissions", user.id)) end + def current_submission + if submissions.present? && !submissions.last.key?("submitted_at") + submissions.last + else + nil + end + end + + def set_submissions(submissions) + PluginStore.set("#{id}_submissions", user.id, Array.wrap(submissions)) + end + + def self.submissions(wizard_id, user) + new({ id: wizard_id }, user).submissions + end + + def self.set_submissions(wizard_id, user, submissions) + new({ id: wizard_id }, user).set_submissions(submissions) + end + def self.create(wizard_id, user = nil) if template = CustomWizard::Template.find(wizard_id) - self.new(template.to_h, user) + new(template.to_h, user) else false end end - def self.list(user=nil) - CustomWizard::Template.list.map { |template| self.new(template.to_h, user) } + def self.list(user, template_opts: {}) + return [] unless user + + CustomWizard::Template.list(template_opts).reduce([]) do |result, template| + wizard = new(template, user) + result.push(wizard) if wizard.can_access? + result + end end def self.after_signup(user) - if (records = CustomWizard::Template.setting_enabled('after_signup')).any? - result = false - - records - .sort_by { |record| record.value['permitted'].present? ? 0 : 1 } - .each do |record| - wizard = self.new(JSON.parse(record.value), user) - - if wizard.permitted? - result = wizard - break - end - end - - result - else - false - end + wizards = list( + user, + template_opts: { + setting: 'after_signup', + order: "(value::json ->> 'permitted') IS NOT NULL DESC" + } + ) + wizards.any? ? wizards.first : false end def self.prompt_completion(user) - if (records = CustomWizard::Template.setting_enabled('prompt_completion')).any? - records.reduce([]) do |result, record| - wizard = self.new(::JSON.parse(record.value), user) - - if wizard.permitted? && !wizard.completed? - result.push(id: wizard.id, name: wizard.name) - end - - result + wizards = list( + user, + template_opts: { + setting: 'prompt_completion', + order: "(value::json ->> 'permitted') IS NOT NULL DESC" + } + ) + + if wizards.any? + wizards.map do |w| + { + id: w.id, + name: w.name + } end else false end end - def self.restart_on_revisit - if (records = CustomWizard::Template.setting_enabled('restart_on_revisit')).any? - records.first.key - else - false - end - end - def self.set_submission_redirect(user, wizard_id, url) PluginStore.set("#{wizard_id.underscore}_submissions", user.id, [{ redirect_to: url }]) end diff --git a/plugin.rb b/plugin.rb index 3a8d48e0..544db890 100644 --- a/plugin.rb +++ b/plugin.rb @@ -42,9 +42,9 @@ after_initialize do ../controllers/custom_wizard/admin/submissions.rb ../controllers/custom_wizard/admin/api.rb ../controllers/custom_wizard/admin/logs.rb + ../controllers/custom_wizard/admin/transfer.rb ../controllers/custom_wizard/wizard.rb ../controllers/custom_wizard/steps.rb - ../controllers/custom_wizard/transfer.rb ../jobs/clear_after_time_wizard.rb ../jobs/refresh_api_access_token.rb ../jobs/set_after_time_wizard.rb @@ -128,8 +128,7 @@ after_initialize do if request.referer !~ /\/w\// && request.referer !~ /\/invites\// CustomWizard::Wizard.set_submission_redirect(current_user, wizard_id, request.referer) end - - if CustomWizard::Wizard.exists?(wizard_id) + if CustomWizard::Template.exists?(wizard_id) redirect_to "/w/#{wizard_id.dasherize}" end end diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 12308ef2..890dde12 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -7,31 +7,62 @@ describe CustomWizard::Action do before do Group.refresh_automatic_group!(:trust_level_2) - CustomWizard::Template.add( + CustomWizard::Template.save( JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - ) + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) @template = CustomWizard::Template.find('super_mega_fun_wizard') end - it 'creates a topic' do - wizard = CustomWizard::Builder.new(@template[:id], user).build - - updater = wizard.create_updater( - wizard.steps[0].id, - step_1_field_1: "Topic Title", - step_1_field_2: "topic body" - ).update - updater2 = wizard.create_updater(wizard.steps[1].id, {}).update + context "creating a topic" do + + end + + context 'creating a topic' do + it "works" do + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater( + wizard.steps.first.id, + step_1_field_1: "Topic Title", + step_1_field_2: "topic body" + ).update + wizard.create_updater(wizard.steps.second.id, {}).update + wizard.create_updater(wizard.steps.last.id, + step_3_field_3: category.id + ).update + + topic = Topic.where( + title: "Topic Title", + category_id: category.id + ) + expect(topic.exists?).to eq(true) + expect(Post.where( + topic_id: topic.pluck(:id), + raw: "topic body" + ).exists?).to eq(true) + end - topic = Topic.where(title: "Topic Title") - - expect(topic.exists?).to eq(true) - expect(Post.where( - topic_id: topic.pluck(:id), - raw: "topic body" - ).exists?).to eq(true) + it "fails silently without basic topic inputs" do + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater( + wizard.steps.first.id, + step_1_field_2: "topic body" + ).update + wizard.create_updater(wizard.steps.second.id, {}).update + updater = wizard.create_updater(wizard.steps.last.id, {}) + updater.update + + expect(updater.success?).to eq(true) + expect(UserHistory.where( + acting_user_id: user.id, + context: "super_mega_fun_wizard", + subject: "step_3" + ).exists?).to eq(true) + expect(Post.where( + raw: "topic body" + ).exists?).to eq(false) + end end it 'sends a message' do @@ -79,8 +110,7 @@ describe CustomWizard::Action do updater = wizard.create_updater(wizard.steps[1].id, {}) updater.update - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) - category = Category.find_by(id: submissions.first['action_8']) + category = Category.find_by(id: wizard.current_submission['action_8']) expect(updater.result[:redirect_on_next]).to eq( "/new-topic?title=Title%20of%20the%20composer%20topic&body=I%20am%20interpolating%20some%20user%20fields%20Angus%20angus%20angus@email.com&category=#{category.slug}/#{category.id}&tags=tag1" @@ -91,24 +121,21 @@ describe CustomWizard::Action do wizard = CustomWizard::Builder.new(@template[:id], user).build wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update wizard.create_updater(wizard.steps[1].id, {}).update - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) - expect(Category.where(id: submissions.first['action_8']).exists?).to eq(true) + expect(Category.where(id: wizard.current_submission['action_8']).exists?).to eq(true) end it 'creates a group' do wizard = CustomWizard::Builder.new(@template[:id], user).build step_id = wizard.steps[0].id updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) - expect(Group.where(name: submissions.first['action_9']).exists?).to eq(true) + expect(Group.where(name: wizard.current_submission['action_9']).exists?).to eq(true) end it 'adds a user to a group' do wizard = CustomWizard::Builder.new(@template[:id], user).build step_id = wizard.steps[0].id updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) - group = Group.find_by(name: submissions.first['action_9']) + group = Group.find_by(name: wizard.current_submission['action_9']) expect(group.users.first.username).to eq('angus') end @@ -116,9 +143,8 @@ describe CustomWizard::Action do wizard = CustomWizard::Builder.new(@template[:id], user).build wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update wizard.create_updater(wizard.steps[1].id, {}).update - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) expect(CategoryUser.where( - category_id: submissions.first['action_8'], + category_id: wizard.current_submission['action_8'], user_id: user.id ).first.notification_level).to eq(2) expect(CategoryUser.where( diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 341cf045..699f5ce9 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -18,7 +18,7 @@ describe CustomWizard::Builder do let(:required_data_json) { JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/required_data.json" + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json" ).read) } @@ -36,11 +36,11 @@ describe CustomWizard::Builder do before do Group.refresh_automatic_group!(:trust_level_3) - CustomWizard::Template.add( + CustomWizard::Template.save( JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - ) + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) @template = CustomWizard::Template.find('super_mega_fun_wizard') end @@ -175,9 +175,8 @@ describe CustomWizard::Builder do context "user has partially completed" do before do - PluginStore.set("super_mega_fun_wizard_submissions", user.id, - step_1_field_1: 'I am a user submission' - ) + wizard = CustomWizard::Wizard.new(@template, user) + wizard.set_submissions(step_1_field_1: 'I am a user submission') end it 'returns saved submissions' do @@ -241,7 +240,7 @@ describe CustomWizard::Builder do end it 'is permitted if required data is present' do - PluginStore.set('super_mega_fun_wizard_submissions', user.id, + CustomWizard::Wizard.set_submissions('super_mega_fun_wizard', user, required_data: "required_value" ) expect( @@ -259,13 +258,10 @@ describe CustomWizard::Builder do end it 'saves permitted params' do - CustomWizard::Builder.new(@template[:id], user).build({}, + wizard = CustomWizard::Builder.new(@template[:id], user).build({}, param: 'param_value' ) - expect( - PluginStore.get("super_mega_fun_wizard_submissions", user.id) - .first['saved_param'] - ).to eq('param_value') + expect(wizard.current_submission['saved_param']).to eq('param_value') end end end @@ -302,7 +298,7 @@ describe CustomWizard::Builder do it 'saves submissions' do perform_update('step_1', step_1_field_1: 'Text input') expect( - PluginStore.get("super_mega_fun_wizard_submissions", user.id) + CustomWizard::Wizard.submissions(@template[:id], user) .first['step_1_field_1'] ).to eq('Text input') end @@ -316,7 +312,7 @@ describe CustomWizard::Builder do it "does not save submissions" do perform_update('step_1', step_1_field_1: 'Text input') expect( - PluginStore.get("super_mega_fun_wizard_submissions", user.id) + CustomWizard::Wizard.submissions(@template[:id], user).first ).to eq(nil) end end @@ -332,7 +328,7 @@ describe CustomWizard::Builder do it 'standardises boolean entries' do perform_update('step_2', step_2_field_5: 'false') expect( - PluginStore.get("super_mega_fun_wizard_submissions", user.id) + CustomWizard::Wizard.submissions(@template[:id], user) .first['step_2_field_5'] ).to eq(false) end diff --git a/spec/components/custom_wizard/field_spec.rb b/spec/components/custom_wizard/field_spec.rb new file mode 100644 index 00000000..d6d6e61a --- /dev/null +++ b/spec/components/custom_wizard/field_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +describe CustomWizard::Field do + before do + CustomWizard::Field.register( + 'location', + 'discourse-locations', + ['components', 'helpers', 'lib', 'stylesheets', 'templates'], + type_opts: { + prefill: { "coordinates": [35.3082, 149.1244] } + } + ) + end + + it "registers custom field types" do + expect(CustomWizard::Field.types[:location].present?).to eq(true) + end + + it "allows custom field types to set default attributes" do + expect( + CustomWizard::Field.types[:location][:prefill] + ).to eq({ "coordinates": [35.3082, 149.1244] }) + end + + it "registers custom field assets" do + expect( + CustomWizard::Field.require_assets['discourse-locations'] + ).to eq(['components', 'helpers', 'lib', 'stylesheets', 'templates']) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/log_spec.rb b/spec/components/custom_wizard/log_spec.rb new file mode 100644 index 00000000..9c8b570d --- /dev/null +++ b/spec/components/custom_wizard/log_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +describe CustomWizard::Log do + before do + CustomWizard::Log.create("First log message") + CustomWizard::Log.create("Second log message") + CustomWizard::Log.create("Third log message") + end + + it "creates logs" do + expect( + CustomWizard::Log.list.length + ).to eq(3) + end + + it "lists logs by time created" do + expect( + CustomWizard::Log.list.first.message + ).to eq("Third log message") + end + + it "paginates logs" do + expect( + CustomWizard::Log.list(0, 2).length + ).to eq(2) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb new file mode 100644 index 00000000..4ddbee57 --- /dev/null +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -0,0 +1,250 @@ +require 'rails_helper' + +describe CustomWizard::Mapper do + fab!(:user1) { + Fabricate(:user, + name: "Angus", + username: "angus", + email: "angus@email.com", + trust_level: TrustLevel[3] + ) + } + fab!(:user2) { + Fabricate(:user, + name: "Patrick", + username: "patrick", + email: "patrick@email2.com", + trust_level: TrustLevel[1] + ) + } + fab!(:user_field) { + field = Fabricate(:user_field, + id: 3, + name: 'dropdown_field', + description: 'field desc', + field_type: 'dropdown', + user_field_options_attributes: [ + { value: "a" }, + { value: "b" }, + { value: "c" } + ] + ) + } + let(:inputs) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/mapper/inputs.json" + ).read) + } + let(:data) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/mapper/data.json" + ).read) + } + + it "maps values" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment'], + data: data, + user: user1 + ).perform).to eq([13]) + end + + it "maps associations" do + association = CustomWizard::Mapper.new( + inputs: inputs['association'], + data: data, + user: user1 + ).perform + expect(association.length).to eq(3) + expect(association.first[:value]).to eq("Choice 1") + end + + context "conditional mapping" do + it "maps when the condition is met" do + expect(CustomWizard::Mapper.new( + inputs: inputs['conditional'], + data: data, + user: user1 + ).perform).to eq("true") + end + + it "does not map when the condition is not met" do + expect(CustomWizard::Mapper.new( + inputs: inputs['conditional'], + data: data, + user: user2 + ).perform).to eq(nil) + end + + it "maps when multiple conditions are met" do + expect(CustomWizard::Mapper.new( + inputs: inputs['conditional_multiple_pairs'], + data: data, + user: user1 + ).perform).to eq("true") + end + + it "does not map when one of multiple conditions are not met" do + user1.email = "angus@other-email.com" + expect(CustomWizard::Mapper.new( + inputs: inputs['conditional_multiple_pairs'], + data: data, + user: user1 + ).perform).to eq(nil) + end + end + + it "validates valid data" do + expect(CustomWizard::Mapper.new( + inputs: inputs['validation'], + data: data, + user: user1 + ).perform).to eq(true) + end + + it "does not validate invalid data" do + data["input_2"] = "value 3" + expect(CustomWizard::Mapper.new( + inputs: inputs['validation'], + data: data, + user: user1 + ).perform).to eq(false) + end + + it "maps text fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment_text'], + data: data, + user: user1 + ).perform).to eq("Value") + end + + it "maps user fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment_user_field'], + data: data, + user: user1 + ).perform).to eq("Angus") + end + + it "maps user field options" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment_user_field_options'], + data: data, + user: user1 + ).perform).to eq(["a", "b", "c"]) + end + + it "maps wizard fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment_wizard_field'], + data: data, + user: user1 + ).perform).to eq("value 1") + end + + it "maps wizard actions" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment_wizard_action'], + data: data, + user: user1 + ).perform).to eq("value 2") + end + + it "interpolates user fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['interpolate_user_field'], + data: data, + user: user1 + ).perform).to eq("Name: Angus") + end + + it "interpolates wizard fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['interpolate_wizard_field'], + data: data, + user: user1 + ).perform).to eq("Input 1: value 1") + end + + it "interpolates date" do + expect(CustomWizard::Mapper.new( + inputs: inputs['interpolate_timestamp'], + data: data, + user: user1 + ).perform).to eq("Time: #{Time.now.strftime("%B %-d, %Y")}") + end + + it "handles greater than pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['greater_than_pair'], + data: data, + user: user1 + ).perform).to eq(true) + expect(CustomWizard::Mapper.new( + inputs: inputs['greater_than_pair'], + data: data, + user: user2 + ).perform).to eq(false) + end + + it "handles less than pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['less_than_pair'], + data: data, + user: user1 + ).perform).to eq(false) + expect(CustomWizard::Mapper.new( + inputs: inputs['less_than_pair'], + data: data, + user: user2 + ).perform).to eq(true) + end + + it "handles greater than or equal pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['greater_than_or_equal_pair'], + data: data, + user: user1 + ).perform).to eq(true) + expect(CustomWizard::Mapper.new( + inputs: inputs['greater_than_or_equal_pair'], + data: data, + user: user2 + ).perform).to eq(true) + end + + it "handles less than or equal pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['less_than_or_equal_pair'], + data: data, + user: user1 + ).perform).to eq(true) + expect(CustomWizard::Mapper.new( + inputs: inputs['less_than_or_equal_pair'], + data: data, + user: user2 + ).perform).to eq(true) + end + + it "handles regex pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['regex_pair'], + data: data, + user: user1 + ).perform).to eq(true) + expect(CustomWizard::Mapper.new( + inputs: inputs['regex_pair'], + data: data, + user: user2 + ).perform).to eq(false) + end + + it "handles shorthand pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['shorthand_pair'], + data: data, + user: user1 + ).perform).to eq(false) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/template_spec.rb b/spec/components/custom_wizard/template_spec.rb new file mode 100644 index 00000000..594009a8 --- /dev/null +++ b/spec/components/custom_wizard/template_spec.rb @@ -0,0 +1,111 @@ +require 'rails_helper' + +describe CustomWizard::Template do + fab!(:user) { Fabricate(:user) } + + let(:template_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + let(:permitted_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" + ).read) + } + let(:after_time) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/after_time.json" + ).read).with_indifferent_access + } + + before do + CustomWizard::Template.save(template_json, skip_jobs: true) + end + + it "saves wizard templates" do + expect( + PluginStoreRow.exists?( + plugin_name: 'custom_wizard', + key: 'super_mega_fun_wizard' + ) + ).to eq(true) + end + + it "finds wizard templates" do + expect( + CustomWizard::Template.find('super_mega_fun_wizard')['id'] + ).to eq('super_mega_fun_wizard') + end + + it "removes wizard templates" do + CustomWizard::Template.remove('super_mega_fun_wizard') + expect( + CustomWizard::Template.find('super_mega_fun_wizard') + ).to eq(nil) + end + + it "checks for wizard template existence" do + expect( + CustomWizard::Template.exists?('super_mega_fun_wizard') + ).to eq(true) + end + + context "wizard template list" do + before do + template_json_2 = template_json.dup + template_json_2["id"] = 'super_mega_fun_wizard_2' + template_json_2["permitted"] = permitted_json['permitted'] + CustomWizard::Template.save(template_json_2, skip_jobs: true) + + template_json_3 = template_json.dup + template_json_3["id"] = 'super_mega_fun_wizard_3' + template_json_3["after_signup"] = true + CustomWizard::Template.save(template_json_3, skip_jobs: true) + end + + it "works" do + expect( + CustomWizard::Template.list.length + ).to eq(3) + end + + it "can be filtered by wizard settings" do + expect( + CustomWizard::Template.list(setting: "after_signup").length + ).to eq(1) + end + + it "can be ordered" do + expect( + CustomWizard::Template.list( + order: "(value::json ->> 'permitted') IS NOT NULL DESC" + ).first['id'] + ).to eq('super_mega_fun_wizard_2') + end + end + + context "after time setting" do + before do + freeze_time Time.now + @after_time_template = template_json.dup + @after_time_template["after_time"] = after_time['after_time'] + @after_time_template["after_time_scheduled"] = after_time['after_time_scheduled'] + end + + it 'if enabled queues jobs after wizard is saved' do + expect_enqueued_with(job: :set_after_time_wizard, at: Time.parse(after_time['after_time_scheduled']).utc) do + CustomWizard::Template.save(@after_time_template) + end + end + + it 'if disabled clears jobs after wizard is saved' do + CustomWizard::Template.save(@after_time_template) + @after_time_template['after_time'] = false + + expect_not_enqueued_with(job: :set_after_time_wizard) do + CustomWizard::Template.save(@after_time_template) + end + end + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/validator_spec.rb b/spec/components/custom_wizard/validator_spec.rb new file mode 100644 index 00000000..5833127e --- /dev/null +++ b/spec/components/custom_wizard/validator_spec.rb @@ -0,0 +1,53 @@ +require 'rails_helper' + +describe CustomWizard::Validator do + fab!(:user) { Fabricate(:user) } + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read).with_indifferent_access + } + + let(:after_time) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/after_time.json" + ).read).with_indifferent_access + } + + it "validates valid templates" do + expect( + CustomWizard::Validator.new(template).perform + ).to eq(true) + end + + it "invalidates templates without required attributes" do + template.delete(:id) + expect( + CustomWizard::Validator.new(template).perform + ).to eq(false) + end + + it "invalidates templates with duplicate ids if creating a new template" do + CustomWizard::Template.save(template) + expect( + CustomWizard::Validator.new(template, create: true).perform + ).to eq(false) + end + + it "validates after time settings" do + template[:after_time] = after_time[:after_time] + template[:after_time_scheduled] = after_time[:after_time_scheduled] + expect( + CustomWizard::Validator.new(template).perform + ).to eq(true) + end + + it "invalidates invalid after time settings" do + template[:after_time] = after_time[:after_time] + template[:after_time_scheduled] = "not a time" + expect( + CustomWizard::Validator.new(template).perform + ).to eq(false) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb new file mode 100644 index 00000000..7724cc64 --- /dev/null +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -0,0 +1,230 @@ +require 'rails_helper' + +describe CustomWizard::Wizard do + fab!(:user) { Fabricate(:user) } + fab!(:trusted_user) { Fabricate(:user, trust_level: TrustLevel[3])} + fab!(:admin_user) { Fabricate(:user, admin: true)} + + let(:template_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + + let(:after_time) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/after_time.json" + ).read) + } + + let(:permitted_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" + ).read) + } + + before do + Group.refresh_automatic_group!(:trust_level_3) + + @permitted_template = template_json.dup + @permitted_template["permitted"] = permitted_json["permitted"] + + @wizard = CustomWizard::Wizard.new(template_json, user) + template_json['steps'].each do |step_template| + @wizard.append_step(step_template['id']) + end + end + + def progress_step(step_id, acting_user = user) + UserHistory.create( + action: UserHistory.actions[:custom_wizard_step], + acting_user_id: acting_user.id, + context: @wizard.id, + subject: step_id + ) + end + + it "appends steps from a template" do + expect(@wizard.steps.length).to eq(3) + end + + it "determines the user's current step" do + expect(@wizard.start.id).to eq('step_1') + progress_step('step_1') + expect(@wizard.start.id).to eq('step_2') + end + + it "creates a step updater" do + expect( + @wizard.create_updater('step_1', step_1_field_1: "Text input") + .class + ).to eq(CustomWizard::StepUpdater) + end + + it "determines whether a wizard is unfinished" do + expect(@wizard.unfinished?).to eq(true) + progress_step("step_1") + expect(@wizard.unfinished?).to eq(true) + progress_step("step_2") + expect(@wizard.unfinished?).to eq(true) + progress_step("step_3") + expect(@wizard.unfinished?).to eq(false) + end + + it "determines whether a wizard has been completed by a user" do + expect(@wizard.completed?).to eq(false) + progress_step("step_1") + progress_step("step_2") + progress_step("step_3") + expect(@wizard.completed?).to eq(true) + end + + it "is not completed if steps submitted before after time" do + progress_step("step_1") + progress_step("step_2") + progress_step("step_3") + + template_json['after_time'] = after_time['after_time'] + template_json['after_time_scheduled'] = after_time['after_time_scheduled'] + + wizard = CustomWizard::Wizard.new(template_json, user) + + expect(wizard.completed?).to eq(false) + end + + it "permits admins" do + expect( + CustomWizard::Wizard.new(@permitted_template, admin_user).permitted? + ).to eq(true) + end + + it "permits permitted users" do + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).permitted? + ).to eq(true) + end + + it "does not permit unpermitted users" do + expect( + CustomWizard::Wizard.new(@permitted_template, user).permitted? + ).to eq(false) + end + + it "does not let an unpermitted user access a wizard" do + expect( + CustomWizard::Wizard.new(@permitted_template, user).can_access? + ).to eq(false) + end + + it "lets a permitted user access an incomplete wizard" do + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? + ).to eq(true) + end + + it "lets a permitted user access a complete wizard with multiple submissions" do + progress_step("step_1", trusted_user) + progress_step("step_2", trusted_user) + progress_step("step_3", trusted_user) + + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? + ).to eq(true) + end + + it "does not let an unpermitted user access a complete wizard without multiple submissions" do + progress_step("step_1", trusted_user) + progress_step("step_2", trusted_user) + progress_step("step_3", trusted_user) + + @permitted_template['multiple_submissions'] = false + + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? + ).to eq(false) + end + + it "lists the site groups" do + expect(@wizard.groups.length).to eq(8) + end + + it "lists the site categories" do + expect(@wizard.categories.length).to eq(1) + end + + context "submissions" do + before do + @wizard.set_submissions(step_1_field_1: 'I am a user submission') + end + + it "sets the user's submission" do + expect( + PluginStore.get("#{template_json['id']}_submissions", user.id) + .first['step_1_field_1'] + ).to eq('I am a user submission') + end + + it "lists the user's submissions" do + expect(@wizard.submissions.length).to eq(1) + end + + it "returns the user's current submission" do + expect(@wizard.current_submission['step_1_field_1']).to eq('I am a user submission') + end + end + + it "provides class methods to set and list submissions" do + CustomWizard::Wizard.set_submissions(template_json['id'], user, + step_1_field_1: 'I am a user submission' + ) + expect( + CustomWizard::Wizard.submissions(template_json['id'], user) + .first['step_1_field_1'] + ).to eq('I am a user submission') + end + + context do + before do + CustomWizard::Template.save(@permitted_template, skip_jobs: true) + + template_json_2 = template_json.dup + template_json_2["id"] = 'super_mega_fun_wizard_2' + template_json_2["prompt_completion"] = true + CustomWizard::Template.save(template_json_2, skip_jobs: true) + + template_json_3 = template_json.dup + template_json_3["id"] = 'super_mega_fun_wizard_3' + template_json_3["after_signup"] = true + CustomWizard::Template.save(template_json_3, skip_jobs: true) + end + + it "lists wizards the user can see" do + expect(CustomWizard::Wizard.list(user).length).to eq(2) + expect(CustomWizard::Wizard.list(trusted_user).length).to eq(3) + end + + it "returns the first after signup wizard" do + expect(CustomWizard::Wizard.after_signup(user).id).to eq('super_mega_fun_wizard_3') + end + + it "lists prompt completion wizards" do + expect(CustomWizard::Wizard.prompt_completion(user).length).to eq(2) + end + end + + it "sets wizard redirects if user is permitted" do + CustomWizard::Template.save(@permitted_template, skip_jobs: true) + CustomWizard::Wizard.set_wizard_redirect('super_mega_fun_wizard', trusted_user) + expect( + trusted_user.custom_fields['redirect_to_wizard'] + ).to eq("super_mega_fun_wizard") + end + + it "does not set a wizard redirect if user is not permitted" do + CustomWizard::Template.save(@permitted_template, skip_jobs: true) + CustomWizard::Wizard.set_wizard_redirect('super_mega_fun_wizard', user) + expect( + trusted_user.custom_fields['redirect_to_wizard'] + ).to eq(nil) + 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 new file mode 100644 index 00000000..bac0f2c6 --- /dev/null +++ b/spec/extensions/extra_locales_controller_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +describe ExtraLocalesControllerCustomWizard, type: :request do + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + end + + before do + @controller = ExtraLocalesController.new + end + + it "returns locales when requested by wizard" do + expect( + ExtraLocalesController.url("wizard") + ).to eq( + "#{Discourse.base_path}/extra-locales/wizard?v=#{ExtraLocalesController.bundle_js_hash("wizard")}" + ) + end +end \ No newline at end of file diff --git a/spec/extensions/invites_controller_spec.rb b/spec/extensions/invites_controller_spec.rb new file mode 100644 index 00000000..05fa58a5 --- /dev/null +++ b/spec/extensions/invites_controller_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +describe InvitesControllerCustomWizard, type: :request do + fab!(:topic) { Fabricate(:topic) } + let(:invite) do + Invite.invite_by_email("angus@email.com", topic.user, topic) + end + let(:template) do + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + end + + before do + @controller = InvitesController.new + end + + it "redirects a user to wizard after invite if after signup is enabled" do + template['after_signup'] = true + CustomWizard::Template.save(template, skip_jobs: true) + put "/invites/show/#{invite.invite_key}.json" + expect(response.parsed_body["redirect_to"]).to eq("/w/super-mega-fun-wizard") + end +end \ No newline at end of file diff --git a/spec/extensions/users_controller_spec.rb b/spec/extensions/users_controller_spec.rb new file mode 100644 index 00000000..5a219dee --- /dev/null +++ b/spec/extensions/users_controller_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +describe CustomWizardUsersController, type: :request do + let(:template) do + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + end + + before do + @controller = UsersController.new + end + + it "redirects a user to wizard after sign up if after signup is enabled" do + template['after_signup'] = true + CustomWizard::Template.save(template, skip_jobs: true) + sign_in(Fabricate(:user)) + get "/u/account-created" + expect(response).to redirect_to("/w/super-mega-fun-wizard") + end +end \ No newline at end of file diff --git a/spec/extensions/wizard_field_spec.rb b/spec/extensions/wizard_field_spec.rb new file mode 100644 index 00000000..d13b3c80 --- /dev/null +++ b/spec/extensions/wizard_field_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +describe CustomWizardFieldExtension do + let(:field_hash) do + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/field.json" + ).read).with_indifferent_access + end + + it "adds custom field attributes" do + field = Wizard::Field.new(field_hash) + expect(field.id).to eq("field_id") + expect(field.label).to eq("

Field Label

") + expect(field.image).to eq("field_image_url.png") + expect(field.description).to eq("Field description") + expect(field.required).to eq(true) + expect(field.key).to eq("field.locale.key") + expect(field.type).to eq("field_type") + expect(field.content).to eq([]) + end +end \ No newline at end of file diff --git a/spec/extensions/wizard_step_spec.rb b/spec/extensions/wizard_step_spec.rb new file mode 100644 index 00000000..7d1fbb68 --- /dev/null +++ b/spec/extensions/wizard_step_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +describe CustomWizardStepExtension do + let(:step_hash) do + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/step.json" + ).read).with_indifferent_access + end + + it "adds custom step attributes" do + step = Wizard::Step.new(step_hash[:id]) + [ + :title, + :description, + :key, + :permitted, + :permitted_message + ].each do |attr| + step.send("#{attr.to_s}=", step_hash[attr]) + expect(step.send(attr)).to eq(step_hash[attr]) + end + end +end \ No newline at end of file diff --git a/spec/fixtures/field/field.json b/spec/fixtures/field/field.json new file mode 100644 index 00000000..c2de266d --- /dev/null +++ b/spec/fixtures/field/field.json @@ -0,0 +1,10 @@ +{ + "id": "field_id", + "label": "Field Label", + "image": "field_image_url.png", + "description": "Field description", + "required": true, + "key": "field.locale.key", + "type": "field_type", + "content": [] +} \ No newline at end of file diff --git a/spec/fixtures/mapper/data.json b/spec/fixtures/mapper/data.json new file mode 100644 index 00000000..eca75a1a --- /dev/null +++ b/spec/fixtures/mapper/data.json @@ -0,0 +1,4 @@ +{ + "input_1": "value 1", + "input_2": "value 2" +} \ No newline at end of file diff --git a/spec/fixtures/mapper/inputs.json b/spec/fixtures/mapper/inputs.json new file mode 100644 index 00000000..8faf2435 --- /dev/null +++ b/spec/fixtures/mapper/inputs.json @@ -0,0 +1,264 @@ +{ + "assignment": [ + { + "type": "assignment", + "output_type": "group", + "output_connector": "set", + "output": [ + 13 + ] + } + ], + "assignment_text": [ + { + "type": "assignment", + "output_type": "text", + "output_connector": "set", + "output": "Value" + } + ], + "assignment_user_field": [ + { + "type": "assignment", + "output_type": "user_field", + "output_connector": "set", + "output": "name" + } + ], + "assignment_user_field_options": [ + { + "type": "assignment", + "output_type": "user_field_options", + "output_connector": "set", + "output": "user_field_3" + } + ], + "assignment_wizard_field": [ + { + "type": "assignment", + "output_type": "wizard_field", + "output_connector": "set", + "output": "input_1" + } + ], + "assignment_wizard_action": [ + { + "type": "assignment", + "output_type": "wizard_action", + "output_connector": "set", + "output": "input_2" + } + ], + "interpolate_user_field": [ + { + "type": "assignment", + "output_type": "text", + "output_connector": "set", + "output": "Name: u{name}" + } + ], + "interpolate_wizard_field": [ + { + "type": "assignment", + "output_type": "text", + "output_connector": "set", + "output": "Input 1: w{input_1}" + } + ], + "interpolate_timestamp": [ + { + "type": "assignment", + "output_type": "text", + "output_connector": "set", + "output": "Time: v{time}" + } + ], + "validation": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "input_1", + "key_type": "wizard_field", + "value": "value 1", + "value_type": "text", + "connector": "equal" + }, + { + "index": 1, + "key": "input_2", + "key_type": "wizard_field", + "value": "value 2", + "value_type": "text", + "connector": "equal" + } + ] + } + ], + "association": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "choice1", + "key_type": "text", + "value": "Choice 1", + "value_type": "text", + "connector": "equal" + }, + { + "index": 1, + "key": "choice2", + "key_type": "text", + "value": "Choice 2", + "value_type": "text", + "connector": "association" + }, + { + "index": 2, + "key": "choice3", + "key_type": "text", + "value": "Choice 3", + "value_type": "text", + "connector": "association" + } + ] + } + ], + "conditional": [ + { + "type": "conditional", + "output": "true", + "output_type": "text", + "output_connector": "then", + "pairs": [ + { + "index": 0, + "key": "name", + "key_type": "user_field", + "value": "Angus", + "value_type": "text", + "connector": "equal" + } + ] + } + ], + "conditional_multiple_pairs": [ + { + "type": "conditional", + "output": "true", + "output_type": "text", + "output_connector": "then", + "pairs": [ + { + "index": 0, + "key": "name", + "key_type": "user_field", + "value": "Angus", + "value_type": "text", + "connector": "equal" + }, + { + "index": 1, + "key": "email", + "key_type": "user_field", + "value": "angus@email.com", + "value_type": "text", + "connector": "equal" + } + ] + } + ], + "greater_than_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "trust_level", + "key_type": "user_field", + "value": "2", + "value_type": "text", + "connector": "greater" + } + ] + } + ], + "less_than_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "trust_level", + "key_type": "user_field", + "value": "2", + "value_type": "text", + "connector": "less" + } + ] + } + ], + "greater_than_or_equal_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "trust_level", + "key_type": "user_field", + "value": "1", + "value_type": "text", + "connector": "greater_or_equal" + } + ] + } + ], + "less_than_or_equal_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "trust_level", + "key_type": "user_field", + "value": "3", + "value_type": "text", + "connector": "less_or_equal" + } + ] + } + ], + "regex_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "email", + "key_type": "user_field", + "value": "@email.com", + "value_type": "text", + "connector": "regex" + } + ] + } + ], + "shorthand_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "bio_raw", + "key_type": "user_field", + "value": "present", + "value_type": "text", + "connector": "is" + } + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/wizard/required_data.json b/spec/fixtures/step/required_data.json similarity index 100% rename from spec/fixtures/wizard/required_data.json rename to spec/fixtures/step/required_data.json diff --git a/spec/fixtures/step/step.json b/spec/fixtures/step/step.json new file mode 100644 index 00000000..5c6c5f58 --- /dev/null +++ b/spec/fixtures/step/step.json @@ -0,0 +1,10 @@ +{ + "id": "step_1", + "title": "Text", + "description": "Step description", + "image": "step_image_url.png", + "key": "step.locale.key", + "fields": [], + "required_data": [], + "permitted": [] +} \ No newline at end of file diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json index 1d5e1bc7..287f3b60 100644 --- a/spec/fixtures/wizard.json +++ b/spec/fixtures/wizard.json @@ -16,6 +16,7 @@ { "id": "step_1_field_1", "label": "Text", + "description": "Text field description.", "type": "text", "min_length": "3", "prefill": [ @@ -351,7 +352,7 @@ }, { "id": "action_1", - "run_after": "step_2", + "run_after": "step_3", "type": "create_topic", "skip_redirect": true, "post": "step_1_field_2", diff --git a/spec/fixtures/wizard/after_time.json b/spec/fixtures/wizard/after_time.json new file mode 100644 index 00000000..691af470 --- /dev/null +++ b/spec/fixtures/wizard/after_time.json @@ -0,0 +1,4 @@ +{ + "after_time": true, + "after_time_scheduled": "2020-11-20T00:59:00.000Z" +} \ No newline at end of file diff --git a/spec/jobs/clear_after_time_wizard_spec.rb b/spec/jobs/clear_after_time_wizard_spec.rb new file mode 100644 index 00000000..b6d675fe --- /dev/null +++ b/spec/jobs/clear_after_time_wizard_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Jobs::ClearAfterTimeWizard do + fab!(:user1) { Fabricate(:user) } + fab!(:user2) { Fabricate(:user) } + fab!(:user3) { Fabricate(:user) } + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read).with_indifferent_access + } + let(:after_time) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/after_time.json" + ).read).with_indifferent_access + } + + it "clears wizard redirect for all users " do + after_time_template = template.dup + after_time_template["after_time"] = after_time['after_time'] + after_time_template["after_time_scheduled"] = after_time['after_time_scheduled'] + + CustomWizard::Template.save(after_time_template) + + described_class.new.execute(wizard_id: 'super_mega_fun_wizard') + + expect( + UserCustomField.where(" + name = 'redirect_to_wizard' AND + value = 'super_mega_fun_wizard' + ").exists? + ).to eq(false) + end +end \ No newline at end of file diff --git a/spec/jobs/set_after_time_wizard_spec.rb b/spec/jobs/set_after_time_wizard_spec.rb new file mode 100644 index 00000000..49ec211f --- /dev/null +++ b/spec/jobs/set_after_time_wizard_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Jobs::SetAfterTimeWizard do + fab!(:user1) { Fabricate(:user) } + fab!(:user2) { Fabricate(:user) } + fab!(:user3) { Fabricate(:user) } + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read).with_indifferent_access + } + let(:after_time) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/after_time.json" + ).read).with_indifferent_access + } + + it "sets wizard redirect for all users " do + after_time_template = template.dup + after_time_template["after_time"] = after_time['after_time'] + after_time_template["after_time_scheduled"] = after_time['after_time_scheduled'] + + CustomWizard::Template.save(after_time_template) + + messages = MessageBus.track_publish("/redirect_to_wizard") do + described_class.new.execute(wizard_id: 'super_mega_fun_wizard') + end + + expect( + UserCustomField.where( + name: 'redirect_to_wizard', + value: 'super_mega_fun_wizard' + ).length + ).to eq(3) + + expect(messages.first.data).to eq("super_mega_fun_wizard") + expect(messages.first.user_ids).to match_array([user1.id,user2.id,user3.id]) + end +end \ No newline at end of file diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index 47368da5..55deb6a2 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -3,6 +3,8 @@ require 'simplecov' SimpleCov.configure do add_filter do |src| src.filename !~ /discourse-custom-wizard/ || - src.filename =~ /spec/ + src.filename =~ /spec/ || + src.filename =~ /db/ || + src.filename =~ /api/ ## API features are currently experimental end end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin/logs_controller_spec.rb b/spec/requests/custom_wizard/admin/logs_controller_spec.rb new file mode 100644 index 00000000..37a83e90 --- /dev/null +++ b/spec/requests/custom_wizard/admin/logs_controller_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +describe CustomWizard::AdminLogsController do + fab!(:admin_user) { Fabricate(:user, admin: true) } + + before do + CustomWizard::Log.create("First log message") + CustomWizard::Log.create("Second log message") + CustomWizard::Log.create("Third log message") + sign_in(admin_user) + end + + it "returns a list of logs" do + get "/admin/wizards/logs.json" + expect(response.parsed_body.length).to eq(3) + end + + it "paginates" do + get "/admin/wizards/logs.json", params: { page: 1, limit: 2 } + expect(response.parsed_body.length).to eq(1) + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb new file mode 100644 index 00000000..51628ea2 --- /dev/null +++ b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb @@ -0,0 +1,45 @@ +require 'rails_helper' + +describe CustomWizard::AdminSubmissionsController do + fab!(:admin_user) {Fabricate(:user, admin: true)} + fab!(:user1) {Fabricate(:user)} + fab!(:user2) {Fabricate(:user)} + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + + before do + CustomWizard::Template.save(template, skip_jobs: true) + CustomWizard::Wizard.set_submissions(template['id'], user1, + step_1_field_1: "I am a user1's submission" + ) + CustomWizard::Wizard.set_submissions(template['id'], user2, + step_1_field_1: "I am a user2's submission" + ) + sign_in(admin_user) + end + + it "returns a basic list of wizards" do + get "/admin/wizards/submissions.json" + expect(response.parsed_body.length).to eq(1) + expect(response.parsed_body.first['id']).to eq(template['id']) + end + + it "returns the all user's submissions for a wizard" do + get "/admin/wizards/submissions/#{template['id']}.json" + expect(response.parsed_body['submissions'].length).to eq(2) + end + + it "returns the all user's submissions for a wizard" do + get "/admin/wizards/submissions/#{template['id']}.json" + expect(response.parsed_body['submissions'].length).to eq(2) + end + + it "downloads all user submissions" do + get "/admin/wizards/submissions/#{template['id']}/download" + expect(response.parsed_body.length).to eq(2) + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin/transfer_controller_spec.rb b/spec/requests/custom_wizard/admin/transfer_controller_spec.rb new file mode 100644 index 00000000..217311c2 --- /dev/null +++ b/spec/requests/custom_wizard/admin/transfer_controller_spec.rb @@ -0,0 +1,52 @@ +require 'rails_helper' + +describe CustomWizard::AdminTransferController do + fab!(:admin_user) { Fabricate(:user, admin: true) } + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + + before do + sign_in(admin_user) + + CustomWizard::Template.save(template, skip_jobs: true) + + template_2 = template.dup + template_2["id"] = 'super_mega_fun_wizard_2' + CustomWizard::Template.save(template_2, skip_jobs: true) + + template_3 = template.dup + template_3["id"] = 'super_mega_fun_wizard_3' + template_3["after_signup"] = true + CustomWizard::Template.save(template_3, skip_jobs: true) + + @template_array = [template, template_2, template_3] + + FileUtils.mkdir_p(file_from_fixtures_tmp_folder) unless Dir.exists?(file_from_fixtures_tmp_folder) + @tmp_file_path = File.join(file_from_fixtures_tmp_folder, SecureRandom.hex << 'wizards.json') + File.write(@tmp_file_path, @template_array.to_json) + end + + it 'exports all the wizard templates' do + get '/admin/wizards/transfer/export.json', params: { + wizards: [ + 'super_mega_fun_wizard', + 'super_mega_fun_wizard_2', + 'super_mega_fun_wizard_3' + ] + } + expect(response.status).to eq(200) + expect(response.parsed_body).to match_array(@template_array) + end + + it 'imports wizard a template' do + post '/admin/wizards/transfer/import.json', params: { + file: fixture_file_upload(File.open(@tmp_file_path)) + } + expect(response.status).to eq(200) + expect(response.parsed_body['success']).to eq(@template_array.map { |t| t['id'] }) + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin/wizard_controller_spec.rb b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb new file mode 100644 index 00000000..d33762e4 --- /dev/null +++ b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb @@ -0,0 +1,66 @@ +require 'rails_helper' + +describe CustomWizard::AdminWizardController do + fab!(:admin_user) {Fabricate(:user, admin: true)} + fab!(:user1) {Fabricate(:user)} + fab!(:user2) {Fabricate(:user)} + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + + before do + CustomWizard::Template.save(template, skip_jobs: true) + + template_2 = template.dup + template_2["id"] = 'super_mega_fun_wizard_2' + template_2["permitted"] = template_2['permitted'] + CustomWizard::Template.save(template_2, skip_jobs: true) + + template_3 = template.dup + template_3["id"] = 'super_mega_fun_wizard_3' + template_3["after_signup"] = true + CustomWizard::Template.save(template_3, skip_jobs: true) + + sign_in(admin_user) + end + + it "returns a basic list of wizard templates and wizard field types" do + get "/admin/wizards/wizard.json" + expect( + response.parsed_body['wizard_list'].map { |w| w['id'] } + ).to match_array(['super_mega_fun_wizard', 'super_mega_fun_wizard_2', 'super_mega_fun_wizard_3']) + expect( + response.parsed_body['field_types'].keys + ).to eq(CustomWizard::Field.types.keys.map(&:to_s)) + end + + it "returns a wizard template" do + get "/admin/wizards/wizard/#{template['id']}.json" + expect(response.parsed_body['id']).to eq(template['id']) + expect(response.parsed_body['steps'].length).to eq(3) + end + + it "removes wizard templates" do + delete "/admin/wizards/wizard/#{template['id']}.json" + expect(response.status).to eq(200) + expect(CustomWizard::Template.exists?(template['id'])).to eq(false) + end + + it "saves wizard templates" do + template_updated = template.dup + template_updated['name'] = "Super Mega Fun Wizard 2" + template_updated['multiple_submissions'] = false + template_updated['steps'][0]['fields'][0]['label'] = "Text 2" + + put "/admin/wizards/wizard/#{template['id']}.json", params: { wizard: template_updated } + expect(response.status).to eq(200) + + updated_template = CustomWizard::Template.find('super_mega_fun_wizard') + expect(updated_template['name']).to eq("Super Mega Fun Wizard 2") + expect(updated_template['multiple_submissions']).to eq("false") + expect(updated_template['steps'][0]['fields'][0]['label']).to eq("Text 2") + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec/requests/custom_wizard/application_controller_spec.rb new file mode 100644 index 00000000..4c67cc41 --- /dev/null +++ b/spec/requests/custom_wizard/application_controller_spec.rb @@ -0,0 +1,61 @@ +require 'rails_helper' + +describe ApplicationController do + fab!(:user) { + Fabricate( + :user, + username: 'angus', + email: "angus@email.com", + trust_level: TrustLevel[3] + ) + } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + @template = CustomWizard::Template.find('super_mega_fun_wizard') + end + + context "with signed in user" do + before do + sign_in(user) + end + + context "who is required to complete wizard" do + before do + user.custom_fields['redirect_to_wizard'] = 'super_mega_fun_wizard' + user.save_custom_fields(true) + end + + it "redirects if user is required to complete a wizard" do + get "/" + expect(response).to redirect_to("/w/super-mega-fun-wizard") + end + + it "saves original destination of user" do + get '/', headers: { 'REFERER' => "/t/2" } + expect( + CustomWizard::Wizard.submissions(@template['id'], user) + .first['redirect_to'] + ).to eq("/t/2") + end + end + + context "who is not required to complete wizard" do + it "does nothing" do + get "/" + expect(response.status).to eq(200) + end + end + end + + context "with guest" do + it "does nothing" do + get "/" + expect(response.status).to eq(200) + end + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb new file mode 100644 index 00000000..eeea6c17 --- /dev/null +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +describe CustomWizard::StepsController do + fab!(:user) { + Fabricate( + :user, + username: 'angus', + email: "angus@email.com", + trust_level: TrustLevel[3] + ) + } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + sign_in(user) + end + + it 'performs a step update' do + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Text input" + } + } + expect(response.status).to eq(200) + + wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build + expect(wizard.current_submission['step_1_field_1']).to eq("Text input") + expect(wizard.start.id).to eq("step_2") + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb new file mode 100644 index 00000000..a9833381 --- /dev/null +++ b/spec/requests/custom_wizard/wizard_controller_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +describe CustomWizard::WizardController do + fab!(:user) { + Fabricate( + :user, + username: 'angus', + email: "angus@email.com", + trust_level: TrustLevel[3] + ) + } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + @template = CustomWizard::Template.find("super_mega_fun_wizard") + sign_in(user) + end + + context 'plugin disabled' do + before do + SiteSetting.custom_wizard_enabled = false + end + + it 'redirects to root' do + get '/w/super-mega-fun-wizard', xhr: true + expect(response).to redirect_to("/") + end + end + + it 'returns wizard' do + get '/w/super-mega-fun-wizard.json' + expect(response.parsed_body["id"]).to eq("super_mega_fun_wizard") + end + + it 'returns missing message if no wizard exists' do + get '/w/super-mega-fun-wizards.json' + expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.") + end + + it 'skips a wizard if user is allowed to skip' do + put '/w/super-mega-fun-wizard/skip.json' + expect(response.status).to eq(200) + end + + it 'returns a no skip message if user is not allowed to skip' do + @template['required'] = 'true' + CustomWizard::Template.save(@template) + put '/w/super-mega-fun-wizard/skip.json' + expect(response.parsed_body['error']).to eq("Wizard can't be skipped") + end + + it 'skip response contains a redirect_to if in users submissions' do + CustomWizard::Wizard.set_submissions(@template['id'], user, + redirect_to: '/t/2' + ) + put '/w/super-mega-fun-wizard/skip.json' + expect(response.parsed_body['redirect_to']).to eq('/t/2') + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb b/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb new file mode 100644 index 00000000..0e088e2d --- /dev/null +++ b/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::BasicWizardSerializer do + fab!(:user) { Fabricate(:user) } + + it 'should return basic wizard attributes' do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + json = CustomWizard::BasicWizardSerializer.new( + CustomWizard::Builder.new("super_mega_fun_wizard", user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:basic_wizard][:id]).to eq("super_mega_fun_wizard") + expect(json[:basic_wizard][:name]).to eq("Super Mega Fun Wizard") + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/log_serializer_spec.rb b/spec/serializers/custom_wizard/log_serializer_spec.rb new file mode 100644 index 00000000..2ea39871 --- /dev/null +++ b/spec/serializers/custom_wizard/log_serializer_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::LogSerializer do + fab!(:user) { Fabricate(:user) } + + it 'should return log attributes' do + CustomWizard::Log.create("First log message") + CustomWizard::Log.create("Second log message") + + json_array = ActiveModel::ArraySerializer.new( + CustomWizard::Log.list(0), + each_serializer: CustomWizard::LogSerializer + ).as_json + expect(json_array.length).to eq(2) + expect(json_array[0][:message]).to eq("Second log message") + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb new file mode 100644 index 00000000..02069a9b --- /dev/null +++ b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::FieldSerializer do + fab!(:user) { Fabricate(:user) } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + @wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build + end + + it "should return basic field attributes" do + json_array = ActiveModel::ArraySerializer.new( + @wizard.steps.first.fields, + each_serializer: CustomWizard::FieldSerializer, + scope: Guardian.new(user) + ).as_json + expect(json_array.length).to eq(4) + expect(json_array[0][:label]).to eq("

Text

") + expect(json_array[0][:description]).to eq("Text field description.") + end + + it "should return optional field attributes" do + json_array = ActiveModel::ArraySerializer.new( + @wizard.steps.second.fields, + each_serializer: CustomWizard::FieldSerializer, + scope: Guardian.new(user) + ).as_json + expect(json_array[0][:format]).to eq("YYYY-MM-DD") + expect(json_array[5][:file_types]).to eq(".jpg,.png") + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/wizard_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_serializer_spec.rb new file mode 100644 index 00000000..16734f83 --- /dev/null +++ b/spec/serializers/custom_wizard/wizard_serializer_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::WizardSerializer do + fab!(:user) { Fabricate(:user) } + fab!(:category) { Fabricate(:category) } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + @template = CustomWizard::Template.find('super_mega_fun_wizard') + end + + it 'should return the wizard attributes' do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:id]).to eq("super_mega_fun_wizard") + expect(json[:wizard][:name]).to eq("Super Mega Fun Wizard") + expect(json[:wizard][:background]).to eq("#333333") + expect(json[:wizard][:required]).to eq(false) + end + + it 'should return the wizard steps' do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:steps].length).to eq(3) + end + + it "should return the wizard user attributes" do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect( + json[:wizard][:user] + ).to eq(BasicUserSerializer.new(user, root: false).as_json) + end + + it "should not return categories if there are no category fields" do + @template[:steps][2][:fields].delete_at(2) + CustomWizard::Template.save(@template) + + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:categories].present?).to eq(false) + expect(json[:wizard][:uncategorized_category_id].present?).to eq(false) + end + + it "should return categories if there is a category selector field" do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:categories].present?).to eq(true) + expect(json[:wizard][:uncategorized_category_id].present?).to eq(true) + end + + it 'should return groups if there is a group selector field' do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:groups].length).to eq(8) + end + + it 'should not return groups if there is not a group selector field' do + @template[:steps][2][:fields].delete_at(3) + CustomWizard::Template.save(@template) + + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:groups].present?).to eq(false) + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/wizard_step_serializer.rb b/spec/serializers/custom_wizard/wizard_step_serializer.rb new file mode 100644 index 00000000..30e8cc5f --- /dev/null +++ b/spec/serializers/custom_wizard/wizard_step_serializer.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::StepSerializer do + fab!(:user) { Fabricate(:user) } + + let(:required_data_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json" + ).read) + } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + @wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build + end + + it 'should return basic step attributes' do + json_array = ActiveModel::ArraySerializer.new( + @wizard.steps, + each_serializer: CustomWizard::StepSerializer, + scope: Guardian.new(user) + ).as_json + expect(json_array[0][:wizard_step][:title]).to eq("Text") + expect(json_array[0][:wizard_step][:description]).to eq("Text inputs!") + end + + it 'should return fields' do + json_array = ActiveModel::ArraySerializer.new( + @wizard.steps, + each_serializer: CustomWizard::StepSerializer, + scope: Guardian.new(user) + ).as_json + expect(json_array[0][:wizard_step][:fields].length).to eq(4) + end + + context 'with required data' do + before do + @template[:steps][0][:required_data] = required_data_json['required_data'] + @template[:steps][0][:required_data_message] = required_data_json['required_data_message'] + CustomWizard::Template.save(@template.as_json) + end + + it 'should return permitted attributes' do + json_array = ActiveModel::ArraySerializer.new( + @wizard.steps, + each_serializer: CustomWizard::StepSerializer, + scope: Guardian.new(user) + ).as_json + expect(json_array[0][:wizard_step][:permitted]).to eq(false) + expect(json_array[0][:wizard_step][:permitted_message]).to eq("Missing required data") + end + end +end \ No newline at end of file diff --git a/spec_offload/components/custom_wizard/api_spec.rb b/spec_offload/components/custom_wizard/api_spec.rb deleted file mode 100644 index a7a8ba1b..00000000 --- a/spec_offload/components/custom_wizard/api_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe CustomWizard::Api do - context 'authorization' do - it 'authorizes with an oauth2 api' do - - end - - it 'refreshes the api access token' do - - end - end - - context 'endpoint' do - it 'requests an api endpoint' do - - end - end -end diff --git a/spec_offload/components/custom_wizard/mapper_spec.rb b/spec_offload/components/custom_wizard/mapper_spec.rb deleted file mode 100644 index b186ddb0..00000000 --- a/spec_offload/components/custom_wizard/mapper_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -describe CustomWizard::Mapper do - fab!(:user) { Fabricate(:user, name: 'Angus', username: 'angus', email: "angus@email.com") } - - it 'interpolates user data' do - expect( - CustomWizard::Mapper.fill_placeholders( - "My name is u{name}", - user, - {} - ) - ).to eq('My name is Angus') - end -end \ No newline at end of file diff --git a/spec_offload/requests/custom_wizard/admin_controller_spec.rb b/spec_offload/requests/custom_wizard/admin_controller_spec.rb deleted file mode 100644 index 731d0de5..00000000 --- a/spec_offload/requests/custom_wizard/admin_controller_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -describe CustomWizard::AdminController do - -end \ No newline at end of file diff --git a/spec_offload/requests/custom_wizard/application_controller_spec.rb b/spec_offload/requests/custom_wizard/application_controller_spec.rb deleted file mode 100644 index 1315748d..00000000 --- a/spec_offload/requests/custom_wizard/application_controller_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -describe ApplicationController do - -end \ No newline at end of file diff --git a/spec_offload/requests/custom_wizard/wizard_controller_spec.rb b/spec_offload/requests/custom_wizard/wizard_controller_spec.rb deleted file mode 100644 index b457eba7..00000000 --- a/spec_offload/requests/custom_wizard/wizard_controller_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'rails_helper' - -describe CustomWizard::WizardController do - it 'returns a wizard if enabled' do - - end - - it 'returns a disabled message if disabled' do - - end - - it 'returns a missing message if no wizard exists' do - - end - - it 'returns a custom wizard theme' do - - end - - it 'updates the page title' do - - end - - it 'skips a wizard if user is allowed to skip' do - - end - - it 'returns a no skip message if user is not allowed to skip' do - - end -end \ No newline at end of file diff --git a/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb b/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb deleted file mode 100644 index 1498ede9..00000000 --- a/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe CustomWizard::WizardSerializer do - fab!(:user) { Fabricate(:user) } - fab!(:category) { Fabricate(:category) } - - before do - template = JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - CustomWizard::Template.add(template) - @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) - end - - it 'should return the wizard attributes' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:id]).to eq("super_mega_fun_wizard") - expect(json[:custom_wizard][:name]).to eq("Super Mega Fun Wizard") - expect(json[:custom_wizard][:background]).to eq("#333333") - expect(json[:custom_wizard][:required]).to eq(false) - end - - it "should return the wizard user attributes" do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:user]).to eq(BasicUserSerializer.new(user, root: false).as_json) - end - - it "should not return category attributes if there are no category fields" do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:categories].present?).to eq(false) - expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(false) - end - - it "should return category attributes if there is a category selector field" do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:categories].present?).to eq(true) - expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(true) - end -end \ No newline at end of file From f419d1c20c64118689729d817971e43a4316f727 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 3 Nov 2020 12:04:26 +1100 Subject: [PATCH 4/4] Update .travis.yml --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9a695031..a2048719 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,10 @@ -# We want to use the KVM-based system, so require sudo -sudo: required services: - docker before_install: - git clone --depth=1 https://github.com/discourse/discourse-plugin-ci -install: true # Prevent travis doing bundle install +install: true script: - discourse-plugin-ci/script.sh