[TikiWiki-commits] [Git][tikiwiki/tiki][29.x] [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 | <69428dc4c82fd_2a17fbb048a7@gitlab-sidekiq-low-urgency-cpu-bound-v2-78c94c7466-6pdrg.mail> |
Merci Jacob pushed to branch 29.x at Tiki Wiki CMS Groupware / Tiki Commits: bb343387 by Merci Jacob at 2025-12-17T11:02:23+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 (cherry picked from commit 2a0489d8d70f5e3f050d3bacdf866596d9adf5b4) c4c0b22d [FIX] Extend the Element Plus fileGalUploader component’s http-request with a... Co-authored-by: Merci Jacob <[email protected]> - - - - - 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/bb3433878cf8fc58c9a5b73a2ae1d657c6df3d6c -- View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/bb3433878cf8fc58c9a5b73a2ae1d657c6df3d6c 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