0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-22 09:20:29 +01:00

Merge pull request #58 from paviliondev/tests_improvements

Rspec Suite
Dieser Commit ist enthalten in:
Angus McLeod 2020-11-03 12:10:24 +11:00 committet von GitHub
Commit 017ac37743
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
60 geänderte Dateien mit 3245 neuen und 805 gelöschten Zeilen

Datei anzeigen

@ -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

Datei anzeigen

@ -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'

Datei anzeigen

@ -27,6 +27,11 @@ en:
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."
wizard_redirect_exclude_paths: "Routes excluded from wizard redirects."

Datei anzeigen

@ -36,8 +36,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

Datei anzeigen

@ -9,6 +9,7 @@ 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
def custom_field_list

Datei anzeigen

@ -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

Datei anzeigen

@ -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,10 +25,10 @@ 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|
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)
@ -43,10 +37,8 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
username = I18n.t('admin.wizard.submissions.no_user', id: row.key)
end
value.map do |submission|
{
username: username
}.merge!(submission.except("redirect_to"))
value.map do |v|
{ username: username }.merge!(v.except("redirect_to"))
end
end.flatten
end

Datei anzeigen

@ -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'
@ -33,39 +30,36 @@ class CustomWizard::TransferController < ::ApplicationController
return
end
fileSize = file.size
maxFileSize = 512 * 1024
file_size = file.size
max_file_size = 512 * 1024
if maxFileSize < fileSize
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
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 }

Datei anzeigen

@ -4,7 +4,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
def index
render_json_dump(
wizard_list: ActiveModel::ArraySerializer.new(
CustomWizard::Wizard.list,
CustomWizard::Wizard.list(current_user),
each_serializer: CustomWizard::BasicWizardSerializer
),
field_types: CustomWizard::Field.types,
@ -15,7 +15,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 }
@ -23,26 +23,22 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
end
def remove
CustomWizard::Wizard.remove(@wizard.id)
if CustomWizard::Template.remove(@wizard.id)
render json: success_json
end
def save
opts = {}
opts[:create] = params[:create] if params[:create]
validator = CustomWizard::Validator.new(save_wizard_params.to_h, opts)
validation = validator.perform
if validation[:error]
render json: { error: validation[:error] }
else
if wizard_id = CustomWizard::Wizard.save(validation[:wizard])
render json: success_json.merge(wizard_id: wizard_id)
else
render json: failed_json
end
end
def save
template = CustomWizard::Template.new(save_wizard_params.to_h)
wizard_id = template.save(create: params[:create])
if template.errors.any?
render json: failed_json.merge(errors: result.errors.full_messages)
else
render json: success_json.merge(wizard_id: wizard_id)
end
end
private

Datei anzeigen

@ -1,25 +1,38 @@
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])
permitted = params.permit(:wizard_id, :step_id)
if params[:fields]
permitted[:fields] = params[:fields].select { |k, v| field_ids.include? k }
permitted.permit!
wizard = @builder.build
step = wizard.steps.select { |s| s.id == update_params[:step_id] }.first
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)
update = update_params.to_h
if params[:fields]
update[:fields] = {}
params[:fields].each do |k, v|
update[:fields][k] = v if field_ids.include? k
end
end
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 = []
@ -29,4 +42,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

Datei anzeigen

@ -2,6 +2,7 @@ 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
@ -24,7 +25,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)
@ -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

Datei anzeigen

@ -83,6 +83,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
@ -130,7 +135,13 @@ class CustomWizard::Action
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] ||= {}
@ -262,7 +273,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
@ -272,7 +283,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
@ -290,7 +301,14 @@ class CustomWizard::Action
}
).perform
groups = group_map.flatten.reduce([]) do |groups, g|
group_map = group_map.flatten.compact
unless group_map.present?
log_error("invalid group map")
return
end
groups = group_map.reduce([]) do |groups, g|
begin
groups.push(Integer(g))
rescue ArgumentError
@ -345,17 +363,33 @@ class CustomWizard::Action
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
@ -372,7 +406,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
@ -385,6 +419,8 @@ class CustomWizard::Action
user: user
).perform
return false unless output.present?
if output.is_a?(Array)
output.first
elsif output.is_a?(Integer)
@ -401,6 +437,8 @@ class CustomWizard::Action
user: user,
).perform
return false unless output.present?
if output.is_a?(Array)
output.flatten
else output.is_a?(String)
@ -472,11 +510,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
@ -528,12 +566,14 @@ class CustomWizard::Action
user: user
).perform
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
add_custom_fields(params)
end
@ -556,6 +596,7 @@ class CustomWizard::Action
user: user
).perform
if value
if attr === "parent_category_id" && value.is_a?(Array)
value = value[0]
end
@ -579,9 +620,14 @@ class CustomWizard::Action
end
end
if attr === 'slug'
value = value.parameterize(separator: '-')
end
params[attr.to_sym] = value
end
end
end
add_custom_fields(params)
end
@ -607,6 +653,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'

