From 1ba2f81192913932ef31b2b5383aa84517fccfc8 Mon Sep 17 00:00:00 2001 From: Dave Roberts <145559566+droberts-ctrlo@users.noreply.github.com> Date: Wed, 29 Apr 2026 16:41:04 +0100 Subject: [PATCH 01/10] Js formatting (#612) * Formatted test and config code and updated test files Also removed extra yarn commands that weren't needed, and automated download of browserslist updates on build. * Updated filter where typeahead is missed * Update component.js * Reset erroneous changes * Updated error handler * Formatted test and config code and updated test files Also removed extra yarn commands that weren't needed, and automated download of browserslist updates on build. * Added editorconfig and upgraded ESLint to current version * Formatted and changed code with ESLint fixes - functionality has not changed Removed cjs from eslint - cjs are used for internal development files, and should never be included * Fixed formatting and code where this was broken on merge with current dev branch Updated files where dev changes weren't included --- .../components/alert/lib/dangerAlert.ts | 2 +- .../components/alert/lib/infoAlert.ts | 4 +- .../components/alert/lib/successAlert.ts | 2 +- .../components/alert/lib/warningAlert.ts | 4 +- .../components/button/lib/RenderableButton.ts | 4 +- .../button/lib/submit-field-button.ts | 9 +- .../form-group/autosave/lib/modal.js | 240 +++++++++--------- .../display-conditions/lib/component.js | 4 +- .../form-group/filter/lib/component.js | 2 +- .../form-group/input/lib/documentComponent.ts | 1 - src/frontend/js/lib/logging.js | 10 +- src/frontend/js/lib/util/renderable/index.ts | 4 +- .../js/lib/util/scriptErrorHandler/index.ts | 14 +- .../scriptErrorHandler/lib/MessageUploader.ts | 4 +- src/frontend/js/lib/validation.js | 26 +- 15 files changed, 166 insertions(+), 164 deletions(-) diff --git a/src/frontend/components/alert/lib/dangerAlert.ts b/src/frontend/components/alert/lib/dangerAlert.ts index af77e65f3..177af9e96 100644 --- a/src/frontend/components/alert/lib/dangerAlert.ts +++ b/src/frontend/components/alert/lib/dangerAlert.ts @@ -1,4 +1,4 @@ -import { AlertBase } from "./alertBase"; +import { AlertBase } from './alertBase'; export class DangerAlert extends AlertBase { /** diff --git a/src/frontend/components/alert/lib/infoAlert.ts b/src/frontend/components/alert/lib/infoAlert.ts index 6a9214dbb..1981226b1 100644 --- a/src/frontend/components/alert/lib/infoAlert.ts +++ b/src/frontend/components/alert/lib/infoAlert.ts @@ -12,6 +12,6 @@ export class InfoAlert extends AlertBase { * @param {string} message - The message to be displayed in the info alert. */ constructor(message: string) { - super(message, "info"); + super(message, 'info'); } -} \ No newline at end of file +} diff --git a/src/frontend/components/alert/lib/successAlert.ts b/src/frontend/components/alert/lib/successAlert.ts index ee49396b6..4f8965244 100644 --- a/src/frontend/components/alert/lib/successAlert.ts +++ b/src/frontend/components/alert/lib/successAlert.ts @@ -1,4 +1,4 @@ -import { AlertBase } from "./alertBase"; +import { AlertBase } from './alertBase'; export class SuccessAlert extends AlertBase { /** diff --git a/src/frontend/components/alert/lib/warningAlert.ts b/src/frontend/components/alert/lib/warningAlert.ts index 2dce92e08..665ce6028 100644 --- a/src/frontend/components/alert/lib/warningAlert.ts +++ b/src/frontend/components/alert/lib/warningAlert.ts @@ -1,4 +1,4 @@ -import { AlertBase } from "./alertBase"; +import { AlertBase } from './alertBase'; export class WarningAlert extends AlertBase { /** @@ -12,6 +12,6 @@ export class WarningAlert extends AlertBase { * @param {string} message - The message to be displayed in the info alert. */ constructor(message: string) { - super(message, "warning"); + super(message, 'warning'); } } diff --git a/src/frontend/components/button/lib/RenderableButton.ts b/src/frontend/components/button/lib/RenderableButton.ts index b8004011a..221f12045 100644 --- a/src/frontend/components/button/lib/RenderableButton.ts +++ b/src/frontend/components/button/lib/RenderableButton.ts @@ -1,4 +1,4 @@ -import { Renderable } from "util/renderable"; +import { Renderable } from 'util/renderable'; export class RenderableButton implements Renderable { classList: string[] = []; @@ -12,7 +12,7 @@ export class RenderableButton implements Renderable { button.textContent = this.text; button.addEventListener('click', this.onClick); button.classList.add(...this.classList, 'btn'); - const btnType = this.classList.find(b=>b.startsWith('btn-')) ? '' : 'btn-default' + const btnType = this.classList.find(b=>b.startsWith('btn-')) ? '' : 'btn-default'; if(btnType) { button.classList.add(btnType); } diff --git a/src/frontend/components/button/lib/submit-field-button.ts b/src/frontend/components/button/lib/submit-field-button.ts index d2107c83e..5d39b5013 100644 --- a/src/frontend/components/button/lib/submit-field-button.ts +++ b/src/frontend/components/button/lib/submit-field-button.ts @@ -43,7 +43,10 @@ export default class SubmitFieldButton { const $displayConditionsField = $('#displayConditions'); const $instanceIDField = $('#refers_to_instance_id'); - const $filterEl = $instanceIDField.length && $(`[data-builder-id='${$instanceIDField.val()}']`); + let $filterEl: JQuery | undefined = undefined; + if($instanceIDField.length) { + $filterEl = $(`[data-builder-id='${$instanceIDField.val()}']`); + } const $permissionTable = $('#default_field_permissions_table'); @@ -69,7 +72,7 @@ export default class SubmitFieldButton { bUpdateTree = true; } - if ($instanceIDField.length && !$instanceIDField.prop('disabled') && $filterEl.length) { + if ($instanceIDField.length && !$instanceIDField.prop('disabled') && $filterEl && $filterEl.length) { bUpdateFilter = true; } @@ -95,7 +98,7 @@ export default class SubmitFieldButton { url: this.getURL(data), data: { data: mytext, csrf_token: data.csrfToken } }).done(() => { - + alert('Tree has been updated'); }); } diff --git a/src/frontend/components/form-group/autosave/lib/modal.js b/src/frontend/components/form-group/autosave/lib/modal.js index c292cb4d1..1fa3ab10f 100644 --- a/src/frontend/components/form-group/autosave/lib/modal.js +++ b/src/frontend/components/form-group/autosave/lib/modal.js @@ -1,8 +1,8 @@ import { setFieldValues } from 'set-field-values'; import AutosaveBase from './autosaveBase'; -import { fromJson } from "util/common"; -import { InfoAlert } from "components/alert/lib/infoAlert"; -import { RenderableButton } from "components/button/lib/RenderableButton"; +import { fromJson } from 'util/common'; +import { InfoAlert } from 'components/alert/lib/infoAlert'; +import { RenderableButton } from 'components/button/lib/RenderableButton'; /** * A modal that allows the user to restore autosaved values. @@ -35,135 +35,135 @@ class AutosaveModal extends AutosaveBase { let errored = false; - let $list = $("
    "); - const $body = $modal.find(".modal-body"); - $body - .html("

    Restoring values...

    Please be aware that linked records may take a moment to finish restoring.

    ") - .append($list); - // Convert the fields to promise functions (using the fields) that are run in parallel - // This is only done because various parts of the codebase use the fields in different ways dependent on types (i.e. curval) - await Promise.all($form.find('.linkspace-field').map(async (_, field) => { - const $field = $(field); - // This was originally a bunch of promises, but as the code is async, we can await things here - try { - const json = await this.storage.getItem(this.columnKey($field)) - let values = json ? JSON.parse(json) : undefined; - // If the value can't be parsed, ignore it - if (!values) return; - // If we are in view mode and we need to switch to edit mode, do that - const $editButton = $field.closest('.card--topic').find('.btn-js-edit'); - if ($editButton && $editButton.length) $editButton.trigger('click'); - if (Array.isArray(values)) { - const name = $field.data("name"); - const type = $field.data("column-type"); - if (type === "curval") { - // Curvals need to work event-driven - this is because the modal doesn't always load fully, - // meaning the setvalue doesn't work correctly for dropdowns (mainly) - $field.off("validationFailed"); - $field.off("validationPassed"); - $field.on("validationFailed", (e) => { - // Decrement the curval count - curvalCount--; - const $li = $(`

  • Error restoring ${name}, please check these values before submission
    • ${e.message}
  • `); - $list.append($li); - // If we've done all fields, turn off the recovery flag - if (!curvalCount) { - // Hide the restore button and show the close button - $modal.find(".modal-footer").find(".btn-cancel").text("Close").show(); - this.storage.removeItem('recovering'); + let $list = $('
      '); + const $body = $modal.find('.modal-body'); + $body + .html('

      Restoring values...

      Please be aware that linked records may take a moment to finish restoring.

      ') + .append($list); + // Convert the fields to promise functions (using the fields) that are run in parallel + // This is only done because various parts of the codebase use the fields in different ways dependent on types (i.e. curval) + await Promise.all($form.find('.linkspace-field').map(async (_, field) => { + const $field = $(field); + // This was originally a bunch of promises, but as the code is async, we can await things here + try { + const json = await this.storage.getItem(this.columnKey($field)); + let values = json ? JSON.parse(json) : undefined; + // If the value can't be parsed, ignore it + if (!values) return; + // If we are in view mode and we need to switch to edit mode, do that + const $editButton = $field.closest('.card--topic').find('.btn-js-edit'); + if ($editButton && $editButton.length) $editButton.trigger('click'); + if (Array.isArray(values)) { + const name = $field.data('name'); + const type = $field.data('column-type'); + if (type === 'curval') { + // Curvals need to work event-driven - this is because the modal doesn't always load fully, + // meaning the setvalue doesn't work correctly for dropdowns (mainly) + $field.off('validationFailed'); + $field.off('validationPassed'); + $field.on('validationFailed', (e) => { + // Decrement the curval count + curvalCount--; + const $li = $(`

    • Error restoring ${name}, please check these values before submission
      • ${e.message}
    • `); + $list.append($li); + // If we've done all fields, turn off the recovery flag + if (!curvalCount) { + // Hide the restore button and show the close button + $modal.find('.modal-footer').find('.btn-cancel').text('Close').show(); + this.storage.removeItem('recovering'); + } + }); + $field.on('validationPassed', () => { + // Decrement the curval count + curvalCount--; + const $li = $(`
    • Restored ${name}
    • `); + $list.append($li); + // If we've done all fields, turn off the recovery flag + if (!curvalCount) { + // Hide the restore button and show the close button + $modal.find('.modal-footer').find('.btn-cancel').text('Close').show(); + this.storage.removeItem('recovering'); + } + }); + } + setFieldValues($field, values); + if (type !== 'curval') { + const $li = $(`
    • Restored ${name}
    • `); + $list.append($li); + } + $field.addClass('field--changed'); + } + } catch (e) { + // Catch anything within the mapped promises + const name = $field.data('name'); + const $li = $(`
    • Failed to restore ${name}
      • ${e.message}
    • `); + console.error(e); + $list.append($li); + errored = true; } - }); - $field.on("validationPassed", () => { - // Decrement the curval count - curvalCount--; - const $li = $(`
    • Restored ${name}
    • `); - $list.append($li); - // If we've done all fields, turn off the recovery flag - if (!curvalCount) { - // Hide the restore button and show the close button - $modal.find(".modal-footer").find(".btn-cancel").text("Close").show(); - this.storage.removeItem('recovering'); + })).then(() => { + // If there are errors, show an appropriate message, otherwise show a success message + $body.append(`

      ${errored ? 'Values restored with errors.' : 'All values restored.'} Please check that all field values are as expected.

      `); + }).catch(e => { + // If there are any errors that can't be handled in the mapped promises, show a critical error message + $body.append(`

      Critical error restoring values

      ${e}

      `); + }).finally(() => { + // Only allow to close once recovery is finished + if (!curvalCount || errored) { + // Show the close button + $modal.find('.modal-footer').find('.btn-cancel').text('Close').show(); + this.storage.removeItem('recovering'); } - }); - } - setFieldValues($field, values); - if (type !== "curval") { - const $li = $(`
    • Restored ${name}
    • `); - $list.append($li); - } - $field.addClass("field--changed"); - } - } catch (e) { - // Catch anything within the mapped promises - const name = $field.data("name"); - const $li = $(`
    • Failed to restore ${name}
      • ${e.message}
    • `); - console.error(e); - $list.append($li); - errored = true; - } - })).then(() => { - // If there are errors, show an appropriate message, otherwise show a success message - $body.append(`

      ${errored ? "Values restored with errors." : "All values restored."} Please check that all field values are as expected.

      `); - }).catch(e => { - // If there are any errors that can't be handled in the mapped promises, show a critical error message - $body.append(`

      Critical error restoring values

      ${e}

      `); - }).finally(() => { - // Only allow to close once recovery is finished - if (!curvalCount || errored) { - // Show the close button - $modal.find(".modal-footer").find(".btn-cancel").text("Close").show(); - this.storage.removeItem('recovering'); - } - }); - }); + }); + }); // Do we need to run an autorecover? const item = await this.storage.getItem(this.table_key); - // If there is no item, or there are already alerts, do not show the alert - if ($('.alert-danger').text() || $('.alert-warning').text() || !item) return; - const alert = new InfoAlert("There are unsaved values from the last time you edited this record. Would you like to preview the changes?"); - const alertElement = alert.render(); + // If there is no item, or there are already alerts, do not show the alert + if ($('.alert-danger').text() || $('.alert-warning').text() || !item) return; + const alert = new InfoAlert('There are unsaved values from the last time you edited this record. Would you like to preview the changes?'); + const alertElement = alert.render(); - alertElement.classList.add('alert-restore'); + alertElement.classList.add('alert-restore'); - const restoreButton = new RenderableButton("Preview", () => { - const $display = $modal.find(".modal-autosave") - const list = $("
    • "); - // Get a list of the field values to restore - Promise.all($form.find('.linkspace-field').map(async (_, field)=>{ - const $field = $(field); - const key = this.columnKey($field); - const value = await this.storage.getItem(key) - if(!value) return; - const fieldName = $field.data('name'); - const li = $(`
    • ${fieldName}
    • `) - list.append(li); - })).then(()=> { - // Append the list to the modal display - $display.append(list) - }).then(()=>{ - // Show the modal - $modal.modal('show'); - alert.hide(); - }); - }, 'btn-primary', 'btn-inverted', 'btn-alert-restore'); - const restoreButtonElement = restoreButton.render(); + const restoreButton = new RenderableButton('Preview', () => { + const $display = $modal.find('.modal-autosave'); + const list = $('
    • '); + // Get a list of the field values to restore + Promise.all($form.find('.linkspace-field').map(async (_, field)=>{ + const $field = $(field); + const key = this.columnKey($field); + const value = await this.storage.getItem(key); + if(!value) return; + const fieldName = $field.data('name'); + const li = $(`
    • ${fieldName}
    • `); + list.append(li); + })).then(()=> { + // Append the list to the modal display + $display.append(list); + }).then(()=>{ + // Show the modal + $modal.modal('show'); + alert.hide(); + }); + }, 'btn-primary', 'btn-inverted', 'btn-alert-restore'); + const restoreButtonElement = restoreButton.render(); - const cancelButton = new RenderableButton("Cancel", () => { - alert.hide(); - }, 'btn-secondary', 'btn-inverted', 'btn-alert-restore-cancel'); - const cancelButtonElement = cancelButton.render(); + const cancelButton = new RenderableButton('Cancel', () => { + alert.hide(); + }, 'btn-secondary', 'btn-inverted', 'btn-alert-restore-cancel'); + const cancelButtonElement = cancelButton.render(); - const buttonDiv = document.createElement('div'); - buttonDiv.className = 'button-group d-flex justify-content-end'; - buttonDiv.appendChild(restoreButtonElement); - buttonDiv.appendChild(cancelButtonElement); + const buttonDiv = document.createElement('div'); + buttonDiv.className = 'button-group d-flex justify-content-end'; + buttonDiv.appendChild(restoreButtonElement); + buttonDiv.appendChild(cancelButtonElement); - alertElement.appendChild(buttonDiv); + alertElement.appendChild(buttonDiv); - $('.content-block').prepend(alertElement); - } + $('.content-block').prepend(alertElement); + } } export default AutosaveModal; diff --git a/src/frontend/components/form-group/display-conditions/lib/component.js b/src/frontend/components/form-group/display-conditions/lib/component.js index 7c2156d54..8735f23c5 100644 --- a/src/frontend/components/form-group/display-conditions/lib/component.js +++ b/src/frontend/components/form-group/display-conditions/lib/component.js @@ -27,8 +27,8 @@ class DisplayConditionsComponent extends Component { { type: 'not_contains', accept_values: true, apply_to: ['string'] } ], allow_empty: true - }).queryBuilder('setRules', builderData.filterBase - ? JSON.parse(atob(builderData.filterBase)) + }).queryBuilder('setRules', builderData.filterBase + ? JSON.parse(atob(builderData.filterBase)) : {rules:[]}); } } diff --git a/src/frontend/components/form-group/filter/lib/component.js b/src/frontend/components/form-group/filter/lib/component.js index f784ab1ff..4321dbae4 100644 --- a/src/frontend/components/form-group/filter/lib/component.js +++ b/src/frontend/components/form-group/filter/lib/component.js @@ -76,7 +76,7 @@ class FilterComponent extends Component { values: { b_red: 'Red', c_amber: 'Amber', -7 c_yellow: 'Yellow', + c_yellow: 'Yellow', d_green: 'Green', a_grey: 'Grey', e_purple: 'Purple', diff --git a/src/frontend/components/form-group/input/lib/documentComponent.ts b/src/frontend/components/form-group/input/lib/documentComponent.ts index 0e441a7bb..bc23a9978 100644 --- a/src/frontend/components/form-group/input/lib/documentComponent.ts +++ b/src/frontend/components/form-group/input/lib/documentComponent.ts @@ -3,7 +3,6 @@ import 'util/filedrag'; import { upload } from 'util/upload/UploadControl'; import { validateCheckboxGroup } from 'validation'; import { formdataMapper } from 'util/mapper/formdataMapper'; -import { logging } from 'logging'; import { RenameEvent } from 'components/button/lib/rename-button'; import { FileDropEvent } from 'util/filedrag'; import ErrorHandler from 'util/errorHandler'; diff --git a/src/frontend/js/lib/logging.js b/src/frontend/js/lib/logging.js index 6c010120f..223648caa 100644 --- a/src/frontend/js/lib/logging.js +++ b/src/frontend/js/lib/logging.js @@ -1,12 +1,12 @@ -import { uploadMessage } from "util/scriptErrorHandler"; +import { uploadMessage } from 'util/scriptErrorHandler'; class Logging { constructor() { this.allowLogging = - window.test || - location.hostname === 'localhost' || - location.hostname === '127.0.0.1' || - location.hostname.endsWith('.peek.digitpaint.nl'); + window.test || + location.hostname === 'localhost' || + location.hostname === '127.0.0.1' || + location.hostname.endsWith('.peek.digitpaint.nl'); } log(...message) { diff --git a/src/frontend/js/lib/util/renderable/index.ts b/src/frontend/js/lib/util/renderable/index.ts index 8cbafad73..d91e1d05d 100644 --- a/src/frontend/js/lib/util/renderable/index.ts +++ b/src/frontend/js/lib/util/renderable/index.ts @@ -1,2 +1,2 @@ -export { Renderable } from "./lib/Renderable"; -export { Hidable } from "./lib/Hidable"; \ No newline at end of file +export { Renderable } from './lib/Renderable'; +export { Hidable } from './lib/Hidable'; diff --git a/src/frontend/js/lib/util/scriptErrorHandler/index.ts b/src/frontend/js/lib/util/scriptErrorHandler/index.ts index 47cd1b857..b5812c89e 100644 --- a/src/frontend/js/lib/util/scriptErrorHandler/index.ts +++ b/src/frontend/js/lib/util/scriptErrorHandler/index.ts @@ -1,4 +1,4 @@ -import { uploadMessage } from "./lib/MessageUploader"; +import { uploadMessage } from './lib/MessageUploader'; const createErrorString = (message: string, source: any, lineno: number, colno: number, error: Error | string | null) => { let errorString = `Error: ${message}\nSource: ${source}\nLine: ${lineno}, Column: ${colno}`; @@ -6,26 +6,26 @@ const createErrorString = (message: string, source: any, lineno: number, colno: errorString += `\nStack: ${(error as Error)?.stack}`; } return errorString; -} +}; window.onerror = function (message: string, source: any, lineno: number, colno: number, error: Error | string | null) { if (location.host === 'localhost') { // If we're on localhost, we log the error to the console. This is useful for development. - console.error("Script error occurred:", message, source, lineno, colno, error); + console.error('Script error occurred:', message, source, lineno, colno, error); } if (location.pathname === '/api/script_error' || location.pathname === '/login') { // If we're on the script error page, we don't want to log it again. - console.error("Script error occurred but not logged to avoid recursion."); + console.error('Script error occurred but not logged to avoid recursion.'); console.error(createErrorString(message, source, lineno, colno, error)); return; } - const description = createErrorString(message, source, lineno, colno, error) + const description = createErrorString(message, source, lineno, colno, error); uploadMessage(description) .catch(err => { - console.error("Failed to upload script error:", err); + console.error('Failed to upload script error:', err); }); -} +}; export { uploadMessage }; diff --git a/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.ts b/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.ts index b362cfeb7..f6f108584 100644 --- a/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.ts +++ b/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.ts @@ -1,4 +1,4 @@ -import { Uploader } from "util/upload/UploadControl"; +import { Uploader } from 'util/upload/UploadControl'; export const uploadMessage = async (message: string) => { const body = { @@ -24,7 +24,7 @@ export class MessageUploader { try { return await this.uploader.upload(body); } catch (err) { - console.error("Failed to upload message:", err); + console.error('Failed to upload message:', err); } } } diff --git a/src/frontend/js/lib/validation.js b/src/frontend/js/lib/validation.js index fda6ce67f..93f2c1986 100644 --- a/src/frontend/js/lib/validation.js +++ b/src/frontend/js/lib/validation.js @@ -1,4 +1,4 @@ -import { DangerAlert } from "components/alert/lib/dangerAlert"; +import { DangerAlert } from 'components/alert/lib/dangerAlert'; // Bind events to a field to trigger validation const initValidationOnField = (field) => { @@ -312,18 +312,18 @@ const validateTree = (field) => { }; const validateQueryBuilder = (field) => { - if(!(field && field.length)) return; - const result = field.queryBuilder('validate'); - if(!result) { - if($('.display-conditions-error').length) return false; - const danger = new DangerAlert('There are errors in the conditions you have set, please fix these before submitting the form'); - const banner = danger.render(); - banner.classList.add('mb-3', 'mt-0', 'display-conditions-error'); - field.closest('.card__content').prepend(banner); - return false; - } - return true; -} + if(!(field && field.length)) return; + const result = field.queryBuilder('validate'); + if(!result) { + if($('.display-conditions-error').length) return false; + const danger = new DangerAlert('There are errors in the conditions you have set, please fix these before submitting the form'); + const banner = danger.render(); + banner.classList.add('mb-3', 'mt-0', 'display-conditions-error'); + field.closest('.card__content').prepend(banner); + return false; + } + return true; +}; // Expand the card with a certain field and scroll it into view const expandCardValidate = (field) => { From 5931f95ec7088c8fa7368d985c185a8a97dc498c Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Wed, 13 May 2026 13:17:34 +0100 Subject: [PATCH 02/10] Fix conflicts and formatting from rebase --- package.json | 4 +--- src/frontend/components/button/lib/common.test.ts | 1 + src/frontend/components/button/lib/component.test.ts | 1 + .../components/button/lib/create-report-button.test.ts | 2 +- .../components/button/lib/remove-curval-button.test.js | 2 +- .../components/button/lib/show-blank-button.test.ts | 2 +- .../components/button/lib/submit-field-button.test.ts | 3 ++- src/frontend/components/collapsible/lib/component.test.ts | 2 +- src/frontend/components/data-table/lib/helper.test.ts | 2 +- .../components/form-group/autosave/lib/autosave.test.ts | 2 ++ .../components/form-group/input/lib/documentComponent.ts | 4 ++-- .../components/form-group/select-widget/lib/component.js | 1 + src/frontend/components/help-view/lib/component.test.ts | 1 + src/frontend/js/lib/set-field-value.test.ts | 2 ++ src/frontend/js/lib/util/common.test.ts | 2 ++ .../util/encryptedStorage/lib/encryptedStorage.test.ts | 6 ++++++ src/frontend/js/lib/util/filedrag/lib/filedrag.test.ts | 1 + src/frontend/js/lib/util/formatters/lua.test.ts | 4 ++-- src/frontend/js/lib/util/formatters/markdown.test.ts | 1 + src/frontend/js/lib/util/mapper/formdataMapper.test.ts | 1 + src/frontend/js/lib/util/mapper/mapper.test.ts | 1 + .../lib/util/storageProvider/lib/storageProvider.test.ts | 2 +- .../js/lib/util/typeahead/lib/TypeaheadBuilder.test.ts | 1 + src/frontend/js/lib/util/upload/UploadControl.test.ts | 4 +--- yarn.lock | 8 +++----- 25 files changed, 38 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 00b471fd8..15a20e78d 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,7 @@ "test": "jest", "build:dev": "webpack --env development --progress -w", "test:watch": "jest --watch", - "e2e": "yarn cypress run", - "prebuild": "npx update-browserslist-db@latest", - "pretest": "npx update-browserslist-db@latest" + "e2e": "yarn cypress run" }, "dependencies": { "@egjs/hammerjs": "^2.0.0", diff --git a/src/frontend/components/button/lib/common.test.ts b/src/frontend/components/button/lib/common.test.ts index 424e19510..ee0c1c25e 100644 --- a/src/frontend/components/button/lib/common.test.ts +++ b/src/frontend/components/button/lib/common.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect } from '@jest/globals'; import { layoutId, recordId, table_key } from './common'; describe('Common button tests', () => { diff --git a/src/frontend/components/button/lib/component.test.ts b/src/frontend/components/button/lib/component.test.ts index 9f1b49ba6..c2267c634 100644 --- a/src/frontend/components/button/lib/component.test.ts +++ b/src/frontend/components/button/lib/component.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect } from '@jest/globals'; import ButtonComponent from './component'; describe('Button Component', () => { diff --git a/src/frontend/components/button/lib/create-report-button.test.ts b/src/frontend/components/button/lib/create-report-button.test.ts index dc73d8b79..db5900a6e 100644 --- a/src/frontend/components/button/lib/create-report-button.test.ts +++ b/src/frontend/components/button/lib/create-report-button.test.ts @@ -1,6 +1,6 @@ import { validateRequiredFields } from 'validation'; import CreateReportButtonComponent from './create-report-button'; -import { describe, it, expect } from '@jest/globals'; +import { describe, it, expect, jest } from '@jest/globals'; describe('create-report-button', () => { it('does not submit form if no checkboxes are checked', () => { diff --git a/src/frontend/components/button/lib/remove-curval-button.test.js b/src/frontend/components/button/lib/remove-curval-button.test.js index a22dd35f8..5d854852c 100644 --- a/src/frontend/components/button/lib/remove-curval-button.test.js +++ b/src/frontend/components/button/lib/remove-curval-button.test.js @@ -61,4 +61,4 @@ describe('RemoveCurvalButton', () => { button.click(); expect(current.children.length).toBe(0); }); -}); \ No newline at end of file +}); diff --git a/src/frontend/components/button/lib/show-blank-button.test.ts b/src/frontend/components/button/lib/show-blank-button.test.ts index 3d13895ca..ee0237e2a 100644 --- a/src/frontend/components/button/lib/show-blank-button.test.ts +++ b/src/frontend/components/button/lib/show-blank-button.test.ts @@ -29,4 +29,4 @@ describe('ShowBlankButton', () => { button.trigger('click'); expect(item.css('display')).toBe('none'); }); -}); \ No newline at end of file +}); diff --git a/src/frontend/components/button/lib/submit-field-button.test.ts b/src/frontend/components/button/lib/submit-field-button.test.ts index 92799b361..420903408 100644 --- a/src/frontend/components/button/lib/submit-field-button.test.ts +++ b/src/frontend/components/button/lib/submit-field-button.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect, beforeEach } from '@jest/globals'; import { initGlobals } from 'testing/globals.definitions'; import SubmitFieldButtonComponent from './submit-field-button'; @@ -36,4 +37,4 @@ describe('Submit field button tests', () => { expect($.ajax).toHaveBeenCalled(); expect(window.alert).toHaveBeenCalled(); }); -}); \ No newline at end of file +}); diff --git a/src/frontend/components/collapsible/lib/component.test.ts b/src/frontend/components/collapsible/lib/component.test.ts index 34f3d67e3..d883d29c3 100644 --- a/src/frontend/components/collapsible/lib/component.test.ts +++ b/src/frontend/components/collapsible/lib/component.test.ts @@ -53,4 +53,4 @@ describe('Collapsible', () => { expect(titleCollapsed.classList.contains('hidden')).toBe(false); expect(titleExpanded.classList.contains('hidden')).toBe(true); }); -}); \ No newline at end of file +}); diff --git a/src/frontend/components/data-table/lib/helper.test.ts b/src/frontend/components/data-table/lib/helper.test.ts index 454cbc18a..e95dbd801 100644 --- a/src/frontend/components/data-table/lib/helper.test.ts +++ b/src/frontend/components/data-table/lib/helper.test.ts @@ -71,4 +71,4 @@ describe('helper', () => { clearTable($(target)); expect(target.querySelectorAll('tbody tr').length).toBe(1); }); -}); \ No newline at end of file +}); diff --git a/src/frontend/components/form-group/autosave/lib/autosave.test.ts b/src/frontend/components/form-group/autosave/lib/autosave.test.ts index 896f1af4f..42ca664f2 100644 --- a/src/frontend/components/form-group/autosave/lib/autosave.test.ts +++ b/src/frontend/components/form-group/autosave/lib/autosave.test.ts @@ -1,5 +1,7 @@ import AutosaveBase from './autosaveBase'; +import { describe, it, expect, beforeAll, afterAll } from '@jest/globals'; +// Mocking the AutosaveBase class for testing class TestAutosave extends AutosaveBase { initAutosave(): void { console.log('initAutosave'); diff --git a/src/frontend/components/form-group/input/lib/documentComponent.ts b/src/frontend/components/form-group/input/lib/documentComponent.ts index bc23a9978..c46d3b9f0 100644 --- a/src/frontend/components/form-group/input/lib/documentComponent.ts +++ b/src/frontend/components/form-group/input/lib/documentComponent.ts @@ -22,8 +22,8 @@ class DocumentComponent { readonly type = 'document'; readonly el: JQuery; readonly fileInput: JQuery; - errors: (string|Error)[]; - handler: ErrorHandler; + errors!: (string|Error)[]; + handler!: ErrorHandler; constructor(el: JQuery | HTMLElement) { this.el = $(el); diff --git a/src/frontend/components/form-group/select-widget/lib/component.js b/src/frontend/components/form-group/select-widget/lib/component.js index c86166e89..9961bcbc5 100644 --- a/src/frontend/components/form-group/select-widget/lib/component.js +++ b/src/frontend/components/form-group/select-widget/lib/component.js @@ -1,5 +1,6 @@ // We import Bootstrap because there is an error that throws if we don't (this.collapse is not a function). /* eslint-disable @typescript-eslint/no-this-alias */ +import 'bootstrap'; import { Component } from 'component'; import { logging } from 'logging'; import { fromJson } from 'util/common'; diff --git a/src/frontend/components/help-view/lib/component.test.ts b/src/frontend/components/help-view/lib/component.test.ts index 1cc998301..1b4882cce 100644 --- a/src/frontend/components/help-view/lib/component.test.ts +++ b/src/frontend/components/help-view/lib/component.test.ts @@ -1,6 +1,7 @@ import HelpView from './component'; import { describe, it, expect } from '@jest/globals'; +// Mock class to test the HelpView component exposing private members class TestHelpView extends HelpView { public get button() { return this.$button; diff --git a/src/frontend/js/lib/set-field-value.test.ts b/src/frontend/js/lib/set-field-value.test.ts index ebf387b5d..1033d5872 100644 --- a/src/frontend/js/lib/set-field-value.test.ts +++ b/src/frontend/js/lib/set-field-value.test.ts @@ -13,6 +13,7 @@ import textAreaComponent from "components/form-group/textarea"; import { describe, it, expect, beforeEach, jest } from '@jest/globals'; import { setFieldValues } from "./set-field-values"; +// Mocking jQuery plugins declare global { interface JQuery { renameButton: (options?: any) => JQuery; @@ -27,6 +28,7 @@ declare global { $.fn.filedrag = jest.fn().mockReturnThis(); })(jQuery); +// DOM elements for testing const stringDom = `
      { it('returns an empty object for null', () => { const json = null; + // @ts-expect-error This is a test for null input const parsed = fromJson(json); expect(parsed).toEqual({}); }); it('returns an empty object for undefined', () => { const json = undefined; + // @ts-expect-error This is a test for undefined input const parsed = fromJson(json); expect(parsed).toEqual({}); }); diff --git a/src/frontend/js/lib/util/encryptedStorage/lib/encryptedStorage.test.ts b/src/frontend/js/lib/util/encryptedStorage/lib/encryptedStorage.test.ts index ee38150c0..5551b1aa1 100644 --- a/src/frontend/js/lib/util/encryptedStorage/lib/encryptedStorage.test.ts +++ b/src/frontend/js/lib/util/encryptedStorage/lib/encryptedStorage.test.ts @@ -1,6 +1,8 @@ import { setupCrypto } from 'testing/globals.definitions'; import { EncryptedStorage } from './encryptedStorage'; +import { describe, it, expect, beforeAll, beforeEach, afterEach } from '@jest/globals'; +// Mock implementation of the Storage interface for testing purposes class TestStorage implements Storage { private map = new Map(); @@ -11,6 +13,7 @@ class TestStorage implements Storage { this.map.clear(); this.length = 0; } + getItem(key: string): string | null { const ret = this.map.get(key); if (ret === undefined) { @@ -18,6 +21,7 @@ class TestStorage implements Storage { } return ret; } + key(index: number): string | null { const keys = Array.from(this.map.keys()); if (keys.length <= index) { @@ -25,6 +29,7 @@ class TestStorage implements Storage { } return keys[index]; } + removeItem(key: string): void { if (this.map.has(key)) { this.map.delete(key); @@ -32,6 +37,7 @@ class TestStorage implements Storage { this[key] = undefined; } } + setItem(key: string, value: string): void { this.map.set(key, value); this[key] = value; diff --git a/src/frontend/js/lib/util/filedrag/lib/filedrag.test.ts b/src/frontend/js/lib/util/filedrag/lib/filedrag.test.ts index d815d4764..411f081cb 100644 --- a/src/frontend/js/lib/util/filedrag/lib/filedrag.test.ts +++ b/src/frontend/js/lib/util/filedrag/lib/filedrag.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, jest } from '@jest/globals'; import FileDrag from './filedrag'; +// Test class implementation to expose private methods for testing class FileDragTest extends FileDrag { constructor(element: HTMLElement, onDrop: (files: File, index?: number, length?: number) => void = ()=>{}) { super(element, { debug: true }, onDrop); diff --git a/src/frontend/js/lib/util/formatters/lua.test.ts b/src/frontend/js/lib/util/formatters/lua.test.ts index cc9d29e4b..2c67246b3 100644 --- a/src/frontend/js/lib/util/formatters/lua.test.ts +++ b/src/frontend/js/lib/util/formatters/lua.test.ts @@ -1,4 +1,4 @@ -/// +import { describe, it, expect } from '@jest/globals'; import { LUA } from './lua'; describe('LUA tests', () => { @@ -35,4 +35,4 @@ describe('LUA tests', () => { it('Should return string with value', () => { expect(LUA`${new TestObject()}`).toBe('test'); }); -}); \ No newline at end of file +}); diff --git a/src/frontend/js/lib/util/formatters/markdown.test.ts b/src/frontend/js/lib/util/formatters/markdown.test.ts index dacbed8d8..67a1db934 100644 --- a/src/frontend/js/lib/util/formatters/markdown.test.ts +++ b/src/frontend/js/lib/util/formatters/markdown.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect } from '@jest/globals'; import { MarkDown } from './markdown'; describe('Markdown formatter tests', () => { diff --git a/src/frontend/js/lib/util/mapper/formdataMapper.test.ts b/src/frontend/js/lib/util/mapper/formdataMapper.test.ts index ab50d2e4c..ee5f56211 100644 --- a/src/frontend/js/lib/util/mapper/formdataMapper.test.ts +++ b/src/frontend/js/lib/util/mapper/formdataMapper.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect } from '@jest/globals'; import { formdataMapper } from './formdataMapper'; describe('Basic formdata mapper tests', () => { diff --git a/src/frontend/js/lib/util/mapper/mapper.test.ts b/src/frontend/js/lib/util/mapper/mapper.test.ts index c44dd815d..0093d3e8c 100644 --- a/src/frontend/js/lib/util/mapper/mapper.test.ts +++ b/src/frontend/js/lib/util/mapper/mapper.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect } from '@jest/globals'; import { map } from './mapper'; describe('mapper', () => { diff --git a/src/frontend/js/lib/util/storageProvider/lib/storageProvider.test.ts b/src/frontend/js/lib/util/storageProvider/lib/storageProvider.test.ts index 4a497eab0..d1b93d78d 100644 --- a/src/frontend/js/lib/util/storageProvider/lib/storageProvider.test.ts +++ b/src/frontend/js/lib/util/storageProvider/lib/storageProvider.test.ts @@ -59,7 +59,7 @@ describe('StorageProvider', () => { await expect(storage.getItem('key')).resolves.toBe(undefined); }); - it('should set multipe key value pairs on the same instance', async () => { + it('should set multiple key value pairs on the same instance', async () => { const storage = new StorageProvider('test', localStorage); await expect(storage.setItem('key1', 'value1')).resolves.toBeUndefined(); await expect(storage.setItem('key2', 'value2')).resolves.toBeUndefined(); diff --git a/src/frontend/js/lib/util/typeahead/lib/TypeaheadBuilder.test.ts b/src/frontend/js/lib/util/typeahead/lib/TypeaheadBuilder.test.ts index 8bc4680e4..a47d4e874 100644 --- a/src/frontend/js/lib/util/typeahead/lib/TypeaheadBuilder.test.ts +++ b/src/frontend/js/lib/util/typeahead/lib/TypeaheadBuilder.test.ts @@ -1,4 +1,5 @@ import { TypeaheadBuilder } from './TypeaheadBuilder'; +import { describe, it, expect } from '@jest/globals'; describe('builder', () => { it('should error on the typeahead input not being set', () => { diff --git a/src/frontend/js/lib/util/upload/UploadControl.test.ts b/src/frontend/js/lib/util/upload/UploadControl.test.ts index 94b02c3b7..dcbaf80eb 100644 --- a/src/frontend/js/lib/util/upload/UploadControl.test.ts +++ b/src/frontend/js/lib/util/upload/UploadControl.test.ts @@ -1,7 +1,5 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import { describe, it, expect } from '@jest/globals'; +import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; import { Uploader, XmlHttpRequestLike } from './UploadControl'; -/* @ts-ignore */ import { initGlobals, MockXhr } from 'testing/globals.definitions'; describe('UploadControl', () => { diff --git a/yarn.lock b/yarn.lock index 1a8464b93..2dc39fe9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -717,8 +717,6 @@ version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz#84c7341ebde35ccd36b137e9e45866825072a30c" integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.5": version "7.28.5" @@ -3014,9 +3012,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001754: - version "1.0.30001759" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz" - integrity sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw== + version "1.0.30001757" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz#a46ff91449c69522a462996c6aac4ef95d7ccc5e" + integrity sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ== caseless@~0.12.0: version "0.12.0" From 00e7d209b5c607ee4ece23e7d2f40f3b988a3415 Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Wed, 16 Jul 2025 14:35:48 +0100 Subject: [PATCH 03/10] Formatted test and config code and updated test files Also removed extra yarn commands that weren't needed, and automated download of browserslist updates on build. --- yarn.lock | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2dc39fe9f..1d3d8c4b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -219,12 +219,22 @@ "@babel/traverse" "^7.27.1" "@babel/types" "^7.27.1" -"@babel/helper-string-parser@^7.23.4", "@babel/helper-string-parser@^7.27.1": +"@babel/helper-string-parser@^7.23.4": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== + +"@babel/helper-string-parser@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== -"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== @@ -243,7 +253,16 @@ "@babel/traverse" "^7.28.3" "@babel/types" "^7.28.2" -"@babel/helpers@^7.24.1", "@babel/helpers@^7.28.4": +"@babel/helpers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.1.tgz#183e44714b9eba36c3038e442516587b1e0a1a94" + integrity sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + +"@babel/helpers@^7.28.4": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== @@ -1050,7 +1069,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@babel/types@^7.24.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5", "@babel/types@^7.4.4": +"@babel/types@^7.22.5": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@babel/types@^7.24.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.5", "@babel/types@^7.4.4": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== From 71181362cd19b773dbb41c16d8d686d4b9982341 Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Tue, 2 Dec 2025 15:06:15 +0000 Subject: [PATCH 04/10] Updated error handler --- eslint.config.mjs | 2 +- package.json | 4 +++- yarn.lock | 35 ++++------------------------------- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 62c1c1a35..b479d4547 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -8,7 +8,7 @@ import stylistic from "@stylistic/eslint-plugin"; export default defineConfig([ { settings: { react: { version: "detect" } } }, - { ignores: ["*.cjs", "eslint.config.mjs", "**/public/**", "**/node_modules/**", "**/cypress/**", "cypress.config.ts", ".stylelintrc.js", "src/frontend/testing/**", "src/frontend/css/stylesheets/external/**", "src/frontend/components/dashboard/lib/react/polyfills/**", "babel.config.js", "webpack.config.js", "jest.config.js", "tsconfig.json", "src/frontend/js/lib/jqplot/**", "src/frontend/js/lib/jquery/**", "src/frontend/js/lib/plotly/**", "src/frontend/components/timeline/**", "fengari-web.js"] }, + { ignores: ["eslint.config.mjs", "**/public/**", "**/node_modules/**", "**/cypress/**", "cypress.config.ts", ".stylelintrc.js", "src/frontend/testing/**", "src/frontend/css/stylesheets/external/**", "src/frontend/components/dashboard/lib/react/polyfills/**", "babel.config.js", "webpack.config.js", "jest.config.js", "tsconfig.json", "src/frontend/js/lib/jqplot/**", "src/frontend/js/lib/jquery/**", "src/frontend/js/lib/plotly/**", "src/frontend/components/timeline/**", "fengari-web.js"] }, { files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], plugins: { js }, extends: ["js/recommended"] }, { files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], languageOptions: { globals: { ...globals.browser, ...globals.jquery } } }, tseslint.configs.recommended, diff --git a/package.json b/package.json index 15a20e78d..00b471fd8 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "test": "jest", "build:dev": "webpack --env development --progress -w", "test:watch": "jest --watch", - "e2e": "yarn cypress run" + "e2e": "yarn cypress run", + "prebuild": "npx update-browserslist-db@latest", + "pretest": "npx update-browserslist-db@latest" }, "dependencies": { "@egjs/hammerjs": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 1d3d8c4b2..2dc39fe9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -219,22 +219,12 @@ "@babel/traverse" "^7.27.1" "@babel/types" "^7.27.1" -"@babel/helper-string-parser@^7.23.4": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" - integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== - -"@babel/helper-string-parser@^7.27.1": +"@babel/helper-string-parser@^7.23.4", "@babel/helper-string-parser@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": +"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== @@ -253,16 +243,7 @@ "@babel/traverse" "^7.28.3" "@babel/types" "^7.28.2" -"@babel/helpers@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.1.tgz#183e44714b9eba36c3038e442516587b1e0a1a94" - integrity sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg== - dependencies: - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.1" - "@babel/types" "^7.24.0" - -"@babel/helpers@^7.28.4": +"@babel/helpers@^7.24.1", "@babel/helpers@^7.28.4": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== @@ -1069,15 +1050,7 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@babel/types@^7.22.5": - version "7.29.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" - integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.28.5" - -"@babel/types@^7.24.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.5", "@babel/types@^7.4.4": +"@babel/types@^7.24.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5", "@babel/types@^7.4.4": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== From 834b9ca6b81f38b1283c3f14ccdb8813511a34cf Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Thu, 17 Jul 2025 12:09:37 +0100 Subject: [PATCH 05/10] Formatted and changed code with ESLint fixes - functionality has not changed Removed cjs from eslint - cjs are used for internal development files, and should never be included --- eslint.config.mjs | 21 +- package.json | 1 + .../components/alert/lib/alertBase.ts | 3 + .../components/alert/lib/dangerAlert.ts | 3 + .../components/alert/lib/infoAlert.ts | 3 + .../components/alert/lib/successAlert.ts | 3 + .../components/alert/lib/warningAlert.ts | 3 + .../components/button/lib/RenderableButton.ts | 14 + .../components/button/lib/cancel-button.ts | 7 +- src/frontend/components/button/lib/common.ts | 19 +- .../components/button/lib/component.ts | 6 +- .../components/button/lib/delete-button.ts | 4 +- .../button/lib/remove-curval-button.test.js | 2 +- .../button/lib/remove-curval-button.ts | 2 +- .../button/lib/remove-unload-button.ts | 2 +- .../components/button/lib/rename-button.ts | 5 + .../components/button/lib/save-view-button.ts | 5 +- .../button/lib/show-blank-button.test.ts | 2 +- .../button/lib/show-blank-button.ts | 4 +- .../button/lib/submit-draft-record-button.ts | 2 +- .../button/lib/submit-field-button.test.ts | 3 + .../button/lib/submit-field-button.ts | 15 +- .../button/lib/submit-record-button.ts | 2 +- .../button/lib/toggle-all-fields-button.ts | 2 +- .../components/calculator/lib/component.js | 13 + src/frontend/components/card/lib/component.js | 28 +- .../components/collapsible/lib/component.js | 14 + .../collapsible/lib/component.test.ts | 2 +- .../dashboard-graph/lib/component.js | 10 + .../components/dashboard/lib/component.js | 10 + .../components/dashboard/lib/react/Footer.tsx | 12 +- .../components/dashboard/lib/react/Header.tsx | 12 +- .../components/dashboard/lib/react/Widget.tsx | 13 +- .../components/dashboard/lib/react/api.tsx | 112 ++++++-- .../components/dashboard/lib/react/app.tsx | 23 +- .../components/data-table/lib/component.js | 259 +++++++++++++++--- .../components/data-table/lib/helper.js | 20 ++ .../components/data-table/lib/helper.test.ts | 2 +- .../components/data-table/lib/toggle-table.js | 26 +- .../components/datepicker/lib/helper.js | 4 + .../form-group/autosave/lib/autosave.test.ts | 2 +- .../form-group/autosave/lib/autosaveBase.ts | 27 +- .../form-group/autosave/lib/component.js | 1 - .../form-group/autosave/lib/modal.js | 4 +- .../form-group/calc-fields/lib/component.js | 18 ++ .../form-group/checkbox/lib/component.js | 16 +- .../form-group/common/bootstrap-select.js | 6 +- .../dependent-fields/lib/component.js | 29 +- .../display-conditions/lib/component.js | 10 + .../form-group/filter/lib/component.js | 42 +++ .../input/lib/autocompleteComponent.ts | 18 ++ .../form-group/input/lib/component.ts | 28 +- .../form-group/input/lib/dateComponent.ts | 14 + .../form-group/input/lib/documentComponent.ts | 80 +++++- .../form-group/input/lib/fileComponent.ts | 27 +- .../form-group/input/lib/logoComponent.ts | 18 ++ .../form-group/input/lib/passwordComponent.ts | 17 ++ .../multiple-select/lib/component.js | 25 ++ .../form-group/people-filter/lib/component.ts | 49 ++++ .../form-group/radio-group/lib/component.js | 7 + .../form-group/select-widget/lib/component.js | 85 +++++- .../form-group/select/lib/component.js | 82 +++++- .../form-group/textarea/lib/component.js | 12 + .../form-group/tree/lib/component.js | 50 +++- .../form-group/value-lookup/lib/component.js | 33 ++- .../components/globe/lib/component.js | 10 + src/frontend/components/graph/lib/chart.js | 14 + .../components/graph/lib/component.js | 30 ++ .../help-view/lib/component.test.ts | 2 +- .../components/help-view/lib/component.ts | 11 +- .../components/markdown/lib/component.js | 16 ++ .../components/modal/lib/component.js | 192 +++++++++++-- src/frontend/components/modal/lib/frame.js | 9 + src/frontend/components/modal/lib/modal.js | 78 ++++-- .../modal/modals/curval/lib/component.js | 60 +++- .../modal/modals/new-table/lib/component.js | 112 +++++++- .../modal/modals/user/lib/component.js | 45 ++- .../components/more-less/lib/component.js | 38 ++- .../more-less/lib/disclosure-widgets.js | 29 ++ .../components/more-less/lib/more-less.js | 24 +- .../components/popover/lib/component.js | 20 +- .../components/record-popup/lib/component.js | 14 + .../components/select-all/lib/component.ts | 16 +- .../components/sidebar/lib/component.js | 26 ++ .../sidebar/lib/sidebarObservable.js | 13 + .../components/sortable/lib/component.js | 25 ++ .../orderable-sortable/lib/component.js | 19 ++ .../components/summernote/lib/component.js | 15 + src/frontend/js/lib/component.js | 45 +-- src/frontend/js/lib/get-field-values.js | 16 +- src/frontend/js/lib/guid.js | 5 + src/frontend/js/lib/logging.js | 25 +- src/frontend/js/lib/set-field-value.test.ts | 18 +- src/frontend/js/lib/set-field-values.js | 56 +++- .../util/actionsHandler/lib/actionsLoader.ts | 4 +- .../lib/clearAutorecoverAction.ts | 5 +- .../js/lib/util/actionsHandler/lib/handler.ts | 4 +- src/frontend/js/lib/util/common.test.ts | 5 +- src/frontend/js/lib/util/common.ts | 13 + .../lib/util/domutils/lib/elementFactory.ts | 8 +- .../lib/encryptedStorage.test.ts | 13 +- .../encryptedStorage/lib/encryptedStorage.ts | 42 ++- .../js/lib/util/encryption/lib/encryption.ts | 20 +- .../lib/util/errorHandler/lib/errorHandler.ts | 22 +- .../js/lib/util/filedrag/lib/filedrag.test.ts | 2 +- .../js/lib/util/filedrag/lib/filedrag.ts | 32 +++ .../js/lib/util/formatters/lua.test.ts | 2 + src/frontend/js/lib/util/formatters/lua.ts | 6 +- .../js/lib/util/formatters/markdown.ts | 6 + .../util/gadsStorage/lib/AppStorage.test.ts | 10 +- .../js/lib/util/gadsStorage/lib/AppStorage.ts | 4 + .../util/gadsStorage/lib/GadsStorage.test.ts | 181 ++++++------ .../lib/util/gadsStorage/lib/GadsStorage.ts | 30 +- .../js/lib/util/mapper/formdataMapper.test.ts | 1 - .../js/lib/util/mapper/formdataMapper.ts | 11 +- src/frontend/js/lib/util/mapper/mapper.ts | 15 +- .../lib/MessageUploader.test.ts | 1 + .../scriptErrorHandler/lib/MessageUploader.ts | 16 ++ .../lib/storageProvider.test.ts | 2 +- .../storageProvider/lib/storageProvider.ts | 39 ++- .../js/lib/util/typeahead/lib/Typeahead.ts | 23 +- .../typeahead/lib/TypeaheadBuilder.test.ts | 1 - .../util/typeahead/lib/TypeaheadBuilder.ts | 83 +++--- .../typeahead/lib/TypeaheadSourceOptions.ts | 13 + .../js/lib/util/upload/UploadControl.test.ts | 4 +- .../js/lib/util/upload/UploadControl.ts | 27 +- src/frontend/js/lib/validation.js | 65 ++++- src/frontend/testing/globals.definitions.ts | 2 +- yarn.lock | 89 +++++- 129 files changed, 2599 insertions(+), 578 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index b479d4547..b402bafb9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,16 +5,17 @@ import pluginReact from "eslint-plugin-react"; import css from "@eslint/css"; import { defineConfig } from "eslint/config"; import stylistic from "@stylistic/eslint-plugin"; +import jsdoc from "eslint-plugin-jsdoc"; export default defineConfig([ { settings: { react: { version: "detect" } } }, - { ignores: ["eslint.config.mjs", "**/public/**", "**/node_modules/**", "**/cypress/**", "cypress.config.ts", ".stylelintrc.js", "src/frontend/testing/**", "src/frontend/css/stylesheets/external/**", "src/frontend/components/dashboard/lib/react/polyfills/**", "babel.config.js", "webpack.config.js", "jest.config.js", "tsconfig.json", "src/frontend/js/lib/jqplot/**", "src/frontend/js/lib/jquery/**", "src/frontend/js/lib/plotly/**", "src/frontend/components/timeline/**", "fengari-web.js"] }, + { ignores: ["*.cjs", "eslint.config.mjs", "**/public/**", "**/node_modules/**", "**/cypress/**", "cypress.config.ts", ".stylelintrc.js", "src/frontend/testing/**", "src/frontend/css/stylesheets/external/**", "src/frontend/components/dashboard/lib/react/polyfills/**", "babel.config.js", "webpack.config.js", "jest.config.js", "tsconfig.json", "src/frontend/js/lib/jqplot/**", "src/frontend/js/lib/jquery/**", "src/frontend/js/lib/plotly/**", "src/frontend/components/timeline/**", "fengari-web.js"] }, { files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], plugins: { js }, extends: ["js/recommended"] }, - { files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], languageOptions: { globals: { ...globals.browser, ...globals.jquery } } }, + { files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], languageOptions: { globals: { ...globals.browser, ...globals.jquery, ...globals.jest } } }, tseslint.configs.recommended, pluginReact.configs.flat.recommended, { files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] }, - { plugins: {'@stylistic': stylistic} }, + { plugins: {'@stylistic': stylistic, jsdoc} }, { rules: { "@typescript-eslint/no-explicit-any": "off", @@ -25,7 +26,19 @@ export default defineConfig([ '@stylistic/semi': ['error', 'always'], '@stylistic/curly-newline': 'error', '@stylistic/indent': ['error', 4], - '@stylistic/comma-dangle': ['error', 'never'] + '@stylistic/comma-dangle': ['error', 'never'], + "jsdoc/require-jsdoc": [ + "error", + { + require: { + FunctionDeclaration: true, + MethodDefinition: true, + ClassDeclaration: true, + ArrowFunctionExpression: false, + FunctionExpression: false + } + } + ], } } ]); diff --git a/package.json b/package.json index 00b471fd8..2bd6bc20f 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "css-loader": "^3.2.0", "cypress": "^13.7.2", "eslint": "^9.31.0", + "eslint-plugin-jsdoc": "^52.0.0", "eslint-plugin-react": "^7.37.5", "globals": "^16.3.0", "jest": "^29.7.0", diff --git a/src/frontend/components/alert/lib/alertBase.ts b/src/frontend/components/alert/lib/alertBase.ts index 9f5b0c617..d9550dfb9 100644 --- a/src/frontend/components/alert/lib/alertBase.ts +++ b/src/frontend/components/alert/lib/alertBase.ts @@ -1,6 +1,9 @@ import { Hidable, Renderable } from 'util/renderable'; import { AlertType } from './types'; +/** + * Base class for alert components that can be rendered to the DOM and hidden when necessary. + */ export abstract class AlertBase extends Hidable implements Renderable { /** * Create an instance of AlertBase. diff --git a/src/frontend/components/alert/lib/dangerAlert.ts b/src/frontend/components/alert/lib/dangerAlert.ts index 177af9e96..8007d12d6 100644 --- a/src/frontend/components/alert/lib/dangerAlert.ts +++ b/src/frontend/components/alert/lib/dangerAlert.ts @@ -1,5 +1,8 @@ import { AlertBase } from './alertBase'; +/** + * A danger alert component that extends the AlertBase class. + */ export class DangerAlert extends AlertBase { /** * Create an instance of InfoAlert. diff --git a/src/frontend/components/alert/lib/infoAlert.ts b/src/frontend/components/alert/lib/infoAlert.ts index 1981226b1..f27a11902 100644 --- a/src/frontend/components/alert/lib/infoAlert.ts +++ b/src/frontend/components/alert/lib/infoAlert.ts @@ -1,5 +1,8 @@ import { AlertBase } from './alertBase'; +/** + * An info alert component that extends the AlertBase class. + */ export class InfoAlert extends AlertBase { /** * Create an instance of InfoAlert. diff --git a/src/frontend/components/alert/lib/successAlert.ts b/src/frontend/components/alert/lib/successAlert.ts index 4f8965244..273d42333 100644 --- a/src/frontend/components/alert/lib/successAlert.ts +++ b/src/frontend/components/alert/lib/successAlert.ts @@ -1,5 +1,8 @@ import { AlertBase } from './alertBase'; +/** + * A success alert component that extends the AlertBase class. + */ export class SuccessAlert extends AlertBase { /** * Create an instance of InfoAlert. diff --git a/src/frontend/components/alert/lib/warningAlert.ts b/src/frontend/components/alert/lib/warningAlert.ts index 665ce6028..4b86e7b72 100644 --- a/src/frontend/components/alert/lib/warningAlert.ts +++ b/src/frontend/components/alert/lib/warningAlert.ts @@ -1,5 +1,8 @@ import { AlertBase } from './alertBase'; +/** + * A warning alert component that extends the AlertBase class. + */ export class WarningAlert extends AlertBase { /** * Create an instance of InfoAlert. diff --git a/src/frontend/components/button/lib/RenderableButton.ts b/src/frontend/components/button/lib/RenderableButton.ts index 221f12045..c22778eb0 100644 --- a/src/frontend/components/button/lib/RenderableButton.ts +++ b/src/frontend/components/button/lib/RenderableButton.ts @@ -1,12 +1,26 @@ import { Renderable } from 'util/renderable'; +/** + * A simple button component that can be rendered to the DOM. + * It accepts text, an onClick handler, and optional CSS classes. + */ export class RenderableButton implements Renderable { classList: string[] = []; + /** + * Create a new RenderableButton. + * @param text The button text + * @param onClick The click event to fire when the button is clicked + * @param classList Any classes to add to the button + */ constructor(private readonly text: string, private readonly onClick: (ev: MouseEvent)=>void, ...classList: string[]) { this.classList = classList; } + /** + * Render the button to the DOM. + * @returns A button to add to the DOM + */ render(): HTMLButtonElement { const button = document.createElement('button'); button.textContent = this.text; diff --git a/src/frontend/components/button/lib/cancel-button.ts b/src/frontend/components/button/lib/cancel-button.ts index d7c7186a2..c9ba511cd 100644 --- a/src/frontend/components/button/lib/cancel-button.ts +++ b/src/frontend/components/button/lib/cancel-button.ts @@ -1,5 +1,10 @@ import { clearSavedFormValues } from './common'; +/** + * Create a cancel button that navigates away from the page + * This component will navigate away to the parameter defined in the data-href attribute, or will navigate back + * @param { HTMLElement | JQuery } el The button element + */ export default function createCancelButton(el: HTMLElement | JQuery) { const $el = $(el); if ($el[0].tagName !== 'BUTTON') return; @@ -12,4 +17,4 @@ export default function createCancelButton(el: HTMLElement | JQuery else window.history.back(); }); -} \ No newline at end of file +} diff --git a/src/frontend/components/button/lib/common.ts b/src/frontend/components/button/lib/common.ts index 90a277767..970a97fec 100644 --- a/src/frontend/components/button/lib/common.ts +++ b/src/frontend/components/button/lib/common.ts @@ -2,7 +2,6 @@ import StorageProvider from 'util/storageProvider'; /** * Clear all saved form values for the current record - * @param $form The form to clear the data for */ export async function clearSavedFormValues() { const ls = storage(); @@ -13,33 +12,33 @@ export async function clearSavedFormValues() { /** * Get the layout identifier from the body data - * @returns The layout identifier + * @returns { number } The layout identifier */ -export function layoutId() { +export function layoutId(): number { return $('body').data('layout-identifier'); } /** * Get the record identifier from the body data - * @returns The record identifier + * @returns { number } The record identifier */ -export function recordId() { +export function recordId(): number { return $('body').find('.form-edit') .data('current-id') || 0; } /** * Get the key for the table used for saving form values - * @returns The key for the table + * @returns {string} The key for the table */ -export function table_key() { +export function table_key(): string { return `linkspace-record-change-${layoutId()}-${recordId()}`; } /** * Get the storage object - this originally was used in debugging to allow for the storage object to be mocked - * @returns The storage object + * @returns { StorageProvider } The storage object */ -export function storage() { +export function storage(): StorageProvider { return new StorageProvider(table_key()); -} \ No newline at end of file +} diff --git a/src/frontend/components/button/lib/component.ts b/src/frontend/components/button/lib/component.ts index 420de8074..1e5ae7768 100644 --- a/src/frontend/components/button/lib/component.ts +++ b/src/frontend/components/button/lib/component.ts @@ -2,7 +2,6 @@ import {Component} from 'component'; /** * Button component - * @extends Component */ class ButtonComponent extends Component { /** @@ -19,6 +18,7 @@ class ButtonComponent extends Component { /** * Get the map of button components + * @returns {Map) => void>} The map of button components */ private get buttonsMap(): Map) => void> { if (!ButtonComponent.staticButtonsMap) ButtonComponent.initMap(); @@ -27,7 +27,7 @@ class ButtonComponent extends Component { /** * Create a button component - * @param element {HTMLElement} The button element + * @param { HTMLElement } element The button element */ constructor(element: HTMLElement) { super(element); @@ -116,7 +116,7 @@ class ButtonComponent extends Component { /** * Initialize the button - * @param element {HTMLElement} The button element + * @param {HTMLElement} element The button element */ private initButton(element: HTMLElement) { const el: JQuery = $(element); diff --git a/src/frontend/components/button/lib/delete-button.ts b/src/frontend/components/button/lib/delete-button.ts index a0ff639eb..a947171d2 100644 --- a/src/frontend/components/button/lib/delete-button.ts +++ b/src/frontend/components/button/lib/delete-button.ts @@ -1,10 +1,8 @@ -// noinspection ExceptionCaughtLocallyJS - import { logging } from 'logging'; /** * Create delete button - * @param element {JQuery} - Element to act as a delete button + * @param {JQuery} element Element to act as a delete button */ export default function createDeleteButton(element: JQuery) { element.on('click', (ev) => { diff --git a/src/frontend/components/button/lib/remove-curval-button.test.js b/src/frontend/components/button/lib/remove-curval-button.test.js index 5d854852c..a22dd35f8 100644 --- a/src/frontend/components/button/lib/remove-curval-button.test.js +++ b/src/frontend/components/button/lib/remove-curval-button.test.js @@ -61,4 +61,4 @@ describe('RemoveCurvalButton', () => { button.click(); expect(current.children.length).toBe(0); }); -}); +}); \ No newline at end of file diff --git a/src/frontend/components/button/lib/remove-curval-button.ts b/src/frontend/components/button/lib/remove-curval-button.ts index 7d615bd19..3250be2bc 100644 --- a/src/frontend/components/button/lib/remove-curval-button.ts +++ b/src/frontend/components/button/lib/remove-curval-button.ts @@ -1,6 +1,6 @@ /** * Create remove curval button - * @param element {JQuery} - The element to function as a remove curval button + * @param {JQuery} element The element to function as a remove curval button */ export default function createRemoveCurvalButton(element: JQuery) { element.on('click', (ev: JQuery.ClickEvent) => { diff --git a/src/frontend/components/button/lib/remove-unload-button.ts b/src/frontend/components/button/lib/remove-unload-button.ts index ddcd9adef..ffc6d39cd 100644 --- a/src/frontend/components/button/lib/remove-unload-button.ts +++ b/src/frontend/components/button/lib/remove-unload-button.ts @@ -1,6 +1,6 @@ /** * Create a button that removes the unload event listener - * @param element {JQuery} - The button element to add the click event to + * @param {JQuery} element The button element to add the click event to */ export default function createRemoveUnloadButton(element: JQuery) { element.on('click', () => { diff --git a/src/frontend/components/button/lib/rename-button.ts b/src/frontend/components/button/lib/rename-button.ts index 023e5e611..09a205b1e 100644 --- a/src/frontend/components/button/lib/rename-button.ts +++ b/src/frontend/components/button/lib/rename-button.ts @@ -170,6 +170,11 @@ class RenameButton { this.hideRenameControls(id, button); } + /** + * Hides the rename controls + * @param {number} id The id of the field + * @param {JQuery} button The button that was clicked + */ private hideRenameControls(id: number, button: JQuery) { $(`#current-${id}`).removeClass('hidden') .attr('aria-hidden', 'false'); diff --git a/src/frontend/components/button/lib/save-view-button.ts b/src/frontend/components/button/lib/save-view-button.ts index 657522ab5..cb17960f3 100644 --- a/src/frontend/components/button/lib/save-view-button.ts +++ b/src/frontend/components/button/lib/save-view-button.ts @@ -2,7 +2,8 @@ import { validateRequiredFields } from 'validation'; import '@lol768/jquery-querybuilder-no-eval'; /** - * SaveViewButtonComponent + * Button component for saving views for an instance. + * @param {JQuery} el The jQuery element that represents the Save View button. */ export default function createSaveViewButtonComponent(el: JQuery) { const $form = el.closest('form'); @@ -34,4 +35,4 @@ export default function createSaveViewButtonComponent(el: JQuery) { .val(JSON.stringify(res, null, 2)); }); }); -} \ No newline at end of file +} diff --git a/src/frontend/components/button/lib/show-blank-button.test.ts b/src/frontend/components/button/lib/show-blank-button.test.ts index ee0237e2a..3d13895ca 100644 --- a/src/frontend/components/button/lib/show-blank-button.test.ts +++ b/src/frontend/components/button/lib/show-blank-button.test.ts @@ -29,4 +29,4 @@ describe('ShowBlankButton', () => { button.trigger('click'); expect(item.css('display')).toBe('none'); }); -}); +}); \ No newline at end of file diff --git a/src/frontend/components/button/lib/show-blank-button.ts b/src/frontend/components/button/lib/show-blank-button.ts index 1a5346064..e668bc64d 100644 --- a/src/frontend/components/button/lib/show-blank-button.ts +++ b/src/frontend/components/button/lib/show-blank-button.ts @@ -1,6 +1,6 @@ /** * Create a button that toggles the visibility of blank fields. - * @param element {JQuery} The element to attach the button to. + * @param {JQuery} element The element to attach the button to. */ export default function createShowBlankButton(element: JQuery) { element.on('click', (ev) => { @@ -14,4 +14,4 @@ export default function createShowBlankButton(element: JQuery) { ? 'Hide blank values' : 'Show blank values'; }); -} \ No newline at end of file +} diff --git a/src/frontend/components/button/lib/submit-draft-record-button.ts b/src/frontend/components/button/lib/submit-draft-record-button.ts index 540535a7b..0f6700592 100644 --- a/src/frontend/components/button/lib/submit-draft-record-button.ts +++ b/src/frontend/components/button/lib/submit-draft-record-button.ts @@ -2,7 +2,7 @@ import { clearSavedFormValues } from './common'; /** * Create a submit draft record button - * @param element {JQuery} The button element + * @param {JQuery} element The button element */ export default function createSubmitDraftRecordButton(element: JQuery) { element.on('click', async (ev: JQuery.ClickEvent) => { diff --git a/src/frontend/components/button/lib/submit-field-button.test.ts b/src/frontend/components/button/lib/submit-field-button.test.ts index 420903408..ea0a4a3f3 100644 --- a/src/frontend/components/button/lib/submit-field-button.test.ts +++ b/src/frontend/components/button/lib/submit-field-button.test.ts @@ -1,4 +1,7 @@ import { describe, it, expect, beforeEach } from '@jest/globals'; +/* eslint-disable jsdoc/require-jsdoc */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* @ts-ignore */ import { initGlobals } from 'testing/globals.definitions'; import SubmitFieldButtonComponent from './submit-field-button'; diff --git a/src/frontend/components/button/lib/submit-field-button.ts b/src/frontend/components/button/lib/submit-field-button.ts index 5d39b5013..905c3885f 100644 --- a/src/frontend/components/button/lib/submit-field-button.ts +++ b/src/frontend/components/button/lib/submit-field-button.ts @@ -1,8 +1,7 @@ -/* eslint-disable */ -import "jstree"; -import "datatables.net"; -import "@lol768/jquery-querybuilder-no-eval"; -import { validateQueryBuilder } from "validation"; +import 'jstree'; +import 'datatables.net'; +import '@lol768/jquery-querybuilder-no-eval'; +import { validateQueryBuilder } from 'validation'; declare global { interface Window { @@ -25,7 +24,7 @@ export default class SubmitFieldButton { /** * Create a submit field button - * @param element The submit button element + * @param {JQuery} element The submit button element */ constructor(element: JQuery) { element.on('click', (ev) => { @@ -130,8 +129,8 @@ export default class SubmitFieldButton { /** * Get the URL for the tree API - * @param data The data for the tree - * @returns The URL for the tree API + * @param {JQuery.PlainObject} data The data for the tree + * @returns {string} The URL for the tree API */ private getURL(data: JQuery.PlainObject): string { if (window.test) return ''; diff --git a/src/frontend/components/button/lib/submit-record-button.ts b/src/frontend/components/button/lib/submit-record-button.ts index 71b0ae1d1..7cb42a193 100644 --- a/src/frontend/components/button/lib/submit-record-button.ts +++ b/src/frontend/components/button/lib/submit-record-button.ts @@ -10,7 +10,7 @@ export default class SubmitRecordButton { /** * Create a button to submit records - * @param el {JQuery} Element to create as a button + * @param {JQuery} el Element to create as a button */ constructor(private el: JQuery) { this.el.on('click', async (ev: JQuery.ClickEvent) => { diff --git a/src/frontend/components/button/lib/toggle-all-fields-button.ts b/src/frontend/components/button/lib/toggle-all-fields-button.ts index 5caae265d..547e620e5 100644 --- a/src/frontend/components/button/lib/toggle-all-fields-button.ts +++ b/src/frontend/components/button/lib/toggle-all-fields-button.ts @@ -1,6 +1,6 @@ /** * Toggles (switches) all fields from the source toggle table to the destination toggle table - * @param element {JQuery} - The button element + * @param {JQuery} element The button element */ export default function createToggleAllFieldsButton(element: JQuery) { element.on('click', (ev) => { diff --git a/src/frontend/components/calculator/lib/component.js b/src/frontend/components/calculator/lib/component.js index 068b80ce4..7dd9ef526 100644 --- a/src/frontend/components/calculator/lib/component.js +++ b/src/frontend/components/calculator/lib/component.js @@ -1,6 +1,14 @@ import { Component } from 'component'; +/** + * CalculatorComponent class to handle calculator functionality in the UI. + * It initializes a calculator dropdown for input fields, allowing users to perform basic arithmetic operations. + */ class CalculatorComponent extends Component { + /** + * Creates an instance of CalculatorComponent. + * @param {HTMLElement} element The element to be initialized as a calculator component. + */ constructor(element) { super(element); this.el = $(this.element); @@ -8,6 +16,11 @@ class CalculatorComponent extends Component { this.initCalculator(); } + /** + * Initializes the calculator functionality by creating a dropdown + * with buttons for arithmetic operations and an input field for numbers. + * @todo This method should be refactored to improve readability and maintainability. + */ initCalculator() { const selector = this.el.find('input:not([type="checkbox"])'); const $nodes = this.el.find('label:not(.checkbox-label)'); diff --git a/src/frontend/components/card/lib/component.js b/src/frontend/components/card/lib/component.js index 398a5c3f1..1bab1d970 100644 --- a/src/frontend/components/card/lib/component.js +++ b/src/frontend/components/card/lib/component.js @@ -1,7 +1,15 @@ -import { Component } from 'component'; import 'bootstrap'; +import { Component } from 'component'; +/** + * Creates an expandable card component. + */ class ExpandableCardComponent extends Component { + + /** + * Creates an instance of ExpandableCardComponent. + * @param {HTMLElement} element The HTML element to attach the component to. + */ constructor(element) { super(element); this.$el = $(this.element); @@ -14,6 +22,9 @@ class ExpandableCardComponent extends Component { } } + /** + * Initializes the expandable card functionality. + */ initExpandableCard() { const $collapsibleElm = this.$el.find('.collapse'); const $btnEdit = this.$el.find('.btn-js-edit'); @@ -65,6 +76,9 @@ class ExpandableCardComponent extends Component { }); } + /** + * Initializes the topic card functionality. + */ initTopicCard() { // Now that fields are shown/hidden on page load, for each topic check // whether it has zero displayed fields, in which case hide the whole @@ -85,6 +99,10 @@ class ExpandableCardComponent extends Component { } } + /** + * Checks if the edit class can be removed from the content block. + * @returns {boolean} True if the edit class can be removed, false otherwise. + */ canRemoveEditClass() { return !this.$contentBlock.find('.card--edit').length; } @@ -98,10 +116,10 @@ class ExpandableCardComponent extends Component { return message; }; - /* - In order to ensure headers on the view filter tables are the correct width, we need to remove any styling that has been added to the header elements. - And for some reason, using JQuery and DataTables, the styling is not reset as we expect it to be. - */ + /** + * In order to ensure headers on the view filter tables are the correct width, we need to remove any styling that has been added to the header elements. + * And for some reason, using JQuery and DataTables, the styling is not reset as we expect it to be. + */ clearupStyling() { const tables = $('.table-toggle'); tables.removeAttr('style'); diff --git a/src/frontend/components/collapsible/lib/component.js b/src/frontend/components/collapsible/lib/component.js index f228b8749..3a323a203 100644 --- a/src/frontend/components/collapsible/lib/component.js +++ b/src/frontend/components/collapsible/lib/component.js @@ -1,6 +1,13 @@ import { Component } from 'component'; +/** + * + */ class CollapsibleComponent extends Component { + /** + * Creates an instance of CollapsibleComponent. + * @param {HTMLElement} element The element to be initialized as a collapsible component. + */ constructor(element) { super(element); this.el = $(this.element); @@ -11,6 +18,10 @@ class CollapsibleComponent extends Component { this.initCollapsible(this.button); } + /** + * Initializes the collapsible component. + * @param {HTMLElement} button The button element that will toggle the collapsible content. + */ initCollapsible(button) { if (!button) { return; @@ -20,6 +31,9 @@ class CollapsibleComponent extends Component { button.click(() => { this.handleClick(); }); } + /** + * Handles the click event on the collapsible button. + */ handleClick() { this.titleExpanded.toggleClass('hidden'); this.titleCollapsed.toggleClass('hidden'); diff --git a/src/frontend/components/collapsible/lib/component.test.ts b/src/frontend/components/collapsible/lib/component.test.ts index d883d29c3..b2ac1af55 100644 --- a/src/frontend/components/collapsible/lib/component.test.ts +++ b/src/frontend/components/collapsible/lib/component.test.ts @@ -19,7 +19,7 @@ describe('Collapsible', () => {
      Please make a secure note of this content now, as it will not be displayed again.
      - `; + `; }); afterEach(() => { diff --git a/src/frontend/components/dashboard/dashboard-graph/lib/component.js b/src/frontend/components/dashboard/dashboard-graph/lib/component.js index 7c4ace897..77d0cf46d 100644 --- a/src/frontend/components/dashboard/dashboard-graph/lib/component.js +++ b/src/frontend/components/dashboard/dashboard-graph/lib/component.js @@ -1,12 +1,22 @@ import { do_plot_json } from '../../../graph/lib/chart'; import GraphComponent from '../../../graph/lib/component'; +/** + * DashboardGraphComponent class that initializes the dashboard graph and renders the graph using do_plot_json. + */ class DashboardGraphComponent extends GraphComponent { + /** + * Create a DashboardGraphComponent instance. + * @param {HTMLElement} element The HTML element that this component will be attached to. + */ constructor(element) { super(element); this.initDashboardGraph(); } + /** + * Initialize the dashboard graph by rendering the graph using do_plot_json. + */ initDashboardGraph() { const $graph = $(this.element); const graph_data = $graph.data('plot-data'); diff --git a/src/frontend/components/dashboard/lib/component.js b/src/frontend/components/dashboard/lib/component.js index 3ed79d0cd..68ec6f9b4 100644 --- a/src/frontend/components/dashboard/lib/component.js +++ b/src/frontend/components/dashboard/lib/component.js @@ -15,7 +15,14 @@ import ReactDOM from 'react-dom'; import App from './react/app'; import ApiClient from './react/api'; +/** + * DashboardComponent class that initializes the dashboard and renders the App component. + */ class DashboardComponent extends Component { + /** + * Create a DashboardComponent instance. + * @param {HTMLElement} element The HTML element that this component will be attached to. + */ constructor(element) { super(element); this.el = $(this.element); @@ -30,6 +37,9 @@ class DashboardComponent extends Component { this.initDashboard(); } + /** + * Initialize the dashboard by rendering the App component with widgets and configurations. + */ initDashboard() { this.element.className = ''; const widgetsEls = Array.prototype.slice.call(document.querySelectorAll('#ld-app > div')); diff --git a/src/frontend/components/dashboard/lib/react/Footer.tsx b/src/frontend/components/dashboard/lib/react/Footer.tsx index 1ab39db38..e62ab4afe 100644 --- a/src/frontend/components/dashboard/lib/react/Footer.tsx +++ b/src/frontend/components/dashboard/lib/react/Footer.tsx @@ -1,6 +1,16 @@ import React from 'react'; -const Footer = ({ addWidget, widgetTypes, currentDashboard, readOnly, noDownload }) => { +/** + * Create a Footer component that displays options for downloading the dashboard and adding widgets. + * @param param0 The properties for the Footer component, including a function to add a widget, a list of widget types, the current dashboard, a read-only flag, and a no-download flag. + * @param {function} param0.addWidget Function to call when adding a widget. + * @param {Array} param0.widgetTypes The list of widget types available for addition. + * @param {object} param0.currentDashboard The currently active dashboard. + * @param {boolean} param0.readOnly A flag indicating whether the dashboard is in read-only mode. + * @param {boolean} param0.noDownload A flag indicating whether the download option + * @returns {JSX.Element} The rendered Footer component. + */ +const Footer = ({ addWidget, widgetTypes, currentDashboard, readOnly, noDownload }): JSX.Element => { return (
      {noDownload ? null :
      diff --git a/src/frontend/components/dashboard/lib/react/Header.tsx b/src/frontend/components/dashboard/lib/react/Header.tsx index 3c886d092..05a41b305 100644 --- a/src/frontend/components/dashboard/lib/react/Header.tsx +++ b/src/frontend/components/dashboard/lib/react/Header.tsx @@ -1,6 +1,16 @@ import React from 'react'; -const Header = ({ hMargin, dashboards, currentDashboard, loading, includeH1 }) => { +/** + * Create a Header component that displays a navigation header for dashboards. + * @param {object} param0 The properties for the Header component, including horizontal margin, list of dashboards, current dashboard, loading state, and a flag to include an H1 element. + * @param {number} param0.hMargin The horizontal margin to apply to the header. + * @param {Array} param0.dashboards The list of dashboards to display in the header. + * @param {{name: string}} param0.currentDashboard The currently active dashboard. + * @param {boolean} param0.loading A flag indicating whether the header is in a loading state. + * @param {boolean} param0.includeH1 A flag indicating whether to include an + * @returns {JSX.Element} The rendered Header component. + */ +const Header = ({ hMargin, dashboards, currentDashboard, loading, includeH1 }: { hMargin: number; dashboards: Array; currentDashboard: {name: string}; loading: boolean; includeH1: boolean; }): JSX.Element => { const renderMenuItem = (dashboard) => { if (dashboard.name === currentDashboard.name) { if (includeH1) { diff --git a/src/frontend/components/dashboard/lib/react/Widget.tsx b/src/frontend/components/dashboard/lib/react/Widget.tsx index 6cc4aace3..c049f54cf 100644 --- a/src/frontend/components/dashboard/lib/react/Widget.tsx +++ b/src/frontend/components/dashboard/lib/react/Widget.tsx @@ -1,10 +1,17 @@ import React from 'react'; import { initializeRegisteredComponents } from 'component'; +/** + * Widget component that renders a widget with HTML content. + */ export default class Widget extends React.Component { private ref; - constructor(props) { + /** + * Create a Widget component. + * @param {*} props The properties passed to the component, including the widget HTML and a flag for read-only mode. + */ + constructor(props: any) { super(props); this.ref = React.createRef(); @@ -25,6 +32,10 @@ export default class Widget extends React.Component { initializeRegisteredComponents(this.ref.current); }; + /** + * Render the Widget component. + * @returns {JSX.Element} The rendered widget component. + */ render() { return ( diff --git a/src/frontend/components/dashboard/lib/react/api.tsx b/src/frontend/components/dashboard/lib/react/api.tsx index 78eddc697..f199cddce 100644 --- a/src/frontend/components/dashboard/lib/react/api.tsx +++ b/src/frontend/components/dashboard/lib/react/api.tsx @@ -1,16 +1,32 @@ +/** + * API Client for interacting with the backend services. + * @todo Cleanup + */ export default class ApiClient { private baseUrl; private headers; private isDev; - constructor(baseUrl = '') { + /** + * Creates a new instance of ApiClient. + * @param {string} baseUrl Base URL for the API endpoints. + */ + constructor(baseUrl: string = '') { this.baseUrl = baseUrl; this.headers = {}; // @ts-expect-error "isDev is not valid" this.isDev = window.siteConfig && window.siteConfig.isDev; } - async _fetch(route, method, body) { + /** + * Execute a fetch request to the API. + * @description This is a basic wrapper around the fetch API. + * @param {string} route The API route to fetch. + * @param { 'GET'|'POST'|'PUT'|'PATCH'|'DELETE' } method The API method (GET, POST, PUT, PATCH, DELETE). + * @param {*} body The body of the request, if applicable. + * @returns {Promise} The response from the fetch call. + */ + async _fetch(route: string, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', body: any): Promise { if (!route) throw new Error('Route is undefined'); let csrfParam = ''; @@ -35,40 +51,100 @@ export default class ApiClient { return fetch(fullRoute, opts); } - GET(route) { return this._fetch(route, 'GET', null); } - - POST(route, body) { return this._fetch(route, 'POST', body); } - - PUT(route, body) { return this._fetch(route, 'PUT', body); } - - PATCH(route, body) { return this._fetch(route, 'PATCH', body); } - - DELETE(route) { return this._fetch(route, 'DELETE', null); } - - saveLayout = (id, layout) => { + /** + * Performs a GET request to the specified route. + * @param {string} route The API route to fetch. + * @returns {Promise} The response from the fetch call. + */ + GET(route: string): Promise { return this._fetch(route, 'GET', null); } + + /** + * Performs a POST request to the specified route. + * @param {string} route The API route to fetch. + * @param {*} body The body of the request, if applicable. + * @returns {Promise} The response from the fetch call. + */ + POST(route: string, body: any): Promise { return this._fetch(route, 'POST', body); } + + /** + * Performs a PUT request to the specified route. + * @param {string} route The API route to fetch. + * @param {*} body The body of the request, if applicable. + * @returns {Promise} The response from the fetch call. + */ + PUT(route: string, body: any): Promise { return this._fetch(route, 'PUT', body); } + + /** + * Performs a PATCH request to the specified route. + * @param {string} route The API route to fetch. + * @param {*} body The body of the request, if applicable. + * @returns {Promise} The response from the fetch call. + */ + PATCH(route: string, body: any): Promise { return this._fetch(route, 'PATCH', body); } + + /** + * Performs a DELETE request to the specified route. + * @param {string} route The API route to fetch. + * @returns {Promise} The response from the fetch call. + */ + DELETE(route: string): Promise { return this._fetch(route, 'DELETE', null); } + + /** + * Save the layout of a dashboard. + * @param {string} id The ID of the dashboard to save the layout for. + * @param {object} layout The layout to save, typically an array of widget configurations. + * @returns {Promise} The response from the save operation. + */ + saveLayout = (id: string, layout: Array): Promise => { if (!this.isDev) { const strippedLayout = layout.map(widget => ({ ...widget, moved: undefined })); return this.PUT(`/dashboard/${id}`, strippedLayout); } }; - createWidget = async type => { + /** + * Create a new widget. + * @param {string} type The type of widget to create. + * @returns {Promise} The response from the widget creation request. + */ + createWidget = async (type: string): Promise => { const response = this.isDev ? await this.GET(`/widget/create.json?type=${type}`) : await this.POST(`/widget?type=${type}`, null); return await response.json(); }; - getWidgetHtml = async id => { + /** + * Get the HTML content of a widget. + * @param {string} id The ID of the widget to retrieve HTML for. + * @returns {Promise} The HTML content of the widget. + */ + getWidgetHtml = async (id: string): Promise => { const html = this.isDev ? await this.GET(`/widget/${id}/create`) : await this.GET(`/widget/${id}`); return html.text(); }; - deleteWidget = id => !this.isDev && this.DELETE(`/widget/${id}`); - - getEditForm = async id => { + /** + * Delete a widget by its ID. + * @param {string} id The ID of the widget to delete. + * @returns {promise} A promise that resolves when the widget is deleted. + */ + deleteWidget = (id: string): Promise => !this.isDev && this.DELETE(`/widget/${id}`); + + /** + * Get the edit form for a widget. + * @param {string} id The ID of the widget to retrieve the edit form for. + * @returns {Promise} The JSON response containing the edit form data for the widget. + */ + getEditForm = async (id: string): Promise => { const response = await this.GET(`/widget/${id}/edit`); return response.json(); }; + /** + * Save a widget. + * @param {string} url The URL to save the widget. + * @param {object} params The parameters to save the widget. + * @returns {Promise} The JSON response containing the saved widget data. + */ saveWidget = async (url, params) => { const result = this.isDev ? await this.GET('/widget/update.json') : await this.PUT(`${url}`, params); return await result.json(); diff --git a/src/frontend/components/dashboard/lib/react/app.tsx b/src/frontend/components/dashboard/lib/react/app.tsx index 0537304db..d6fba359a 100644 --- a/src/frontend/components/dashboard/lib/react/app.tsx +++ b/src/frontend/components/dashboard/lib/react/app.tsx @@ -39,10 +39,18 @@ const modalStyle = { } }; +/** + * App component for the dashboard, managing widgets and layout. + */ class App extends React.Component { private formRef; - constructor(props) { + /** + * Create an instance of the App component. + * @param {any} props The properties passed to the component, including widgets, api, dashboardId, etc. + * @todo Use concrete types instead of `any` for better type safety. + */ + constructor(props: any) { super(props); Modal.setAppElement('#ld-app'); @@ -66,7 +74,7 @@ class App extends React.Component { this.initializeGlobeComponents(); }; - componentDidUpdate = (prevProps, prevState) => { + componentDidUpdate = (prevProps: any, prevState: any) => { window.requestAnimationFrame(this.overWriteSubmitEventListener); if (this.state.editModalOpen && prevState.loadingEditHtml && !this.state.loadingEditHtml && this.formRef) { @@ -92,7 +100,7 @@ class App extends React.Component { const arrGlobe = document.querySelectorAll('.globe'); import('../../../globe/lib/component').then(({ default: GlobeComponent }) => { arrGlobe.forEach((globe) => { - new GlobeComponent(globe); + new GlobeComponent(globe as HTMLElement); }); }); }; @@ -135,7 +143,6 @@ class App extends React.Component { }; deleteActiveWidget = () => { - if (!window.confirm('Deleting a widget is permanent! Are you sure?')) return; @@ -150,7 +157,6 @@ class App extends React.Component { event.preventDefault(); const formEl = this.formRef.current.querySelector('form'); if (!formEl) { - console.error('No form element was found!'); return; } @@ -194,7 +200,6 @@ class App extends React.Component { return { x, y }; }; - addWidget = async (type) => { this.setState({ loading: true }); const result = await this.props.api.createWidget(type); @@ -308,7 +313,11 @@ class App extends React.Component { window.dispatchEvent(new Event('resize')); }; - render() { + /** + * Renders the App component. + * @returns {React.JSX.Element} The rendered component, including the header, footer, and grid layout with widgets. + */ + render(): React.JSX.Element { return (
      {this.props.hideMenu ? null :
      { + this.el.on('draw.dt', () => { this.initClickableTable(); }); } @@ -76,9 +86,12 @@ class DataTableComponent extends Component { }); } + /** + * Clears the table state for the current page + */ clearTableStateForPage() { for (let i = 0; i < localStorage.length; i++) { - const storageKey = localStorage.key( i ); + const storageKey = localStorage.key(i); if (!storageKey.startsWith('DataTables')) { continue; @@ -90,12 +103,15 @@ class DataTableComponent extends Component { continue; } - if(window.location.href.indexOf('/' + keySegments.slice(1).join('/')) !== -1) { + if (window.location.href.indexOf('/' + keySegments.slice(1).join('/')) !== -1) { localStorage.removeItem(storageKey); } } } + /** + * Initializes the clickable table functionality + */ initClickableTable() { const links = this.el.find('tbody td .link'); // Remove all existing click events to prevent multiple bindings @@ -107,6 +123,11 @@ class DataTableComponent extends Component { links.on('blur', (ev) => { this.toggleFocus(ev, false); }); } + /** + * Toggles focus on a row + * @param {JQuery.FocusEvent} ev The event that triggered the focus change + * @param {boolean} hasFocus Whether the row has focus or not + */ toggleFocus(ev, hasFocus) { const row = $(ev.target).closest('tr'); if (hasFocus) { @@ -116,6 +137,10 @@ class DataTableComponent extends Component { } } + /** + * Handle click event on a row + * @param {JQuery.ClickEvent} ev The click event + */ handleClick(ev) { const rowClicked = $(ev.target).closest('tr'); ev.preventDefault(); @@ -123,6 +148,10 @@ class DataTableComponent extends Component { $(this.modal).modal('show'); } + /** + * Fill the modal data from the clicked row + * @param {HTMLTableRowElement} row The row to fill the modal data from + */ fillModalData(row) { const fields = $(this.modal).find('input, textarea'); const btnReject = $(this.modal).find('.btn-js-reject-request-send'); @@ -156,15 +185,24 @@ class DataTableComponent extends Component { }); } + /** + * Get a checkbox element as an HTML string + * @param {number } id The ID for the checkbox element + * @param {string} label The label for the checkbox element + * @returns {string} The HTML string for the checkbox element + */ getCheckboxElement(id, label) { return ( '
      ' + - `` + - `` + - '
      ' + `` + + `` + + '
      ' ); } + /** + * Add a select all checkbox to the table header + */ addSelectAllCheckbox() { const $selectAllElm = this.el.find('thead th.check'); const $checkBoxes = this.el.find('tbody .check .checkbox input'); @@ -181,10 +219,10 @@ class DataTableComponent extends Component { }); // Check if the 'select all' checkbox is checked and all checkboxes need to be checked - $selectAllElm.find('input').on( 'click', (ev) => { + $selectAllElm.find('input').on('click', (ev) => { const checkbox = $(ev.target); - if ($(checkbox).is( ':checked' )) { + if ($(checkbox).is(':checked')) { this.checkAllCheckboxes($checkBoxes, true); } else { this.checkAllCheckboxes($checkBoxes, false); @@ -192,14 +230,24 @@ class DataTableComponent extends Component { }); } + /** + * Check or uncheck all checkboxes in the table + * @param {JQuery} $checkBoxes The checkboxes to check or uncheck + * @param {boolean} bCheckAll True to check all checkboxes, false to uncheck all + */ checkAllCheckboxes($checkBoxes, bCheckAll) { if (bCheckAll) { - $checkBoxes.prop( 'checked', true ); + $checkBoxes.prop('checked', true); } else { $checkBoxes.prop('checked', false); } } + /** + * Check or uncheck the 'select all' checkbox based on the state of the individual checkboxes + * @param {JQuery} $checkBoxes The checkboxes to check or uncheck + * @param {JQuery} $selectAllCheckBox The select all checkbox to update + */ checkSelectAll($checkBoxes, $selectAllCheckBox) { let bSelectAll = true; @@ -215,6 +263,12 @@ class DataTableComponent extends Component { } } + /** + * Add a sort button to the column header + * @param {DataTable} dataTable The DataTable instance + * @param {any} column The column to add the sort button to + * @param {any} headerContent The content of the column header + */ addSortButton(dataTable, column, headerContent) { const $header = $(column.header()); const $button = $(` @@ -231,9 +285,13 @@ class DataTableComponent extends Component { .find('.data-table__header-wrapper') .html($button); - dataTable.order.listener($button, column.index() ); + dataTable.order.listener($button, column.index()); } + /** + * Toggle the filter for a column + * @param {any} column The column to toggle the filter for + */ toggleFilter(column) { const $header = $(column.header()); @@ -246,15 +304,21 @@ class DataTableComponent extends Component { } } - // Self reference included due to scoping + /** + * Add a search dropdown to the column header + * @param {any} column The column to add the search dropdown to + * @param {string} id The ID of the column + * @param {number} index The index of the column + */ async addSearchDropdown(column, id, index) { + // Self reference included due to scoping const $header = $(column.header()); const title = $header.text().trim(); const searchValue = column.search(); const self = this; - const {context} = column; - const {oAjaxData} = context[0]; - const {columns} = oAjaxData; + const { context } = column; + const { oAjaxData } = context[0]; + const { columns } = oAjaxData; const columnId = columns[column.index()].name; const col = this.columns[column.index()]; @@ -313,6 +377,37 @@ class DataTableComponent extends Component { this.toggleFilter(column); + if (col && col.typeahead) { + import(/*webpackChunkName: "typeahead" */ 'util/typeahead') + .then(({default: TypeaheadBuilder})=>{ + const builder = new TypeaheadBuilder(); + builder + .withAjaxSource(this.getApiEndpoint(columnId)) + .withMethod('POST') + .withData({csrf_token: $('body').data('csrf')}) + .withInput($('input', $header)) + .withAppendQuery() + .withDefaultMapper() + .withName(columnId.replace(/\s+/g, '') + 'Search') + .withCallback((data) => { + if (col.typeahead_use_id) { + $searchInput.val(data.name); + $('input.search', $searchElement).val(data.id) + .trigger('change'); + } else { + $('input', $searchElement).addClass('search') + .val(data.name) + .trigger('change'); + } + }) + .build(); + }); + } + + $header.find('.data-table__header-wrapper').prepend($searchElement); + + this.toggleFilter(column); + if (col && col.typeahead) { import(/*webpackChunkName: "typeahead" */ 'util/typeahead') .then(({default: TypeaheadBuilder})=>{ @@ -352,7 +447,7 @@ class DataTableComponent extends Component { self.toggleFilter(column); // Update or add the filter to the searchParams - if(self.searchParams.has(id)) { + if (self.searchParams.has(id)) { self.searchParams.set(id, this.value); } else { self.searchParams.append(id, this.value); @@ -390,21 +485,42 @@ class DataTableComponent extends Component { }); } + /** + * Get the API endpoint for the column typeahead + * @param {number} columnId The ID of the column to get the API endpoint for + * @returns {string} The API endpoint for the column typeahead + */ getApiEndpoint(columnId) { const table = $('body').data('layout-identifier'); return `/${table}/match/layout/${columnId}?q=`; } + /** + * Encode HTML entities in a string + * @param {string} text The text to encode + * @returns {string} The encoded text + */ encodeHTMLEntities(text) { return $('