merged 'main' and resolved conflicts
Dieser Commit ist enthalten in:
Commit
1a08c9b0c0
10 geänderte Dateien mit 112 neuen und 260 gelöschten Zeilen
|
@ -1,2 +1,2 @@
|
||||||
2.7.8: e07a57e398b6b1676ab42a7e34467556fca5416b
|
2.7.99: e07a57e398b6b1676ab42a7e34467556fca5416b
|
||||||
2.5.1: bb85b3a0d2c0ab6b59bcb405731c39089ec6731c
|
2.5.1: bb85b3a0d2c0ab6b59bcb405731c39089ec6731c
|
|
@ -1,6 +1,7 @@
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
|
import getURL from "discourse-common/lib/get-url";
|
||||||
|
|
||||||
const CustomWizardManager = EmberObject.extend();
|
const CustomWizardManager = EmberObject.extend();
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ CustomWizardManager.reopenClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
export(wizardIds) {
|
export(wizardIds) {
|
||||||
let url = `${Discourse.BaseUrl}/${basePath}/export?`;
|
let url = `${getURL()}/${basePath}/export?`;
|
||||||
|
|
||||||
wizardIds.forEach((wizardId, index) => {
|
wizardIds.forEach((wizardId, index) => {
|
||||||
let step = "wizard_ids[]=" + wizardId;
|
let step = "wizard_ids[]=" + wizardId;
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
//= require discourse/app/mixins/singleton
|
//= require discourse/app/mixins/singleton
|
||||||
//= require discourse/app/mixins/upload
|
//= require discourse/app/mixins/upload
|
||||||
//= require discourse/app/mixins/composer-upload
|
|
||||||
//= require discourse/app/mixins/textarea-text-manipulation
|
//= require discourse/app/mixins/textarea-text-manipulation
|
||||||
|
|
||||||
//= require discourse/app/adapters/rest
|
//= require discourse/app/adapters/rest
|
||||||
|
@ -25,6 +24,7 @@
|
||||||
|
|
||||||
//= require discourse/app/services/app-events
|
//= require discourse/app/services/app-events
|
||||||
//= require discourse/app/services/emoji-store
|
//= require discourse/app/services/emoji-store
|
||||||
|
//= require discourse/app/services/store
|
||||||
|
|
||||||
//= require discourse/app/components/user-selector
|
//= require discourse/app/components/user-selector
|
||||||
//= require discourse/app/components/conditional-loading-spinner
|
//= require discourse/app/components/conditional-loading-spinner
|
||||||
|
|
|
@ -4,24 +4,14 @@ import {
|
||||||
on,
|
on,
|
||||||
} from "discourse-common/utils/decorators";
|
} from "discourse-common/utils/decorators";
|
||||||
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
||||||
import { next, scheduleOnce, throttle } from "@ember/runloop";
|
import { scheduleOnce, throttle } from "@ember/runloop";
|
||||||
import { caretPosition, inCodeBlock } from "discourse/lib/utilities";
|
import { caretPosition, inCodeBlock } from "discourse/lib/utilities";
|
||||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||||
import { getToken } from "wizard/lib/ajax";
|
|
||||||
import {
|
|
||||||
displayErrorForUpload,
|
|
||||||
getUploadMarkdown,
|
|
||||||
uploadIcon,
|
|
||||||
validateUploadedFiles,
|
|
||||||
} from "discourse/lib/uploads";
|
|
||||||
import { cacheShortUploadUrl } from "pretty-text/upload-short-url";
|
|
||||||
import { alias } from "@ember/object/computed";
|
import { alias } from "@ember/object/computed";
|
||||||
import WizardI18n from "../lib/wizard-i18n";
|
|
||||||
import Site from "../models/site";
|
import Site from "../models/site";
|
||||||
|
import { uploadIcon } from "discourse/lib/uploads";
|
||||||
|
import { dasherize } from "@ember/string";
|
||||||
|
|
||||||
const uploadMarkdownResolvers = [];
|
|
||||||
|
|
||||||
const uploadHandlers = [];
|
|
||||||
export default ComposerEditor.extend({
|
export default ComposerEditor.extend({
|
||||||
classNameBindings: ["fieldClass"],
|
classNameBindings: ["fieldClass"],
|
||||||
allowUpload: true,
|
allowUpload: true,
|
||||||
|
@ -35,6 +25,7 @@ export default ComposerEditor.extend({
|
||||||
popupMenuOptions: [],
|
popupMenuOptions: [],
|
||||||
draftStatus: "null",
|
draftStatus: "null",
|
||||||
replyPlaceholder: alias("field.placeholder"),
|
replyPlaceholder: alias("field.placeholder"),
|
||||||
|
uploadingFieldId: null,
|
||||||
|
|
||||||
@on("didInsertElement")
|
@on("didInsertElement")
|
||||||
_composerEditorInit() {
|
_composerEditorInit() {
|
||||||
|
@ -90,6 +81,50 @@ export default ComposerEditor.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
this._bindUploadTarget();
|
this._bindUploadTarget();
|
||||||
|
|
||||||
|
const wizardEventNames = ["insert-text", "replace-text"];
|
||||||
|
const eventPrefix = this.eventPrefix;
|
||||||
|
const session = this.get("session");
|
||||||
|
this.appEvents.reopen({
|
||||||
|
trigger(name, ...args) {
|
||||||
|
let eventParts = name.split(":");
|
||||||
|
let currentEventPrefix = eventParts[0];
|
||||||
|
let currentEventName = eventParts[1];
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentEventPrefix !== "wizard-editor" &&
|
||||||
|
wizardEventNames.some((wen) => wen === currentEventName)
|
||||||
|
) {
|
||||||
|
let wizardName = name.replace(eventPrefix, "wizard-editor");
|
||||||
|
if (currentEventName === "insert-text") {
|
||||||
|
args = {
|
||||||
|
text: args[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (currentEventName === "replace-text") {
|
||||||
|
args = {
|
||||||
|
oldVal: args[0],
|
||||||
|
newVal: args[1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let wizardArgs = Object.assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
fieldId: session.get("uploadingFieldId"),
|
||||||
|
},
|
||||||
|
args
|
||||||
|
);
|
||||||
|
return this._super(wizardName, wizardArgs);
|
||||||
|
} else {
|
||||||
|
return this._super(name, ...args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("field.id")
|
||||||
|
fileUploadElementId(fieldId) {
|
||||||
|
return `file-uploader-${dasherize(fieldId)}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed
|
@discourseComputed
|
||||||
|
@ -105,192 +140,6 @@ export default ComposerEditor.extend({
|
||||||
return uploadIcon(false, this.siteSettings);
|
return uploadIcon(false, this.siteSettings);
|
||||||
},
|
},
|
||||||
|
|
||||||
_setUploadPlaceholderSend() {
|
|
||||||
if (!this.composer.get("reply")) {
|
|
||||||
this.composer.set("reply", "");
|
|
||||||
}
|
|
||||||
this._super(...arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
_bindUploadTarget() {
|
|
||||||
this._super(...arguments);
|
|
||||||
const $element = $(this.element);
|
|
||||||
// adding dropZone property post initialization
|
|
||||||
$element.fileupload("option", "dropZone", $element);
|
|
||||||
|
|
||||||
$element.off("fileuploadsubmit");
|
|
||||||
|
|
||||||
$element.on("fileuploadsubmit", (e, data) => {
|
|
||||||
const max = this.siteSettings.simultaneous_uploads;
|
|
||||||
|
|
||||||
// Limit the number of simultaneous uploads
|
|
||||||
if (max > 0 && data.files.length > max) {
|
|
||||||
bootbox.alert(
|
|
||||||
WizardI18n("post.errors.too_many_dragged_and_dropped_files", { max })
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for a matching file upload handler contributed from a plugin
|
|
||||||
const matcher = (handler) => {
|
|
||||||
const ext = handler.extensions.join("|");
|
|
||||||
const regex = new RegExp(`\\.(${ext})$`, "i");
|
|
||||||
return regex.test(data.files[0].name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const matchingHandler = uploadHandlers.find(matcher);
|
|
||||||
if (data.files.length === 1 && matchingHandler) {
|
|
||||||
if (!matchingHandler.method(data.files[0], this)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no plugin, continue as normal
|
|
||||||
const isPrivateMessage = this.get("composer.privateMessage");
|
|
||||||
|
|
||||||
data.formData = { type: "composer" };
|
|
||||||
data.formData.authenticity_token = getToken();
|
|
||||||
if (isPrivateMessage) {
|
|
||||||
data.formData.for_private_message = true;
|
|
||||||
}
|
|
||||||
if (this._pasted) {
|
|
||||||
data.formData.pasted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const opts = {
|
|
||||||
user: this.currentUser,
|
|
||||||
siteSettings: this.siteSettings,
|
|
||||||
isPrivateMessage,
|
|
||||||
allowStaffToUploadAnyFileInPm: this.siteSettings
|
|
||||||
.allow_staff_to_upload_any_file_in_pm,
|
|
||||||
};
|
|
||||||
|
|
||||||
const isUploading = validateUploadedFiles(data.files, opts);
|
|
||||||
|
|
||||||
this.setProperties({ uploadProgress: 0, isUploading });
|
|
||||||
|
|
||||||
return isUploading;
|
|
||||||
});
|
|
||||||
|
|
||||||
$element.on("fileuploadprogressall", (e, data) => {
|
|
||||||
this.set(
|
|
||||||
"uploadProgress",
|
|
||||||
parseInt((data.loaded / data.total) * 100, 10)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
$element.on("fileuploadfail", (e, data) => {
|
|
||||||
this._setUploadPlaceholderDone(data);
|
|
||||||
this._resetUpload(true);
|
|
||||||
|
|
||||||
const userCancelled = this._xhr && this._xhr._userCancelled;
|
|
||||||
this._xhr = null;
|
|
||||||
|
|
||||||
if (!userCancelled) {
|
|
||||||
displayErrorForUpload(data, this.siteSettings);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$element.on("fileuploadsend", (e, data) => {
|
|
||||||
this._pasted = false;
|
|
||||||
this._validUploads++;
|
|
||||||
|
|
||||||
this._setUploadPlaceholderSend(data);
|
|
||||||
|
|
||||||
this.appEvents.trigger("wizard-editor:insert-text", {
|
|
||||||
fieldId: this.field.id,
|
|
||||||
text: this.uploadPlaceholder,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.xhr && data.originalFiles.length === 1) {
|
|
||||||
this.set("isCancellable", true);
|
|
||||||
this._xhr = data.xhr();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$element.on("fileuploaddone", (e, data) => {
|
|
||||||
let upload = data.result;
|
|
||||||
|
|
||||||
this._setUploadPlaceholderDone(data);
|
|
||||||
|
|
||||||
if (!this._xhr || !this._xhr._userCancelled) {
|
|
||||||
const markdown = uploadMarkdownResolvers.reduce(
|
|
||||||
(md, resolver) => resolver(upload) || md,
|
|
||||||
getUploadMarkdown(upload)
|
|
||||||
);
|
|
||||||
|
|
||||||
cacheShortUploadUrl(upload.short_url, upload);
|
|
||||||
this.appEvents.trigger("wizard-editor:replace-text", {
|
|
||||||
fieldId: this.field.id,
|
|
||||||
oldVal: this.uploadPlaceholder.trim(),
|
|
||||||
newVal: markdown,
|
|
||||||
});
|
|
||||||
this._resetUpload(false);
|
|
||||||
} else {
|
|
||||||
this._resetUpload(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_resetUpload(removePlaceholder) {
|
|
||||||
next(() => {
|
|
||||||
if (this._validUploads > 0) {
|
|
||||||
this._validUploads--;
|
|
||||||
}
|
|
||||||
if (this._validUploads === 0) {
|
|
||||||
this.setProperties({
|
|
||||||
uploadProgress: 0,
|
|
||||||
isUploading: false,
|
|
||||||
isCancellable: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (removePlaceholder) {
|
|
||||||
this.appEvents.trigger("wizard-editor:replace-text", {
|
|
||||||
fieldId: this.field.id,
|
|
||||||
oldVal: this.uploadPlaceholder,
|
|
||||||
newVal: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this._resetUploadFilenamePlaceholder();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_registerImageScaleButtonClick($preview) {
|
|
||||||
const imageScaleRegex = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
|
|
||||||
$preview.off("click", ".scale-btn").on("click", ".scale-btn", (e) => {
|
|
||||||
const index = parseInt($(e.target).parent().attr("data-image-index"), 10);
|
|
||||||
|
|
||||||
const scale = e.target.attributes["data-scale"].value;
|
|
||||||
const matchingPlaceholder = this.get("composer.reply").match(
|
|
||||||
imageScaleRegex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchingPlaceholder) {
|
|
||||||
const match = matchingPlaceholder[index];
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
const replacement = match.replace(
|
|
||||||
imageScaleRegex,
|
|
||||||
`![$1|$2, ${scale}%$4]($5)`
|
|
||||||
);
|
|
||||||
|
|
||||||
this.appEvents.trigger("wizard-editor:replace-text", {
|
|
||||||
fieldId: this.field.id,
|
|
||||||
oldVal: matchingPlaceholder[index],
|
|
||||||
newVal: replacement,
|
|
||||||
options: {
|
|
||||||
regex: imageScaleRegex,
|
|
||||||
index,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
click(e) {
|
click(e) {
|
||||||
if ($(e.target).hasClass("wizard-composer-hyperlink")) {
|
if ($(e.target).hasClass("wizard-composer-hyperlink")) {
|
||||||
this.set("showHyperlinkBox", false);
|
this.set("showHyperlinkBox", false);
|
||||||
|
@ -372,7 +221,8 @@ export default ComposerEditor.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
showUploadModal() {
|
showUploadModal() {
|
||||||
$(this.element.querySelector(".wizard-composer-upload")).trigger("click");
|
this.session.set("uploadingFieldId", this.field.id);
|
||||||
|
document.getElementById(this.fileUploadElementId).click();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import DateInput from "discourse/components/date-input";
|
import DateInput from "discourse/components/date-input";
|
||||||
import loadScript from "discourse/lib/load-script";
|
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import I18n from "I18n";
|
|
||||||
/* global Pikaday:true */
|
|
||||||
|
|
||||||
export default DateInput.extend({
|
export default DateInput.extend({
|
||||||
useNativePicker: false,
|
useNativePicker: false,
|
||||||
|
@ -11,32 +8,9 @@ export default DateInput.extend({
|
||||||
placeholder() {
|
placeholder() {
|
||||||
return this.format;
|
return this.format;
|
||||||
},
|
},
|
||||||
|
_opts() {
|
||||||
_loadPikadayPicker(container) {
|
return {
|
||||||
return loadScript("/javascripts/pikaday.js").then(() => {
|
format: this.format || "LL",
|
||||||
let defaultOptions = {
|
};
|
||||||
field: this.element.querySelector(".date-picker"),
|
|
||||||
container: container || this.element.querySelector(".picker-container"),
|
|
||||||
bound: container === null,
|
|
||||||
format: this.format,
|
|
||||||
firstDay: 1,
|
|
||||||
i18n: {
|
|
||||||
previousMonth: I18n.t("dates.previous_month"),
|
|
||||||
nextMonth: I18n.t("dates.next_month"),
|
|
||||||
months: moment.months(),
|
|
||||||
weekdays: moment.weekdays(),
|
|
||||||
weekdaysShort: moment.weekdaysShort(),
|
|
||||||
},
|
|
||||||
onSelect: (date) => this._handleSelection(date),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.relativeDate) {
|
|
||||||
defaultOptions = Object.assign({}, defaultOptions, {
|
|
||||||
minDate: moment(this.relativeDate).toDate(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Pikaday(Object.assign({}, defaultOptions, this._opts()));
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,7 @@ export default Ember.Component.extend({
|
||||||
"composer",
|
"composer",
|
||||||
EmberObject.create({
|
EmberObject.create({
|
||||||
loading: false,
|
loading: false,
|
||||||
reply: this.get("field.value"),
|
reply: this.get("field.value") || "",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,7 +9,7 @@ export default {
|
||||||
const Router = requirejs("wizard/router").default;
|
const Router = requirejs("wizard/router").default;
|
||||||
const ApplicationRoute = requirejs("wizard/routes/application").default;
|
const ApplicationRoute = requirejs("wizard/routes/application").default;
|
||||||
const getUrl = requirejs("discourse-common/lib/get-url").default;
|
const getUrl = requirejs("discourse-common/lib/get-url").default;
|
||||||
const Store = requirejs("discourse/models/store").default;
|
const Store = requirejs("discourse/services/store").default;
|
||||||
const registerRawHelpers = requirejs(
|
const registerRawHelpers = requirejs(
|
||||||
"discourse-common/lib/raw-handlebars-helpers"
|
"discourse-common/lib/raw-handlebars-helpers"
|
||||||
).registerRawHelpers;
|
).registerRawHelpers;
|
||||||
|
@ -111,8 +111,16 @@ export default {
|
||||||
model() {},
|
model() {},
|
||||||
});
|
});
|
||||||
|
|
||||||
$.ajaxPrefilter(function (_, __, jqXHR) {
|
// Add a CSRF token to all AJAX requests
|
||||||
jqXHR.setRequestHeader("X-CSRF-Token", getToken());
|
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"));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,24 +16,10 @@
|
||||||
disabled=disableTextarea
|
disabled=disableTextarea
|
||||||
outletArgs=(hash composer=composer editorType="composer")}}
|
outletArgs=(hash composer=composer editorType="composer")}}
|
||||||
|
|
||||||
{{input
|
<input
|
||||||
class="wizard-composer-upload hidden-upload-field"
|
|
||||||
disabled=isUploading
|
|
||||||
type="file"
|
type="file"
|
||||||
accept=allowedFileTypes
|
id={{fileUploadElementId}}
|
||||||
multiple=true}}
|
class="wizard-composer-upload"
|
||||||
|
accept={{allowedFileTypes}}
|
||||||
{{#if showHyperlinkBox}}
|
multiple
|
||||||
{{wizard-composer-hyperlink
|
>
|
||||||
addLink=(action "addLink")
|
|
||||||
hideBox=(action "hideBox")}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if isUploading}}
|
|
||||||
<div id="file-uploading">
|
|
||||||
{{loading-spinner size="small"}}<span>{{wizard-i18n "upload_selector.uploading"}} {{uploadProgress}}%</span>
|
|
||||||
{{#if isCancellable}}
|
|
||||||
<a href id="cancel-file-upload" {{action "cancelUpload"}}>{{d-icon "times"}}</a>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
|
@ -18,8 +18,8 @@ class CustomWizard::TemplateValidator
|
||||||
data[:steps].each do |step|
|
data[:steps].each do |step|
|
||||||
check_required(step, :step)
|
check_required(step, :step)
|
||||||
|
|
||||||
if data[:fields].present?
|
if step[:fields].present?
|
||||||
data[:fields].each do |field|
|
step[:fields].each do |field|
|
||||||
check_required(field, :field)
|
check_required(field, :field)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,4 +45,37 @@ describe CustomWizard::TemplateValidator do
|
||||||
CustomWizard::TemplateValidator.new(template).perform
|
CustomWizard::TemplateValidator.new(template).perform
|
||||||
).to eq(false)
|
).to eq(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "steps" do
|
||||||
|
CustomWizard::TemplateValidator.required[:step].each do |attribute|
|
||||||
|
it "invalidates if \"#{attribute.to_s}\" is not present" do
|
||||||
|
template[:steps][0][attribute] = nil
|
||||||
|
expect(
|
||||||
|
CustomWizard::TemplateValidator.new(template).perform
|
||||||
|
).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "fields" do
|
||||||
|
CustomWizard::TemplateValidator.required[:field].each do |attribute|
|
||||||
|
it "invalidates if \"#{attribute.to_s}\" is not present" do
|
||||||
|
template[:steps][0][:fields][0][attribute] = nil
|
||||||
|
expect(
|
||||||
|
CustomWizard::TemplateValidator.new(template).perform
|
||||||
|
).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "actions" do
|
||||||
|
CustomWizard::TemplateValidator.required[:action].each do |attribute|
|
||||||
|
it "invalidates if \"#{attribute.to_s}\" is not present" do
|
||||||
|
template[:actions][0][attribute] = nil
|
||||||
|
expect(
|
||||||
|
CustomWizard::TemplateValidator.new(template).perform
|
||||||
|
).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Laden …
In neuem Issue referenzieren