Datei anzeigen

@ -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,26 @@ class CustomWizard::Builder
return nil if !SiteSetting.custom_wizard_enabled || !@wizard
return @wizard if !@wizard.can_access?
reset_submissions if build_opts[:reset]
build_opts[:reset] = build_opts[:reset] || @wizard.restart_on_revisit
@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 +77,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
@ -146,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
@ -170,28 +131,30 @@ class CustomWizard::Builder
end
end
if updater.errors.empty?
if route_to = data['route_to']
data.delete('route_to')
end
if @wizard.save_submissions && updater.errors.empty?
if @wizard.save_submissions
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']
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
if updater.errors.empty?
if final_step
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
@ -211,15 +174,13 @@ 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
if !build_opts[:reset] && (submission = @wizard.current_submission)
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
end
@ -395,13 +356,47 @@ 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
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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -144,7 +144,7 @@ class CustomWizard::Mapper
end
if operator == '=~'
result == 0 ? true : false
result.nil? ? false : true
else
result
end

115
lib/custom_wizard/template.rb Normale Datei
Datei anzeigen

@ -0,0 +1,115 @@
class CustomWizard::Template
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.remove(wizard_id)
wizard = CustomWizard::Wizard.create(wizard_id)
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)
end
end
end
def self.exists?(wizard_id)
PluginStoreRow.exists?(plugin_name: 'custom_wizard', key: wizard_id)
end
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)
if attrs.present? &&
attrs.is_a?(Hash) &&
attrs['id'].present? &&
attrs['name'].present?
result.push(attrs)
end
result
end
end
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

Datei anzeigen

@ -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]
check_id(data, :wizard)
check_required(data, :wizard)
validate_after_time
end
if !@error
params[:steps].each do |step|
data[: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|
if data[:fields].present?
data[:fields].each do |field|
check_required(field, :field)
check_depdendent(field, :field)
break if @error.present?
end
end
end
if params[:actions].present?
params[:actions].each do |action|
if data[:actions].present?
data[:actions].each do |action|
check_required(action, :action)
check_depdendent(action, :action)
break if @error.present?
end
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 }
}
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 }
}
errors.add :validation, I18n.t("wizard.validation.required", property: property)
end
end
end
def check_id(object, type)
if type === :wizard && @opts[:create] && CustomWizard::Wizard.exists?(object[:id])
@error = {
type: 'conflict',
params: { type: type, property: 'id', value: object[:id] }
}
if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(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]
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

Datei anzeigen

