From 60823cd87ae695dd8c5b033d808df775eac5356d Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 27 Jul 2022 11:47:50 +0100 Subject: [PATCH] First release candidate --- .github/workflows/plugin-linting.yml | 5 +- .github/workflows/plugin-tests.yml | 5 + .../components/custom-wizard-no-access.js.es6 | 2 +- .../components/custom-wizard-step.js.es6 | 2 +- .../custom-wizard-category-settings.js.es6 | 4 +- .../controllers/admin-wizards-api-show.js.es6 | 27 +- .../custom-wizard-admin-route-map.js.es6 | 55 ++++ .../discourse/custom-wizard-route-map.js.es6 | 51 --- .../discourse/helpers/custom-wizard.js.es6 | 6 - .../discourse/helpers/date-node.js.es6 | 23 -- .../discourse/helpers/dir-span.js.es6 | 6 - .../discourse/helpers/loading-spinner.es6 | 17 - .../initializers/custom-wizard-edits.js.es6 | 1 + .../models/custom-wizard-admin.js.es6 | 227 +++++++++++++ ...ield.js.es6 => custom-wizard-field.js.es6} | 0 ...{step.js.es6 => custom-wizard-step.js.es6} | 0 .../discourse/models/custom-wizard.js.es6 | 310 ++++++------------ .../javascripts/discourse/models/site.js.es6 | 11 - .../discourse/models/wizard.js.es6 | 164 --------- .../routes/admin-wizards-manager.js.es6 | 4 +- .../admin-wizards-submissions-show.js.es6 | 4 +- .../routes/admin-wizards-wizard-show.js.es6 | 6 +- .../routes/custom-wizard-index.js.es6 | 2 +- .../routes/custom-wizard-step.js.es6 | 2 +- .../discourse/routes/custom-wizard.js.es6 | 21 +- .../common/custom/autocomplete.scss | 145 ++++---- assets/stylesheets/common/custom/field.scss | 14 +- config/locales/client.en.yml | 171 +++++----- spec/extensions/sprockets_directive_spec.rb | 53 --- .../realtime_validations_spec.rb | 15 +- .../{field-test.js.es6 => field-test.js} | 68 ++-- .../{step-test.js.es6 => step-test.js} | 31 +- test/javascripts/acceptance/wizard-test.js | 96 ++++++ .../javascripts/acceptance/wizard-test.js.es6 | 73 ----- test/javascripts/helpers/wizard.js | 47 +++ 35 files changed, 788 insertions(+), 880 deletions(-) create mode 100644 assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 delete mode 100644 assets/javascripts/discourse/helpers/custom-wizard.js.es6 delete mode 100644 assets/javascripts/discourse/helpers/date-node.js.es6 delete mode 100644 assets/javascripts/discourse/helpers/dir-span.js.es6 delete mode 100644 assets/javascripts/discourse/helpers/loading-spinner.es6 create mode 100644 assets/javascripts/discourse/models/custom-wizard-admin.js.es6 rename assets/javascripts/discourse/models/{field.js.es6 => custom-wizard-field.js.es6} (100%) rename assets/javascripts/discourse/models/{step.js.es6 => custom-wizard-step.js.es6} (100%) delete mode 100644 assets/javascripts/discourse/models/site.js.es6 delete mode 100644 assets/javascripts/discourse/models/wizard.js.es6 delete mode 100644 spec/extensions/sprockets_directive_spec.rb rename test/javascripts/acceptance/{field-test.js.es6 => field-test.js} (79%) rename test/javascripts/acceptance/{step-test.js.es6 => step-test.js} (59%) create mode 100644 test/javascripts/acceptance/wizard-test.js delete mode 100644 test/javascripts/acceptance/wizard-test.js.es6 create mode 100644 test/javascripts/helpers/wizard.js diff --git a/.github/workflows/plugin-linting.yml b/.github/workflows/plugin-linting.yml index a7cbe811..b5203bb9 100644 --- a/.github/workflows/plugin-linting.yml +++ b/.github/workflows/plugin-linting.yml @@ -46,8 +46,9 @@ jobs: yarn prettier --list-different "assets/javascripts/{discourse,wizard}/**/*.{scss,js,es6}" ; \ fi - - name: Ember template lint - run: yarn ember-template-lint assets/javascripts + # Until templates are converted + #- name: Ember template lint + #run: yarn ember-template-lint assets/javascripts - name: Rubocop run: bundle exec rubocop . diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index a7951188..b0ee161d 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -132,3 +132,8 @@ jobs: export COVERAGE=1 fi bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}] + + - name: Plugin QUnit + if: matrix.build_type == 'frontend' + run: bin/rake plugin:qunit['${{ steps.repo-name.outputs.value }}','1200000'] + timeout-minutes: 30 diff --git a/assets/javascripts/discourse/components/custom-wizard-no-access.js.es6 b/assets/javascripts/discourse/components/custom-wizard-no-access.js.es6 index 9b3170fb..b814f0c8 100644 --- a/assets/javascripts/discourse/components/custom-wizard-no-access.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-no-access.js.es6 @@ -1,4 +1,4 @@ -import CustomWizard from "../models/wizard"; +import CustomWizard from "../models/custom-wizard"; import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; import { dasherize } from "@ember/string"; diff --git a/assets/javascripts/discourse/components/custom-wizard-step.js.es6 b/assets/javascripts/discourse/components/custom-wizard-step.js.es6 index 54c1b9eb..4cc00d14 100644 --- a/assets/javascripts/discourse/components/custom-wizard-step.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-step.js.es6 @@ -7,7 +7,7 @@ import { schedule } from "@ember/runloop"; import { cookAsync } from "discourse/lib/text"; import CustomWizard, { updateCachedWizard, -} from "discourse/plugins/discourse-custom-wizard/discourse/models/wizard"; +} from "discourse/plugins/discourse-custom-wizard/discourse/models/custom-wizard"; import { alias, not } from "@ember/object/computed"; const alreadyWarned = {}; diff --git a/assets/javascripts/discourse/connectors/category-custom-settings/custom-wizard-category-settings.js.es6 b/assets/javascripts/discourse/connectors/category-custom-settings/custom-wizard-category-settings.js.es6 index 16352f95..7004c317 100644 --- a/assets/javascripts/discourse/connectors/category-custom-settings/custom-wizard-category-settings.js.es6 +++ b/assets/javascripts/discourse/connectors/category-custom-settings/custom-wizard-category-settings.js.es6 @@ -1,9 +1,9 @@ -import CustomWizard from "../../models/custom-wizard"; +import CustomWizardAdmin from "../../models/custom-wizard-admin"; import { popupAjaxError } from "discourse/lib/ajax-error"; export default { setupComponent(attrs, component) { - CustomWizard.all() + CustomWizardAdmin.all() .then((result) => { component.set("wizardList", result); }) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 index 5dba2d7f..a820c664 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 @@ -20,29 +20,8 @@ export default Controller.extend({ "application/x-www-form-urlencoded", ]), successCodes: selectKitContent([ - 100, - 101, - 102, - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 226, - 300, - 301, - 302, - 303, - 303, - 304, - 305, - 306, - 307, - 308, + 100, 101, 102, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, + 302, 303, 303, 304, 305, 306, 307, 308, ]), @discourseComputed( @@ -166,7 +145,7 @@ export default Controller.extend({ const originalTitle = this.get("api.originalTitle"); if (api.get("isNew") || (originalTitle && api.title !== originalTitle)) { - refreshList = true; + refreshList = true; // eslint-disable-line } if (api.get("isNew")) { diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 new file mode 100644 index 00000000..90ab5359 --- /dev/null +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -0,0 +1,55 @@ +export default { + resource: "admin", + map() { + this.route( + "adminWizards", + { path: "/wizards", resetNamespace: true }, + function () { + this.route( + "adminWizardsWizard", + { path: "/wizard/", resetNamespace: true }, + function () { + this.route("adminWizardsWizardShow", { + path: "/:wizardId/", + resetNamespace: true, + }); + } + ); + + this.route("adminWizardsCustomFields", { + path: "/custom-fields", + resetNamespace: true, + }); + + this.route( + "adminWizardsSubmissions", + { path: "/submissions", resetNamespace: true }, + function () { + this.route("adminWizardsSubmissionsShow", { + path: "/:wizardId/", + resetNamespace: true, + }); + } + ); + + this.route( + "adminWizardsApi", + { path: "/api", resetNamespace: true }, + function () { + this.route("adminWizardsApiShow", { + path: "/:name", + resetNamespace: true, + }); + } + ); + + this.route("adminWizardsLogs", { path: "/logs", resetNamespace: true }); + + this.route("adminWizardsManager", { + path: "/manager", + resetNamespace: true, + }); + } + ); + }, +}; diff --git a/assets/javascripts/discourse/custom-wizard-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-route-map.js.es6 index e5e3a226..08606301 100644 --- a/assets/javascripts/discourse/custom-wizard-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-route-map.js.es6 @@ -9,55 +9,4 @@ export default function () { }); } ); - - this.route( - "adminWizards", - { path: "/wizards", resetNamespace: true }, - function () { - this.route( - "adminWizardsWizard", - { path: "/wizard/", resetNamespace: true }, - function () { - this.route("adminWizardsWizardShow", { - path: "/:wizardId/", - resetNamespace: true, - }); - } - ); - - this.route("adminWizardsCustomFields", { - path: "/custom-fields", - resetNamespace: true, - }); - - this.route( - "adminWizardsSubmissions", - { path: "/submissions", resetNamespace: true }, - function () { - this.route("adminWizardsSubmissionsShow", { - path: "/:wizardId/", - resetNamespace: true, - }); - } - ); - - this.route( - "adminWizardsApi", - { path: "/api", resetNamespace: true }, - function () { - this.route("adminWizardsApiShow", { - path: "/:name", - resetNamespace: true, - }); - } - ); - - this.route("adminWizardsLogs", { path: "/logs", resetNamespace: true }); - - this.route("adminWizardsManager", { - path: "/manager", - resetNamespace: true, - }); - } - ); } diff --git a/assets/javascripts/discourse/helpers/custom-wizard.js.es6 b/assets/javascripts/discourse/helpers/custom-wizard.js.es6 deleted file mode 100644 index fb5063cc..00000000 --- a/assets/javascripts/discourse/helpers/custom-wizard.js.es6 +++ /dev/null @@ -1,6 +0,0 @@ -import { registerUnbound } from "discourse-common/lib/helpers"; -import { dasherize } from "@ember/string"; - -registerUnbound("dasherize", function (string) { - return dasherize(string); -}); diff --git a/assets/javascripts/discourse/helpers/date-node.js.es6 b/assets/javascripts/discourse/helpers/date-node.js.es6 deleted file mode 100644 index b7d19d0f..00000000 --- a/assets/javascripts/discourse/helpers/date-node.js.es6 +++ /dev/null @@ -1,23 +0,0 @@ -import { registerUnbound } from "discourse-common/lib/helpers"; -import { longDate, relativeAge } from "discourse/lib/formatter"; -import Handlebars from "handlebars"; - -export default registerUnbound("date-node", function (dt) { - if (typeof dt === "string") { - dt = new Date(dt); - } - if (dt) { - const attributes = { - title: longDate(dt), - "data-time": dt.getTime(), - "data-format": "tiny", - }; - - const finalString = `${relativeAge(dt)}`; - return new Handlebars.SafeString(finalString); - } -}); diff --git a/assets/javascripts/discourse/helpers/dir-span.js.es6 b/assets/javascripts/discourse/helpers/dir-span.js.es6 deleted file mode 100644 index 45720796..00000000 --- a/assets/javascripts/discourse/helpers/dir-span.js.es6 +++ /dev/null @@ -1,6 +0,0 @@ -import { registerUnbound } from "discourse-common/lib/helpers"; -import Handlebars from "handlebars"; - -export default registerUnbound("dir-span", function (str) { - return new Handlebars.SafeString(str); -}); diff --git a/assets/javascripts/discourse/helpers/loading-spinner.es6 b/assets/javascripts/discourse/helpers/loading-spinner.es6 deleted file mode 100644 index 5f15403d..00000000 --- a/assets/javascripts/discourse/helpers/loading-spinner.es6 +++ /dev/null @@ -1,17 +0,0 @@ -import { htmlHelper } from "discourse-common/lib/helpers"; - -function renderSpinner(cssClass) { - var html = "
"; -} -var spinnerHTML = renderSpinner(); - -export default htmlHelper((params) => { - const hash = params.hash; - return renderSpinner(hash && hash.size ? hash.size : undefined); -}); - -export { spinnerHTML, renderSpinner }; diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index cecf2a03..dadf82f1 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -38,6 +38,7 @@ export default { }); api.modifyClass("component:uppy-image-uploader", { + pluginId: "custom-wizard", // Needed to ensure appEvents get registered when navigating between steps @observes("id") initOnStepChange() { diff --git a/assets/javascripts/discourse/models/custom-wizard-admin.js.es6 b/assets/javascripts/discourse/models/custom-wizard-admin.js.es6 new file mode 100644 index 00000000..bb4f7001 --- /dev/null +++ b/assets/javascripts/discourse/models/custom-wizard-admin.js.es6 @@ -0,0 +1,227 @@ +import EmberObject from "@ember/object"; +import { buildProperties, mapped, present } from "../lib/wizard-json"; +import { listProperties, snakeCase } from "../lib/wizard"; +import wizardSchema from "../lib/wizard-schema"; +import { Promise } from "rsvp"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; + +const CustomWizardAdmin = EmberObject.extend({ + save(opts) { + return new Promise((resolve, reject) => { + let wizard = this.buildJson(this, "wizard"); + + if (wizard.error) { + reject(wizard); + } + + let data = { + wizard, + }; + + if (opts.create) { + data.create = true; + } + + ajax(`/admin/wizards/wizard/${wizard.id}`, { + type: "PUT", + contentType: "application/json", + data: JSON.stringify(data), + }).then((result) => { + if (result.backend_validation_error) { + reject(result); + } else { + resolve(result); + } + }); + }); + }, + + buildJson(object, type, result = {}) { + let objectType = object.type || null; + + if (wizardSchema[type].types) { + if (!objectType) { + result.error = { + type: "required", + params: { type, property: "type" }, + }; + return result; + } + } + + for (let property of listProperties(type, { objectType })) { + let value = object.get(property); + + result = this.validateValue(property, value, object, type, result); + + if (result.error) { + break; + } + + if (mapped(property, type)) { + value = this.buildMappedJson(value); + } + + if (value !== undefined && value !== null) { + result[property] = value; + } + } + + if (!result.error) { + for (let arrayObjectType of Object.keys( + wizardSchema[type].objectArrays + )) { + let arraySchema = wizardSchema[type].objectArrays[arrayObjectType]; + let objectArray = object.get(arraySchema.property); + + if (arraySchema.required && !present(objectArray)) { + result.error = { + type: "required", + params: { type, property: arraySchema.property }, + }; + break; + } + + result[arraySchema.property] = []; + + for (let item of objectArray) { + let itemProps = this.buildJson(item, arrayObjectType); + + if (itemProps.error) { + result.error = itemProps.error; + break; + } else { + result[arraySchema.property].push(itemProps); + } + } + } + } + + return result; + }, + + validateValue(property, value, object, type, result) { + if (wizardSchema[type].required.indexOf(property) > -1 && !value) { + result.error = { + type: "required", + params: { type, property }, + }; + } + + let dependent = wizardSchema[type].dependent[property]; + if (dependent && value && !object[dependent]) { + result.error = { + type: "dependent", + params: { property, dependent }, + }; + } + + if (property === "api_body") { + try { + value = JSON.parse(value); + } catch (e) { + result.error = { + type: "invalid", + params: { type, property }, + }; + } + } + + return result; + }, + + buildMappedJson(value) { + if (typeof value === "string" || Number.isInteger(value)) { + return value; + } + if (!value || !value.length) { + return false; + } + + let inputs = value; + let result = []; + + inputs.forEach((inpt) => { + let input = { + type: inpt.type, + }; + + if (inpt.connector) { + input.connector = inpt.connector; + } + + if (present(inpt.output)) { + input.output = inpt.output; + input.output_type = snakeCase(inpt.output_type); + input.output_connector = inpt.output_connector; + } + + if (present(inpt.pairs)) { + input.pairs = []; + + inpt.pairs.forEach((pr) => { + if (present(pr.key) && present(pr.value)) { + let pairParams = { + index: pr.index, + key: pr.key, + key_type: snakeCase(pr.key_type), + value: pr.value, + value_type: snakeCase(pr.value_type), + connector: pr.connector, + }; + + input.pairs.push(pairParams); + } + }); + } + + if ( + (input.type === "assignment" && present(input.output)) || + present(input.pairs) + ) { + result.push(input); + } + }); + + if (!result.length) { + result = false; + } + + return result; + }, + + remove() { + return ajax(`/admin/wizards/wizard/${this.id}`, { + type: "DELETE", + }) + .then(() => this.destroy()) + .catch(popupAjaxError); + }, +}); + +CustomWizardAdmin.reopenClass({ + all() { + return ajax("/admin/wizards/wizard", { + type: "GET", + }) + .then((result) => { + return result.wizard_list; + }) + .catch(popupAjaxError); + }, + + submissions(wizardId) { + return ajax(`/admin/wizards/submissions/${wizardId}`, { + type: "GET", + }).catch(popupAjaxError); + }, + + create(wizardJson = {}) { + const wizard = this._super.apply(this); + wizard.setProperties(buildProperties(wizardJson)); + return wizard; + }, +}); + +export default CustomWizardAdmin; diff --git a/assets/javascripts/discourse/models/field.js.es6 b/assets/javascripts/discourse/models/custom-wizard-field.js.es6 similarity index 100% rename from assets/javascripts/discourse/models/field.js.es6 rename to assets/javascripts/discourse/models/custom-wizard-field.js.es6 diff --git a/assets/javascripts/discourse/models/step.js.es6 b/assets/javascripts/discourse/models/custom-wizard-step.js.es6 similarity index 100% rename from assets/javascripts/discourse/models/step.js.es6 rename to assets/javascripts/discourse/models/custom-wizard-step.js.es6 diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index 2fa6bc4c..77f439c7 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -1,227 +1,127 @@ -import { ajax } from "discourse/lib/ajax"; import EmberObject from "@ember/object"; -import { buildProperties, mapped, present } from "../lib/wizard-json"; -import { listProperties, snakeCase } from "../lib/wizard"; -import wizardSchema from "../lib/wizard-schema"; -import { Promise } from "rsvp"; +import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import discourseComputed from "discourse-common/utils/decorators"; +import getUrl from "discourse-common/lib/get-url"; +import CustomWizardField from "./custom-wizard-field"; +import CustomWizardStep from "./custom-wizard-step"; const CustomWizard = EmberObject.extend({ - save(opts) { - return new Promise((resolve, reject) => { - let wizard = this.buildJson(this, "wizard"); + @discourseComputed("steps.length") + totalSteps: (length) => length, - if (wizard.error) { - reject(wizard); - } - - let data = { - wizard, - }; - - if (opts.create) { - data.create = true; - } - - ajax(`/admin/wizards/wizard/${wizard.id}`, { - type: "PUT", - contentType: "application/json", - data: JSON.stringify(data), - }).then((result) => { - if (result.backend_validation_error) { - reject(result); - } else { - resolve(result); - } - }); - }); + skip() { + if (this.required && !this.completed && this.permitted) { + return; + } + CustomWizard.skip(this.id); }, - buildJson(object, type, result = {}) { - let objectType = object.type || null; - - if (wizardSchema[type].types) { - if (!objectType) { - result.error = { - type: "required", - params: { type, property: "type" }, - }; - return result; - } - } - - for (let property of listProperties(type, { objectType })) { - let value = object.get(property); - - result = this.validateValue(property, value, object, type, result); - - if (result.error) { - break; - } - - if (mapped(property, type)) { - value = this.buildMappedJson(value); - } - - if (value !== undefined && value !== null) { - result[property] = value; - } - } - - if (!result.error) { - for (let arrayObjectType of Object.keys( - wizardSchema[type].objectArrays - )) { - let arraySchema = wizardSchema[type].objectArrays[arrayObjectType]; - let objectArray = object.get(arraySchema.property); - - if (arraySchema.required && !present(objectArray)) { - result.error = { - type: "required", - params: { type, property: arraySchema.property }, - }; - break; - } - - result[arraySchema.property] = []; - - for (let item of objectArray) { - let itemProps = this.buildJson(item, arrayObjectType); - - if (itemProps.error) { - result.error = itemProps.error; - break; - } else { - result[arraySchema.property].push(itemProps); - } - } - } - } - - return result; - }, - - validateValue(property, value, object, type, result) { - if (wizardSchema[type].required.indexOf(property) > -1 && !value) { - result.error = { - type: "required", - params: { type, property }, - }; - } - - let dependent = wizardSchema[type].dependent[property]; - if (dependent && value && !object[dependent]) { - result.error = { - type: "dependent", - params: { property, dependent }, - }; - } - - if (property === "api_body") { - try { - value = JSON.parse(value); - } catch (e) { - result.error = { - type: "invalid", - params: { type, property }, - }; - } - } - - return result; - }, - - buildMappedJson(value) { - if (typeof value === "string" || Number.isInteger(value)) { - return value; - } - if (!value || !value.length) { - return false; - } - - let inputs = value; - let result = []; - - inputs.forEach((inpt) => { - let input = { - type: inpt.type, - }; - - if (inpt.connector) { - input.connector = inpt.connector; - } - - if (present(inpt.output)) { - input.output = inpt.output; - input.output_type = snakeCase(inpt.output_type); - input.output_connector = inpt.output_connector; - } - - if (present(inpt.pairs)) { - input.pairs = []; - - inpt.pairs.forEach((pr) => { - if (present(pr.key) && present(pr.value)) { - let pairParams = { - index: pr.index, - key: pr.key, - key_type: snakeCase(pr.key_type), - value: pr.value, - value_type: snakeCase(pr.value_type), - connector: pr.connector, - }; - - input.pairs.push(pairParams); - } - }); - } - - if ( - (input.type === "assignment" && present(input.output)) || - present(input.pairs) - ) { - result.push(input); - } - }); - - if (!result.length) { - result = false; - } - - return result; - }, - - remove() { - return ajax(`/admin/wizards/wizard/${this.id}`, { - type: "DELETE", - }) - .then(() => this.destroy()) - .catch(popupAjaxError); + restart() { + CustomWizard.restart(this.id); }, }); CustomWizard.reopenClass({ - all() { - return ajax("/admin/wizards/wizard", { - type: "GET", - }) + skip(wizardId) { + ajax({ url: `/w/${wizardId}/skip`, type: "PUT" }) .then((result) => { - return result.wizard_list; + CustomWizard.finished(result); }) .catch(popupAjaxError); }, - submissions(wizardId) { - return ajax(`/admin/wizards/submissions/${wizardId}`, { - type: "GET", - }).catch(popupAjaxError); + restart(wizardId) { + ajax({ url: `/w/${wizardId}/skip`, type: "PUT" }) + .then(() => { + window.location.href = `/w/${wizardId}`; + }) + .catch(popupAjaxError); }, - create(wizardJson = {}) { - const wizard = this._super.apply(this); - wizard.setProperties(buildProperties(wizardJson)); - return wizard; + finished(result) { + let url = "/"; + if (result.redirect_on_complete) { + url = result.redirect_on_complete; + } + window.location.href = getUrl(url); + }, + + build(wizardJson) { + if (!wizardJson) { + return null; + } + + if (!wizardJson.completed && wizardJson.steps) { + wizardJson.steps = wizardJson.steps + .map((step) => { + const stepObj = CustomWizardStep.create(step); + stepObj.wizardId = wizardJson.id; + + stepObj.fields.sort((a, b) => { + return parseFloat(a.number) - parseFloat(b.number); + }); + + let tabindex = 1; + stepObj.fields.forEach((f) => { + f.tabindex = tabindex; + + if (["date_time"].includes(f.type)) { + tabindex = tabindex + 2; + } else { + tabindex++; + } + }); + + stepObj.fields = stepObj.fields.map((f) => { + f.wizardId = wizardJson.id; + f.stepId = stepObj.id; + return CustomWizardField.create(f); + }); + + return stepObj; + }) + .sort((a, b) => { + return parseFloat(a.index) - parseFloat(b.index); + }); + } + return CustomWizard.create(wizardJson); }, }); +export function findCustomWizard(wizardId, params = {}) { + let url = `/w/${wizardId}.json`; + + let paramKeys = Object.keys(params).filter((k) => { + if (k === "wizard_id") { + return false; + } + return !!params[k]; + }); + + if (paramKeys.length) { + url += "?"; + paramKeys.forEach((k, i) => { + if (i > 0) { + url += "&"; + } + url += `${k}=${params[k]}`; + }); + } + + return ajax(url).then((result) => { + return CustomWizard.build(result); + }); +} + +let _wizard_store; + +export function updateCachedWizard(wizard) { + _wizard_store = wizard; +} + +export function getCachedWizard() { + return _wizard_store; +} + export default CustomWizard; diff --git a/assets/javascripts/discourse/models/site.js.es6 b/assets/javascripts/discourse/models/site.js.es6 deleted file mode 100644 index 96837ff2..00000000 --- a/assets/javascripts/discourse/models/site.js.es6 +++ /dev/null @@ -1,11 +0,0 @@ -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 = getOwner(this).lookup("service:store"); - return store.createRecord("site", {}); - }, -}); diff --git a/assets/javascripts/discourse/models/wizard.js.es6 b/assets/javascripts/discourse/models/wizard.js.es6 deleted file mode 100644 index 1725ae5d..00000000 --- a/assets/javascripts/discourse/models/wizard.js.es6 +++ /dev/null @@ -1,164 +0,0 @@ -import { default as computed } from "discourse-common/utils/decorators"; -import getUrl from "discourse-common/lib/get-url"; -import Field from "./field"; -import { ajax } from "discourse/lib/ajax"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import Step from "./step"; -import EmberObject from "@ember/object"; -import Site from "./site"; - -const CustomWizard = EmberObject.extend({ - @computed("steps.length") - totalSteps: (length) => length, - - skip() { - if (this.required && !this.completed && this.permitted) { - return; - } - CustomWizard.skip(this.id); - }, - - restart() { - CustomWizard.restart(this.id); - }, -}); - -CustomWizard.reopenClass({ - skip(wizardId) { - ajax({ url: `/w/${wizardId}/skip`, type: "PUT" }) - .then((result) => { - CustomWizard.finished(result); - }) - .catch(popupAjaxError); - }, - - restart(wizardId) { - ajax({ url: `/w/${wizardId}/skip`, type: "PUT" }) - .then(() => { - window.location.href = `/w/${wizardId}`; - }) - .catch(popupAjaxError); - }, - - finished(result) { - let url = "/"; - if (result.redirect_on_complete) { - url = result.redirect_on_complete; - } - window.location.href = getUrl(url); - }, - - build(wizardJson) { - if (!wizardJson) { - return null; - } - - if (!wizardJson.completed && wizardJson.steps) { - wizardJson.steps = wizardJson.steps - .map((step) => { - const stepObj = Step.create(step); - stepObj.wizardId = wizardJson.id; - - stepObj.fields.sort((a, b) => { - return parseFloat(a.number) - parseFloat(b.number); - }); - - let tabindex = 1; - stepObj.fields.forEach((f) => { - f.tabindex = tabindex; - - if (["date_time"].includes(f.type)) { - tabindex = tabindex + 2; - } else { - tabindex++; - } - }); - - stepObj.fields = stepObj.fields.map((f) => { - f.wizardId = wizardJson.id; - f.stepId = stepObj.id; - return Field.create(f); - }); - - return stepObj; - }) - .sort((a, b) => { - return parseFloat(a.index) - parseFloat(b.index); - }); - } - - if (wizardJson.categories) { - let subcatMap = {}; - let categoriesById = {}; - let categories = wizardJson.categories.map((c) => { - if (c.parent_category_id) { - subcatMap[c.parent_category_id] = - subcatMap[c.parent_category_id] || []; - subcatMap[c.parent_category_id].push(c.id); - } - return (categoriesById[c.id] = EmberObject.create(c)); - }); - - // Associate the categories with their parents - categories.forEach((c) => { - let subcategoryIds = subcatMap[c.get("id")]; - if (subcategoryIds) { - c.set( - "subcategories", - subcategoryIds.map((id) => categoriesById[id]) - ); - } - if (c.get("parent_category_id")) { - c.set("parentCategory", categoriesById[c.get("parent_category_id")]); - } - }); - - Site.currentProp("categories", categories); - Site.currentProp("listByActivity", categories); - Site.currentProp("categoriesById", categoriesById); - Site.currentProp( - "uncategorized_category_id", - wizardJson.uncategorized_category_id - ); - } - - return CustomWizard.create(wizardJson); - }, -}); - -export function findCustomWizard(wizardId, params = {}) { - let url = `/w/${wizardId}`; - - let paramKeys = Object.keys(params).filter((k) => { - if (k === "wizard_id") { - return false; - } - return !!params[k]; - }); - - if (paramKeys.length) { - url += "?"; - paramKeys.forEach((k, i) => { - if (i > 0) { - url += "&"; - } - url += `${k}=${params[k]}`; - }); - } - - return ajax({ url, cache: false, dataType: "json" }).then((result) => { - return CustomWizard.build(result); - }); -} - -let _wizard_store; - -export function updateCachedWizard(wizard) { - _wizard_store = wizard; -} - -export function getCachedWizard() { - return _wizard_store; -} - -export default CustomWizard; diff --git a/assets/javascripts/discourse/routes/admin-wizards-manager.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-manager.js.es6 index dfbfc472..b3314186 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-manager.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-manager.js.es6 @@ -1,9 +1,9 @@ -import CustomWizard from "../models/custom-wizard"; +import CustomWizardAdmin from "../models/custom-wizard-admin"; import DiscourseRoute from "discourse/routes/discourse"; export default DiscourseRoute.extend({ model() { - return CustomWizard.all(); + return CustomWizardAdmin.all(); }, setupController(controller, model) { diff --git a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 index 73168ff3..64847a9f 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 @@ -1,11 +1,11 @@ -import CustomWizard from "../models/custom-wizard"; +import CustomWizardAdmin from "../models/custom-wizard-admin"; import DiscourseRoute from "discourse/routes/discourse"; const excludedMetaFields = ["route_to", "redirect_on_complete", "redirect_to"]; export default DiscourseRoute.extend({ model(params) { - return CustomWizard.submissions(params.wizardId); + return CustomWizardAdmin.submissions(params.wizardId); }, setupController(controller, model) { diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index cb2d54c3..f55ff19e 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -1,4 +1,4 @@ -import CustomWizard from "../models/custom-wizard"; +import CustomWizardAdmin from "../models/custom-wizard-admin"; import { ajax } from "discourse/lib/ajax"; import DiscourseRoute from "discourse/routes/discourse"; import I18n from "I18n"; @@ -20,7 +20,9 @@ export default DiscourseRoute.extend({ setupController(controller, model) { const parentModel = this.modelFor("adminWizardsWizard"); - const wizard = CustomWizard.create(!model || model.create ? {} : model); + const wizard = CustomWizardAdmin.create( + !model || model.create ? {} : model + ); const fieldTypes = Object.keys(parentModel.field_types).map((type) => { return { id: type, diff --git a/assets/javascripts/discourse/routes/custom-wizard-index.js.es6 b/assets/javascripts/discourse/routes/custom-wizard-index.js.es6 index a7f5b606..1d5a71c7 100644 --- a/assets/javascripts/discourse/routes/custom-wizard-index.js.es6 +++ b/assets/javascripts/discourse/routes/custom-wizard-index.js.es6 @@ -1,4 +1,4 @@ -import { getCachedWizard } from "../models/wizard"; +import { getCachedWizard } from "../models/custom-wizard"; import Route from "@ember/routing/route"; export default Route.extend({ diff --git a/assets/javascripts/discourse/routes/custom-wizard-step.js.es6 b/assets/javascripts/discourse/routes/custom-wizard-step.js.es6 index 2c18c63d..d2e141eb 100644 --- a/assets/javascripts/discourse/routes/custom-wizard-step.js.es6 +++ b/assets/javascripts/discourse/routes/custom-wizard-step.js.es6 @@ -1,5 +1,5 @@ import I18n from "I18n"; -import { getCachedWizard } from "../models/wizard"; +import { getCachedWizard } from "../models/custom-wizard"; import Route from "@ember/routing/route"; export default Route.extend({ diff --git a/assets/javascripts/discourse/routes/custom-wizard.js.es6 b/assets/javascripts/discourse/routes/custom-wizard.js.es6 index 7e0816a4..21c716ac 100644 --- a/assets/javascripts/discourse/routes/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/routes/custom-wizard.js.es6 @@ -1,4 +1,4 @@ -import { findCustomWizard, updateCachedWizard } from "../models/wizard"; +import { findCustomWizard, updateCachedWizard } from "../models/custom-wizard"; import I18n from "I18n"; import Route from "@ember/routing/route"; @@ -50,6 +50,7 @@ export default Route.extend({ customWizard: true, logoUrl: this.siteSettings.logo_small, reset: null, + model, }); const stepModel = this.modelFor("step"); @@ -60,22 +61,17 @@ export default Route.extend({ ) { this.showDialog(model); } - }, - getWizardBackground() { - const wizard = this.controllerFor("custom-wizard").get("model"); - return wizard ? wizard.get("background") : ""; + const background = model.get("background"); + if (background) { + document.body.style.background = background; + } }, activate() { if (!document.body.classList.contains("custom-wizard")) { document.body.classList.add("custom-wizard"); } - - const background = this.getWizardBackground(); - if (background) { - document.body.style.background = background; - } }, deactivate() { @@ -83,9 +79,6 @@ export default Route.extend({ document.body.classList.remove("custom-wizard"); } - const background = this.getWizardBackground(); - if (background) { - document.body.style.background = ""; - } + document.body.style.background = ""; }, }); diff --git a/assets/stylesheets/common/custom/autocomplete.scss b/assets/stylesheets/common/custom/autocomplete.scss index 0e3e73e4..28386e2d 100644 --- a/assets/stylesheets/common/custom/autocomplete.scss +++ b/assets/stylesheets/common/custom/autocomplete.scss @@ -1,15 +1,23 @@ +img.avatar { + border-radius: 50%; + vertical-align: middle; +} + div.ac-wrap { + box-sizing: border-box; + position: relative; overflow: visible; max-height: 150px; min-height: 34px; background-color: var(--secondary); border: 1px solid var(--primary-medium); - padding: 5px 4px 1px 4px; + padding: 5px; div.item { float: left; margin-bottom: 4px; margin-right: 10px; + line-height: 1.6; span { height: 24px; @@ -46,73 +54,6 @@ div.ac-wrap { color: var(--danger); } } -} - -img.avatar { - border-radius: 50%; - vertical-align: middle; -} - -.autocomplete { - z-index: 999999; - position: absolute; - width: 240px; - background-color: var(--secondary); - border: 1px solid var(--primary-low); - - ul { - list-style: none; - padding: 0; - margin: 0; - - li { - .d-users { - color: var(--primary-medium); - padding: 0 2px; - } - - border-bottom: 1px solid var(--primary-low); - - a { - padding: 5px; - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: 14px; - text-decoration: none; - color: var(--primary); - - img { - margin-right: 5px; - } - - span.username { - color: var(--primary); - vertical-align: middle; - } - span.name { - font-size: $font-down-1; - vertical-align: middle; - margin-left: 5px; - color: var(--primary); - } - &.selected { - background-color: var(--tertiary); - color: var(--secondary); - } - &:hover { - background-color: var(--highlight-low); - text-decoration: none; - } - } - } - } -} - -.ac-wrap { - box-sizing: border-box; - position: relative; .ac-loading { position: absolute; @@ -120,22 +61,80 @@ img.avatar { right: 5px; } - .item { - line-height: 1.6; + input { + margin-bottom: 0; } .autocomplete { + z-index: 999999; + position: absolute; width: inherit; - left: 0 !important; - width: 100%; - top: 30px !important; + max-width: 240px; box-sizing: border-box; + background-color: var(--secondary); + border: 1px solid var(--primary-low); li, .no-results { padding: 10px; } + ul { + list-style: none; + padding: 0; + margin: 0; + + li { + .d-users { + color: var(--primary-medium); + padding: 0 2px; + } + + border-bottom: 1px solid var(--primary-low); + + a { + padding: 5px; + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 14px; + text-decoration: none; + color: var(--primary); + + img { + margin-right: 5px; + } + + span.username { + color: var(--primary); + vertical-align: middle; + } + + span.name { + font-size: $font-down-1; + vertical-align: middle; + margin-left: 5px; + color: var(--primary); + } + + &.selected { + background-color: var(--tertiary); + + span.username, + span.name { + color: var(--secondary); + } + } + + &:hover:not(.selected) { + background-color: var(--highlight-low); + text-decoration: none; + } + } + } + } + ul > label { margin: 5px 0; padding: 0 5px; diff --git a/assets/stylesheets/common/custom/field.scss b/assets/stylesheets/common/custom/field.scss index 9570c858..b6c75b3e 100644 --- a/assets/stylesheets/common/custom/field.scss +++ b/assets/stylesheets/common/custom/field.scss @@ -60,6 +60,7 @@ body.custom-wizard { & > label.field-label { order: 1; font-weight: 400; + margin-bottom: 0; } & > .field-description { @@ -70,6 +71,7 @@ body.custom-wizard { .url-field input { width: 100%; + max-width: 500px; font-size: 1.1487em; padding: 6px; transition: border-color 0.5s; @@ -136,9 +138,15 @@ body.custom-wizard { position: absolute !important; } - .d-time-input .select-kit.combo-box .select-kit-header { - font-size: 1.1487em; - padding: 6px; + .d-time-input { + .select-kit.combo-box { + width: 90px; + + .select-kit-header { + font-size: 1.1487em; + padding: 6px; + } + } } .d-date-time-input { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index b51421a7..e24077b1 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2,6 +2,66 @@ en: js: wizard: complete_custom: "Complete the {{name}}" + completed: "You have completed this wizard." + not_permitted: "You are not permitted to access this wizard." + none: "There is no wizard here." + return_to_site: "Return to {{siteName}}" + requires_login: "You need to be logged in to access this wizard." + reset: "Reset this wizard." + step_not_permitted: "You're not permitted to view this step." + incomplete_submission: + title: "Continue editing your draft submission from %{date}?" + resume: "Continue" + restart: "Start over" + x_characters: + one: "%{count} Character" + other: "%{count} Characters" + + wizard_composer: + show_preview: "Preview Post" + hide_preview: "Edit Post" + quote_post_title: "Quote whole post" + bold_label: "B" + bold_title: "Strong" + bold_text: "strong text" + italic_label: "I" + italic_title: "Emphasis" + italic_text: "emphasized text" + link_title: "Hyperlink" + link_description: "enter link description here" + link_dialog_title: "Insert Hyperlink" + link_optional_text: "optional title" + link_url_placeholder: "http://example.com" + quote_title: "Blockquote" + quote_text: "Blockquote" + blockquote_text: "Blockquote" + code_title: "Preformatted text" + code_text: "indent preformatted text by 4 spaces" + paste_code_text: "type or paste code here" + upload_title: "Upload" + upload_description: "enter upload description here" + olist_title: "Numbered List" + ulist_title: "Bulleted List" + list_item: "List item" + toggle_direction: "Toggle Direction" + help: "Markdown Editing Help" + collapse: "minimize the composer panel" + abandon: "close composer and discard draft" + modal_ok: "OK" + modal_cancel: "Cancel" + cant_send_pm: "Sorry, you can't send a message to %{username}." + yourself_confirm: + title: "Did you forget to add recipients?" + body: "Right now this message is only being sent to yourself!" + + realtime_validations: + similar_topics: + insufficient_characters: "Type a minimum 5 characters to start looking for similar topics" + insufficient_characters_categories: "Type a minimum 5 characters to start looking for similar topics in %{catLinks}" + results: "Your topic is similar to..." + no_results: "No similar topics." + loading: "Looking for similar topics..." + show: "show" admin_js: admin: @@ -49,7 +109,7 @@ en: translation: "Translation" translation_placeholder: "key" type: "Type" - none: "Make a selection" + none: "Make a selection" submission_key: 'submission key' param_key: 'param' group: "Group" @@ -97,15 +157,15 @@ en: destroying: Destroying wizards... import_complete: Import complete destroy_complete: Destruction complete - + editor: show: "Show" hide: "Hide" preview: "{{action}} Preview" popover: "{{action}} Fields" - + input: - conditional: + conditional: name: 'if' output: 'then' assignment: @@ -114,7 +174,7 @@ en: name: 'map' validation: name: 'ensure' - + selector: label: text: "text" @@ -152,7 +212,7 @@ en: dependent: "{{property}} is dependent on {{dependent}}" conflict: "{{type}} with {{property}} '{{value}}' already exists" after_time: "After time invalid" - + step: header: "Steps" title: "Title" @@ -166,7 +226,7 @@ en: force_final: label: "Conditional Final Step" description: "Display this step as the final step if conditions on later steps have not passed when the user reaches this step." - + field: header: "Fields" label: "Label" @@ -189,7 +249,7 @@ en: prefill: "Prefill" content: "Content" tag_groups: "Tag Groups" - date_time_format: + date_time_format: label: "Format" instructions: "Moment.js format" validations: @@ -206,7 +266,7 @@ en: weeks: "Weeks" months: "Months" years: "Years" - + type: text: "Text" textarea: Textarea @@ -225,7 +285,7 @@ en: date: Date time: Time date_time: Date & Time - + connector: and: "and" or: "or" @@ -239,7 +299,7 @@ en: regex: '=~' association: '→' is: 'is' - + action: header: "Actions" include: "Include Fields" @@ -247,8 +307,8 @@ en: post: "Post" topic_attr: "Topic Attribute" interpolate_fields: "Insert wizard fields using the field_id in w{}. Insert user fields using field key in u{}." - - run_after: + + run_after: label: "Run After" wizard_completion: "Wizard Completion" custom_fields: @@ -330,11 +390,11 @@ en: messageable_level: Messageable Level visibility_level: Visibility Level members_visibility_level: Members Visibility Level - + custom_field: nav_label: "Custom Fields" add: "Add" - external: + external: label: "from another plugin" title: "This custom field has been added by another plugin. You can use it in your wizards but you can't edit the field here." name: @@ -363,7 +423,7 @@ en: basic_category: "Category" basic_group: "Group" post: "Post" - + submissions: nav_label: "Submissions" title: "{{name}} Submissions" @@ -421,7 +481,7 @@ en: log: label: "Logs" - + log: nav_label: "Logs" @@ -433,7 +493,7 @@ en: imported: imported upload: Select wizards.json destroy: Destroy - destroyed: destroyed + destroyed: destroyed wizard_js: group: @@ -483,78 +543,3 @@ en: countrycode: "Please select a country." coordinates: "Please complete the set of coordinates." geo_location: "Search and select a result." - - select_kit: - default_header_text: Select... - no_content: No matches found - filter_placeholder: Search... - filter_placeholder_with_any: Search or create... - create: "Create: '{{content}}'" - max_content_reached: - one: "You can only select {{count}} item." - other: "You can only select {{count}} items." - min_content_not_reached: - one: "Select at least {{count}} item." - other: "Select at least {{count}} items." - - wizard: - completed: "You have completed this wizard." - not_permitted: "You are not permitted to access this wizard." - none: "There is no wizard here." - return_to_site: "Return to {{siteName}}" - requires_login: "You need to be logged in to access this wizard." - reset: "Reset this wizard." - step_not_permitted: "You're not permitted to view this step." - incomplete_submission: - title: "Continue editing your draft submission from %{date}?" - resume: "Continue" - restart: "Start over" - x_characters: - one: "%{count} Character" - other: "%{count} Characters" - - wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" - quote_post_title: "Quote whole post" - bold_label: "B" - bold_title: "Strong" - bold_text: "strong text" - italic_label: "I" - italic_title: "Emphasis" - italic_text: "emphasized text" - link_title: "Hyperlink" - link_description: "enter link description here" - link_dialog_title: "Insert Hyperlink" - link_optional_text: "optional title" - link_url_placeholder: "http://example.com" - quote_title: "Blockquote" - quote_text: "Blockquote" - blockquote_text: "Blockquote" - code_title: "Preformatted text" - code_text: "indent preformatted text by 4 spaces" - paste_code_text: "type or paste code here" - upload_title: "Upload" - upload_description: "enter upload description here" - olist_title: "Numbered List" - ulist_title: "Bulleted List" - list_item: "List item" - toggle_direction: "Toggle Direction" - help: "Markdown Editing Help" - collapse: "minimize the composer panel" - abandon: "close composer and discard draft" - modal_ok: "OK" - modal_cancel: "Cancel" - cant_send_pm: "Sorry, you can't send a message to %{username}." - yourself_confirm: - title: "Did you forget to add recipients?" - body: "Right now this message is only being sent to yourself!" - - realtime_validations: - similar_topics: - insufficient_characters: "Type a minimum 5 characters to start looking for similar topics" - insufficient_characters_categories: "Type a minimum 5 characters to start looking for similar topics in %{catLinks}" - results: "Your topic is similar to..." - no_results: "No similar topics." - loading: "Looking for similar topics..." - show: "show" diff --git a/spec/extensions/sprockets_directive_spec.rb b/spec/extensions/sprockets_directive_spec.rb deleted file mode 100644 index db54c5dc..00000000 --- a/spec/extensions/sprockets_directive_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -describe "Sprockets: require_tree_discourse directive" do - let(:discourse_asset_path) { - "#{Rails.root}/app/assets/javascripts/" - } - let(:fixture_asset_path) { - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/" - } - let(:test_file_contents) { - "console.log('hello')" - } - let(:resolved_file_contents) { - File.read( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/resolved_js_file_contents.txt" - ) - } - - before do - @env ||= Sprockets::Environment.new - discourse_asset_path = "#{Rails.root}/app/assets/javascripts/" - fixture_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/" - @env.append_path(discourse_asset_path) - @env.append_path(fixture_asset_path) - @env.cache = {} - end - - def create_tmp_folder_and_run(path, file_contents, &block) - dir = File.dirname(path) - unless File.directory?(dir) - FileUtils.mkdir_p(dir) - end - - File.new(path, 'w') - File.write(path, file_contents) - yield block if block_given? - FileUtils.rm_r(dir) - end - - it "includes assets from the discourse core" do - create_tmp_folder_and_run("#{discourse_asset_path}/sptest/test.js", test_file_contents) do - expect(@env.find_asset("require_tree_discourse_test.js").to_s).to eq(resolved_file_contents) - end - end - - it "throws ArgumentError if path is empty" do - expect { @env.find_asset("require_tree_discourse_empty.js") }.to raise_error(CustomWizard::SprocketsEmptyPath).with_message("path cannot be empty") - end - - it "throws ArgumentError if path is non non-existent" do - expect { @env.find_asset("require_tree_discourse_non_existant.js") }.to raise_error(CustomWizard::SprocketsFileNotFound) - end -end diff --git a/spec/requests/custom_wizard/realtime_validations_spec.rb b/spec/requests/custom_wizard/realtime_validations_spec.rb index a57333b8..0fd8829f 100644 --- a/spec/requests/custom_wizard/realtime_validations_spec.rb +++ b/spec/requests/custom_wizard/realtime_validations_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true describe CustomWizard::RealtimeValidationsController do - - fab!(:validation_type) { "test_stub" } - fab!(:validation_type_stub) { + fab!(:user) { Fabricate(:user) } + let(:validation_type) { "test_stub" } + let(:validation_type_stub) { { types: [:text], component: "similar-topics-validator", @@ -12,9 +12,8 @@ describe CustomWizard::RealtimeValidationsController do } } - before(:all) do - sign_in(Fabricate(:user)) - CustomWizard::RealtimeValidation.types = { test_stub: validation_type_stub } + before do + sign_in(user) class CustomWizard::RealtimeValidation::TestStub attr_accessor :user @@ -40,6 +39,7 @@ describe CustomWizard::RealtimeValidationsController do end it "gives the correct response for a given type" do + CustomWizard::RealtimeValidation.types = { test_stub: validation_type_stub } get '/realtime-validations.json', params: { type: validation_type } expect(response.status).to eq(200) expected_response = [ @@ -50,11 +50,13 @@ describe CustomWizard::RealtimeValidationsController do end it "gives 400 error when no type is passed" do + CustomWizard::RealtimeValidation.types = { test_stub: validation_type_stub } get '/realtime-validations.json' expect(response.status).to eq(400) end it "gives 400 error when a required additional param is missing" do + CustomWizard::RealtimeValidation.types = { test_stub: validation_type_stub } CustomWizard::RealtimeValidation.types[:test_stub][:required_params] = [:test1] get '/realtime-validations.json', params: { type: validation_type } expect(response.status).to eq(400) @@ -63,6 +65,7 @@ describe CustomWizard::RealtimeValidationsController do end it "gives 500 response code when a non existant type is passed" do + CustomWizard::RealtimeValidation.types = { test_stub: validation_type_stub } get '/realtime-validations.json', params: { type: "random_type" } expect(response.status).to eq(500) end diff --git a/test/javascripts/acceptance/field-test.js.es6 b/test/javascripts/acceptance/field-test.js similarity index 79% rename from test/javascripts/acceptance/field-test.js.es6 rename to test/javascripts/acceptance/field-test.js index 9300fc09..56368396 100644 --- a/test/javascripts/acceptance/field-test.js.es6 +++ b/test/javascripts/acceptance/field-test.js @@ -1,38 +1,42 @@ import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers"; import { test } from "qunit"; -import { exists } from "../helpers/test"; -import acceptance, { +import { + acceptance, count, query, - server, - visible, -} from "../helpers/acceptance"; -import { allFieldsWizard, getWizard } from "../helpers/wizard"; + exists, + visible +} from "discourse/tests/helpers/qunit-helpers"; +import { allFieldsWizard } from "../helpers/wizard"; import tagsJson from "../fixtures/tags"; import usersJson from "../fixtures/users"; -import { response } from "../pretender"; -acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { +acceptance("Field | Fields", function (needs) { + needs.pretender((server, helper) => { + server.get("/w/wizard.json", (request) => (helper.response(allFieldsWizard))); + server.get("/tags/filter/search", (request) => (helper.response({ results: tagsJson["tags"] }))); + server.get("/u/search/users", (request) => (helper.response(usersJson))); + }); + test("Text", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok(exists(".wizard-field.text-field input.wizard-focusable")); }); test("Textarea", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok( visible(".wizard-field.textarea-field textarea.wizard-focusable") ); }); test("Composer", async function (assert) { - await visit("/wizard"); + await visit("/w/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( + exists(".wizard-field.composer-field .d-editor-button-bar button") ); assert.ok(visible(".wizard-btn.toggle-preview")); @@ -50,19 +54,19 @@ acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { }); test("Text Only", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok(visible(".wizard-field.text-only-field label.field-label")); }); test("Date", async function (assert) { - await visit("/wizard"); + await visit("/w/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"); + await visit("/w/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" @@ -71,7 +75,7 @@ acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { }); test("Date Time", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok( visible(".wizard-field.date-time-field .d-date-time-input .select-kit") ); @@ -88,22 +92,22 @@ acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { }); test("Number", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok(visible(".wizard-field.number-field input[type='number']")); }); test("Checkbox", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok(visible(".wizard-field.checkbox-field input[type='checkbox']")); }); test("Url", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok(visible(".wizard-field.url-field input[type='text']")); }); test("Upload", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok( visible(".wizard-field.upload-field label.wizard-btn-upload-file") ); @@ -111,7 +115,7 @@ acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { }); test("Dropdown", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok(visible(".wizard-field.dropdown-field .single-select-header")); await click(".wizard-field.dropdown-field .select-kit-header"); assert.strictEqual( @@ -121,10 +125,7 @@ acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { }); test("Tag", async function (assert) { - server.get("/tags/filter/search", () => - response(200, { results: tagsJson["tags"] }) - ); - await visit("/wizard"); + await visit("/w/wizard"); assert.ok(visible(".wizard-field.tag-field .multi-select-header")); await click(".wizard-field.tag-field .select-kit-header"); assert.strictEqual( @@ -134,17 +135,16 @@ acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { }); test("Category", async function (assert) { - await visit("/wizard"); + await visit("/w/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 + assert.ok( + exists(".wizard-field.category-field .select-kit-collection li") ); }); test("Group", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok(visible(".wizard-field.group-field .single-select-header")); await click(".wizard-field.group-field .select-kit-header"); assert.strictEqual( @@ -154,9 +154,7 @@ acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { }); test("User", async function (assert) { - server.get("/u/search/users", () => response(200, usersJson)); - - await visit("/wizard"); + await visit("/w/wizard"); await fillIn( ".wizard-field.user-selector-field input.ember-text-field", "a" diff --git a/test/javascripts/acceptance/step-test.js.es6 b/test/javascripts/acceptance/step-test.js similarity index 59% rename from test/javascripts/acceptance/step-test.js.es6 rename to test/javascripts/acceptance/step-test.js index 1537f1c7..8e411243 100644 --- a/test/javascripts/acceptance/step-test.js.es6 +++ b/test/javascripts/acceptance/step-test.js @@ -1,20 +1,33 @@ import { click, visit } from "@ember/test-helpers"; import { test } from "qunit"; -import { exists } from "../helpers/test"; -import acceptance, { count, query, visible } from "../helpers/acceptance"; -import { getWizard, stepNotPermitted, wizard } from "../helpers/wizard"; -import { saveStep, update } from "../helpers/step"; +import { + acceptance, + count, + query, + exists, + visible +} from "discourse/tests/helpers/qunit-helpers"; +import { stepNotPermitted, wizard, update } from "../helpers/wizard"; + +acceptance("Step | Not permitted", function (needs) { + needs.pretender((server, helper) => { + server.get("/w/wizard.json", (request) => (helper.response(stepNotPermitted))); + }); -acceptance("Step | Not permitted", [getWizard(stepNotPermitted)], function () { test("Shows not permitted message", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.ok(exists(".step-message.not-permitted")); }); }); -acceptance("Step | Step", [getWizard(wizard), saveStep(update)], function () { +acceptance("Step | Step", function (needs) { + needs.pretender((server, helper) => { + server.get("/w/wizard.json", (request) => (helper.response(wizard))); + server.put("/w/wizard/steps/:step_id", (request) => (helper.response(update))); + }); + test("Renders the step", async function (assert) { - await visit("/wizard"); + await visit("/w/wizard"); assert.strictEqual( query(".wizard-step-title p").textContent.trim(), "Text" @@ -33,7 +46,7 @@ acceptance("Step | Step", [getWizard(wizard), saveStep(update)], function () { }); test("Goes to the next step", async function (assert) { - await visit("/wizard"); + await visit("/w/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/test/javascripts/acceptance/wizard-test.js b/test/javascripts/acceptance/wizard-test.js new file mode 100644 index 00000000..e371ae14 --- /dev/null +++ b/test/javascripts/acceptance/wizard-test.js @@ -0,0 +1,96 @@ +import { visit, settled } from "@ember/test-helpers"; +import { test } from "qunit"; +import { + acceptance, + count, + query, + exists, +} from "discourse/tests/helpers/qunit-helpers"; +import { + wizard, + wizardCompleted, + wizardNoUser, + wizardNotPermitted, +} from "../helpers/wizard"; + +acceptance("Wizard | Not logged in", function (needs) { + needs.pretender((server, helper) => { + server.get("/w/wizard.json", (request) => (helper.response(wizardNoUser))); + }); + + test("Wizard no access requires login", async function (assert) { + await visit("/w/wizard"); + assert.ok(exists(".wizard-no-access.requires-login")); + }); +}); + +acceptance( + "Wizard | Not permitted", + function (needs) { + needs.user(); + needs.pretender((server, helper) => { + server.get("/w/wizard.json", (request) => (helper.response(wizardNotPermitted))); + }); + + test("Wizard no access not permitted", async function (assert) { + await visit("/w/wizard"); + assert.ok(exists(".wizard-no-access.not-permitted")); + }); + } +); + +acceptance("Wizard | Completed", function (needs) { + needs.user(); + needs.pretender((server, helper) => { + server.get("/w/wizard.json", (request) => (helper.response(wizardCompleted))); + }); + + test("Wizard no access completed", async function (assert) { + await visit("/w/wizard"); + assert.ok(exists(".wizard-no-access.completed")); + }); +}); + +acceptance("Wizard | Wizard", function (needs) { + needs.user(); + needs.pretender((server, helper) => { + server.get("/w/wizard.json", (request) => { + return helper.response(wizard); + }); + }); + + test("Starts", async function (assert) { + await visit("/w/wizard"); + assert.ok(query(".wizard-column"), true); + }); + + test("Applies the body background color", async function (assert) { + await visit("/w/wizard"); + assert.ok($("body")[0].style.background); + }); + + test("Renders the wizard form", async function (assert) { + await visit("/w/wizard"); + assert.ok(exists(".wizard-column-contents .wizard-step"), true); + assert.ok(exists(".wizard-footer img"), true); + }); + + test("Renders the first step", async function (assert) { + await visit("/w/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(exists(".wizard-step-footer .wizard-progress"), true); + assert.ok(exists(".wizard-step-footer .wizard-buttons"), true); + }); +}); diff --git a/test/javascripts/acceptance/wizard-test.js.es6 b/test/javascripts/acceptance/wizard-test.js.es6 deleted file mode 100644 index 8fd82d11..00000000 --- a/test/javascripts/acceptance/wizard-test.js.es6 +++ /dev/null @@ -1,73 +0,0 @@ -import { visit } from "@ember/test-helpers"; -import { test } from "qunit"; -import { exists } from "../helpers/test"; -import acceptance, { count, query, visible } from "../helpers/acceptance"; -import { - getWizard, - wizard, - wizardCompleted, - wizardNoUser, - wizardNotPermitted, -} from "../helpers/wizard"; - -acceptance("Wizard | Not logged in", [getWizard(wizardNoUser)], function () { - 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 () { - 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 () { - test("Wizard no access completed", async function (assert) { - await visit("/wizard"); - assert.ok(exists(".wizard-no-access.completed")); - }); -}); - -acceptance("Wizard | Wizard", [getWizard(wizard)], function () { - 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/test/javascripts/helpers/wizard.js b/test/javascripts/helpers/wizard.js new file mode 100644 index 00000000..7e9cd9d9 --- /dev/null +++ b/test/javascripts/helpers/wizard.js @@ -0,0 +1,47 @@ +import wizardJson from "../fixtures/wizard"; +import userJson from "../fixtures/user"; +import categoriesJson from "../fixtures/categories"; +import groupsJson from "../fixtures/groups"; +import updateJson from "../fixtures/update"; +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 update = cloneJSON(updateJson); +update.wizard = cloneJSON(wizardJson); + +export { + wizardNoUser, + wizardNotPermitted, + wizardCompleted, + stepNotPermitted, + allFieldsWizard, + wizard, + update +};