From a19a1fa3b1627b7847ff7fa07abdf710003c80d4 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 12:33:34 +0100 Subject: [PATCH] Refactor wizard client and add tests --- app/controllers/custom_wizard/wizard.rb | 115 +++-- app/views/layouts/qunit.html.erb | 28 ++ app/views/layouts/wizard.html.erb | 6 +- assets/javascripts/wizard-custom-globals.js | 6 - assets/javascripts/wizard-custom-start.js | 2 +- assets/javascripts/wizard-custom.js | 14 +- assets/javascripts/wizard-qunit.js | 16 + assets/javascripts/wizard/application.js.es6 | 19 + .../components/custom-user-selector.js.es6 | 9 +- .../wizard/components/field-validators.js.es6 | 3 + .../similar-topics-validator.js.es6 | 1 + .../wizard/components/validator.js.es6 | 3 +- .../components/wizard-composer-editor.js.es6 | 1 + .../wizard-composer-hyperlink.js.es6 | 1 + .../components/wizard-date-input.js.es6 | 1 + .../components/wizard-date-time-input.js.es6 | 2 + .../components/wizard-field-category.js.es6 | 2 + .../components/wizard-field-checkbox.js.es6 | 5 + .../wizard-field-composer-preview.js.es6 | 2 + .../components/wizard-field-composer.js.es6 | 2 + .../components/wizard-field-date-time.js.es6 | 2 + .../components/wizard-field-date.js.es6 | 2 + .../components/wizard-field-dropdown.js.es6 | 15 + .../components/wizard-field-group.js.es6 | 5 + .../components/wizard-field-number.js.es6 | 5 + .../wizard/components/wizard-field-tag.js.es6 | 5 + .../components/wizard-field-text.js.es6 | 9 + .../components/wizard-field-textarea.js.es6 | 9 + .../components/wizard-field-time.js.es6 | 2 + .../components/wizard-field-upload.js.es6 | 4 + .../wizard/components/wizard-field-url.js.es6 | 5 + .../wizard-field-user-selector.js.es6 | 5 + .../wizard/components/wizard-field.js.es6 | 34 ++ .../components/wizard-group-selector.js.es6 | 1 + .../wizard/components/wizard-no-access.js.es6 | 23 +- .../components/wizard-similar-topics.js.es6 | 1 + .../wizard/components/wizard-step-form.js.es6 | 9 + .../wizard/components/wizard-step.js.es6 | 247 +++++++++ .../components/wizard-text-field.js.es6 | 6 +- .../components/wizard-time-input.js.es6 | 4 +- .../wizard/controllers/custom.js.es6 | 3 - .../{custom-step.js.es6 => step.js.es6} | 11 +- .../wizard/controllers/wizard-index.js.es6 | 24 + .../wizard/controllers/wizard.js.es6 | 5 + .../javascripts/wizard/custom-wizard.js.es6 | 39 -- .../initializers/custom-wizard-step.js.es6 | 199 -------- .../wizard/initializers/custom-wizard.js.es6 | 126 ----- .../lib/initialize/create-contexts.js.es6 | 12 + .../lib/initialize/inject-objects.js.es6 | 49 ++ .../initialize/patch-components.js.es6} | 116 ++--- .../lib/initialize/register-files.js.es6 | 26 + .../wizard/lib/initialize/wizard.js.es6 | 49 ++ .../wizard/lib/utilities-lite.js.es6 | 71 --- assets/javascripts/wizard/models/field.js.es6 | 79 +++ assets/javascripts/wizard/models/site.js.es6 | 3 +- assets/javascripts/wizard/models/step.js.es6 | 114 +++++ .../models/{custom.js.es6 => wizard.js.es6} | 10 +- assets/javascripts/wizard/router.js.es6 | 17 + .../wizard/routes/application.js.es6 | 3 + .../wizard/routes/custom-steps.js.es6 | 5 - assets/javascripts/wizard/routes/index.js.es6 | 9 + .../{custom-step.js.es6 => step.js.es6} | 11 +- assets/javascripts/wizard/routes/steps.js.es6 | 7 + ...ustom-index.js.es6 => wizard-index.js.es6} | 26 +- .../routes/{custom.js.es6 => wizard.js.es6} | 59 ++- .../components/wizard-field-dropdown.hbs | 1 + .../wizard/templates/custom.index.hbs | 15 - assets/javascripts/wizard/templates/index.hbs | 1 + .../templates/{custom.step.hbs => step.hbs} | 0 .../wizard/templates/wizard-index.hbs | 3 + .../templates/{custom.hbs => wizard.hbs} | 4 - .../wizard/tests/acceptance/field-test.js.es6 | 135 +++++ .../wizard/tests/acceptance/step-test.js.es6 | 47 ++ .../tests/acceptance/wizard-test.js.es6 | 72 +++ .../javascripts/wizard/tests/bootstrap.js.es6 | 17 + .../wizard/tests/fixtures/categories.js.es6 | 209 ++++++++ .../wizard/tests/fixtures/groups.js.es6 | 313 ++++++++++++ .../tests/fixtures/site-settings.js.es6 | 283 +++++++++++ .../wizard/tests/fixtures/tags.js.es6 | 22 + .../wizard/tests/fixtures/update.js.es6 | 5 + .../wizard/tests/fixtures/user.js.es6 | 34 ++ .../wizard/tests/fixtures/users.js.es6 | 14 + .../wizard/tests/fixtures/wizard.js.es6 | 469 ++++++++++++++++++ .../wizard/tests/helpers/acceptance.js.es6 | 53 ++ .../wizard/tests/helpers/start-app.js.es6 | 19 + .../wizard/tests/helpers/step.js.es6 | 20 + .../wizard/tests/helpers/test.js.es6 | 7 + .../wizard/tests/helpers/wizard.js.es6 | 52 ++ .../javascripts/wizard/tests/pretender.js.es6 | 53 ++ .../stylesheets/wizard/custom/composer.scss | 2 +- config/routes.rb | 1 + .../extensions/extra_locales_controller.rb | 1 + lib/custom_wizard/validators/update.rb | 2 +- plugin.rb | 3 +- 94 files changed, 2887 insertions(+), 674 deletions(-) create mode 100644 app/views/layouts/qunit.html.erb delete mode 100644 assets/javascripts/wizard-custom-globals.js create mode 100644 assets/javascripts/wizard-qunit.js create mode 100644 assets/javascripts/wizard/application.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-group.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-number.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-tag.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-text.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-textarea.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-url.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-step-form.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-step.js.es6 delete mode 100644 assets/javascripts/wizard/controllers/custom.js.es6 rename assets/javascripts/wizard/controllers/{custom-step.js.es6 => step.js.es6} (76%) create mode 100644 assets/javascripts/wizard/controllers/wizard-index.js.es6 create mode 100644 assets/javascripts/wizard/controllers/wizard.js.es6 delete mode 100644 assets/javascripts/wizard/custom-wizard.js.es6 delete mode 100644 assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 delete mode 100644 assets/javascripts/wizard/initializers/custom-wizard.js.es6 create mode 100644 assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 create mode 100644 assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 rename assets/javascripts/wizard/{initializers/custom-wizard-field.js.es6 => lib/initialize/patch-components.js.es6} (60%) create mode 100644 assets/javascripts/wizard/lib/initialize/register-files.js.es6 create mode 100644 assets/javascripts/wizard/lib/initialize/wizard.js.es6 delete mode 100644 assets/javascripts/wizard/lib/utilities-lite.js.es6 create mode 100644 assets/javascripts/wizard/models/field.js.es6 create mode 100644 assets/javascripts/wizard/models/step.js.es6 rename assets/javascripts/wizard/models/{custom.js.es6 => wizard.js.es6} (94%) create mode 100644 assets/javascripts/wizard/router.js.es6 create mode 100644 assets/javascripts/wizard/routes/application.js.es6 delete mode 100644 assets/javascripts/wizard/routes/custom-steps.js.es6 create mode 100644 assets/javascripts/wizard/routes/index.js.es6 rename assets/javascripts/wizard/routes/{custom-step.js.es6 => step.js.es6} (78%) create mode 100644 assets/javascripts/wizard/routes/steps.js.es6 rename assets/javascripts/wizard/routes/{custom-index.js.es6 => wizard-index.js.es6} (50%) rename assets/javascripts/wizard/routes/{custom.js.es6 => wizard.js.es6} (59%) delete mode 100644 assets/javascripts/wizard/templates/custom.index.hbs create mode 100644 assets/javascripts/wizard/templates/index.hbs rename assets/javascripts/wizard/templates/{custom.step.hbs => step.hbs} (100%) create mode 100644 assets/javascripts/wizard/templates/wizard-index.hbs rename assets/javascripts/wizard/templates/{custom.hbs => wizard.hbs} (86%) create mode 100644 assets/javascripts/wizard/tests/acceptance/field-test.js.es6 create mode 100644 assets/javascripts/wizard/tests/acceptance/step-test.js.es6 create mode 100644 assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 create mode 100644 assets/javascripts/wizard/tests/bootstrap.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/categories.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/groups.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/tags.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/update.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/user.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/users.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/wizard.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/acceptance.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/start-app.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/step.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/test.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/wizard.js.es6 create mode 100644 assets/javascripts/wizard/tests/pretender.js.es6 diff --git a/app/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb index 12e6bdff..43db5df8 100644 --- a/app/controllers/custom_wizard/wizard.rb +++ b/app/controllers/custom_wizard/wizard.rb @@ -1,54 +1,39 @@ # frozen_string_literal: true -class CustomWizard::WizardController < ::ApplicationController - include ApplicationHelper - prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'app', 'views')) - layout 'wizard' +class CustomWizard::WizardController < ::ActionController::Base + helper ApplicationHelper + include CurrentUser + include CanonicalURL::ControllerExtensions + include GlobalPath + + prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'views')) + layout :set_wizard_layout + + before_action :preload_wizard_json before_action :ensure_plugin_enabled before_action :ensure_logged_in, only: [:skip] + helper_method :wizard_page_title helper_method :wizard_theme_id helper_method :wizard_theme_lookup helper_method :wizard_theme_translations_lookup - def wizard - @builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) - @wizard ||= @builder.build - @wizard - end - - def wizard_page_title - wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title') - end - - def wizard_theme_id - wizard ? wizard.theme_id : nil - end - - def wizard_theme_lookup(name) - Theme.lookup_field(wizard_theme_id, mobile_view? ? :mobile : :desktop, name) - end - - def wizard_theme_translations_lookup - Theme.lookup_field(wizard_theme_id, :translations, I18n.locale) + def set_wizard_layout + action_name === 'qunit' ? 'qunit' : 'wizard' end def index respond_to do |format| format.json do - builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) - - if builder.wizard.present? - builder_opts = {} - builder_opts[:reset] = params[:reset] - built_wizard = builder.build(builder_opts, params) - - render_serialized(built_wizard, ::CustomWizard::WizardSerializer, root: false) + if wizard.present? + render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200 else render json: { error: I18n.t('wizard.none') } end end - format.html {} + format.html do + render "default/empty" + end end end @@ -62,8 +47,10 @@ class CustomWizard::WizardController < ::ApplicationController result = success_json if current_user && wizard.can_access? - if redirect_to = wizard.current_submission&.redirect_to - result.merge!(redirect_to: redirect_to) + submission = wizard.current_submission + + if submission.present? && submission.redirect_to + result.merge!(redirect_to: submission.redirect_to) end wizard.cleanup_on_skip! @@ -72,6 +59,64 @@ class CustomWizard::WizardController < ::ApplicationController render json: result end + def qunit + raise Discourse::InvalidAccess.new if Rails.env.production? + + respond_to do |format| + format.html do + render "default/empty" + end + end + end + + protected + + def ensure_logged_in + raise Discourse::NotLoggedIn.new unless current_user.present? + end + + def guardian + @guardian ||= Guardian.new(current_user, request) + end + + def wizard + @wizard ||= begin + builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) + return nil unless builder.present? + opts = {} + opts[:reset] = params[:reset] + builder.build(opts, params) + end + end + + def wizard_page_title + wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title') + end + + def wizard_theme_id + wizard ? wizard.theme_id : nil + end + + def wizard_theme_lookup(name) + Theme.lookup_field(wizard_theme_id, view_context.mobile_view? ? :mobile : :desktop, name) + end + + def wizard_theme_translations_lookup + Theme.lookup_field(wizard_theme_id, :translations, I18n.locale) + end + + def preload_wizard_json + return if request.xhr? || request.format.json? + return if request.method != "GET" + + store_preloaded("siteSettings", SiteSetting.client_settings_json) + end + + def store_preloaded(key, json) + @preloaded ||= {} + @preloaded[key] = json.gsub(" + + + Custom Wizard QUnit Test Runner + <%= discourse_stylesheet_link_tag(:test_helper, theme_id: nil) %> + <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> + <%= discourse_stylesheet_link_tag :wizard_custom %> + <%= preload_script "locales/en" %> + <%= preload_script "ember_jquery" %> + <%= preload_script "wizard-vendor" %> + <%= preload_script "wizard-custom" %> + <%= preload_script "wizard-raw-templates" %> + <%= preload_script "wizard-plugin" %> + <%= preload_script "pretty-text-bundle" %> + <%= preload_script "wizard-qunit" %> + <%= csrf_meta_tags %> + + + + <%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %> + + + + +
+
+ + diff --git a/app/views/layouts/wizard.html.erb b/app/views/layouts/wizard.html.erb index 16d119b7..f909ae84 100644 --- a/app/views/layouts/wizard.html.erb +++ b/app/views/layouts/wizard.html.erb @@ -11,10 +11,8 @@ <%= preload_script "locales/#{I18n.locale}" %> <%= preload_script "ember_jquery" %> <%= preload_script "wizard-vendor" %> - <%= preload_script "wizard-application" %> - <%= preload_script "wizard-custom-globals" %> - <%= preload_script "wizard-raw-templates" %> <%= preload_script "wizard-custom" %> + <%= preload_script "wizard-raw-templates" %> <%= preload_script "wizard-plugin" %> <%= preload_script "pretty-text-bundle" %> @@ -58,5 +56,7 @@ <%= raw SvgSprite.bundle %> + + diff --git a/assets/javascripts/wizard-custom-globals.js b/assets/javascripts/wizard-custom-globals.js deleted file mode 100644 index 83923cba..00000000 --- a/assets/javascripts/wizard-custom-globals.js +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint no-undef: 0*/ -window.Discourse = {}; -window.Wizard = {}; -Wizard.SiteSettings = {}; -Discourse.__widget_helpers = {}; -Discourse.SiteSettings = Wizard.SiteSettings; diff --git a/assets/javascripts/wizard-custom-start.js b/assets/javascripts/wizard-custom-start.js index 3ffa9c5a..b92d2871 100644 --- a/assets/javascripts/wizard-custom-start.js +++ b/assets/javascripts/wizard-custom-start.js @@ -1,4 +1,4 @@ (function () { - let wizard = require("discourse/plugins/discourse-custom-wizard/wizard/custom-wizard").default.create(); + let wizard = require("discourse/plugins/discourse-custom-wizard/wizard/application").default.create(); wizard.start(); })(); diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 99a96a00..667d23fd 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -1,10 +1,13 @@ +//= require_tree_discourse truth-helpers/addon +//= require_tree_discourse discourse-common/addon +//= require_tree_discourse select-kit/addon +//= require_tree_discourse wizard/lib +//= require_tree_discourse wizard/mixins //= require_tree_discourse discourse/app/lib //= require_tree_discourse discourse/app/mixins //= require discourse/app/adapters/rest - //= require message-bus - //= require_tree_discourse discourse/app/models //= require discourse/app/helpers/category-link @@ -12,9 +15,6 @@ //= require discourse/app/helpers/format-username //= require discourse/app/helpers/share-url //= require discourse/app/helpers/decorate-username-selector -//= require discourse-common/addon/helpers/component-for-collection -//= require discourse-common/addon/helpers/component-for-row -//= require discourse-common/addon/lib/raw-templates //= require discourse/app/helpers/discourse-tag //= require discourse/app/services/app-events @@ -70,11 +70,11 @@ //= require bootbox.js //= require discourse-shims -//= require ./wizard/custom-wizard +//= require ./wizard/application +//= require ./wizard/router //= require_tree ./wizard/components //= require_tree ./wizard/controllers //= require_tree ./wizard/helpers -//= require_tree ./wizard/initializers //= require_tree ./wizard/lib //= require_tree ./wizard/models //= require_tree ./wizard/routes diff --git a/assets/javascripts/wizard-qunit.js b/assets/javascripts/wizard-qunit.js new file mode 100644 index 00000000..2866cb5d --- /dev/null +++ b/assets/javascripts/wizard-qunit.js @@ -0,0 +1,16 @@ +//= require route-recognizer +//= require fake_xml_http_request +//= require pretender +//= require qunit +//= require ember-qunit +//= require test-shims +//= require jquery.debug +//= require ember.debug +//= require ember-template-compiler + +//= require_tree ./wizard/tests/fixtures +//= require ./wizard/tests/pretender +//= require_tree ./wizard/tests/helpers +//= require_tree ./wizard/tests/acceptance + +//= require ./wizard/tests/bootstrap diff --git a/assets/javascripts/wizard/application.js.es6 b/assets/javascripts/wizard/application.js.es6 new file mode 100644 index 00000000..012949b3 --- /dev/null +++ b/assets/javascripts/wizard/application.js.es6 @@ -0,0 +1,19 @@ +import { buildResolver } from "discourse-common/resolver"; +import Application from "@ember/application"; +import WizardInitializer from "./lib/initialize/wizard"; +import { isTesting } from "discourse-common/config/environment"; + +export default Application.extend({ + rootElement: "#custom-wizard-main", + Resolver: buildResolver("discourse/plugins/discourse-custom-wizard/wizard"), + + customEvents: { + paste: "paste", + }, + + start() { + if (!isTesting()) { + this.initializer(WizardInitializer); + } + }, +}); diff --git a/assets/javascripts/wizard/components/custom-user-selector.js.es6 b/assets/javascripts/wizard/components/custom-user-selector.js.es6 index b2f08ede..1698a008 100644 --- a/assets/javascripts/wizard/components/custom-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/custom-user-selector.js.es6 @@ -1,6 +1,6 @@ import { default as computed, - observes, + observes } from "discourse-common/utils/decorators"; import { renderAvatar } from "discourse/helpers/user-avatar"; import userSearch from "../lib/user-search"; @@ -55,7 +55,6 @@ export default Ember.TextField.extend({ let self = this, selected = [], groups = [], - currentUser = this.currentUser, includeMentionableGroups = this.get("includeMentionableGroups") === "true", includeMessageableGroups = @@ -66,13 +65,8 @@ export default Ember.TextField.extend({ function excludedUsernames() { // hack works around some issues with allowAny eventing const usernames = self.get("single") ? [] : selected; - - if (currentUser && self.get("excludeCurrentUser")) { - return usernames.concat([currentUser.get("username")]); - } return usernames; } - $(this.element) .val(this.get("usernames")) .autocomplete({ @@ -84,7 +78,6 @@ export default Ember.TextField.extend({ dataSource(term) { const termRegex = /[^a-zA-Z0-9_\-\.@\+]/; - let results = userSearch({ term: term.replace(termRegex, ""), topicId: self.get("topicId"), diff --git a/assets/javascripts/wizard/components/field-validators.js.es6 b/assets/javascripts/wizard/components/field-validators.js.es6 index a315020d..15cfc181 100644 --- a/assets/javascripts/wizard/components/field-validators.js.es6 +++ b/assets/javascripts/wizard/components/field-validators.js.es6 @@ -1,5 +1,8 @@ import Component from "@ember/component"; + export default Component.extend({ + layoutName: 'wizard/templates/components/field-validators', + actions: { perform() { this.appEvents.trigger("custom-wizard:validate"); diff --git a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 index 98ea9270..e5133d4f 100644 --- a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 +++ b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 @@ -10,6 +10,7 @@ import { dasherize } from "@ember/string"; export default WizardFieldValidator.extend({ classNames: ["similar-topics-validator"], + layoutName: 'wizard/templates/components/similar-topics-validator', similarTopics: null, hasInput: notEmpty("field.value"), hasSimilarTopics: notEmpty("similarTopics"), diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6 index 6227cc64..ab442d97 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/wizard/components/validator.js.es6 @@ -6,11 +6,12 @@ import { getToken } from "wizard/lib/ajax"; export default Component.extend({ classNames: ["validator"], classNameBindings: ["isValid", "isInvalid"], + layoutName: 'wizard/templates/components/validator', validMessageKey: null, invalidMessageKey: null, isValid: null, isInvalid: equal("isValid", false), - layoutName: "components/validator", // useful for sharing the template with extending components + layoutName: "wizard/templates/components/validator", init() { this._super(...arguments); diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index faada1f4..e4ce3ec0 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -13,6 +13,7 @@ import { uploadIcon } from "discourse/lib/uploads"; import { dasherize } from "@ember/string"; export default ComposerEditor.extend({ + layoutName: 'wizard/templates/components/wizard-composer-editor', classNameBindings: ["fieldClass"], allowUpload: true, showLink: false, diff --git a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 index a56b7aff..c700d3ce 100644 --- a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 @@ -2,6 +2,7 @@ import Component from "@ember/component"; export default Component.extend({ classNames: ["wizard-composer-hyperlink"], + layoutName: 'wizard/templates/components/wizard-composer-hyperlink', actions: { addLink() { diff --git a/assets/javascripts/wizard/components/wizard-date-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-input.js.es6 index 9c8e4bff..375b7195 100644 --- a/assets/javascripts/wizard/components/wizard-date-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-input.js.es6 @@ -3,6 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators"; export default DateInput.extend({ useNativePicker: false, + layoutName: 'wizard/templates/components/wizard-date-input', @discourseComputed() placeholder() { diff --git a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 index 44b675b0..0fe1e68a 100644 --- a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 @@ -2,6 +2,8 @@ import DateTimeInput from "discourse/components/date-time-input"; import discourseComputed from "discourse-common/utils/decorators"; export default DateTimeInput.extend({ + layoutName: 'wizard/templates/components/wizard-date-time-input', + @discourseComputed("timeFirst", "tabindex") timeTabindex(timeFirst, tabindex) { return timeFirst ? tabindex : tabindex + 1; diff --git a/assets/javascripts/wizard/components/wizard-field-category.js.es6 b/assets/javascripts/wizard/components/wizard-field-category.js.es6 index a7452214..dc20538e 100644 --- a/assets/javascripts/wizard/components/wizard-field-category.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-category.js.es6 @@ -2,6 +2,8 @@ import { observes } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; export default Ember.Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-category', + didInsertElement() { const property = this.field.property || "id"; const value = this.field.value; diff --git a/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 new file mode 100644 index 00000000..f9653cd2 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-checkbox' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 index b49233f2..0aee0d13 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 @@ -7,6 +7,8 @@ import { ajax } from "discourse/lib/ajax"; import { on } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-composer-preview', + @on("init") updatePreview() { if (this.isDestroyed) { diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 index 8b9ecb82..0ef5fe20 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 @@ -5,6 +5,8 @@ import { import EmberObject from "@ember/object"; export default Ember.Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-composer', + showPreview: false, classNameBindings: [ ":wizard-field-composer", diff --git a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 index 2d918636..a916f18e 100644 --- a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-date-time', + @observes("dateTime") setValue() { this.set("field.value", this.dateTime.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-date.js.es6 b/assets/javascripts/wizard/components/wizard-field-date.js.es6 index d5d0a830..a06d582a 100644 --- a/assets/javascripts/wizard/components/wizard-field-date.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-date', + @observes("date") setValue() { this.set("field.value", this.date.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 new file mode 100644 index 00000000..4b8b7e63 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 @@ -0,0 +1,15 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-dropdown', + + keyPress(e) { + e.stopPropagation(); + }, + + actions: { + onChangeValue(value) { + this.set("field.value", value); + }, + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-group.js.es6 b/assets/javascripts/wizard/components/wizard-field-group.js.es6 new file mode 100644 index 00000000..93538071 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-group.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-group' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-number.js.es6 b/assets/javascripts/wizard/components/wizard-field-number.js.es6 new file mode 100644 index 00000000..e7c4d77f --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-number.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-number' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-tag.js.es6 b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 new file mode 100644 index 00000000..45343522 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-tag' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-text.js.es6 b/assets/javascripts/wizard/components/wizard-field-text.js.es6 new file mode 100644 index 00000000..f9d5b056 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-text.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-text', + + keyPress(e) { + e.stopPropagation(); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 new file mode 100644 index 00000000..54865d3c --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-textarea', + + keyPress(e) { + e.stopPropagation(); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-time.js.es6 index 82f9c68b..bf954ec4 100644 --- a/assets/javascripts/wizard/components/wizard-field-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-time.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-time', + @observes("time") setValue() { this.set("field.value", this.time.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 index 876cdf0e..edadb4f0 100644 --- a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 @@ -3,12 +3,16 @@ import Component from "@ember/component"; import { computed } from "@ember/object"; export default Component.extend(UppyUploadMixin, { + layoutName: 'wizard/templates/components/wizard-field-upload', classNames: ["wizard-field-upload"], classNameBindings: ["isImage"], uploading: false, type: computed(function () { return `wizard_${this.field.id}`; }), + id: computed(function () { + return `wizard_field_upload_${this.field.id}`; + }), isImage: computed("field.value.extension", function () { return ( this.field.value && diff --git a/assets/javascripts/wizard/components/wizard-field-url.js.es6 b/assets/javascripts/wizard/components/wizard-field-url.js.es6 new file mode 100644 index 00000000..411b5fe9 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-url.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-url' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 new file mode 100644 index 00000000..c2a32f44 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-user-selector' +}); diff --git a/assets/javascripts/wizard/components/wizard-field.js.es6 b/assets/javascripts/wizard/components/wizard-field.js.es6 new file mode 100644 index 00000000..372ee182 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field.js.es6 @@ -0,0 +1,34 @@ +import Component from "@ember/component"; +import { dasherize } from "@ember/string"; +import discourseComputed from "discourse-common/utils/decorators"; +import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field', + classNameBindings: [":wizard-field", "typeClasses", "field.invalid", "field.id"], + + @discourseComputed("field.type", "field.id") + typeClasses: (type, id) => + `${dasherize(type)}-field ${dasherize(type)}-${dasherize(id)}`, + + @discourseComputed("field.id") + fieldClass: (id) => `field-${dasherize(id)} wizard-focusable`, + + @discourseComputed("field.type", "field.id") + inputComponentName(type, id) { + if (["text_only"].includes(type)) { + return false; + } + return dasherize(type === "component" ? id : `wizard-field-${type}`); + }, + + @discourseComputed("field.translatedDescription") + cookedDescription(description) { + return cook(description); + }, + + @discourseComputed("field.type") + textType(fieldType) { + return ["text", "textarea"].includes(fieldType); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 index cb613107..b7523f9a 100644 --- a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 +++ b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 @@ -3,6 +3,7 @@ import { computed } from "@ember/object"; import { makeArray } from "discourse-common/lib/helpers"; export default ComboBox.extend({ + layoutName: 'wizard/templates/components/wizard-group-selector', content: computed("groups.[]", "field.content.[]", function () { const whitelist = makeArray(this.field.content); return this.groups diff --git a/assets/javascripts/wizard/components/wizard-no-access.js.es6 b/assets/javascripts/wizard/components/wizard-no-access.js.es6 index 57fe9111..78a23aa8 100644 --- a/assets/javascripts/wizard/components/wizard-no-access.js.es6 +++ b/assets/javascripts/wizard/components/wizard-no-access.js.es6 @@ -1,10 +1,21 @@ -import CustomWizard from "../models/custom"; +import CustomWizard from "../models/wizard"; +import discourseComputed from "discourse-common/utils/decorators"; +import Component from "@ember/component"; +import { dasherize } from "@ember/string"; -export default Ember.Component.extend({ - siteName: function () { - /*eslint no-undef:0*/ - return Wizard.SiteSettings.title; - }.property(), +export default Component.extend({ + classNameBindings: [':wizard-no-access', 'reasonClass'], + layoutName: 'wizard/templates/components/wizard-no-access', + + @discourseComputed('reason') + reasonClass(reason) { + return dasherize(reason); + }, + + @discourseComputed + siteName() { + return (this.siteSettings.title || ''); + }, actions: { skip() { diff --git a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 index 687cfa86..c52bfeae 100644 --- a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 +++ b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 @@ -4,6 +4,7 @@ import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["wizard-similar-topics"], + layoutName: 'wizard/templates/components/wizard-similar-topics', showTopics: true, didInsertElement() { diff --git a/assets/javascripts/wizard/components/wizard-step-form.js.es6 b/assets/javascripts/wizard/components/wizard-step-form.js.es6 new file mode 100644 index 00000000..73406b4f --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-step-form.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default Component.extend({ + classNameBindings: [":wizard-step-form", "customStepClass"], + + @discourseComputed("step.id") + customStepClass: (stepId) => `wizard-step-${stepId}`, +}); diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 new file mode 100644 index 00000000..4518afee --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -0,0 +1,247 @@ +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import Component from "@ember/component"; +import I18n from "I18n"; +import getUrl from "discourse-common/lib/get-url"; +import { htmlSafe } from "@ember/template"; +import { schedule } from "@ember/runloop"; +import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; +import { updateCachedWizard } from "discourse/plugins/discourse-custom-wizard/wizard/models/wizard"; +import { alias, not } from "@ember/object/computed"; +import CustomWizard from "../models/wizard"; + +const alreadyWarned = {}; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-step', + classNameBindings: [":wizard-step", "step.id"], + saving: null, + + init() { + this._super(...arguments); + this.set("stylingDropdown", {}); + }, + + didInsertElement() { + this._super(...arguments); + this.autoFocus(); + }, + + @discourseComputed("step.index", "wizard.required") + showQuitButton: (index, required) => (index === 0 && !required), + + showNextButton: not("step.final"), + showDoneButton: alias("step.final"), + + @discourseComputed("step.translatedTitle") + cookedTitle(title) { + return cook(title); + }, + + @discourseComputed("step.translatedDescription") + cookedDescription(description) { + return cook(description); + }, + + @discourseComputed( + "step.index", + "step.displayIndex", + "wizard.totalSteps", + "wizard.completed" + ) + showFinishButton: (index, displayIndex, total, completed) => { + return index !== 0 && displayIndex !== total && completed; + }, + + @discourseComputed("step.index") + showBackButton: (index) => index > 0, + + @discourseComputed("step.banner") + bannerImage(src) { + if (!src) { + return; + } + return getUrl(src); + }, + + @discourseComputed("step.id") + bannerAndDescriptionClass(id) { + return `wizard-banner-and-description wizard-banner-and-description-${id}`; + }, + + @discourseComputed("step.fields.[]") + primaryButtonIndex(fields) { + return fields.length + 1; + }, + + @discourseComputed("step.fields.[]") + secondaryButtonIndex(fields) { + return fields.length + 2; + }, + + @observes("step.id") + _stepChanged() { + this.set("saving", false); + this.autoFocus(); + }, + + @observes("step.message") + _handleMessage: function () { + const message = this.get("step.message"); + this.sendAction("showMessage", message); + }, + + keyPress(event) { + if (event.key === "Enter") { + if (this.showDoneButton) { + this.send("quit"); + } else { + this.send("nextStep"); + } + } + }, + + @discourseComputed("step.index", "wizard.totalSteps") + barStyle(displayIndex, totalSteps) { + let ratio = parseFloat(displayIndex) / parseFloat(totalSteps - 1); + if (ratio < 0) { + ratio = 0; + } + if (ratio > 1) { + ratio = 1; + } + + return htmlSafe(`width: ${ratio * 200}px`); + }, + + @discourseComputed("step.fields") + includeSidebar(fields) { + return !!fields.findBy("show_in_sidebar"); + }, + + autoFocus() { + schedule("afterRender", () => { + const $invalid = $( + ".wizard-field.invalid:nth-of-type(1) .wizard-focusable" + ); + + if ($invalid.length) { + return $invalid.focus(); + } + + $(".wizard-focusable:first").focus(); + }); + }, + + animateInvalidFields() { + schedule("afterRender", () => { + let $element = $( + ".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit" + ); + + if ($element.length) { + $([document.documentElement, document.body]).animate( + { + scrollTop: $element.offset().top - 200, + }, + 400, + function () { + $element.wiggle(2, 100); + } + ); + } + }); + }, + + advance() { + this.set("saving", true); + this.get("step") + .save() + .then((response) => { + updateCachedWizard(CustomWizard.build(response["wizard"])); + + if (response["final"]) { + CustomWizard.finished(response); + } else { + this.sendAction("goNext", response); + } + }) + .catch(() => this.animateInvalidFields()) + .finally(() => this.set("saving", false)); + }, + + keyPress() {}, + + actions: { + quit() { + this.get("wizard").skip(); + }, + + done() { + this.send("nextStep"); + }, + + showMessage(message) { + this.sendAction("showMessage", message); + }, + + stylingDropdownChanged(id, value) { + this.set("stylingDropdown", { id, value }); + }, + + exitEarly() { + const step = this.step; + step.validate(); + + if (step.get("valid")) { + this.set("saving", true); + + step + .save() + .then(() => this.send("quit")) + .finally(() => this.set("saving", false)); + } else { + this.autoFocus(); + } + }, + + backStep() { + if (this.saving) { + return; + } + + this.goBack(); + }, + + nextStep() { + if (this.saving) { + return; + } + + const step = this.step; + const result = step.validate(); + + if (result.warnings.length) { + const unwarned = result.warnings.filter((w) => !alreadyWarned[w]); + if (unwarned.length) { + unwarned.forEach((w) => (alreadyWarned[w] = true)); + return window.bootbox.confirm( + unwarned.map((w) => I18n.t(`wizard.${w}`)).join("\n"), + I18n.t("no_value"), + I18n.t("yes_value"), + (confirmed) => { + if (confirmed) { + this.advance(); + } + } + ); + } + } + + if (step.get("valid")) { + this.advance(); + } else { + this.autoFocus(); + } + }, + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-text-field.js.es6 b/assets/javascripts/wizard/components/wizard-text-field.js.es6 index c522eb2c..3d87be09 100644 --- a/assets/javascripts/wizard/components/wizard-text-field.js.es6 +++ b/assets/javascripts/wizard/components/wizard-text-field.js.es6 @@ -1,5 +1,3 @@ -/* eslint no-undef: 0*/ - import computed from "discourse-common/utils/decorators"; import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction"; import WizardI18n from "../lib/wizard-i18n"; @@ -15,7 +13,7 @@ export default Ember.TextField.extend({ @computed dir() { - if (Wizard.SiteSettings.support_mixed_text_direction) { + if (this.siteSettings.support_mixed_text_direction) { let val = this.value; if (val) { return isRTL(val) ? "rtl" : "ltr"; @@ -26,7 +24,7 @@ export default Ember.TextField.extend({ }, keyUp() { - if (Wizard.SiteSettings.support_mixed_text_direction) { + if (this.siteSettings.support_mixed_text_direction) { let val = this.value; if (isRTL(val)) { this.set("dir", "rtl"); diff --git a/assets/javascripts/wizard/components/wizard-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-time-input.js.es6 index 0bca244d..ec121002 100644 --- a/assets/javascripts/wizard/components/wizard-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-time-input.js.es6 @@ -1,3 +1,5 @@ import TimeInput from "discourse/components/time-input"; -export default TimeInput.extend(); +export default TimeInput.extend({ + layoutName: 'wizard/templates/components/wizard-time-input' +}); diff --git a/assets/javascripts/wizard/controllers/custom.js.es6 b/assets/javascripts/wizard/controllers/custom.js.es6 deleted file mode 100644 index 5f168f3a..00000000 --- a/assets/javascripts/wizard/controllers/custom.js.es6 +++ /dev/null @@ -1,3 +0,0 @@ -export default Ember.Controller.extend({ - queryParams: ["reset"], -}); diff --git a/assets/javascripts/wizard/controllers/custom-step.js.es6 b/assets/javascripts/wizard/controllers/step.js.es6 similarity index 76% rename from assets/javascripts/wizard/controllers/custom-step.js.es6 rename to assets/javascripts/wizard/controllers/step.js.es6 index b44c0fca..4b321173 100644 --- a/assets/javascripts/wizard/controllers/custom-step.js.es6 +++ b/assets/javascripts/wizard/controllers/step.js.es6 @@ -1,7 +1,10 @@ -import StepController from "wizard/controllers/step"; +import Controller from "@ember/controller"; import getUrl from "discourse-common/lib/get-url"; -export default StepController.extend({ +export default Controller.extend({ + wizard: null, + step: null, + actions: { goNext(response) { let nextStepId = response["next_step_id"]; @@ -12,12 +15,12 @@ export default StepController.extend({ const wizardId = this.get("wizard.id"); window.location.href = getUrl(`/w/${wizardId}/steps/${nextStepId}`); } else { - this.transitionToRoute("custom.step", nextStepId); + this.transitionToRoute("step", nextStepId); } }, goBack() { - this.transitionToRoute("custom.step", this.get("step.previous")); + this.transitionToRoute("step", this.get("step.previous")); }, showMessage(message) { diff --git a/assets/javascripts/wizard/controllers/wizard-index.js.es6 b/assets/javascripts/wizard/controllers/wizard-index.js.es6 new file mode 100644 index 00000000..2dfdee40 --- /dev/null +++ b/assets/javascripts/wizard/controllers/wizard-index.js.es6 @@ -0,0 +1,24 @@ +import Controller from "@ember/controller"; +import { or } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; + +const reasons = { + noWizard: "none", + requiresLogin: "requires_login", + notPermitted: "not_permitted", + completed: "completed" +} + +export default Controller.extend({ + noAccess: or('noWizard', 'requiresLogin', 'notPermitted', 'completed'), + + @discourseComputed('noAccessReason') + noAccessI18nKey(reason) { + return reason ? `wizard.${reasons[reason]}` : 'wizard.none'; + }, + + @discourseComputed + noAccessReason() { + return Object.keys(reasons).find(reason => this.get(reason)); + } +}); diff --git a/assets/javascripts/wizard/controllers/wizard.js.es6 b/assets/javascripts/wizard/controllers/wizard.js.es6 new file mode 100644 index 00000000..35f964e2 --- /dev/null +++ b/assets/javascripts/wizard/controllers/wizard.js.es6 @@ -0,0 +1,5 @@ +import Controller from "@ember/controller"; + +export default Controller.extend({ + queryParams: ["reset"], +}); diff --git a/assets/javascripts/wizard/custom-wizard.js.es6 b/assets/javascripts/wizard/custom-wizard.js.es6 deleted file mode 100644 index 8c0a473c..00000000 --- a/assets/javascripts/wizard/custom-wizard.js.es6 +++ /dev/null @@ -1,39 +0,0 @@ -import { buildResolver } from "discourse-common/resolver"; - -export default Ember.Application.extend({ - rootElement: "#custom-wizard-main", - Resolver: buildResolver("wizard"), - - customEvents: { - paste: "paste", - }, - - start() { - Object.keys(requirejs._eak_seen).forEach((key) => { - if (/\/pre\-initializers\//.test(key)) { - const module = requirejs(key, null, null, true); - if (!module) { - throw new Error(key + " must export an initializer."); - } - - const init = module.default; - const oldInitialize = init.initialize; - init.initialize = () => { - oldInitialize.call(this, this.__container__, this); - }; - - this.initializer(init); - } - }); - - Object.keys(requirejs._eak_seen).forEach((key) => { - if (/\/initializers\//.test(key)) { - const module = requirejs(key, null, null, true); - if (!module) { - throw new Error(key + " must export an initializer."); - } - this.initializer(module.default); - } - }); - }, -}); diff --git a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 deleted file mode 100644 index fbbe7d8b..00000000 --- a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 +++ /dev/null @@ -1,199 +0,0 @@ -export default { - name: "custom-wizard-step", - initialize() { - if (window.location.pathname.indexOf("/w/") < 0) { - return; - } - - const CustomWizard = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/custom" - ).default; - const updateCachedWizard = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/custom" - ).updateCachedWizard; - const StepModel = requirejs("wizard/models/step").default; - const StepComponent = requirejs("wizard/components/wizard-step").default; - const ajax = requirejs("wizard/lib/ajax").ajax; - const getUrl = requirejs("discourse-common/lib/get-url").default; - const discourseComputed = requirejs("discourse-common/utils/decorators") - .default; - const cook = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite" - ).cook; - const { schedule } = requirejs("@ember/runloop"); - const { alias, not } = requirejs("@ember/object/computed"); - - StepModel.reopen({ - save() { - const wizardId = this.get("wizardId"); - const fields = {}; - - this.get("fields").forEach((f) => { - if (f.type !== "text_only") { - fields[f.id] = f.value; - } - }); - - return ajax({ - url: `/w/${wizardId}/steps/${this.get("id")}`, - type: "PUT", - data: { fields }, - }).catch((response) => { - if ( - response && - response.responseJSON && - response.responseJSON.errors - ) { - let wizardErrors = []; - response.responseJSON.errors.forEach((err) => { - if (err.field === wizardId) { - wizardErrors.push(err.description); - } else if (err.field) { - this.fieldError(err.field, err.description); - } else if (err) { - wizardErrors.push(err); - } - }); - if (wizardErrors.length) { - this.handleWizardError(wizardErrors.join("\n")); - } - this.animateInvalidFields(); - throw response; - } - - if (response && response.responseText) { - const responseText = response.responseText; - const start = responseText.indexOf(">") + 1; - const end = responseText.indexOf("plugins"); - const message = responseText.substring(start, end); - this.handleWizardError(message); - throw message; - } - }); - }, - - handleWizardError(message) { - this.set("message", { - state: "error", - text: message, - }); - Ember.run.later(() => this.set("message", null), 6000); - }, - }); - - StepComponent.reopen({ - classNameBindings: ["step.id"], - - autoFocus() { - schedule("afterRender", () => { - const $invalid = $( - ".wizard-field.invalid:nth-of-type(1) .wizard-focusable" - ); - - if ($invalid.length) { - return $invalid.focus(); - } - - $(".wizard-focusable:first").focus(); - }); - }, - - animateInvalidFields() { - schedule("afterRender", () => { - let $element = $( - ".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit" - ); - - if ($element.length) { - $([document.documentElement, document.body]).animate( - { - scrollTop: $element.offset().top - 200, - }, - 400, - function () { - $element.wiggle(2, 100); - } - ); - } - }); - }, - - ensureStartsAtTop: function () { - window.scrollTo(0, 0); - }.observes("step.id"), - - showQuitButton: function () { - const index = this.get("step.index"); - const required = this.get("wizard.required"); - return index === 0 && !required; - }.property("step.index", "wizard.required"), - - cookedTitle: function () { - return cook(this.get("step.title")); - }.property("step.title"), - - cookedDescription: function () { - return cook(this.get("step.description")); - }.property("step.description"), - - bannerImage: function () { - const src = this.get("step.banner"); - if (!src) { - return; - } - return getUrl(src); - }.property("step.banner"), - - @discourseComputed("step.fields.[]") - primaryButtonIndex(fields) { - return fields.length + 1; - }, - - @discourseComputed("step.fields.[]") - secondaryButtonIndex(fields) { - return fields.length + 2; - }, - - handleMessage: function () { - const message = this.get("step.message"); - this.sendAction("showMessage", message); - }.observes("step.message"), - - showNextButton: not("step.final"), - showDoneButton: alias("step.final"), - - advance() { - this.set("saving", true); - this.get("step") - .save() - .then((response) => { - updateCachedWizard(CustomWizard.build(response["wizard"])); - - if (response["final"]) { - CustomWizard.finished(response); - } else { - this.sendAction("goNext", response); - } - }) - .catch(() => this.animateInvalidFields()) - .finally(() => this.set("saving", false)); - }, - - keyPress() {}, - - actions: { - quit() { - this.get("wizard").skip(); - }, - - done() { - this.send("nextStep"); - }, - - showMessage(message) { - this.sendAction("showMessage", message); - }, - }, - }); - }, -}; diff --git a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 deleted file mode 100644 index f2095827..00000000 --- a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 +++ /dev/null @@ -1,126 +0,0 @@ -export default { - name: "custom-routes", - initialize(app) { - if (window.location.pathname.indexOf("/w/") < 0) { - return; - } - - const EmberObject = requirejs("@ember/object").default; - const Router = requirejs("wizard/router").default; - const ApplicationRoute = requirejs("wizard/routes/application").default; - const getUrl = requirejs("discourse-common/lib/get-url").default; - const Store = requirejs("discourse/services/store").default; - const registerRawHelpers = requirejs( - "discourse-common/lib/raw-handlebars-helpers" - ).registerRawHelpers; - const createHelperContext = requirejs("discourse-common/lib/helpers") - .createHelperContext; - const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars") - .default; - const Handlebars = requirejs("handlebars").default; - const Site = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/site" - ).default; - const RestAdapter = requirejs("discourse/adapters/rest").default; - const Session = requirejs("discourse/models/session").default; - const setDefaultOwner = requirejs("discourse-common/lib/get-owner") - .setDefaultOwner; - const messageBus = requirejs("message-bus-client").default; - const getToken = requirejs("wizard/lib/ajax").getToken; - const setEnvironment = requirejs("discourse-common/config/environment") - .setEnvironment; - const container = app.__container__; - Discourse.Model = EmberObject.extend(); - Discourse.__container__ = container; - setDefaultOwner(container); - registerRawHelpers(RawHandlebars, Handlebars); - - // IE11 Polyfill - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill - if (!Object.entries) { - Object.entries = function (obj) { - let ownProps = Object.keys(obj), - i = ownProps.length, - resArray = new Array(i); // preallocate the Array - while (i--) { - resArray[i] = [ownProps[i], obj[ownProps[i]]]; - } - - return resArray; - }; - } - - Object.keys(Ember.TEMPLATES).forEach((k) => { - if (k.indexOf("select-kit") === 0) { - let template = Ember.TEMPLATES[k]; - define(k, () => template); - } - }); - - const targets = ["controller", "component", "route", "model", "adapter"]; - /*eslint no-undef: 0*/ - const siteSettings = Wizard.SiteSettings; - app.register("site-settings:main", siteSettings, { instantiate: false }); - createHelperContext({ siteSettings }); - targets.forEach((t) => app.inject(t, "siteSettings", "site-settings:main")); - - app.register("message-bus:main", messageBus, { instantiate: false }); - targets.forEach((t) => app.inject(t, "messageBus", "message-bus:main")); - - app.register("service:store", Store); - targets.forEach((t) => app.inject(t, "store", "service:store")); - targets.forEach((t) => app.inject(t, "appEvents", "service:app-events")); - - app.register("adapter:rest", RestAdapter); - - const site = Site.current(); - app.register("site:main", site, { instantiate: false }); - targets.forEach((t) => app.inject(t, "site", "site:main")); - - site.set("can_create_tag", false); - app.register("session:main", Session.current(), { instantiate: false }); - targets.forEach((t) => app.inject(t, "session", "session:main")); - - createHelperContext({ - siteSettings: container.lookup("site-settings:main"), - currentUser: container.lookup("current-user:main"), - site: container.lookup("site:main"), - session: container.lookup("session:main"), - capabilities: container.lookup("capabilities:main"), - }); - - const session = container.lookup("session:main"); - const setupData = document.getElementById("data-discourse-setup").dataset; - session.set("highlightJsPath", setupData.highlightJsPath); - setEnvironment(setupData.environment); - - Router.reopen({ - rootURL: getUrl("/w/"), - }); - - Router.map(function () { - this.route("custom", { path: "/:wizard_id" }, function () { - this.route("steps"); - this.route("step", { path: "/steps/:step_id" }); - }); - }); - - ApplicationRoute.reopen({ - redirect() { - this.transitionTo("custom"); - }, - model() {}, - }); - - // Add a CSRF token to all AJAX requests - let token = getToken(); - session.set("csrfToken", token); - let callbacks = $.Callbacks(); - $.ajaxPrefilter(callbacks.fire); - - callbacks.add(function (options, originalOptions, xhr) { - if (!options.crossDomain) { - xhr.setRequestHeader("X-CSRF-Token", session.get("csrfToken")); - } - }); - }, -}; diff --git a/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 new file mode 100644 index 00000000..022ac48e --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 @@ -0,0 +1,12 @@ +export default { + run(app, container) { + const { createHelperContext } = requirejs("discourse-common/lib/helpers"); + + createHelperContext({ + siteSettings: container.lookup('site-settings:main'), + site: container.lookup("site:main"), + session: container.lookup("session:main"), + capabilities: container.lookup("capabilities:main"), + }); + } +} diff --git a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 new file mode 100644 index 00000000..d31efb4d --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 @@ -0,0 +1,49 @@ +export default { + run(app, container) { + const Store = requirejs("discourse/services/store").default; + const Site = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/models/site" + ).default; + const Session = requirejs("discourse/models/session").default; + const RestAdapter = requirejs("discourse/adapters/rest").default; + const messageBus = requirejs("message-bus-client").default; + const sniffCapabilites = requirejs("discourse/pre-initializers/sniff-capabilities").default; + const site = Site.current(); + const session = Session.current(); + + const registrations = [ + ["site-settings:main", app.SiteSettings, false], + ["message-bus:main", messageBus, false], + ["site:main", site, false], + ["session:main", session, false], + ["service:store", Store, true], + ["adapter:rest", RestAdapter, true] + ]; + + registrations.forEach(registration => { + if (!app.hasRegistration(registration[0])) { + app.register(registration[0], registration[1], { instantiate: registration[2] }); + } + }); + + const targets = ["controller", "component", "route", "model", "adapter", "mixin"]; + const injections = [ + ["siteSettings", "site-settings:main"], + ["messageBus", "message-bus:main"], + ["site", "site:main"], + ["session", "session:main"], + ["store", "service:store"], + ["appEvents", "service:app-events"] + ]; + + injections.forEach(injection => { + targets.forEach((t) => app.inject(t, injection[0], injection[1])); + }); + + if (!app.hasRegistration("capabilities:main")) { + sniffCapabilites.initialize(null, app); + } + + site.set("can_create_tag", false); + } +} diff --git a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 similarity index 60% rename from assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 rename to assets/javascripts/wizard/lib/initialize/patch-components.js.es6 index e397af5f..529a4cd9 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 @@ -1,93 +1,25 @@ -import { dasherize } from "@ember/string"; -import discourseComputed from "discourse-common/utils/decorators"; - export default { - name: "custom-wizard-field", - initialize() { - if (window.location.pathname.indexOf("/w/") < 0) { - return; + run(app, container) { + const getToken = requirejs("wizard/lib/ajax").getToken; + const isTesting = requirejs("discourse-common/config/environment").isTesting; + + if (!isTesting) { + // Add a CSRF token to all AJAX requests + let token = getToken(); + session.set("csrfToken", token); + let callbacks = $.Callbacks(); + $.ajaxPrefilter(callbacks.fire); + + callbacks.add(function (options, originalOptions, xhr) { + if (!options.crossDomain) { + xhr.setRequestHeader("X-CSRF-Token", session.get("csrfToken")); + } + }); } - const FieldComponent = requirejs("wizard/components/wizard-field").default; - const FieldModel = requirejs("wizard/models/wizard-field").default; - const { cook } = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite" - ); const DEditor = requirejs("discourse/components/d-editor").default; const { clipboardHelpers } = requirejs("discourse/lib/utilities"); const toMarkdown = requirejs("discourse/lib/to-markdown").default; - - FieldComponent.reopen({ - classNameBindings: ["field.id"], - - @discourseComputed("field.type") - textType(fieldType) { - return ["text", "textarea"].includes(fieldType); - }, - - cookedDescription: function () { - return cook(this.get("field.description")); - }.property("field.description"), - - inputComponentName: function () { - const type = this.get("field.type"); - const id = this.get("field.id"); - if (["text_only"].includes(type)) { - return false; - } - return dasherize(type === "component" ? id : `wizard-field-${type}`); - }.property("field.type", "field.id"), - }); - - const StandardFieldValidation = [ - "text", - "number", - "textarea", - "dropdown", - "tag", - "image", - "user_selector", - "text_only", - "composer", - "category", - "group", - "date", - "time", - "date_time", - ]; - - FieldModel.reopen({ - check() { - if (this.customCheck) { - return this.customCheck(); - } - - let valid = this.valid; - - if (!this.required) { - this.setValid(true); - return true; - } - - const val = this.get("value"); - const type = this.get("type"); - - if (type === "checkbox") { - valid = val; - } else if (type === "upload") { - valid = val && val.id > 0; - } else if (StandardFieldValidation.indexOf(type) > -1) { - valid = val && val.toString().length > 0; - } else if (type === "url") { - valid = true; - } - - this.setValid(valid); - - return valid; - }, - }); - const isInside = (text, regex) => { const matches = text.match(regex); return matches && matches.length % 2; @@ -194,5 +126,19 @@ export default { } }, }); - }, + + // IE11 Polyfill - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill + if (!Object.entries) { + Object.entries = function (obj) { + let ownProps = Object.keys(obj), + i = ownProps.length, + resArray = new Array(i); // preallocate the Array + while (i--) { + resArray[i] = [ownProps[i], obj[ownProps[i]]]; + } + + return resArray; + }; + } + } }; diff --git a/assets/javascripts/wizard/lib/initialize/register-files.js.es6 b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 new file mode 100644 index 00000000..8d4b850e --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 @@ -0,0 +1,26 @@ +export default { + run(app, container) { + const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars").default; + const Handlebars = requirejs("handlebars").default; + const registerRawHelpers = requirejs("discourse-common/lib/raw-handlebars-helpers").registerRawHelpers; + const { registerHelpers } = requirejs("discourse-common/lib/helpers"); + const jqueryPlugins = requirejs("discourse/initializers/jquery-plugins").default; + + Object.keys(Ember.TEMPLATES).forEach((k) => { + if (k.indexOf("select-kit") === 0) { + let template = Ember.TEMPLATES[k]; + define(k, () => template); + } + }); + + Object.keys(requirejs.entries).forEach((entry) => { + if (/\/helpers\//.test(entry)) { + requirejs(entry, null, null, true); + } + }); + + registerRawHelpers(RawHandlebars, Handlebars); + registerHelpers(app); + jqueryPlugins.initialize(container, app); + } +} diff --git a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 new file mode 100644 index 00000000..134257a1 --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 @@ -0,0 +1,49 @@ +export default { + name: "custom-wizard", + initialize(app) { + const isTesting = requirejs("discourse-common/config/environment").isTesting; + const isWizard = window.location.pathname.indexOf("/w/") > -1; + + if (!isWizard && !isTesting()) { + return; + } + + const container = app.__container__; + const setDefaultOwner = requirejs("discourse-common/lib/get-owner").setDefaultOwner; + setDefaultOwner(container); + + if (!isTesting()) { + const PreloadStore = requirejs("discourse/lib/preload-store").default; + + let preloaded; + const preloadedDataElement = document.getElementById("data-preloaded-wizard"); + if (preloadedDataElement) { + preloaded = JSON.parse(preloadedDataElement.dataset.preloadedWizard); + } + + Object.keys(preloaded).forEach(function (key) { + PreloadStore.store(key, JSON.parse(preloaded[key])); + }); + + app.SiteSettings = PreloadStore.get("siteSettings"); + } + + const setEnvironment = requirejs("discourse-common/config/environment").setEnvironment; + const setupData = document.getElementById("data-discourse-setup").dataset; + setEnvironment(setupData.environment); + + const Session = requirejs("discourse/models/session").default; + const session = Session.current(); + session.set("highlightJsPath", setupData.highlightJsPath); + + [ + 'register-files', + 'inject-objects', + 'create-contexts', + 'patch-components' + ].forEach(fileName => { + const initializer = requirejs(`discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/${fileName}`).default; + initializer.run(app, container); + }); + } +}; diff --git a/assets/javascripts/wizard/lib/utilities-lite.js.es6 b/assets/javascripts/wizard/lib/utilities-lite.js.es6 deleted file mode 100644 index 19517b2b..00000000 --- a/assets/javascripts/wizard/lib/utilities-lite.js.es6 +++ /dev/null @@ -1,71 +0,0 @@ -// lite version of discourse/lib/utilities - -export function determinePostReplaceSelection({ - selection, - needle, - replacement, -}) { - const diff = - replacement.end - replacement.start - (needle.end - needle.start); - - if (selection.end <= needle.start) { - // Selection ends (and starts) before needle. - return { start: selection.start, end: selection.end }; - } else if (selection.start <= needle.start) { - // Selection starts before needle... - if (selection.end < needle.end) { - // ... and ends inside needle. - return { start: selection.start, end: needle.start }; - } else { - // ... and spans needle completely. - return { start: selection.start, end: selection.end + diff }; - } - } else if (selection.start < needle.end) { - // Selection starts inside needle... - if (selection.end <= needle.end) { - // ... and ends inside needle. - return { start: replacement.end, end: replacement.end }; - } else { - // ... and spans end of needle. - return { start: replacement.end, end: selection.end + diff }; - } - } else { - // Selection starts (and ends) behind needle. - return { start: selection.start + diff, end: selection.end + diff }; - } -} - -const toArray = (items) => { - items = items || []; - - if (!Array.isArray(items)) { - return Array.from(items); - } - - return items; -}; - -export function clipboardData(e, canUpload) { - const clipboard = - e.clipboardData || - e.originalEvent.clipboardData || - e.delegatedEvent.originalEvent.clipboardData; - - const types = toArray(clipboard.types); - let files = toArray(clipboard.files); - - if (types.includes("Files") && files.length === 0) { - // for IE - files = toArray(clipboard.items).filter((i) => i.kind === "file"); - } - - canUpload = files && canUpload && !types.includes("text/plain"); - const canUploadImage = - canUpload && files.filter((f) => f.type.match("^image/"))[0]; - const canPasteHtml = - Discourse.SiteSettings.enable_rich_text_paste && - types.includes("text/html") && - !canUploadImage; - - return { clipboard, types, canUpload, canPasteHtml }; -} diff --git a/assets/javascripts/wizard/models/field.js.es6 b/assets/javascripts/wizard/models/field.js.es6 new file mode 100644 index 00000000..5f76074e --- /dev/null +++ b/assets/javascripts/wizard/models/field.js.es6 @@ -0,0 +1,79 @@ +import EmberObject from "@ember/object"; +import ValidState from "wizard/mixins/valid-state"; +import discourseComputed from "discourse-common/utils/decorators"; +import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; + +const StandardFieldValidation = [ + "text", + "number", + "textarea", + "dropdown", + "tag", + "image", + "user_selector", + "text_only", + "composer", + "category", + "group", + "date", + "time", + "date_time", +]; + +export default EmberObject.extend(ValidState, { + id: null, + type: null, + value: null, + required: null, + warning: null, + + @discourseComputed("wizardId", "stepId", "id") + i18nKey(wizardId, stepId, id) { + return `${wizardId}.${stepId}.${id}`; + }, + + @discourseComputed("i18nKey", "label") + translatedLabel(i18nKey, label) { + return translatedText(`${i18nKey}.label`, label); + }, + + @discourseComputed("i18nKey", "placeholder") + translatedPlaceholder(i18nKey, placeholder) { + return translatedText(`${i18nKey}.placeholder`, placeholder); + }, + + @discourseComputed("i18nKey", "description") + translatedDescription(i18nKey, description) { + return translatedText(`${i18nKey}.description`, description); + }, + + check() { + if (this.customCheck) { + return this.customCheck(); + } + + let valid = this.valid; + + if (!this.required) { + this.setValid(true); + return true; + } + + const val = this.get("value"); + const type = this.get("type"); + + if (type === "checkbox") { + valid = val; + } else if (type === "upload") { + valid = val && val.id > 0; + } else if (StandardFieldValidation.indexOf(type) > -1) { + valid = val && val.toString().length > 0; + } else if (type === "url") { + valid = true; + } + + this.setValid(valid); + + return valid; + } +}); diff --git a/assets/javascripts/wizard/models/site.js.es6 b/assets/javascripts/wizard/models/site.js.es6 index c15ce98f..96837ff2 100644 --- a/assets/javascripts/wizard/models/site.js.es6 +++ b/assets/javascripts/wizard/models/site.js.es6 @@ -1,10 +1,11 @@ import Site from "discourse/models/site"; +import { getOwner } from "discourse-common/lib/get-owner"; export default Site.reopenClass({ // There is no site data actually loaded by the CW yet. This placeholder is // needed by imported classes createCurrent() { - const store = Discourse.__container__.lookup("service:store"); + const store = getOwner(this).lookup("service:store"); return store.createRecord("site", {}); }, }); diff --git a/assets/javascripts/wizard/models/step.js.es6 b/assets/javascripts/wizard/models/step.js.es6 new file mode 100644 index 00000000..e18657b5 --- /dev/null +++ b/assets/javascripts/wizard/models/step.js.es6 @@ -0,0 +1,114 @@ +import EmberObject from "@ember/object"; +import ValidState from "wizard/mixins/valid-state"; +import { ajax } from "wizard/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; +import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; + +export default EmberObject.extend(ValidState, { + id: null, + + @discourseComputed("wizardId", "id") + i18nKey(wizardId, stepId) { + return `${wizardId}.${stepId}`; + }, + + @discourseComputed("i18nKey", "title") + translatedTitle(i18nKey, title) { + return translatedText(`${i18nKey}.title`, title); + }, + + @discourseComputed("i18nKey", "description") + translatedDescription(i18nKey, description) { + return translatedText(`${i18nKey}.description`, description); + }, + + @discourseComputed("index") + displayIndex: (index) => index + 1, + + @discourseComputed("fields.[]") + fieldsById(fields) { + const lookup = {}; + fields.forEach((field) => (lookup[field.get("id")] = field)); + return lookup; + }, + + validate() { + let allValid = true; + const result = { warnings: [] }; + + this.fields.forEach((field) => { + allValid = allValid && field.check(); + const warning = field.get("warning"); + if (warning) { + result.warnings.push(warning); + } + }); + + this.setValid(allValid); + + return result; + }, + + fieldError(id, description) { + const field = this.fields.findBy("id", id); + if (field) { + field.setValid(false, description); + } + }, + + save() { + const wizardId = this.get("wizardId"); + const fields = {}; + + this.get("fields").forEach((f) => { + if (f.type !== "text_only") { + fields[f.id] = f.value; + } + }); + + return ajax({ + url: `/w/${wizardId}/steps/${this.get("id")}`, + type: "PUT", + data: { fields }, + }).catch((response) => { + if ( + response && + response.responseJSON && + response.responseJSON.errors + ) { + let wizardErrors = []; + response.responseJSON.errors.forEach((err) => { + if (err.field === wizardId) { + wizardErrors.push(err.description); + } else if (err.field) { + this.fieldError(err.field, err.description); + } else if (err) { + wizardErrors.push(err); + } + }); + if (wizardErrors.length) { + this.handleWizardError(wizardErrors.join("\n")); + } + this.animateInvalidFields(); + throw response; + } + + if (response && response.responseText) { + const responseText = response.responseText; + const start = responseText.indexOf(">") + 1; + const end = responseText.indexOf("plugins"); + const message = responseText.substring(start, end); + this.handleWizardError(message); + throw message; + } + }); + }, + + handleWizardError(message) { + this.set("message", { + state: "error", + text: message, + }); + Ember.run.later(() => this.set("message", null), 6000); + }, +}); diff --git a/assets/javascripts/wizard/models/custom.js.es6 b/assets/javascripts/wizard/models/wizard.js.es6 similarity index 94% rename from assets/javascripts/wizard/models/custom.js.es6 rename to assets/javascripts/wizard/models/wizard.js.es6 index 0e7c557f..7fbe2c10 100644 --- a/assets/javascripts/wizard/models/custom.js.es6 +++ b/assets/javascripts/wizard/models/wizard.js.es6 @@ -1,9 +1,9 @@ import { default as computed } from "discourse-common/utils/decorators"; import getUrl from "discourse-common/lib/get-url"; -import WizardField from "wizard/models/wizard-field"; +import Field from "./field"; import { ajax } from "wizard/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import Step from "wizard/models/step"; +import Step from "./step"; import EmberObject from "@ember/object"; import Site from "./site"; @@ -73,7 +73,11 @@ CustomWizard.reopenClass({ } }); - stepObj.fields = stepObj.fields.map((f) => WizardField.create(f)); + stepObj.fields = stepObj.fields.map((f) => { + f.wizardId = wizardJson.id; + f.stepId = stepObj.id; + return Field.create(f); + }); return stepObj; }) diff --git a/assets/javascripts/wizard/router.js.es6 b/assets/javascripts/wizard/router.js.es6 new file mode 100644 index 00000000..5178c91f --- /dev/null +++ b/assets/javascripts/wizard/router.js.es6 @@ -0,0 +1,17 @@ +import EmberRouter from "@ember/routing/router"; +import getUrl from "discourse-common/lib/get-url"; +import { isTesting } from "discourse-common/config/environment"; + +const Router = EmberRouter.extend({ + rootURL: isTesting() ? getUrl("/") : getUrl("/w/"), + location: isTesting() ? "none" : "history", +}); + +Router.map(function () { + this.route("wizard", { path: "/:wizard_id" }, function () { + this.route("steps", { path: "/steps", resetNamespace: true }); + this.route("step", { path: "/steps/:step_id", resetNamespace: true }); + }); +}); + +export default Router; diff --git a/assets/javascripts/wizard/routes/application.js.es6 b/assets/javascripts/wizard/routes/application.js.es6 new file mode 100644 index 00000000..0051f5ce --- /dev/null +++ b/assets/javascripts/wizard/routes/application.js.es6 @@ -0,0 +1,3 @@ +import Route from "@ember/routing/route"; + +export default Route.extend(); diff --git a/assets/javascripts/wizard/routes/custom-steps.js.es6 b/assets/javascripts/wizard/routes/custom-steps.js.es6 deleted file mode 100644 index c58d1251..00000000 --- a/assets/javascripts/wizard/routes/custom-steps.js.es6 +++ /dev/null @@ -1,5 +0,0 @@ -export default Ember.Route.extend({ - redirect() { - this.transitionTo("custom.index"); - }, -}); diff --git a/assets/javascripts/wizard/routes/index.js.es6 b/assets/javascripts/wizard/routes/index.js.es6 new file mode 100644 index 00000000..b24b3631 --- /dev/null +++ b/assets/javascripts/wizard/routes/index.js.es6 @@ -0,0 +1,9 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + beforeModel(transition) { + if (transition.intent.params) { + this.transitionTo("wizard"); + } + }, +}); diff --git a/assets/javascripts/wizard/routes/custom-step.js.es6 b/assets/javascripts/wizard/routes/step.js.es6 similarity index 78% rename from assets/javascripts/wizard/routes/custom-step.js.es6 rename to assets/javascripts/wizard/routes/step.js.es6 index 8088727a..2454fc95 100644 --- a/assets/javascripts/wizard/routes/custom-step.js.es6 +++ b/assets/javascripts/wizard/routes/step.js.es6 @@ -1,7 +1,8 @@ import WizardI18n from "../lib/wizard-i18n"; -import { getCachedWizard } from "../models/custom"; +import { getCachedWizard } from "../models/wizard"; +import Route from "@ember/routing/route"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel() { this.set("wizard", getCachedWizard()); }, @@ -19,11 +20,15 @@ export default Ember.Route.extend({ afterModel(model) { if (model.completed) { - return this.transitionTo("index"); + return this.transitionTo("wizard.index"); } return model.set("wizardId", this.wizard.id); }, + renderTemplate() { + this.render('wizard/templates/step'); + }, + setupController(controller, model) { let props = { step: model, diff --git a/assets/javascripts/wizard/routes/steps.js.es6 b/assets/javascripts/wizard/routes/steps.js.es6 new file mode 100644 index 00000000..6f35d152 --- /dev/null +++ b/assets/javascripts/wizard/routes/steps.js.es6 @@ -0,0 +1,7 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + redirect() { + this.transitionTo("wizard.index"); + }, +}); diff --git a/assets/javascripts/wizard/routes/custom-index.js.es6 b/assets/javascripts/wizard/routes/wizard-index.js.es6 similarity index 50% rename from assets/javascripts/wizard/routes/custom-index.js.es6 rename to assets/javascripts/wizard/routes/wizard-index.js.es6 index a8abc152..16b1140a 100644 --- a/assets/javascripts/wizard/routes/custom-index.js.es6 +++ b/assets/javascripts/wizard/routes/wizard-index.js.es6 @@ -1,10 +1,11 @@ -import { getCachedWizard } from "../models/custom"; +import { getCachedWizard } from "../models/wizard"; +import Route from "@ember/routing/route"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel() { const wizard = getCachedWizard(); - if (wizard && wizard.permitted && !wizard.completed && wizard.start) { - this.replaceWith("custom.step", wizard.start); + if (wizard && wizard.user && wizard.permitted && !wizard.completed && wizard.start) { + this.replaceWith("step", wizard.start); } }, @@ -12,6 +13,10 @@ export default Ember.Route.extend({ return getCachedWizard(); }, + renderTemplate() { + this.render('wizard/templates/wizard-index'); + }, + setupController(controller, model) { if (model && model.id) { const completed = model.get("completed"); @@ -19,17 +24,20 @@ export default Ember.Route.extend({ const wizardId = model.get("id"); const user = model.get("user"); const name = model.get("name"); + const requiresLogin = !user; + const notPermitted = !permitted; - controller.setProperties({ - requiresLogin: !user, + const props = { + requiresLogin, user, name, completed, - notPermitted: !permitted, + notPermitted, wizardId, - }); + }; + controller.setProperties(props); } else { controller.set("noWizard", true); } - }, + } }); diff --git a/assets/javascripts/wizard/routes/custom.js.es6 b/assets/javascripts/wizard/routes/wizard.js.es6 similarity index 59% rename from assets/javascripts/wizard/routes/custom.js.es6 rename to assets/javascripts/wizard/routes/wizard.js.es6 index a312db3a..2910ee6d 100644 --- a/assets/javascripts/wizard/routes/custom.js.es6 +++ b/assets/javascripts/wizard/routes/wizard.js.es6 @@ -1,37 +1,26 @@ -/* eslint no-undef: 0*/ - -import { findCustomWizard, updateCachedWizard } from "../models/custom"; +import { findCustomWizard, updateCachedWizard } from "../models/wizard"; import { ajax } from "wizard/lib/ajax"; import WizardI18n from "../lib/wizard-i18n"; +import Route from "@ember/routing/route"; +import { scheduleOnce } from "@ember/runloop"; +import { getOwner } from "discourse-common/lib/get-owner"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel(transition) { - this.set("queryParams", transition.intent.queryParams); + if (transition.intent.queryParams) { + this.set("queryParams", transition.intent.queryParams); + } }, model(params) { return findCustomWizard(params.wizard_id, this.get("queryParams")); }, - renderTemplate() { - this.render("custom"); - const wizardModel = this.modelFor("custom"); - const stepModel = this.modelFor("custom.step"); - - if ( - wizardModel.resume_on_revisit && - wizardModel.submission_last_updated_at && - stepModel.index > 0 - ) { - this.showDialog(wizardModel); - } - }, - showDialog(wizardModel) { const title = WizardI18n("wizard.incomplete_submission.title", { date: moment(wizardModel.submission_last_updated_at).format( "MMMM Do YYYY" - ), + ) }); const buttons = [ @@ -57,28 +46,36 @@ export default Ember.Route.extend({ afterModel(model) { updateCachedWizard(model); + }, - return ajax({ - url: `/site/settings`, - type: "GET", - }).then((result) => { - $.extend(Wizard.SiteSettings, result); - }); + renderTemplate() { + this.render('wizard/templates/wizard'); }, setupController(controller, model) { - const background = model ? model.get("background") : "AliceBlue"; - Ember.run.scheduleOnce("afterRender", this, function () { - $("body.custom-wizard").css("background", background); + const background = model ? model.get("background") : ""; + + scheduleOnce("afterRender", this, function () { + $("body").css("background", background); if (model && model.id) { - $("#custom-wizard-main").addClass(model.id.dasherize()); + $(getOwner(this).rootElement).addClass(model.id.dasherize()); } }); + controller.setProperties({ customWizard: true, - logoUrl: Wizard.SiteSettings.logo_small, + logoUrl: this.siteSettings.logo_small, reset: null, }); + + const stepModel = this.modelFor("step"); + if ( + model.resume_on_revisit && + model.submission_last_updated_at && + stepModel.index > 0 + ) { + this.showDialog(model); + } }, }); diff --git a/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs index 7ce4c298..42fc63e8 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs @@ -3,6 +3,7 @@ value=field.value content=field.content tabindex=field.tabindex + onChange=(action "onChangeValue") options=(hash none="select_kit.default_header_text" )}} diff --git a/assets/javascripts/wizard/templates/custom.index.hbs b/assets/javascripts/wizard/templates/custom.index.hbs deleted file mode 100644 index 3cd02e05..00000000 --- a/assets/javascripts/wizard/templates/custom.index.hbs +++ /dev/null @@ -1,15 +0,0 @@ -{{#if noWizard}} - {{wizard-no-access text=(wizard-i18n "wizard.none") wizardId=wizardId}} -{{else}} - {{#if requiresLogin}} - {{wizard-no-access text=(wizard-i18n "wizard.requires_login") wizardId=wizardId}} - {{else}} - {{#if notPermitted}} - {{wizard-no-access text=(wizard-i18n "wizard.not_permitted") wizardId=wizardId}} - {{else}} - {{#if completed}} - {{wizard-no-access text=(wizard-i18n "wizard.completed") wizardId=wizardId}} - {{/if}} - {{/if}} - {{/if}} -{{/if}} diff --git a/assets/javascripts/wizard/templates/index.hbs b/assets/javascripts/wizard/templates/index.hbs new file mode 100644 index 00000000..c24cd689 --- /dev/null +++ b/assets/javascripts/wizard/templates/index.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/assets/javascripts/wizard/templates/custom.step.hbs b/assets/javascripts/wizard/templates/step.hbs similarity index 100% rename from assets/javascripts/wizard/templates/custom.step.hbs rename to assets/javascripts/wizard/templates/step.hbs diff --git a/assets/javascripts/wizard/templates/wizard-index.hbs b/assets/javascripts/wizard/templates/wizard-index.hbs new file mode 100644 index 00000000..a8f1b9ba --- /dev/null +++ b/assets/javascripts/wizard/templates/wizard-index.hbs @@ -0,0 +1,3 @@ +{{#if noAccess}} + {{wizard-no-access text=(wizard-i18n noAccessI18nKey) wizardId=wizardId reason=noAccessReason}} +{{/if}} diff --git a/assets/javascripts/wizard/templates/custom.hbs b/assets/javascripts/wizard/templates/wizard.hbs similarity index 86% rename from assets/javascripts/wizard/templates/custom.hbs rename to assets/javascripts/wizard/templates/wizard.hbs index 4701fec2..f6d6127e 100644 --- a/assets/javascripts/wizard/templates/custom.hbs +++ b/assets/javascripts/wizard/templates/wizard.hbs @@ -1,7 +1,3 @@ -{{#if showCanvas}} - {{wizard-canvas}} -{{/if}} -
{{outlet}} diff --git a/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 new file mode 100644 index 00000000..f73d1ab7 --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 @@ -0,0 +1,135 @@ +import { + visit, + click, + fillIn, + triggerKeyEvent +} from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { + query, + count, + visible, + server +} from "../helpers/acceptance"; +import { + allFieldsWizard, + getWizard +} from "../helpers/wizard"; +import tagsJson from "../fixtures/tags"; +import usersJson from "../fixtures/users"; +import { response } from "../pretender"; + +acceptance("Field | Fields", [ getWizard(allFieldsWizard) ], + function(hooks) { + test("Text", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-field.text-field input.wizard-focusable")); + }); + + test("Textarea", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.textarea-field textarea.wizard-focusable")); + }); + + test("Composer", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.composer-field .wizard-field-composer textarea")); + assert.strictEqual(count(".wizard-field.composer-field .d-editor-button-bar button"), 8); + assert.ok(visible(".wizard-btn.toggle-preview")); + + await fillIn(".wizard-field.composer-field .wizard-field-composer textarea", "Input in composer"); + await click(".wizard-btn.toggle-preview"); + assert.strictEqual(query('.wizard-field.composer-field .wizard-field-composer .d-editor-preview-wrapper p').textContent.trim(), "Input in composer"); + }); + + test("Text Only", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.text-only-field label.field-label")); + }); + + test("Date", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.date-field input.date-picker")); + await click(".wizard-field.date-field input.date-picker"); + assert.ok(visible(".wizard-field.date-field .pika-single")); + }); + + test("Time", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.time-field .d-time-input .select-kit")); + await click(".wizard-field.time-field .d-time-input .select-kit .select-kit-header"); + assert.ok(visible(".wizard-field.time-field .select-kit-collection")); + }); + + test("Date Time", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.date-time-field .d-date-time-input .select-kit")); + await click(".wizard-field.date-time-field .d-date-input input.date-picker"); + assert.ok(visible(".wizard-field.date-time-field .d-date-input .pika-single")); + await click(".wizard-field.date-time-field .d-time-input .select-kit .select-kit-header"); + assert.ok(visible(".wizard-field.date-time-field .select-kit-collection")); + }); + + test("Number", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.number-field input[type='number']")); + }); + + test("Checkbox", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.checkbox-field input[type='checkbox']")); + }); + + test("Url", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.url-field input[type='text']")); + }); + + test("Upload", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.upload-field label.wizard-btn-upload-file")); + assert.ok(exists(".wizard-field.upload-field input.hidden-upload-field")); + }); + + test("Dropdown", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.dropdown-field .single-select-header")); + await click(".wizard-field.dropdown-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.dropdown-field .select-kit-collection li"), 3); + }); + + test("Tag", async function (assert) { + server.get("/tags/filter/search", () => (response(200, { results: tagsJson['tags']}))); + await visit("/wizard"); + assert.ok(visible(".wizard-field.tag-field .multi-select-header")); + await click(".wizard-field.tag-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.tag-field .select-kit-collection li"), 2); + }); + + test("Category", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.category-field .multi-select-header")); + await click(".wizard-field.category-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.category-field .select-kit-collection li"), 5); + }); + + test("Group", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.group-field .single-select-header")); + await click(".wizard-field.group-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.group-field .select-kit-collection li"), 10); + }); + + test("User", async function (assert) { + server.get("/u/search/users", () => (response(200, usersJson))); + + await visit("/wizard"); + await fillIn(".wizard-field.user-selector-field input.ember-text-field", "a"); + await triggerKeyEvent(".wizard-field.user-selector-field input.ember-text-field", "keyup", "a".charCodeAt(0)); + + assert.ok(visible(".wizard-field.user-selector-field .ac-wrap")); + // TODO: add assertion for ac results. autocomplete does not appear in time. + }); + } +); diff --git a/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 new file mode 100644 index 00000000..731c9b76 --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 @@ -0,0 +1,47 @@ +import { visit, click } from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { + query, + count, + visible +} from "../helpers/acceptance"; +import { + stepNotPermitted, + wizard, + getWizard +} from "../helpers/wizard"; +import { + saveStep, + update +} from "../helpers/step"; + +acceptance("Step | Not permitted", [ getWizard(stepNotPermitted) ], + function(hooks) { + test("Shows not permitted message", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".step-message.not-permitted")); + }); + } +); + +acceptance("Step | Step", [ getWizard(wizard), saveStep(update) ], + function(hooks) { + test("Renders the step", async function (assert) { + await visit("/wizard"); + assert.strictEqual(query('.wizard-step-title p').textContent.trim(), "Text"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(count('.wizard-step-form .wizard-field'), 6); + assert.ok(visible('.wizard-step-footer .wizard-progress'), true); + assert.ok(visible('.wizard-step-footer .wizard-buttons'), true); + }); + + test("Goes to the next step", async function (assert) { + await visit("/wizard"); + assert.ok(visible('.wizard-step.step_1'), true); + await click('.wizard-btn.next'); + assert.ok(visible('.wizard-step.step_2'), true); + }); + } +); diff --git a/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 new file mode 100644 index 00000000..e68f59ae --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 @@ -0,0 +1,72 @@ +import { visit } from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { + query, + count, + visible +} from "../helpers/acceptance"; +import { + wizardNoUser, + wizardNotPermitted, + wizardCompleted, + wizard, + getWizard +} from "../helpers/wizard"; + +acceptance("Wizard | Not logged in", [ getWizard(wizardNoUser) ], + function(hooks) { + test("Wizard no access requires login", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.requires-login")); + }); + } +); + +acceptance("Wizard | Not permitted", [ getWizard(wizardNotPermitted) ], + function(hooks) { + test("Wizard no access not permitted", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.not-permitted")); + }); + } +); + +acceptance("Wizard | Completed", [ getWizard(wizardCompleted) ], + function(hooks) { + test("Wizard no access completed", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.completed")); + }); + } +); + +acceptance("Wizard | Wizard", [ getWizard(wizard) ], + function(hooks) { + test("Starts", async function (assert) { + await visit("/wizard"); + assert.ok(query('.wizard-column'), true); + }); + + test("Applies the body background color", async function (assert) { + await visit("/wizard"); + assert.ok($("body")[0].style.background); + }); + + test("Renders the wizard form", async function (assert) { + await visit("/wizard"); + assert.ok(visible('.wizard-column-contents .wizard-step'), true); + assert.ok(visible('.wizard-footer img'), true); + }); + + test("Renders the first step", async function (assert) { + await visit("/wizard"); + assert.strictEqual(query('.wizard-step-title p').textContent.trim(), "Text"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(count('.wizard-step-form .wizard-field'), 6); + assert.ok(visible('.wizard-step-footer .wizard-progress'), true); + assert.ok(visible('.wizard-step-footer .wizard-buttons'), true); + }); + } +); diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 new file mode 100644 index 00000000..d2c503ad --- /dev/null +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -0,0 +1,17 @@ +// discourse-skip-module + +document.addEventListener("DOMContentLoaded", function () { + document.body.insertAdjacentHTML( + "afterbegin", + ` +
+ + ` + ); +}); + +Object.keys(requirejs.entries).forEach(function (entry) { + if (/\-test/.test(entry)) { + requirejs(entry); + } +}); diff --git a/assets/javascripts/wizard/tests/fixtures/categories.js.es6 b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 new file mode 100644 index 00000000..e862f54a --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 @@ -0,0 +1,209 @@ +export default { + "categories": [ + { + "id": 1, + "name": "Uncategorized", + "color": "0088CC", + "text_color": "FFFFFF", + "slug": "uncategorized", + "topic_count": 1, + "post_count": 1, + "position": 0, + "description": "Topics that don't need a category, or don't fit into any other existing category.", + "description_text": "Topics that don't need a category, or don't fit into any other existing category.", + "description_excerpt": "Topics that don't need a category, or don't fit into any other existing category.", + "topic_url": "/t/", + "read_restricted": false, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 2, + "name": "Site Feedback", + "color": "808281", + "text_color": "FFFFFF", + "slug": "site-feedback", + "topic_count": 20, + "post_count": 21, + "position": 1, + "description": "

Discussion about this site, its organization, how it works, and how we can improve it.

", + "description_text": "Discussion about this site, its organization, how it works, and how we can improve it.", + "description_excerpt": "Discussion about this site, its organization, how it works, and how we can improve it.", + "topic_url": "/t/about-the-site-feedback-category/1", + "read_restricted": false, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 3, + "name": "Staff", + "color": "E45735", + "text_color": "FFFFFF", + "slug": "staff", + "topic_count": 4, + "post_count": 7, + "position": 2, + "description": "

Private category for staff discussions. Topics are only visible to admins and moderators.

", + "description_text": "Private category for staff discussions. Topics are only visible to admins and moderators.", + "description_excerpt": "Private category for staff discussions. Topics are only visible to admins and moderators.", + "topic_url": "/t/about-the-staff-category/2", + "read_restricted": true, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 4, + "name": "Lounge", + "color": "A461EF", + "text_color": "652D90", + "slug": "lounge", + "topic_count": 1, + "post_count": 1, + "position": 3, + "description": "

A category exclusive to members with trust level 3 and higher.

", + "description_text": "A category exclusive to members with trust level 3 and higher.", + "description_excerpt": "A category exclusive to members with trust level 3 and higher.", + "topic_url": "/t/about-the-lounge-category/3", + "read_restricted": true, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 5, + "name": "Custom Categories", + "color": "0088CC", + "text_color": "FFFFFF", + "slug": "custom-category", + "topic_count": 0, + "post_count": 0, + "position": 10, + "description": "Description of custom category", + "description_text": "Description of custom category", + "description_excerpt": "Description of custom category", + "topic_url": "/t/about-the-custom-category/5", + "read_restricted": false, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/groups.js.es6 b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 new file mode 100644 index 00000000..16f8150d --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 @@ -0,0 +1,313 @@ +export default { + "groups": [ + { + "id": 1, + "automatic": true, + "name": "admins", + "display_name": "admins", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 0, + "automatic": true, + "name": "everyone", + "display_name": "everyone", + "user_count": 0, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 3, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 15, + "automatic": false, + "name": "custom_group", + "user_count": 1, + "mentionable_level": 1, + "messageable_level": 2, + "visibility_level": 3, + "primary_group": false, + "title": "Custom Group", + "grant_trust_level": 3, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": "I am prefilled", + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 99, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 2, + "automatic": true, + "name": "moderators", + "display_name": "moderators", + "user_count": 0, + "mentionable_level": 0, + "messageable_level": 99, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 2, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 3, + "automatic": true, + "name": "staff", + "display_name": "staff", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 10, + "automatic": true, + "name": "trust_level_0", + "display_name": "trust_level_0", + "user_count": 2, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 11, + "automatic": true, + "name": "trust_level_1", + "display_name": "trust_level_1", + "user_count": 2, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 12, + "automatic": true, + "name": "trust_level_2", + "display_name": "trust_level_2", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 13, + "automatic": true, + "name": "trust_level_3", + "display_name": "trust_level_3", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 14, + "automatic": true, + "name": "trust_level_4", + "display_name": "trust_level_4", + "user_count": 0, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 new file mode 100644 index 00000000..f71ace8e --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 @@ -0,0 +1,283 @@ +export default { + "default_locale": "en", + "title": "Discourse", + "short_site_description": "", + "exclude_rel_nofollow_domains": "", + "logo": "/images/discourse-logo-sketch.png", + "logo_small": "/images/discourse-logo-sketch-small.png", + "digest_logo": "", + "mobile_logo": "", + "logo_dark": "", + "logo_small_dark": "", + "mobile_logo_dark": "", + "large_icon": "", + "favicon": "", + "apple_touch_icon": "", + "display_local_time_in_user_card": false, + "allow_user_locale": false, + "set_locale_from_accept_language_header": false, + "support_mixed_text_direction": false, + "suggested_topics": 5, + "ga_universal_tracking_code": "", + "ga_universal_domain_name": "auto", + "gtm_container_id": "", + "top_menu": "categories|latest", + "post_menu": "read|like|share|flag|edit|bookmark|delete|admin|reply", + "post_menu_hidden_items": "flag|bookmark|edit|delete|admin", + "share_links": "twitter|facebook|email", + "share_quote_visibility": "anonymous", + "share_quote_buttons": "twitter|email", + "desktop_category_page_style": "categories_and_latest_topics", + "category_colors": "BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|E45735", + "category_style": "bullet", + "max_category_nesting": 2, + "enable_mobile_theme": true, + "enable_direct_s3_uploads": false, + "enable_upload_debug_mode": false, + "default_dark_mode_color_scheme_id": 1, + "relative_date_duration": 30, + "fixed_category_positions": false, + "fixed_category_positions_on_create": false, + "enable_badges": true, + "enable_badge_sql": true, + "max_favorite_badges": 2, + "enable_whispers": false, + "enable_bookmarks_with_reminders": true, + "push_notifications_prompt": true, + "vapid_public_key_bytes": "4|29|219|88|202|66|198|62|182|204|66|176|229|200|131|26|141|21|178|231|150|161|2|128|228|200|179|126|118|232|196|19|232|76|108|189|54|211|210|155|55|228|173|112|38|158|114|127|18|95|7|56|110|183|192|92|43|0|243|249|233|89|9|207|255", + "invite_only": false, + "login_required": false, + "must_approve_users": false, + "enable_local_logins": true, + "enable_local_logins_via_email": true, + "allow_new_registrations": true, + "enable_signup_cta": true, + "facebook_app_id": "", + "auth_skip_create_confirm": false, + "auth_overrides_email": false, + "enable_discourse_connect": true, + "discourse_connect_overrides_avatar": false, + "hide_email_address_taken": false, + "min_username_length": 3, + "max_username_length": 20, + "unicode_usernames": false, + "min_password_length": 10, + "min_admin_password_length": 15, + "email_editable": true, + "logout_redirect": "", + "full_name_required": false, + "enable_names": true, + "invite_expiry_days": 90, + "invites_per_page": 40, + "delete_user_max_post_age": 60, + "delete_all_posts_max": 15, + "prioritize_username_in_ux": true, + "enable_user_directory": true, + "allow_anonymous_posting": false, + "anonymous_posting_min_trust_level": 1, + "allow_users_to_hide_profile": true, + "hide_user_profiles_from_public": false, + "allow_featured_topic_on_user_profiles": true, + "hide_suspension_reasons": false, + "ignored_users_count_message_threshold": 5, + "ignored_users_message_gap_days": 365, + "user_selected_primary_groups": false, + "gravatar_name": "Gravatar", + "gravatar_base_url": "www.gravatar.com", + "gravatar_login_url": "/emails", + "enable_group_directory": true, + "enable_category_group_moderation": false, + "min_post_length": 20, + "min_first_post_length": 20, + "min_personal_message_post_length": 10, + "max_post_length": 32000, + "topic_featured_link_enabled": true, + "min_topic_views_for_delete_confirm": 5000, + "min_topic_title_length": 15, + "max_topic_title_length": 255, + "enable_filtered_replies_view": false, + "min_personal_message_title_length": 2, + "allow_uncategorized_topics": true, + "min_title_similar_length": 10, + "enable_personal_messages": true, + "edit_history_visible_to_public": true, + "delete_removed_posts_after": 24, + "traditional_markdown_linebreaks": false, + "enable_markdown_typographer": true, + "enable_markdown_linkify": true, + "markdown_linkify_tlds": "com|net|org|io|onion|co|tv|ru|cn|us|uk|me|de|fr|fi|gov", + "markdown_typographer_quotation_marks": "“|”|‘|’", + "enable_rich_text_paste": true, + "suppress_reply_directly_below": true, + "suppress_reply_directly_above": true, + "max_reply_history": 1, + "enable_mentions": true, + "here_mention": "here", + "newuser_max_embedded_media": 1, + "newuser_max_attachments": 0, + "show_pinned_excerpt_mobile": true, + "show_pinned_excerpt_desktop": true, + "display_name_on_posts": false, + "show_time_gap_days": 7, + "short_progress_text_threshold": 10000, + "default_code_lang": "auto", + "autohighlight_all_code": false, + "highlighted_languages": "apache|bash|cs|cpp|css|coffeescript|diff|xml|http|ini|json|java|javascript|makefile|markdown|nginx|objectivec|ruby|perl|php|python|sql|handlebars", + "show_copy_button_on_codeblocks": false, + "enable_emoji": true, + "enable_emoji_shortcuts": true, + "emoji_set": "twitter", + "emoji_autocomplete_min_chars": 0, + "enable_inline_emoji_translation": false, + "code_formatting_style": "code-fences", + "allowed_href_schemes": "", + "watched_words_regular_expressions": false, + "enable_diffhtml_preview": false, + "enable_fast_edit": true, + "old_post_notice_days": 14, + "blur_tl0_flagged_posts_media": true, + "email_time_window_mins": 10, + "disable_digest_emails": false, + "email_in": false, + "enable_imap": false, + "enable_smtp": false, + "disable_emails": "no", + "bounce_score_threshold": 4, + "enable_secondary_emails": true, + "max_image_size_kb": 4096, + "max_attachment_size_kb": 4096, + "authorized_extensions": "jpg|jpeg|png|gif|heic|heif|webp", + "authorized_extensions_for_staff": "", + "max_image_width": 690, + "max_image_height": 500, + "prevent_anons_from_downloading_files": false, + "secure_media": false, + "enable_s3_uploads": false, + "allow_profile_backgrounds": true, + "allow_uploaded_avatars": "0", + "default_avatars": "", + "external_system_avatars_enabled": true, + "external_system_avatars_url": "/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png", + "external_emoji_url": "", + "selectable_avatars_mode": "disabled", + "selectable_avatars": "", + "allow_staff_to_upload_any_file_in_pm": true, + "simultaneous_uploads": 5, + "composer_media_optimization_image_enabled": true, + "composer_media_optimization_image_bytes_optimization_threshold": 524288, + "composer_media_optimization_image_resize_dimensions_threshold": 1920, + "composer_media_optimization_image_resize_width_target": 1920, + "composer_media_optimization_image_resize_pre_multiply": false, + "composer_media_optimization_image_resize_linear_rgb": false, + "composer_media_optimization_image_encode_quality": 75, + "composer_media_optimization_debug_mode": false, + "min_trust_level_to_allow_profile_background": 0, + "min_trust_level_to_allow_user_card_background": 0, + "min_trust_level_to_allow_ignore": 2, + "tl1_requires_read_posts": 30, + "tl3_links_no_follow": false, + "enforce_second_factor": "no", + "moderators_change_post_ownership": false, + "moderators_view_emails": false, + "use_admin_ip_allowlist": false, + "allowed_iframes": "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com|http://localhost:3000/discobot/certificate.svg", + "can_permanently_delete": false, + "max_oneboxes_per_post": 50, + "reviewable_claiming": "disabled", + "reviewable_default_topics": false, + "reviewable_default_visibility": "low", + "alert_admins_if_errors_per_minute": 0, + "alert_admins_if_errors_per_hour": 0, + "max_prints_per_hour_per_user": 5, + "invite_link_max_redemptions_limit": 5000, + "invite_link_max_redemptions_limit_users": 10, + "enable_long_polling": true, + "enable_chunked_encoding": true, + "long_polling_base_url": "/", + "background_polling_interval": 60000, + "polling_interval": 3000, + "anon_polling_interval": 25000, + "flush_timings_secs": 60, + "verbose_localization": false, + "max_new_topics": 500, + "enable_safe_mode": true, + "tos_url": "", + "privacy_policy_url": "", + "faq_url": "", + "enable_backups": true, + "backup_location": "local", + "maximum_backups": 5, + "use_pg_headlines_for_excerpt": false, + "min_search_term_length": 3, + "log_search_queries": true, + "version_checks": true, + "suppress_uncategorized_badge": true, + "header_dropdown_category_count": 8, + "slug_generation_method": "ascii", + "summary_timeline_button": false, + "topic_views_heat_low": 1000, + "topic_views_heat_medium": 2000, + "topic_views_heat_high": 3500, + "topic_post_like_heat_low": 0.5, + "topic_post_like_heat_medium": 1, + "topic_post_like_heat_high": 2, + "history_hours_low": 12, + "history_hours_medium": 24, + "history_hours_high": 48, + "cold_age_days_low": 14, + "cold_age_days_medium": 90, + "cold_age_days_high": 180, + "global_notice": "", + "show_create_topics_notice": true, + "bootstrap_mode_min_users": 50, + "bootstrap_mode_enabled": true, + "automatically_unpin_topics": true, + "read_time_word_count": 500, + "topic_page_title_includes_category": true, + "svg_icon_subset": "", + "allow_bulk_invite": true, + "disable_mailing_list_mode": true, + "default_topics_automatic_unpin": true, + "mute_all_categories_by_default": false, + "tagging_enabled": true, + "tag_style": "simple", + "max_tags_per_topic": 5, + "max_tag_length": 20, + "min_trust_level_to_tag_topics": "0", + "max_tag_search_results": 5, + "max_tags_in_filter_list": 30, + "tags_sort_alphabetically": false, + "tags_listed_by_group": false, + "suppress_overlapping_tags_in_list": false, + "remove_muted_tags_from_latest": "always", + "force_lowercase_tags": true, + "dashboard_hidden_reports": "", + "dashboard_visible_tabs": "moderation|security|reports", + "dashboard_general_tab_activity_metrics": "page_view_total_reqs|visits|time_to_first_response|likes|flags|user_to_user_private_messages_with_replies", + "discourse_narrative_bot_enabled": true, + "details_enabled": true, + "custom_wizard_enabled": true, + "wizard_redirect_exclude_paths": "admin", + "wizard_recognised_image_upload_formats": "jpg|jpeg|png|gif", + "wizard_important_notices_on_dashboard": true, + "discourse_local_dates_email_format": "YYYY-MM-DDTHH:mm:ss[Z]", + "discourse_local_dates_enabled": true, + "discourse_local_dates_default_formats": "LLL|LTS|LL|LLLL", + "discourse_local_dates_default_timezones": "Europe/Paris|America/Los_Angeles", + "poll_enabled": true, + "poll_maximum_options": 20, + "poll_minimum_trust_level_to_create": 1, + "poll_groupable_user_fields": "", + "poll_export_data_explorer_query_id": -16, + "presence_enabled": true, + "presence_max_users_shown": 5, + "available_locales": "[{\"name\":\"اللغة العربية\",\"value\":\"ar\"},{\"name\":\"беларуская мова\",\"value\":\"be\"},{\"name\":\"български език\",\"value\":\"bg\"},{\"name\":\"bosanski jezik\",\"value\":\"bs_BA\"},{\"name\":\"català\",\"value\":\"ca\"},{\"name\":\"čeština\",\"value\":\"cs\"},{\"name\":\"dansk\",\"value\":\"da\"},{\"name\":\"Deutsch\",\"value\":\"de\"},{\"name\":\"ελληνικά\",\"value\":\"el\"},{\"name\":\"English (US)\",\"value\":\"en\"},{\"name\":\"English (UK)\",\"value\":\"en_GB\"},{\"name\":\"Español\",\"value\":\"es\"},{\"name\":\"eesti\",\"value\":\"et\"},{\"name\":\"فارسی\",\"value\":\"fa_IR\"},{\"name\":\"suomi\",\"value\":\"fi\"},{\"name\":\"Français\",\"value\":\"fr\"},{\"name\":\"galego\",\"value\":\"gl\"},{\"name\":\"עברית\",\"value\":\"he\"},{\"name\":\"magyar\",\"value\":\"hu\"},{\"name\":\"Հայերեն\",\"value\":\"hy\"},{\"name\":\"Indonesian\",\"value\":\"id\"},{\"name\":\"Italiano\",\"value\":\"it\"},{\"name\":\"日本語\",\"value\":\"ja\"},{\"name\":\"한국어\",\"value\":\"ko\"},{\"name\":\"lietuvių kalba\",\"value\":\"lt\"},{\"name\":\"latviešu valoda\",\"value\":\"lv\"},{\"name\":\"Norsk bokmål\",\"value\":\"nb_NO\"},{\"name\":\"Nederlands\",\"value\":\"nl\"},{\"name\":\"polski\",\"value\":\"pl_PL\"},{\"name\":\"Português\",\"value\":\"pt\"},{\"name\":\"Português (BR)\",\"value\":\"pt_BR\"},{\"name\":\"limba română\",\"value\":\"ro\"},{\"name\":\"Русский\",\"value\":\"ru\"},{\"name\":\"slovenčina\",\"value\":\"sk\"},{\"name\":\"slovenščina\",\"value\":\"sl\"},{\"name\":\"Shqip\",\"value\":\"sq\"},{\"name\":\"српски језик\",\"value\":\"sr\"},{\"name\":\"svenska\",\"value\":\"sv\"},{\"name\":\"Kiswahili\",\"value\":\"sw\"},{\"name\":\"తెలుగు\",\"value\":\"te\"},{\"name\":\"ไทย\",\"value\":\"th\"},{\"name\":\"Türkçe\",\"value\":\"tr_TR\"},{\"name\":\"українська мова\",\"value\":\"uk\"},{\"name\":\"اردو\",\"value\":\"ur\"},{\"name\":\"Việt Nam\",\"value\":\"vi\"},{\"name\":\"简体中文\",\"value\":\"zh_CN\"},{\"name\":\"繁體中文\",\"value\":\"zh_TW\"}]", + "require_invite_code": false, + "site_logo_url": "http://localhost:3000/images/discourse-logo-sketch.png", + "site_logo_small_url": "http://localhost:3000/images/discourse-logo-sketch-small.png", + "site_mobile_logo_url": "http://localhost:3000/images/discourse-logo-sketch.png", + "site_favicon_url": "http://localhost:3000/uploads/default/optimized/1X/_129430568242d1b7f853bb13ebea28b3f6af4e7_2_32x32.png", + "site_logo_dark_url": "", + "site_logo_small_dark_url": "", + "site_mobile_logo_dark_url": "" +} diff --git a/assets/javascripts/wizard/tests/fixtures/tags.js.es6 b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 new file mode 100644 index 00000000..a072772e --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 @@ -0,0 +1,22 @@ +export default { + "tags": [ + { + "id": "tag1", + "text": "tag1", + "name": "tag1", + "description": null, + "count": 1, + "pm_count": 0, + "target_tag": null + }, + { + "id": "tag2", + "text": "tag2", + "name": "tag2", + "description": null, + "count": 1, + "pm_count": 0, + "target_tag": null + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/update.js.es6 b/assets/javascripts/wizard/tests/fixtures/update.js.es6 new file mode 100644 index 00000000..3908525c --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/update.js.es6 @@ -0,0 +1,5 @@ +export default { + "final": false, + "next_step_id": "step_2", + "wizard": {} +} diff --git a/assets/javascripts/wizard/tests/fixtures/user.js.es6 b/assets/javascripts/wizard/tests/fixtures/user.js.es6 new file mode 100644 index 00000000..8acd7392 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/user.js.es6 @@ -0,0 +1,34 @@ +export default { + id: 19, + username: "angus", + uploaded_avatar_id: 5275, + avatar_template: "/user_avatar/localhost/angus/{size}/5275.png", + name: "Angus McLeod", + unread_notifications: 0, + unread_private_messages: 0, + unread_high_priority_notifications: 0, + admin: true, + notification_channel_position: null, + site_flagged_posts_count: 1, + moderator: true, + staff: true, + can_create_group: true, + title: "", + reply_count: 859, + topic_count: 36, + enable_quoting: true, + external_links_in_new_tab: false, + dynamic_favicon: true, + trust_level: 4, + can_edit: true, + can_invite_to_forum: true, + should_be_redirected_to_top: false, + custom_fields: {}, + muted_category_ids: [], + dismissed_banner_key: null, + akismet_review_count: 0, + title_count_mode: "notifications", + timezone: "Australia/Perth", + skip_new_user_tips: false, + can_review: true +} diff --git a/assets/javascripts/wizard/tests/fixtures/users.js.es6 b/assets/javascripts/wizard/tests/fixtures/users.js.es6 new file mode 100644 index 00000000..267d4909 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/users.js.es6 @@ -0,0 +1,14 @@ +export default { + "users": [ + { + "username": "angus", + "name": "Angus", + "avatar_template": "/user_avatar/localhost/angus/{size}/12_2.png" + }, + { + "username": "angus_2", + "name": "Angus 2", + "avatar_template": "/letter_avatar_proxy/v4/letter/a/e9a140/{size}.png" + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 new file mode 100644 index 00000000..be4fa8b2 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 @@ -0,0 +1,469 @@ +export default { + "id": "wizard", + "name": "Wizard", + "start": "step_1", + "background": "#333333", + "submission_last_updated_at": "2022-03-15T21:11:01+01:00", + "theme_id": 2, + "required": false, + "permitted": true, + "uncategorized_category_id": 1, + "categories": [], + "subscribed": false, + "resume_on_revisit": false, + "steps": [ + { + "id": "step_1", + "index": 0, + "next": "step_2", + "description": "

Text inputs!

", + "title": "Text", + "permitted": true, + "permitted_message": null, + "final": false, + "fields": [ + { + "id": "step_1_field_1", + "index": 0, + "type": "text", + "required": false, + "value": "I am prefilled", + "label": "

Text

", + "description": "Text field description.", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 1, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_2", + "index": 0, + "type": "textarea", + "required": false, + "value": "", + "label": "

Textarea

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 2, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_3", + "index": 2, + "type": "composer", + "required": false, + "value": "", + "label": "

Composer

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 3, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_4", + "index": 3, + "type": "text_only", + "required": false, + "value": null, + "label": "

I’m only text

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 4, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_5", + "index": 4, + "type": "composer_preview", + "required": false, + "value": "", + "label": "

I’m a preview

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": "

I am prefilled

", + "tabindex": 5, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_6", + "index": 5, + "type": "composer_preview", + "required": false, + "value": "", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": "

This is the preview of the composer

", + "tabindex": 6, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + } + ], + "_validState": 0, + "wizardId": "super_mega_fun_wizard" + }, + { + "id": "step_2", + "index": 1, + "next": "step_3", + "previous": "step_1", + "description": "

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

", + "title": "Values", + "permitted": true, + "permitted_message": null, + "final": false, + "fields": [ + { + "id": "step_2_field_1", + "index": 0, + "type": "date", + "required": false, + "value": "", + "label": "

Date

", + "file_types": null, + "format": "YYYY-MM-DD", + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 1, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_2", + "index": 0, + "type": "time", + "required": false, + "value": "", + "label": "

Time

", + "file_types": null, + "format": "HH:mm", + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 2, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_3", + "index": 2, + "type": "date_time", + "required": false, + "value": "", + "label": "

Date & Time

", + "file_types": null, + "format": "", + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 3, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_4", + "index": 3, + "type": "number", + "required": false, + "value": "", + "label": "

Number

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 5, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_5", + "index": 4, + "type": "checkbox", + "required": false, + "value": false, + "label": "

Checkbox

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 6, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_6", + "index": 5, + "type": "url", + "required": false, + "value": "", + "label": "

Url

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 7, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_7", + "index": 6, + "type": "upload", + "required": false, + "value": "", + "label": "

Upload

", + "file_types": ".jpg,.jpeg,.png", + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 8, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + } + ], + "_validState": 0, + "wizardId": "super_mega_fun_wizard" + }, + { + "id": "step_3", + "index": 2, + "previous": "step_2", + "description": "

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

", + "title": "Combo-boxes", + "permitted": true, + "permitted_message": null, + "final": true, + "fields": [ + { + "id": "step_3_field_1", + "index": 0, + "type": "dropdown", + "required": false, + "value": "choice1", + "label": "

Custom Dropdown

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": [ + { + "id": "one", + "name": "One" + }, + { + "id": "two", + "name": "Two" + } + ], + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 1, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_2", + "index": 0, + "type": "tag", + "required": false, + "value": null, + "label": "

Tag

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 2, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_3", + "index": 2, + "type": "category", + "required": false, + "value": null, + "label": "

Category

", + "file_types": null, + "format": null, + "limit": 1, + "property": "id", + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 3, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_4", + "index": 3, + "type": "group", + "required": false, + "value": null, + "label": "

Group

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 4, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_5", + "index": 4, + "type": "user_selector", + "required": false, + "value": null, + "label": "

User Selector

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 5, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_6", + "index": 5, + "type": "user_selector", + "required": false, + "value": null, + "label": "

Conditional User Selector

", + "description": "Shown when checkbox in step_2_field_5 is true", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 6, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + } + ], + "_validState": 0, + "wizardId": "super_mega_fun_wizard" + } + ], + "groups": [] +} diff --git a/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 new file mode 100644 index 00000000..f5c1175f --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 @@ -0,0 +1,53 @@ +import { module } from "qunit"; +import setupPretender, { response } from "../pretender"; +import startApp from "../helpers/start-app"; + +let server; +let app; + +function acceptance(name, requests, cb) { + module(`Acceptance: ${name}`, function(hooks) { + hooks.beforeEach(function() { + server = setupPretender(function(pretender) { + requests.forEach(req => { + pretender[req.verb](req.path, () => (response(req.status, req.response))); + }); + return pretender; + }); + app = startApp(); + }); + + hooks.afterEach(function() { + app.destroy(); + server.shutdown(); + }); + + cb(hooks); + }); +} + +export default acceptance; + +export { + server +}; + +// The discourse/test/helpers/qunit-helpers file has many functions and imports +// we don't need, so there will be some duplciation here. + +export function queryAll(selector, context) { + context = context || "#ember-testing"; + return $(selector, context); +} + +export function query() { + return document.querySelector("#ember-testing").querySelector(...arguments); +} + +export function visible(selector) { + return queryAll(selector + ":visible").length > 0; +} + +export function count(selector) { + return queryAll(selector).length; +} diff --git a/assets/javascripts/wizard/tests/helpers/start-app.js.es6 b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 new file mode 100644 index 00000000..6afe6eb9 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 @@ -0,0 +1,19 @@ +const CustomWizard = requirejs("discourse/plugins/discourse-custom-wizard/wizard/application").default; +const initializer = requirejs("discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/wizard").default; +const siteSettings = requirejs("discourse/plugins/discourse-custom-wizard/wizard/tests/fixtures/site-settings").default; +const { cloneJSON } = requirejs("discourse-common/lib/object").default; + +let app; + +export default function () { + app = CustomWizard.create({ rootElement: "#ember-testing" }); + app.start(); + + app.SiteSettings = cloneJSON(siteSettings); + initializer.initialize(app); + + app.setupForTesting(); + app.injectTestHelpers(); + + return app; +} diff --git a/assets/javascripts/wizard/tests/helpers/step.js.es6 b/assets/javascripts/wizard/tests/helpers/step.js.es6 new file mode 100644 index 00000000..a24e04e1 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/step.js.es6 @@ -0,0 +1,20 @@ +import updateJson from "../fixtures/update"; +import { cloneJSON } from "discourse-common/lib/object"; +import wizardJson from "../fixtures/wizard"; + +const update = cloneJSON(updateJson); +update.wizard = cloneJSON(wizardJson); + +const saveStep = function(response) { + return { + verb: "put", + path: '/w/wizard/steps/:step_id', + status: 200, + response + } +} + +export { + saveStep, + update +} diff --git a/assets/javascripts/wizard/tests/helpers/test.js.es6 b/assets/javascripts/wizard/tests/helpers/test.js.es6 new file mode 100644 index 00000000..c7401fd5 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/test.js.es6 @@ -0,0 +1,7 @@ +function exists(selector) { + return document.querySelector(selector) !== null; +} + +export { + exists +} diff --git a/assets/javascripts/wizard/tests/helpers/wizard.js.es6 b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 new file mode 100644 index 00000000..997f6c36 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 @@ -0,0 +1,52 @@ +import wizardJson from "../fixtures/wizard"; +import userJson from "../fixtures/user"; +import categoriesJson from "../fixtures/categories"; +import groupsJson from "../fixtures/groups"; +import { cloneJSON } from "discourse-common/lib/object"; + +const wizardNoUser = cloneJSON(wizardJson); +const wizard = cloneJSON(wizardJson); +wizard.user = cloneJSON(userJson); + +const wizardNotPermitted = cloneJSON(wizard); +wizardNotPermitted.permitted = false; + +const wizardCompleted = cloneJSON(wizard); +wizardCompleted.completed = true; + +wizard.start = "step_1"; +wizard.resume_on_revisit = false; +wizard.submission_last_updated_at = "2022-03-11T20:00:18+01:00"; +wizard.subscribed = false; + +const stepNotPermitted = cloneJSON(wizard); +stepNotPermitted.steps[0].permitted = false; + +const allFieldsWizard = cloneJSON(wizard); +allFieldsWizard.steps[0].fields = [ + ...allFieldsWizard.steps[0].fields, + ...allFieldsWizard.steps[1].fields, + ...allFieldsWizard.steps[2].fields +]; +allFieldsWizard.steps = [cloneJSON(allFieldsWizard.steps[0])]; +allFieldsWizard.categories = cloneJSON(categoriesJson['categories']); +allFieldsWizard.groups = cloneJSON(groupsJson['groups']); + +const getWizard = function(response) { + return { + verb: "get", + path: "/w/wizard", + status: 200, + response + } +} + +export { + getWizard, + wizardNoUser, + wizardNotPermitted, + wizardCompleted, + stepNotPermitted, + allFieldsWizard, + wizard +} diff --git a/assets/javascripts/wizard/tests/pretender.js.es6 b/assets/javascripts/wizard/tests/pretender.js.es6 new file mode 100644 index 00000000..88eae666 --- /dev/null +++ b/assets/javascripts/wizard/tests/pretender.js.es6 @@ -0,0 +1,53 @@ +import Pretender from "pretender"; + +function parsePostData(query) { + const result = {}; + query.split("&").forEach(function (part) { + const item = part.split("="); + const firstSeg = decodeURIComponent(item[0]); + const m = /^([^\[]+)\[([^\]]+)\]/.exec(firstSeg); + + const val = decodeURIComponent(item[1]).replace(/\+/g, " "); + if (m) { + result[m[1]] = result[m[1]] || {}; + result[m[1]][m[2]] = val; + } else { + result[firstSeg] = val; + } + }); + return result; +} + +function response(code, obj) { + if (typeof code === "object") { + obj = code; + code = 200; + } + return [code, { "Content-Type": "application/json" }, obj]; +} + +export { response }; + +export default function (cb) { + let server = new Pretender(); + + if (cb) { + server = cb(server); + } + + server.prepareBody = function (body) { + if (body && typeof body === "object") { + return JSON.stringify(body); + } + return body; + }; + + server.unhandledRequest = function (verb, path, request) { + const error = + "Unhandled request in test environment: " + path + " (" + verb + ")"; + window.console.error(error); + throw error; + }; + + return server; +} diff --git a/assets/stylesheets/wizard/custom/composer.scss b/assets/stylesheets/wizard/custom/composer.scss index be866b8a..aea17d63 100644 --- a/assets/stylesheets/wizard/custom/composer.scss +++ b/assets/stylesheets/wizard/custom/composer.scss @@ -46,7 +46,7 @@ position: relative; } -.wizard-field-composer.show-preview .d-editor-textarea-wrapper { +.wizard-field-composer.show-preview .d-editor-textarea-column { display: none; } diff --git a/config/routes.rb b/config/routes.rb index 28fcbb82..1948b799 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true CustomWizard::Engine.routes.draw do + get 'qunit' => 'wizard#qunit' get ':wizard_id' => 'wizard#index' put ':wizard_id/skip' => 'wizard#skip' get ':wizard_id/steps' => 'wizard#index' diff --git a/lib/custom_wizard/extensions/extra_locales_controller.rb b/lib/custom_wizard/extensions/extra_locales_controller.rb index 6242f7ca..f6672f4a 100644 --- a/lib/custom_wizard/extensions/extra_locales_controller.rb +++ b/lib/custom_wizard/extensions/extra_locales_controller.rb @@ -6,6 +6,7 @@ module ExtraLocalesControllerCustomWizard path = URI(request.referer).path wizard_path = path.split('/w/').last wizard_id = wizard_path.split('/').first + return true if wizard_id == "qunit" CustomWizard::Template.exists?(wizard_id.underscore) end end diff --git a/lib/custom_wizard/validators/update.rb b/lib/custom_wizard/validators/update.rb index 2301760f..d71bded1 100644 --- a/lib/custom_wizard/validators/update.rb +++ b/lib/custom_wizard/validators/update.rb @@ -20,7 +20,7 @@ class ::CustomWizard::UpdateValidator field_id = field.id.to_s value = @updater.submission[field_id] min_length = false - label = field.raw[:label] || I18n.t("#{field.key}.label") + label = field.raw[:label] type = field.type required = field.required min_length = field.min_length if is_text_type(field) diff --git a/plugin.rb b/plugin.rb index a1b377ac..6f5719fb 100644 --- a/plugin.rb +++ b/plugin.rb @@ -21,9 +21,8 @@ config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard" if Rails.env.production? config.assets.precompile += %w{ wizard-custom-guest.js - wizard-custom-globals.js - wizard-custom.js wizard-custom-start.js + wizard-custom.js wizard-plugin.js.erb wizard-raw-templates.js.erb }