@ -24,23 +24,26 @@ class CustomWizard::Wizard
:needs_categories,
:needs_groups,
: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,9 +56,17 @@ class CustomWizard::Wizard
@first_step = nil
@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)
end
@ -65,12 +76,10 @@ class CustomWizard::Wizard
yield step if block_given?
last_step = @steps.last
last_step = steps.last
steps << step
@steps << step
# If it's the first step
if @steps.size == 1
if steps.size == 1
@first_step = step
step.index = 0
elsif last_step.present?
@ -81,67 +90,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
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?
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
steps = CustomWizard::Wizard.step_ids(@id)
return nil if !user
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?
@ -171,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
@ -178,173 +183,89 @@ 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
")
end
def self.after_signup(user)
if (records = filter_records('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
def current_submission
if submissions.present? && !submissions.last.key?("submitted_at")
submissions.last
else
false
nil
end
end
def self.prompt_completion(user)
if (records = filter_records('prompt_completion')).any?
records.reduce([]) do |result, record|
wizard = CustomWizard::Wizard.new(::JSON.parse(record.value), user)
if wizard.permitted? && !wizard.completed?
result.push(id: wizard.id, name: wizard.name)
def set_submissions(submissions)
PluginStore.set("#{id}_submissions", user.id, Array.wrap(submissions))
end
result
end
else
false
end
def self.submissions(wizard_id, user)
new({ id: wizard_id }, user).submissions
end
def self.restart_on_revisit
if (records = filter_records('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)
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 wizard = self.find(wizard_id)
self.new(wizard.to_h, user)
if template = CustomWizard::Template.find(wizard_id)
new(template.to_h, user)
else
false
end
end
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)
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)
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
@ -364,12 +285,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

Datei anzeigen

@ -42,10 +42,10 @@ 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/admin/custom_fields.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
@ -57,6 +57,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
@ -130,8 +131,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
@ -161,7 +161,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
CustomWizard::CustomField::CLASSES.each do |klass|
add_model_callback(klass.to_sym, :after_initialize) do

Datei anzeigen

@ -1,19 +1,41 @@
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) }
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"
before do
Group.refresh_automatic_group!(:trust_level_2)
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 "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
)
topic = Topic.where(title: "Topic Title")
expect(topic.exists?).to eq(true)
expect(Post.where(
topic_id: topic.pluck(:id),
@ -21,72 +43,120 @@ describe CustomWizard::Action do
).exists?).to eq(true)
end
it 'sends a message' do
fields = [text_field, textarea_field]
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
if extra_field
fields.push(extra_field)
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
template['steps'][0]['fields'] = fields
template['steps'][0]["actions"] = [send_message_action.merge(extra_action_opts)]
it 'sends a message' do
User.create(username: 'angus1', email: "angus1@email.com")
run_update(template, nil,
text: "Message Title",
textarea: "message body"
)
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,
title: "Message Title"
title: "Message title"
)
post = Post.where(
topic_id: topic.pluck(:id),
raw: "I will interpolate some wizard fields"
)
expect(topic.exists?).to eq(true)
expect(
topic.first.topic_allowed_users.first.user.username
).to eq('angus')
expect(Post.where(
topic_id: topic.pluck(:id),
raw: "message body"
).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')
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 = 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
template['steps'][0]['fields'] = [text_field, textarea_field]
template['steps'][0]["actions"] = [open_composer_action]
wizard = CustomWizard::Builder.new(@template[:id], user).build
wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
updater = run_update(template, nil,
text: "Topic Title",
textarea: "topic body"
updater = wizard.create_updater(wizard.steps[1].id, {})
updater.update
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"
)
end
expect(updater.result.blank?).to eq(true)
it 'creates a category' 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
expect(Category.where(id: wizard.current_submission['action_8']).exists?).to eq(true)
end
updater = run_update(template, template['steps'][1]['id'])
expect(updater.result[:redirect_on_complete]).to eq(
"/new-topic?title=Topic%20Title&body=topic%20body"
)
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
expect(Group.where(name: wizard.current_submission['action_9']).exists?).to eq(true)
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)
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
group = Group.find_by(name: wizard.current_submission['action_9'])
expect(group.users.first.username).to eq('angus')
end
it 'watches categories' 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
expect(CategoryUser.where(
category_id: wizard.current_submission['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"
)
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
end

Datei anzeigen

@ -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

Datei anzeigen

@ -3,39 +3,45 @@
require 'rails_helper'
describe CustomWizard::Builder do
fab!(:user) { Fabricate(:user, username: 'angus') }
fab!(:trusted_user) { Fabricate(:user, trust_level: 3) }
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) }
let!(:template) do
let(:required_data_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json"
).read)
}
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.save(
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
).read),
skip_jobs: true)
@template = CustomWizard::Template.find('super_mega_fun_wizard')
end
context 'disabled' do
@ -43,15 +49,10 @@ 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(@template[:id], user).build
).to eq(nil)
end
end
@ -60,123 +61,287 @@ describe CustomWizard::Builder do
SiteSetting.custom_wizard_enabled = true
end
it "returns steps" do
expect(build_wizard.steps.length).to eq(2)
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 no steps if multiple submissions are disabled and user has completed' do
history_params = {
it "returns steps" do
expect(
CustomWizard::Builder.new(@template[:id], user).build
.steps.length
).to eq(3)
end
context "with multiple submissions disabled" do
before do
@template[:multiple_submissions] = false
CustomWizard::Template.save(@template.as_json)
end
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 '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']
}
UserHistory.create!(history_params.merge(subject: template['steps'][0]['id']))
UserHistory.create!(history_params.merge(subject: template['steps'][1]['id']))
context: @template[:id]
}.merge(
subject: step[:id]
)
)
end
template["multiple_submissions"] = false
expect(build_wizard(template).steps.length).to eq(0)
expect(
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 'user cannot access if not permitted' do
expect(
CustomWizard::Builder.new(@template[:id], user).build
.can_access?
).to eq(false)
end
it 'returns wizard metadata if user is not permitted' do
expect(
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
template["min_trust"] = 3
expect(build_wizard(template).steps.length).to eq(0)
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 'user can access if permitted' do
expect(
CustomWizard::Builder.new(@template[:id], trusted_user).build
.can_access?
).to eq(true)
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(@template[:id], trusted_user).build
.steps.length
).to eq(3)
end
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')
it 'returns prefilled data' do
expect(
CustomWizard::Builder.new(@template[:id], user).build
.steps.first
.fields.first
.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)
context "user has partially completed" do
before do
wizard = CustomWizard::Wizard.new(@template, user)
wizard.set_submissions(step_1_field_1: 'I am a user submission')
end
context 'building steps' do
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
expect(build_wizard.steps[0].title).to eq('Welcome to Pavilion')
expect(build_wizard.steps[1].title).to eq('Tell us about you')
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("<p>Text inputs!</p>")
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')
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
template['steps'][0]['required_data'] = required_data
expect(build_wizard(template, user).steps[0].permitted).to eq(false)
expect(
CustomWizard::Builder.new(@template[:id], user).build
.steps.first
.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)
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
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)
CustomWizard::Wizard.set_submissions('super_mega_fun_wizard', user,
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
wizard = CustomWizard::Builder.new(@template[:id], user).build({},
param: 'param_value'
)
expect(wizard.current_submission['saved_param']).to eq('param_value')
end
end
end
context 'building field' do
it 'returns field metadata' do
expect(build_wizard(template, user).steps[0].fields[0].label).to eq("<p>Name</p>")
expect(build_wizard(template, user).steps[0].fields[0].type).to eq("text")
wizard = CustomWizard::Builder.new(@template[:id], user).build
field = wizard.steps.first.fields.first
expect(field.label).to eq("<p>Text</p>")
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 fields' do
template['steps'][0]['fields'][1] = checkbox_field
expect(build_wizard(template, user).steps[0].fields.length).to eq(2)
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
run_update(template, nil, name: 'Angus')
expect(get_submission_data.first['name']).to eq('Angus')
perform_update('step_1', step_1_field_1: 'Text input')
expect(
CustomWizard::Wizard.submissions(@template[:id], user)
.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(
CustomWizard::Wizard.submissions(@template[:id], user).first
).to eq(nil)
end
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)
)
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
template['steps'][0]['fields'][0] = checkbox_field
run_update(template, nil, checkbox: 'false')
expect(get_submission_data.first['checkbox']).to eq(false)
perform_update('step_2', step_2_field_5: 'false')
expect(
CustomWizard::Wizard.submissions(@template[:id], user)
.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')
)
end
end
@template[:steps][0][:fields][1][:required] = true
CustomWizard::Template.save(@template.as_json)
it 'runs actions attached to a step' do
run_update(template, template['steps'][1]['id'], name: "Gus")
expect(user.name).to eq('Gus')
expect(
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
end

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -1,14 +1,250 @@
require 'rails_helper'
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,
{}
fab!(:user1) {
Fabricate(:user,
name: "Angus",
username: "angus",
email: "angus@email.com",
trust_level: TrustLevel[3]
)
).to eq('My name is Angus')
}
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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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("<p>Field Label</p>")
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

