[TikiWiki-commits] [Git][tikiwiki/tiki][master ] [FIX] Extend the Element Plus fileGalUploader component ’s http-request with a...
"Merci Jacob \(@mercihabam\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <69428a3526027_2a17fa0c472d0@gitlab-sidekiq-low-urgency-cpu-bound-v2-78c94c7466-mjcjg.mail> |
Merci Jacob pushed to branch master at Tiki Wiki CMS Groupware / Tiki
Commits:
2a0489d8 by Merci Jacob at 2025-12-17T10:38:39+00:00
[FIX] Extend the Element Plus fileGalUploader component’s http-request with a TikiFeedback handler to catch and display server-side 'Feedback:error' messages
---
* [FIX] Extend the Element Plus fileGalUploader component’s http-request with a TikiFeedback handler to catch and display server-side 'Feedback:error' messages
See merge request tikiwiki/tiki!9231
- - - - -
6 changed files:
- src/js/vue-widgets/element-plus-ui/src/components/FileGalUploader/FileGalUploader.vue
- + src/js/vue-widgets/element-plus-ui/src/helpers/fileGalUploader/getUploadAjaxError.js
- + src/js/vue-widgets/element-plus-ui/src/helpers/fileGalUploader/handleTikiFeedback.js
- src/js/vue-widgets/element-plus-ui/src/tests/components/FileGalUploader.test.js
- + src/js/vue-widgets/element-plus-ui/src/tests/helpers/fileGalUploader/getUploadAjaxError.test.js
- + src/js/vue-widgets/element-plus-ui/src/tests/helpers/fileGalUploader/handleTikiFeedback.test.js
Changes:
=====================================
src/js/vue-widgets/element-plus-ui/src/components/FileGalUploader/FileGalUploader.vue
=====================================
@@ -1,9 +1,12 @@
<script setup>
import { UploadFilled } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
+import { ajaxUpload as defaultHttpRequest } from 'element-plus/es/components/upload/src/ajax';
import { ref, onMounted } from 'vue'
import getUploadData from '../../helpers/fileGalUploader/getUploadData';
import ConfigWrapper from '../ConfigWrapper.vue';
+import handleTikiFeedback from '../../helpers/fileGalUploader/handleTikiFeedback';
+import getUploadAjaxError from '../../helpers/fileGalUploader/getUploadAjaxError';
const props = defineProps(['accept', 'maxSize', 'maxFiles', 'maxWidth', 'maxHeight', 'vimeoUrl', 'language']);
const maxSize = JSON.parse(props.maxSize);
@@ -63,6 +66,15 @@ const copyToClipboard = (text) => {
ElMessage.error('Failed to copy');
});
}
+
+const httpRequest = (option) => {
+ const xhr = defaultHttpRequest(option);
+ const originalOnError = option.onError;
+ option.onError = () => {
+ originalOnError(getUploadAjaxError(option, xhr));
+ handleTikiFeedback(xhr);
+ };
+}
</script>
<script>
@@ -93,6 +105,7 @@ export const DEFAULT_ACTION_URL = 'tiki-ajax_services.php?controller=file&action
:action="vimeoUrl ? vimeoUrl : DEFAULT_ACTION_URL"
:method="vimeoUrl ? 'PUT' : 'POST'"
:data-testid="DATA_TEST_ID.UPLOAD_ELEMENT"
+ :http-request="httpRequest"
>
<el-icon class="el-icon--upload" :data-testid="DATA_TEST_ID.UPLOAD_ICON"><upload-filled /></el-icon>
<div class="el-upload__text" :data-testid="DATA_TEST_ID.UPLOAD_TEXT">
=====================================
src/js/vue-widgets/element-plus-ui/src/helpers/fileGalUploader/getUploadAjaxError.js
=====================================
@@ -0,0 +1,20 @@
+import { UploadAjaxError } from "element-plus/es/components/upload/src/ajax";
+
+/**
+ * Returns an instance of UploadAjaxError with the appropriate message and status.
+ * @param {Object} option
+ * @param {XMLHttpRequest} xhr
+ * @returns
+ */
+export default function (option, xhr) {
+ let msg;
+ if (xhr.response) {
+ msg = `${xhr.response.error || xhr.response}`;
+ } else if (xhr.responseText) {
+ msg = `${xhr.responseText}`;
+ } else {
+ msg = `fail to ${option.method} ${option.action} ${xhr.status}`;
+ }
+
+ return new UploadAjaxError(msg, xhr.status, option.method, option.action);
+}
=====================================
src/js/vue-widgets/element-plus-ui/src/helpers/fileGalUploader/handleTikiFeedback.js
=====================================
@@ -0,0 +1,30 @@
+/**
+ * Shows any Tiki feedback message received from the ajax response headers.
+ * @param {XMLHttpRequest} xhr
+ */
+export default function (xhr) {
+ const feedback = xhr.getResponseHeader("X-Tiki-Feedback");
+ const tikiFeedbackElement = $("#tikifeedback");
+ if (feedback) {
+ const feedbackContent = decodeURIComponent(feedback);
+ tikiFeedbackElement.fadeIn(200, function () {
+ tikiFeedbackElement.html($($.parseHTML(feedbackContent)).filter("#tikifeedback").html());
+ tikiFeedbackElement.find("div.alert").each(function () {
+ const title = $(this).find("span.rboxtitle").text().trim();
+ const content = $(this).find("div.rboxcontent").text().trim();
+ $(this).find("span.rboxtitle").text(title);
+ $(this).find("div.rboxcontent").text(content);
+ });
+
+ placeFeedback(tikiFeedbackElement);
+ });
+ }
+
+ tikiFeedbackElement.find(".clear").on("click", function () {
+ $(tikiFeedbackElement).empty();
+ //move back to usual position and clear style attribute so subsequent feedback appears properly
+ $("div#col1").prepend(tikiFeedbackElement);
+ tikiFeedbackElement.css({ "z-index": "", position: "", top: "" });
+ return true;
+ });
+}
=====================================
src/js/vue-widgets/element-plus-ui/src/tests/components/FileGalUploader.test.js
=====================================
@@ -4,6 +4,9 @@ import FileGalUploader, { DATA_TEST_ID, DEFAULT_ACTION_URL } from "../../compone
import { h } from "vue";
import { ElMessage, ElUpload } from "element-plus";
import ConfigWrapper from "../../components/ConfigWrapper.vue";
+import * as AjaxUploadHelpers from "element-plus/es/components/upload/src/ajax.mjs";
+import getUploadAjaxError from "../../helpers/fileGalUploader/getUploadAjaxError";
+import handleTikiFeedback from "../../helpers/fileGalUploader/handleTikiFeedback";
vi.mock("element-plus", async (importOriginal) => {
const actual = await importOriginal();
@@ -19,6 +22,26 @@ vi.mock("../../components/ConfigWrapper.vue", () => {
};
});
+vi.mock("element-plus/es/components/upload/src/ajax.mjs", async (importOriginal) => {
+ const actual = await importOriginal();
+ return {
+ ...actual,
+ ajaxUpload: vi.fn(),
+ };
+});
+
+vi.mock("../../helpers/fileGalUploader/getUploadAjaxError", () => {
+ return {
+ default: vi.fn(),
+ };
+});
+
+vi.mock("../../helpers/fileGalUploader/handleTikiFeedback", () => {
+ return {
+ default: vi.fn(),
+ };
+});
+
describe("FileGalUploader", () => {
const consoleErrorSpy = vi.spyOn(console, "error");
const consoleWarnSpy = vi.spyOn(console, "warn");
@@ -46,6 +69,7 @@ describe("FileGalUploader", () => {
"auto-upload": false,
method: "POST",
headers: { accept: "application/json" },
+ "http-request": expect.any(Function),
}),
expect.any(Object)
);
@@ -130,6 +154,49 @@ describe("FileGalUploader", () => {
expect(uploadValidation).toBe(false);
});
+ test("correctly executes the http request function", async () => {
+ const givenFile = new File(["foo"], "foo.txt", { type: "text/plain" });
+
+ const givenProps = {
+ maxSize: "100",
+ maxFiles: "10",
+ };
+
+ render(FileGalUploader, { props: givenProps });
+
+ const httpRequestFunction = ElUpload.mock.calls[0][0]["http-request"];
+
+ const mockOption = {
+ action: DEFAULT_ACTION_URL,
+ method: "POST",
+ data: {},
+ filename: "file",
+ file: givenFile,
+ onProgress: vi.fn(),
+ onError: vi.fn(),
+ onSuccess: vi.fn(),
+ };
+
+ const mockXMLHttpRequestInstance = new XMLHttpRequest();
+ vi.spyOn(AjaxUploadHelpers, "ajaxUpload").mockReturnValueOnce(mockXMLHttpRequestInstance);
+
+ const alteredOnErrorSpy = vi.spyOn(mockOption, "onError");
+
+ const mockUploadAjaxErrorInstance = { error: "mock error" };
+ getUploadAjaxError.mockReturnValueOnce(mockUploadAjaxErrorInstance);
+
+ httpRequestFunction(mockOption);
+
+ expect(AjaxUploadHelpers.ajaxUpload).toHaveBeenCalledWith(mockOption);
+
+ mockOption.onError();
+ await waitFor(() => {
+ expect(alteredOnErrorSpy).toHaveBeenCalledWith(mockUploadAjaxErrorInstance);
+ });
+ expect(getUploadAjaxError).toHaveBeenCalledWith(mockOption, mockXMLHttpRequestInstance);
+ expect(handleTikiFeedback).toHaveBeenCalledWith(mockXMLHttpRequestInstance);
+ });
+
test("show an error message when the upload fails", async () => {
const givenProps = {
maxSize: "1",
=====================================
src/js/vue-widgets/element-plus-ui/src/tests/helpers/fileGalUploader/getUploadAjaxError.test.js
=====================================
@@ -0,0 +1,37 @@
+import { describe, test } from "vitest";
+import { UploadAjaxError } from "element-plus/es/components/upload/src/ajax";
+import getUploadAjaxError from "../../../helpers/fileGalUploader/getUploadAjaxError";
+
+describe("fileGalUploader getUploadAjaxError helper", () => {
+ test.each([
+ [{ response: { error: "Server response error" } }],
+ [{ response: "Server response string" }],
+ [{ responseText: "Server response text" }],
+ [{}],
+ ])("returns UploadAjaxError with correct message and status for xhr: %o", (mockXhr) => {
+ const mockOption = {
+ method: "POST",
+ action: "/upload",
+ };
+
+ const uploadAjaxErrorInstance = getUploadAjaxError(mockOption, {
+ ...mockXhr,
+ status: 500,
+ });
+
+ let expectedMessage;
+ if (mockXhr.response) {
+ expectedMessage = `${mockXhr.response.error || mockXhr.response}`;
+ } else if (mockXhr.responseText) {
+ expectedMessage = `${mockXhr.responseText}`;
+ } else {
+ expectedMessage = `fail to ${mockOption.method} ${mockOption.action} 500`;
+ }
+
+ expect(uploadAjaxErrorInstance).toBeInstanceOf(UploadAjaxError);
+ expect(uploadAjaxErrorInstance.message).toBe(expectedMessage);
+ expect(uploadAjaxErrorInstance.status).toBe(500);
+ expect(uploadAjaxErrorInstance.method).toBe("POST");
+ expect(uploadAjaxErrorInstance.url).toBe("/upload");
+ });
+});
=====================================
src/js/vue-widgets/element-plus-ui/src/tests/helpers/fileGalUploader/handleTikiFeedback.test.js
=====================================
@@ -0,0 +1,59 @@
+import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
+import $ from "jquery";
+import handleTikiFeedback from "../../../helpers/fileGalUploader/handleTikiFeedback";
+
+describe("fileGalUploader handleTikiFeedback helper", () => {
+ beforeAll(() => {
+ window.$ = $;
+ window.placeFeedback = vi.fn();
+ $.fn.fadeIn = vi.fn(function (duration, callback) {
+ $(this).data("mocked-fadein", duration);
+ callback.call(this);
+ });
+ });
+
+ beforeEach(() => {
+ $("body").empty();
+ const feedbackContainer = $(
+ "<div id='tikifeedback'><div class='alert'><span class='rboxtitle'> Title </span><div class='rboxcontent'> Content </div></div><button class='clear'>Clear</button></div>"
+ );
+ $("body").append(feedbackContainer);
+ });
+
+ test("places the given feedback on the page", () => {
+ const givenFeedbackHeader = encodeURIComponent(
+ "<div id='tikifeedback'><div class='alert'><span class='rboxtitle'> New Title </span><div class='rboxcontent'> New Content </div></div></div>"
+ );
+ const mockXhr = {
+ getResponseHeader: (headerName) => {
+ if (headerName === "X-Tiki-Feedback") {
+ return givenFeedbackHeader;
+ }
+ },
+ };
+
+ handleTikiFeedback(mockXhr);
+
+ const tikiFeedbackElement = $("#tikifeedback");
+ expect(tikiFeedbackElement.data("mocked-fadein")).toBe(200);
+ expect(tikiFeedbackElement.find("span.rboxtitle").text().trim()).toBe("New Title");
+ expect(tikiFeedbackElement.find("div.rboxcontent").text().trim()).toBe("New Content");
+ expect(window.placeFeedback).toHaveBeenCalledWith(tikiFeedbackElement);
+ });
+
+ test("clears the feedback when the clear button is clicked", () => {
+ handleTikiFeedback({
+ getResponseHeader: (headerName) => null,
+ });
+
+ const tikiFeedbackElement = $("#tikifeedback");
+
+ const clearButton = tikiFeedbackElement.find("button.clear");
+ clearButton.trigger("click");
+
+ expect(tikiFeedbackElement.html()).toBe("");
+ expect(tikiFeedbackElement.css("z-index")).toBe("");
+ expect(tikiFeedbackElement.css("position")).toBe("");
+ expect(tikiFeedbackElement.css("top")).toBe("");
+ });
+});
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/2a0489d8d70f5e3f050d3bacdf866596d9adf5b4
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/2a0489d8d70f5e3f050d3bacdf866596d9adf5b4
You're receiving this email because of your account on gitlab.com.
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs