From a4da1b83f9c0f5c0e009f1aee912661308130ee2 Mon Sep 17 00:00:00 2001 From: Teisha McRae Date: Wed, 5 Aug 2026 10:59:24 -0400 Subject: [PATCH 1/4] Add URL validation before opening external links Add null/undefined checks in openLink() and openExternalLink() methods to prevent attempting to open windows with invalid URLs. This prevents potential errors when URLs are missing or not set. --- src/components/renderer/form-empty-table.vue | 3 +++ src/components/renderer/form-list-table.vue | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/renderer/form-empty-table.vue b/src/components/renderer/form-empty-table.vue index 1796536de..6d8db6b15 100644 --- a/src/components/renderer/form-empty-table.vue +++ b/src/components/renderer/form-empty-table.vue @@ -21,6 +21,9 @@ export default { }, methods: { openLink() { + if (!this.url) { + return; + } window.open(this.url, "_blank"); } } diff --git a/src/components/renderer/form-list-table.vue b/src/components/renderer/form-list-table.vue index b7a9a32c9..1d940b205 100755 --- a/src/components/renderer/form-list-table.vue +++ b/src/components/renderer/form-list-table.vue @@ -184,7 +184,11 @@ export default { this.dataControl = data.dataControls; }, openExternalLink() { - window.open(this.dataControl.url, "_blank"); + const url = this.dataControl?.url; + if (!url) { + return; + } + window.open(url, "_blank"); }, handleDropdownSelection(listType, valueSelected) { const combinedFilter = []; From b716c8b92ab1fe42c97fbdd83add963c94835ded Mon Sep 17 00:00:00 2001 From: Teisha McRae Date: Wed, 5 Aug 2026 11:12:20 -0400 Subject: [PATCH 2/4] Improve user data access safety and consistency Extract user data access into a computed property to safely handle cases where ProcessMaker/Processmaker may be undefined. Add null checks throughout the component to prevent errors when user data is unavailable. Refactor dataControls object creation into a reusable emitDataControls() method to eliminate code duplication. Ensures the component gracefully handles missing user context by hiding the table and emitting an empty state. --- src/components/renderer/form-requests.vue | 55 +++++++++++++++-------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/components/renderer/form-requests.vue b/src/components/renderer/form-requests.vue index 8939f6458..6adc5f0fc 100755 --- a/src/components/renderer/form-requests.vue +++ b/src/components/renderer/form-requests.vue @@ -107,17 +107,39 @@ export default { }, computed: { noDataUrl() { - return `${window.ProcessMaker?.app?.url}/cases`; + return `${window.ProcessMaker?.app?.url || ""}/cases`; + }, + currentUser() { + return window.ProcessMaker?.user || window.Processmaker?.user || {}; } }, mounted() { this.setupColumns(); - this.pmql = `requester = "${Processmaker.user.username}"`; - this.fetch(); + const username = this.currentUser.username; + if (username) { + this.pmql = `requester = "${username}"`; + this.fetch(); + } else { + this.showTable = false; + this.emitDataControls(0); + } this.$root.$on("dropdownSelectionRequest", this.fetchData); this.$root.$on("searchRequest", this.fetchSearch); }, methods: { + emitDataControls(count = 0) { + const dataControls = { + count: `${count}`, + showControl: true, + showAvatar: true, + variant: "primary", + textColor: "text-primary", + colorText: "color: #1572C2", + url: "/cases", + dropdownShow: "requests" + }; + this.$emit("requestsCount", { dataControls, tasksDropdown: [] }); + }, fetch() { Vue.nextTick(() => { let pmql = ""; @@ -169,21 +191,12 @@ export default { } this.tableData = response.data; this.countResponse = this.tableData.meta.total; - const dataControls = { - count: `${this.countResponse}`, - showControl: true, - showAvatar: true, - variant: "primary", - textColor: "text-primary", - colorText: "color: #1572C2", - url: "/cases", - dropdownShow: "requests" - }; - const tasksDropdown = []; - this.$emit("requestsCount", { dataControls, tasksDropdown }); + this.emitDataControls(this.countResponse); }) .catch(() => { this.tableData = []; + this.showTable = false; + this.emitDataControls(0); }); }); }, @@ -239,17 +252,23 @@ export default { : "text-dark"; }, fetchData(selectedOptions) { + const { id: userId, username } = this.currentUser; + if (!userId && !username) { + this.showTable = false; + this.emitDataControls(0); + return; + } if (selectedOptions[0] === "by_me" && selectedOptions[1] !== "View All") { - this.pmql = `(user_id = ${ProcessMaker.user.id}) AND (status = "${selectedOptions[1]}")`; + this.pmql = `(user_id = ${userId}) AND (status = "${selectedOptions[1]}")`; } if ( selectedOptions[0] === "as_participant" && selectedOptions[1] !== "View All" ) { - this.pmql = `(status = "${selectedOptions[1]}") AND (participant = "${Processmaker.user.username}")`; + this.pmql = `(status = "${selectedOptions[1]}") AND (participant = "${username}")`; } if (selectedOptions[1] === "View All") { - this.pmql = `(user_id = ${ProcessMaker.user.id}) AND ((status = "In Progress") OR (status = "Completed"))`; + this.pmql = `(user_id = ${userId}) AND ((status = "In Progress") OR (status = "Completed"))`; } this.fetch(); }, From c55de811358b001bd4d25b4e40c30c1e42072167 Mon Sep 17 00:00:00 2001 From: Teisha McRae Date: Wed, 5 Aug 2026 11:43:37 -0400 Subject: [PATCH 3/4] Add preview resilience tests for form components Add unit tests for FormListTable and FormRequests components to ensure preview functionality handles edge cases gracefully, including missing URLs, missing usernames, and user preference logic for ProcessMaker vs Processmaker global objects. --- tests/unit/FormListTablePreview.spec.js | 181 ++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 tests/unit/FormListTablePreview.spec.js diff --git a/tests/unit/FormListTablePreview.spec.js b/tests/unit/FormListTablePreview.spec.js new file mode 100644 index 000000000..f9b4e55bb --- /dev/null +++ b/tests/unit/FormListTablePreview.spec.js @@ -0,0 +1,181 @@ +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); + +function loadComponentOptions(relativePath, { processMakerUser = {}, processmakerUser = {} } = {}) { + const componentPath = path.join(process.cwd(), relativePath); + const source = fs.readFileSync(componentPath, "utf8"); + const scriptMatch = source.match(/