Datei anzeigen

@ -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

10
spec/fixtures/field/field.json gevendort Normale Datei
Datei anzeigen

@ -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": []
}

4
spec/fixtures/mapper/data.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,4 @@
{
"input_1": "value 1",
"input_2": "value 2"
}

264
spec/fixtures/mapper/inputs.json gevendort Normale Datei
Datei anzeigen

@ -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"
}
]
}
]
}

17
spec/fixtures/step/permitted_params.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,17 @@
{
"permitted_params": [
{
"type": "association",
"pairs": [
{
"index": 0,
"key": "param",
"key_type": "text",
"value": "saved_param",
"value_type": "text",
"connector": "association"
}
]
}
]
}

18
spec/fixtures/step/required_data.json gevendort Normale Datei
Datei anzeigen

@ -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"
}
]
}
]
}

10
spec/fixtures/step/step.json gevendort Normale Datei
Datei anzeigen

@ -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": []
}

Datei anzeigen

@ -1,50 +1,517 @@
{
"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,
"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": "<p>Hey there, thanks for signing up.</p>\n<p>Were Pavilion, an international freelancer cooperative that specialises in online communities.</p>\n<p>This site is our own community, where we work with our clients, users of our open source work and our broader community.</p>",
"id": "step_1",
"title": "Text",
"raw_description": "Text inputs!",
"fields": [
{
"id": "name",
"id": "step_1_field_1",
"label": "Text",
"description": "Text field description.",
"type": "text",
"label": "Name"
"min_length": "3",
"prefill": [
{
"type": "assignment",
"output": "I am prefilled",
"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": "<p>Wed like to know a little more about you. Add a your name and your website below. This will update your user profile.</p>",
"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": "<p>Text inputs!</p>"
},
{
"id": "step_2",
"title": "Values",
"raw_description": "Because I couldn't think of another name for this step :)",
"fields": [
{
"id": "website",
"label": "Website",
"type": "text"
"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": "<p>Because I couldnt think of another name for this step <img src=\"/images/emoji/twitter/slight_smile.png?v=9\" title=\":slight_smile:\" class=\"emoji\" alt=\":slight_smile:\"></p>"
},
{
"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": "<p>Unfortunately not the edible type <img src=\"/images/emoji/twitter/sushi.png?v=9\" title=\":sushi:\" class=\"emoji\" alt=\":sushi:\"></p>"
}
],
"actions": [
{
"id": "update_profile",
"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": "action_6",
"run_after": "step_1",
"type": "add_to_group",
"group": [
{
"type": "assignment",
"output": "action_9",
"output_type": "wizard_action",
"output_connector": "set"
}
]
},
{
"id": "action_8",
"run_after": "step_1",
"type": "create_category",
"custom_fields": [
{
"type": "association",
"pairs": [
{
"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_3",
"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": [
{
"key": "name",
"value": "name"
"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"
}
]
}
]
}

4
spec/fixtures/wizard/after_time.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,4 @@
{
"after_time": true,
"after_time_scheduled": "2020-11-20T00:59:00.000Z"
}

12
spec/fixtures/wizard/permitted.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,12 @@
{
"permitted": [
{
"type": "assignment",
"output_type": "group",
"output_connector": "set",
"output": [
13
]
}
]
}

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -1,5 +0,0 @@
require 'rails_helper'
describe CustomWizard::AdminController do
end

Datei anzeigen

@ -1,5 +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

Datei anzeigen

@ -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

Datei anzeigen

@ -1,31 +1,63 @@
require 'rails_helper'
describe CustomWizard::WizardController do
it 'returns a wizard if enabled' 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
it 'returns a disabled message if disabled' do
context 'plugin disabled' do
before do
SiteSetting.custom_wizard_enabled = false
end
it 'returns a missing message if no wizard exists' do
it 'redirects to root' do
get '/w/super-mega-fun-wizard', xhr: true
expect(response).to redirect_to("/")
end
end
it 'returns a custom wizard theme' do
it 'returns wizard' do
get '/w/super-mega-fun-wizard.json'
expect(response.parsed_body["id"]).to eq("super_mega_fun_wizard")
end
it 'updates the page title' do
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

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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("<p>Text</p>")
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

Datei anzeigen

@ -2,48 +2,85 @@
require 'rails_helper'
describe CustomWizardSerializer do
describe CustomWizard::WizardSerializer do
fab!(:user) { Fabricate(:user) }
fab!(:category) { Fabricate(:category) }
let!(:template) do
before do
CustomWizard::Template.save(
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)
).read),
skip_jobs: true)
@template = CustomWizard::Template.find('super_mega_fun_wizard')
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)
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 = 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)
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 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)
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 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)
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

Datei anzeigen

@ -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