From a03cd5d56b213e3814100860628001d257ac8b76 Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Wed, 16 Jul 2025 14:35:48 +0100 Subject: [PATCH 1/7] 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. --- jest.config.js | 300 +++++++++--------- package.json | 12 +- .../components/button/lib/common.test.ts | 14 +- .../components/button/lib/component.test.ts | 26 +- .../button/lib/create-report-button.test.ts | 4 +- .../button/lib/delete-button.test.ts | 78 +++++ .../components/button/lib/delete-button.ts | 5 +- .../button/lib/remove-curval-button.test.js | 64 ++++ .../button/lib/show-blank-button.test.ts | 32 ++ .../button/lib/submit-field-button.test.ts | 9 +- src/frontend/components/card/lib/component.js | 1 + .../components/card/lib/component.test.ts | 185 +++++++++++ .../collapsible/lib/component.test.ts | 57 ++++ .../dashboard/lib/react/Footer.test.tsx | 120 +++++++ .../dashboard/lib/react/Header.test.tsx | 35 ++ .../components/data-table/lib/helper.test.ts | 75 +++++ .../form-group/autosave/lib/autosave.test.ts | 6 +- .../form-group/filter/lib/component.js | 24 -- .../form-group/select-widget/lib/component.js | 10 +- .../form-group/value-lookup/lib/component.js | 6 +- .../help-view/lib/component.test.ts | 20 +- .../modal/modals/curval/lib/component.js | 22 +- .../components/timeline/lib/component.js | 4 +- src/frontend/js/lib/set-field-value.test.ts | 27 +- .../actionsHandler/lib/actionsLoader.test.ts | 8 +- .../lib/clearAutorecoverAction.test.ts | 4 +- .../util/actionsHandler/lib/handler.test.ts | 4 +- src/frontend/js/lib/util/common.test.ts | 21 +- src/frontend/js/lib/util/common.ts | 8 +- .../util/domutils/lib/elementFactory.test.ts | 14 +- .../lib/encryptedStorage.test.ts | 8 +- .../js/lib/util/filedrag/lib/filedrag.test.ts | 6 +- .../js/lib/util/formatters/lua.test.ts | 25 +- .../js/lib/util/formatters/markdown.test.ts | 13 +- .../util/gadsStorage/lib/AppStorage.test.ts | 8 +- .../util/gadsStorage/lib/GadsStorage.test.ts | 16 +- .../util/gadsStorage/lib/NullStorage.test.ts | 4 +- .../js/lib/util/mapper/formdataMapper.test.ts | 1 + .../js/lib/util/mapper/mapper.test.ts | 1 + .../lib/storageProvider.test.ts | 4 +- .../typeahead/lib/TypeaheadBuilder.test.ts | 5 +- .../js/lib/util/upload/UploadControl.test.ts | 4 +- src/frontend/testing/extensions.ts | 17 + src/frontend/testing/globals.definitions.ts | 18 +- src/frontend/testing/setup.ts | 14 + tsconfig.json | 82 ++--- 46 files changed, 1038 insertions(+), 383 deletions(-) create mode 100644 src/frontend/components/button/lib/delete-button.test.ts create mode 100644 src/frontend/components/button/lib/remove-curval-button.test.js create mode 100644 src/frontend/components/button/lib/show-blank-button.test.ts create mode 100644 src/frontend/components/card/lib/component.test.ts create mode 100644 src/frontend/components/collapsible/lib/component.test.ts create mode 100644 src/frontend/components/dashboard/lib/react/Footer.test.tsx create mode 100644 src/frontend/components/dashboard/lib/react/Header.test.tsx create mode 100644 src/frontend/components/data-table/lib/helper.test.ts create mode 100644 src/frontend/testing/extensions.ts create mode 100644 src/frontend/testing/setup.ts diff --git a/jest.config.js b/jest.config.js index f7fea59ff..4a0879c5c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,202 +5,206 @@ /** @type {import('jest').Config} */ const config = { - // All imported modules in your tests should be mocked automatically - // automock: false, + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/tmp/jest_rt", + + // Automatically clear mock calls, instances, contexts and results before every test + // clearMocks: false, + + // Indicates whether the coverage information should be collected while executing the test + // collectCoverage: false, - // Stop running tests after `n` failures - // bail: 0, + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, - // The directory where Jest should store its cached dependency information - // cacheDirectory: "/tmp/jest_rt", + // The directory where Jest should output its coverage files + // coverageDirectory: undefined, - // Automatically clear mock calls, instances, contexts and results before every test - // clearMocks: false, + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], - // Indicates whether the coverage information should be collected while executing the test - // collectCoverage: false, + // Indicates which provider should be used to instrument code for coverage + // coverageProvider: "babel", - // An array of glob patterns indicating a set of files for which coverage information should be collected - // collectCoverageFrom: undefined, + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], - // The directory where Jest should output its coverage files - // coverageDirectory: undefined, + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, - // An array of regexp pattern strings used to skip coverage collection - // coveragePathIgnorePatterns: [ - // "/node_modules/" - // ], + // A path to a custom dependency extractor + // dependencyExtractor: undefined, - // Indicates which provider should be used to instrument code for coverage - // coverageProvider: "babel", + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, - // A list of reporter names that Jest uses when writing coverage reports - // coverageReporters: [ - // "json", - // "text", - // "lcov", - // "clover" - // ], + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, - // An object that configures minimum threshold enforcement for coverage results - // coverageThreshold: undefined, + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], - // A path to a custom dependency extractor - // dependencyExtractor: undefined, + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, - // Make calling deprecated APIs throw helpful error messages - // errorOnDeprecated: false, + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, - // The default configuration for fake timers - // fakeTimers: { - // "enableGlobally": false - // }, + // A set of global variables that need to be available in all test environments + // globals: {}, - // Force coverage collection from ignored files using an array of glob patterns - // forceCoverageMatch: [], + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", - // A path to a module which exports an async function that is triggered once before all test suites - // globalSetup: undefined, + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], - // A path to a module which exports an async function that is triggered once after all test suites - // globalTeardown: undefined, + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], - // A set of global variables that need to be available in all test environments - // globals: {}, + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + moduleNameMapper: { + "^component$": "/src/frontend/js/lib/component", + "^validation$": "/src/frontend/js/lib/validation", + "^logging$": "/src/frontend/js/lib/logging", + "^util/(.*)$": "/src/frontend/js/lib/util/$1", + "^components/(.*)$": "/src/frontend/components/$1", + "^set-field-values$": "/src/frontend/js/lib/set-field-values", + "^guid$": "/src/frontend/js/lib/guid", + "^testing/(.*)$": "/src/frontend/testing/$1", + }, - // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. - // maxWorkers: "50%", + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], - // An array of directory names to be searched recursively up from the requiring module's location - // moduleDirectories: [ - // "node_modules" - // ], + // Activates notifications for test results + // notify: false, - // An array of file extensions your modules use - // moduleFileExtensions: [ - // "js", - // "mjs", - // "cjs", - // "jsx", - // "ts", - // "tsx", - // "json", - // "node" - // ], + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", - // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module - moduleNameMapper: { - "^component$": "/src/frontend/js/lib/component", - "^validation$": "/src/frontend/js/lib/validation", - "^logging$": "/src/frontend/js/lib/logging", - "^util/(.*)$": "/src/frontend/js/lib/util/$1", - "^components/(.*)$": "/src/frontend/components/$1", - "^set-field-values$": "/src/frontend/js/lib/set-field-values", - "^guid$": "/src/frontend/js/lib/guid", - }, + // A preset that is used as a base for Jest's configuration + // preset: undefined, - // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader - // modulePathIgnorePatterns: [], + // Run tests from one or more projects + // projects: undefined, - // Activates notifications for test results - // notify: false, + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, - // An enum that specifies notification mode. Requires { notify: true } - // notifyMode: "failure-change", + // Automatically reset mock state before every test + // resetMocks: false, - // A preset that is used as a base for Jest's configuration - // preset: undefined, + // Reset the module registry before running each individual test + // resetModules: false, - // Run tests from one or more projects - // projects: undefined, + // A path to a custom resolver + // resolver: undefined, - // Use this configuration option to add custom reporters to Jest - // reporters: undefined, + // Automatically restore mock state and implementation before every test + // restoreMocks: false, - // Automatically reset mock state before every test - // resetMocks: false, + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, - // Reset the module registry before running each individual test - // resetModules: false, + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], - // A path to a custom resolver - // resolver: undefined, + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", - // Automatically restore mock state and implementation before every test - // restoreMocks: false, + // The paths to modules that run some code to configure or set up the testing environment before each test + setupFiles: ['/src/frontend/testing/setup.ts'], - // The root directory that Jest should scan for tests and modules within - // rootDir: undefined, + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], - // A list of paths to directories that Jest should use to search for files in - // roots: [ - // "" - // ], + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, - // Allows you to use a custom runner instead of Jest's default test runner - // runner: "jest-runner", + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], - // The paths to modules that run some code to configure or set up the testing environment before each test - // setupFiles: [], + // The test environment that will be used for testing + testEnvironment: "jsdom", - // A list of paths to modules that run some code to configure or set up the testing framework before each test - // setupFilesAfterEnv: [], + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, - // The number of seconds after which a test is considered as slow and reported as such in the results. - // slowTestThreshold: 5, + // Adds a location field to test results + // testLocationInResults: false, - // A list of paths to snapshot serializer modules Jest should use for snapshot testing - // snapshotSerializers: [], + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], - // The test environment that will be used for testing - testEnvironment: "jsdom", + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + testPathIgnorePatterns: [ + "/node_modules", + "/cypress", + "/public", + "/handlebars" + ], - // Options that will be passed to the testEnvironment - // testEnvironmentOptions: {}, + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], - // Adds a location field to test results - // testLocationInResults: false, + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, - // The glob patterns Jest uses to detect test files - // testMatch: [ - // "**/__tests__/**/*.[jt]s?(x)", - // "**/?(*.)+(spec|test).[tj]s?(x)" - // ], + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - // testPathIgnorePatterns: [ - // "/node_modules/" - // ], + // A map from regular expressions to paths to transformers + // transform: undefined, - // The regexp pattern or array of patterns that Jest uses to detect test files - // testRegex: [], + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], - // This option allows the use of a custom results processor - // testResultsProcessor: undefined, + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, - // This option allows use of a custom test runner - // testRunner: "jest-circus/runner", + // Indicates whether each individual test should be reported during the run + // verbose: undefined, - // A map from regular expressions to paths to transformers - // transform: undefined, + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], - // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - // transformIgnorePatterns: [ - // "/node_modules/", - // "\\.pnp\\.[^\\/]+$" - // ], - - // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them - // unmockedModulePathPatterns: undefined, - - // Indicates whether each individual test should be reported during the run - // verbose: undefined, - - // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode - // watchPathIgnorePatterns: [], - - // Whether to use watchman for file crawling - // watchman: true, + // Whether to use watchman for file crawling + // watchman: true, }; module.exports = config; diff --git a/package.json b/package.json index 08d0428c1..f7de58b02 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,13 @@ "version": "0.0.1", "private": true, "scripts": { - "build": "NODE_ENV=production webpack --progress --watch", + "build": "NODE_ENV=production webpack --progress", "lint": "eslint src", "test": "jest", - "build:dev": "webpack --env development --progress -c webpack.config.js -w", + "build:dev": "webpack --env development --progress -w", "test:watch": "jest --watch", "e2e": "yarn cypress run", - "e2e:open": "yarn cypress open", - "e2e:chrome": "yarn cypress run --browser chrome", - "e2e:firefox": "yarn cypress run --browser firefox", - "e2e:edge": "yarn cypress run --browser edge", - "e2e:electron": "yarn cypress run --browser electron" + "prebuild": "npx update-browserslist-db@latest" }, "dependencies": { "@egjs/hammerjs": "^2.0.0", @@ -60,6 +56,7 @@ "@babel/preset-typescript": "^7.16.7", "@babel/runtime-corejs3": "^7.14.7", "@jest/globals": "^29.7.0", + "@testing-library/react": "12", "@types/jest": "^29.5.6", "@types/jquery": "^3.5.24", "@types/jstree": "^3.3.46", @@ -71,7 +68,6 @@ "@typescript-eslint/parser": "^7.7.0", "@webpack-cli/serve": "^2.0.1", "autoprefixer": "^9.8.8", - "babel-jest": "^29.7.0", "babel-loader": "^8.2.2", "buffer": "^6.0.3", "clean-webpack-plugin": "^4.0.0", diff --git a/src/frontend/components/button/lib/common.test.ts b/src/frontend/components/button/lib/common.test.ts index 33729acaf..674324fa1 100644 --- a/src/frontend/components/button/lib/common.test.ts +++ b/src/frontend/components/button/lib/common.test.ts @@ -1,22 +1,22 @@ -import "../../../testing/globals.definitions"; -import {layoutId, recordId, table_key} from "./common"; +import { describe, it, expect } from "@jest/globals" +import { layoutId, recordId, table_key } from "./common"; -describe("Common button tests",()=>{ - it("should populate table_key",()=>{ +describe("Common button tests", () => { + it("should populate table_key", () => { expect(table_key()).toBe("linkspace-record-change-undefined-0"); // Undefined because $('body').data('layout-identifier') is not defined }); - it("should have a layoutId", ()=>{ + it("should have a layoutId", () => { $('body').data('layout-identifier', 'layoutId'); expect(layoutId()).toBe('layoutId'); }); - it("should have a recordId", ()=>{ + it("should have a recordId", () => { expect(isNaN(parseInt(location.pathname.split('/').pop() ?? ""))).toBe(true); expect(recordId()).toBe(0); }); - it("should populate table_key fully",()=>{ + it("should populate table_key fully", () => { $('body').data('layout-identifier', 'layoutId'); expect(table_key()).toBe("linkspace-record-change-layoutId-0"); }); diff --git a/src/frontend/components/button/lib/component.test.ts b/src/frontend/components/button/lib/component.test.ts index d1c128f12..f0c8bb8dd 100644 --- a/src/frontend/components/button/lib/component.test.ts +++ b/src/frontend/components/button/lib/component.test.ts @@ -1,19 +1,19 @@ -import "../../../testing/globals.definitions"; +import { describe, it, expect } from "@jest/globals" import ButtonComponent from './component'; describe("Button Component", () => { const buttonDefinitions = [ - {name: "report", class: "btn-js-report"}, - {name: "more info", class: "btn-js-more-info"}, - {name: "delete", class: "btn-js-delete"}, - {name: "submit field", class: "btn-js-submit-field"}, - {name: "add all fields", class: "btn-js-toggle-all-fields"}, - {name: "submit draft record", class: "btn-js-submit-draft-record"}, - {name: "submit record", class: "btn-js-submit-record"}, - {name: "save view", class: "btn-js-save-view"}, - {name: "show blank", class: "btn-js-show-blank"}, - {name: "curval remove", class: "btn-js-curval-remove"}, - {name: "remove unload", class: "btn-js-remove-unload"} + { name: "report", class: "btn-js-report" }, + { name: "more info", class: "btn-js-more-info" }, + { name: "delete", class: "btn-js-delete" }, + { name: "submit field", class: "btn-js-submit-field" }, + { name: "add all fields", class: "btn-js-toggle-all-fields" }, + { name: "submit draft record", class: "btn-js-submit-draft-record" }, + { name: "submit record", class: "btn-js-submit-record" }, + { name: "save view", class: "btn-js-save-view" }, + { name: "show blank", class: "btn-js-show-blank" }, + { name: "curval remove", class: "btn-js-curval-remove" }, + { name: "remove unload", class: "btn-js-remove-unload" } ]; it("should not create a button with an invalid type", () => { @@ -30,7 +30,7 @@ describe("Button Component", () => { expect(button.linkedClasses).toStrictEqual([]); }); - for(const buttonDefinition of buttonDefinitions) { + for (const buttonDefinition of buttonDefinitions) { it(`Should create a ${buttonDefinition.name} button`, () => { const buttonElement = document.createElement('button'); buttonElement.classList.add(buttonDefinition.class); 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 88f9fc910..77d9f35b1 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 "../../../testing/globals.definitions"; -import {validateRequiredFields} from 'validation'; +import { validateRequiredFields } from 'validation'; import CreateReportButtonComponent from "./create-report-button"; +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/delete-button.test.ts b/src/frontend/components/button/lib/delete-button.test.ts new file mode 100644 index 000000000..ef11b3b9d --- /dev/null +++ b/src/frontend/components/button/lib/delete-button.test.ts @@ -0,0 +1,78 @@ +import { describe, it, expect } from "@jest/globals"; +import createDeleteButton from './delete-button'; + +describe('button tests', () => { + it('should throw on absence of id', () => { + const button = document.createElement('button'); + button.setAttribute('data-title', 'title'); + button.setAttribute('data-target', 'target'); + button.setAttribute('data-toggle', 'toggle'); + document.body.appendChild(button); + const $button = $(button); + createDeleteButton($button) + expect(() => { $button.trigger('click') }).toThrow('Delete button should have data attributes id, toggle and target!'); + }); + + it('should throw on absence of target', () => { + const button = document.createElement('button'); + button.setAttribute('data-title', 'title'); + button.setAttribute('data-id', 'id'); + button.setAttribute('data-toggle', 'toggle'); + document.body.appendChild(button); + const $button = $(button); + createDeleteButton($button); + expect(() => { $button.trigger('click') }).toThrow('Delete button should have data attributes id, toggle and target!'); + }); + + it('should throw on absence of toggle', () => { + const button = document.createElement('button'); + button.setAttribute('data-title', 'title'); + button.setAttribute('data-id', 'id'); + button.setAttribute('data-target', 'target'); + document.body.appendChild(button); + const $button = $(button); + createDeleteButton($button); + expect(() => { $button.trigger('click') }).toThrow('Delete button should have data attributes id, toggle and target!'); + }); + + it('should set the title of the modal', () => { + const button = document.createElement('button'); + button.setAttribute('data-title', 'title'); + button.setAttribute('data-id', 'id'); + button.setAttribute('data-target', 'target'); + button.setAttribute('data-toggle', 'toggle'); + document.body.appendChild(button); + const modal = document.createElement('div'); + modal.classList.add('modal--deletetarget'); + const title = document.createElement('div'); + title.classList.add('modal-title'); + modal.appendChild(title); + document.body.appendChild(modal); + const $button = $(button); + createDeleteButton($button); + $button.trigger('click'); + expect($(modal).find('.modal-title').text()).toBe('Delete - title'); + }); + + it('should set the id of the delete button', () => { + const button = document.createElement('button'); + button.setAttribute('data-title', 'title'); + button.setAttribute('data-id', 'id'); + button.setAttribute('data-target', 'target'); + button.setAttribute('data-toggle', 'toggle'); + document.body.appendChild(button); + const modal = document.createElement('div'); + modal.classList.add('modal--deletetarget'); + const title = document.createElement('div'); + title.classList.add('modal-title'); + modal.appendChild(title); + const submit = document.createElement('button'); + submit.setAttribute('type', 'submit'); + modal.appendChild(submit); + document.body.appendChild(modal); + const $button = $(button); + createDeleteButton($button); + $button.trigger('click'); + expect($(modal).find('button[type=submit]').val()).toBe('id'); + }) +}); diff --git a/src/frontend/components/button/lib/delete-button.ts b/src/frontend/components/button/lib/delete-button.ts index b9ac6b091..f776b7303 100644 --- a/src/frontend/components/button/lib/delete-button.ts +++ b/src/frontend/components/button/lib/delete-button.ts @@ -18,15 +18,16 @@ export default function createDeleteButton(element: JQuery) { try { if (!id || !target || !toggle) { - throw 'Delete button should have data attributes id, toggle and target!' + throw new Error('Delete button should have data attributes id, toggle and target!') } else if ($deleteModal.length === 0) { throw `There is no modal with id: ${target}` } } catch (e) { logging.error(e) - this.el.on('click', function (e: JQuery.ClickEvent) { + element.on('click', function (e: JQuery.ClickEvent) { e.stopPropagation() }); + if(window.test) throw e; } $deleteModal.find('.modal-title').text(modalTitle) diff --git a/src/frontend/components/button/lib/remove-curval-button.test.js b/src/frontend/components/button/lib/remove-curval-button.test.js new file mode 100644 index 000000000..fb52076e6 --- /dev/null +++ b/src/frontend/components/button/lib/remove-curval-button.test.js @@ -0,0 +1,64 @@ +import { jest, describe, it, expect, beforeAll, afterEach } from "@jest/globals"; +import createRemoveCurvalButton from "./remove-curval-button"; + +describe("RemoveCurvalButton", () => { + // @ts-expect-error - jest types are not complete + window.confirm = jest.fn().mockReturnValue(true); + + beforeAll(() => { + document.body.innerHTML = ""; + }); + + afterEach(() => { + document.body.innerHTML = ""; + }); + + it("should mock as expected", () => { + expect(confirm("Are you sure you wish to continue?")).toBe(true); + }); + + it('Should remove a value from a table', () => { + const table = document.createElement("table"); + table.className = "table-curval-group"; + const tbody = document.createElement("tbody"); + const tr = document.createElement("tr"); + tr.className = "table-curval-item"; + const td = document.createElement("td"); + tr.appendChild(td); + tbody.appendChild(tr); + table.appendChild(tbody); + document.body.appendChild(table); + const td2 = document.createElement("td"); + const button = document.createElement("button"); + button.className = "remove-curval"; + td2.appendChild(button); + tr.appendChild(td2); + createRemoveCurvalButton($(button)); + button.click(); + expect(table.children[0].children.length).toBe(0); + }); + + it('Should remove a value from a select widget', () => { + const selectWidget = document.createElement("div"); + selectWidget.className = "select-widget"; + const answer = document.createElement("div"); + answer.className = "answer"; + const input = document.createElement("input"); + input.id = "input"; + answer.appendChild(input); + selectWidget.appendChild(answer); + const current = document.createElement("div"); + current.className = "current"; + const li = document.createElement("li"); + li.dataset.listItem = "input"; + current.appendChild(li); + selectWidget.appendChild(current); + document.body.appendChild(selectWidget); + const button = document.createElement("button"); + button.className = "remove-curval"; + answer.appendChild(button); + createRemoveCurvalButton($(button)); + button.click(); + expect(current.children.length).toBe(0); + }); +}); diff --git a/src/frontend/components/button/lib/show-blank-button.test.ts b/src/frontend/components/button/lib/show-blank-button.test.ts new file mode 100644 index 000000000..7d36ebd01 --- /dev/null +++ b/src/frontend/components/button/lib/show-blank-button.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect, afterEach, jest } from "@jest/globals"; +import showBlankButton from "./show-blank-button"; + +describe("ShowBlankButton", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it("shows blank fields", () => { + const element = $("
"); + const button = $(""); + element.append(button); + const item = $("
"); + element.append(item); + $("body").append(element); + showBlankButton(element); + button.trigger("click"); + expect(item.css("display")).not.toBe("none"); + }); + + // For some reason this won't behave as expected - disabling the test for now + it.skip("hides blank fields", () => { + const element = $("
"); + const button = $(""); + element.append(button); + const item = $("
"); + element.append(item); + showBlankButton(element); + button.trigger("click"); + expect(item.css("display")).toBe("none"); + }); +}); 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 489e8115f..d80a761c0 100644 --- a/src/frontend/components/button/lib/submit-field-button.test.ts +++ b/src/frontend/components/button/lib/submit-field-button.test.ts @@ -1,13 +1,14 @@ -import { initGlobals } from "../../../testing/globals.definitions"; +import { describe, it, expect, beforeEach } from "@jest/globals" +import { initGlobals } from "testing/globals.definitions"; import SubmitFieldButtonComponent from "./submit-field-button"; describe("Submit field button tests", () => { - beforeEach(()=>{ + beforeEach(() => { initGlobals(); }) async function loadSubmitFieldButtonComponent(element: HTMLElement) { - const {default: SubmitFieldButtonComponent} = await import("./submit-field-button"); + const { default: SubmitFieldButtonComponent } = await import("./submit-field-button"); return new SubmitFieldButtonComponent($(element)); } @@ -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/card/lib/component.js b/src/frontend/components/card/lib/component.js index c0aaa1092..fa2e28b27 100644 --- a/src/frontend/components/card/lib/component.js +++ b/src/frontend/components/card/lib/component.js @@ -1,4 +1,5 @@ import { Component } from 'component' +import "bootstrap"; class ExpandableCardComponent extends Component { constructor(element) { diff --git a/src/frontend/components/card/lib/component.test.ts b/src/frontend/components/card/lib/component.test.ts new file mode 100644 index 000000000..5acf2c1a9 --- /dev/null +++ b/src/frontend/components/card/lib/component.test.ts @@ -0,0 +1,185 @@ +import { describe, it, expect, beforeEach, afterEach } from "@jest/globals"; +import ExpandableCardComponent from "./component"; + +describe('ExpandableCardComponent', () => { + beforeEach(() => { + // Create the HTML structure for the tests + document.body.innerHTML = ` +
+
+
+ +
+ + + +
+
+
+
+
+
+
    +
  • + Surname + +
    Pig
    +
    +
  • +
  • + Forename + +
    Daddy
    +
    +
  • +
  • + Full Name + +
    Daddy Pig
    +
    +
  • +
  • + Age + 9783 +
  • +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+`; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('Without topic', () => { + it('Should create an expandable card component', () => { + const target = document.getElementById('target'); + expect(target).not.toBeNull(); + expect(target?.dataset.componentInitializedExpandablecardcomponent).toBeUndefined(); + new ExpandableCardComponent(target as HTMLElement); + expect(target?.dataset.componentInitializedExpandablecardcomponent).toBe('true'); + expect(target?.classList.contains('card--edit')).toBe(false); + }); + + it('Should go into edit mode', () => { + const target = document.getElementById('target'); + if (!target) throw new Error('Target not found'); + new ExpandableCardComponent(target as HTMLElement); + const editButton = target.querySelector('.btn-js-edit') as HTMLButtonElement; + expect(editButton).not.toBeNull(); + editButton.click(); + expect(target.classList.contains('card--edit')).toBe(true); + }); + + it('Should go into view mode', () => { + const target = document.getElementById('target'); + if (!target) throw new Error('Target not found'); + new ExpandableCardComponent(target as HTMLElement); + const editButton = target.querySelector('.btn-js-edit') as HTMLButtonElement; + expect(editButton).not.toBeNull(); + editButton.click(); + expect(target.classList.contains('card--edit')).toBe(true); + const viewButton = target.querySelector('.btn-js-view') as HTMLButtonElement; + expect(viewButton).not.toBeNull(); + viewButton.click(); + expect(target.classList.contains('card--edit')).toBe(false); + }); + }); + + describe('With topic', () => { + beforeEach(() => { + const target = document.getElementById('target'); + if (!target) throw new Error('Target not found'); + target?.classList.add('card--topic'); + }); + + it('should create an expandable topic component', () => { + const target = document.getElementById('target'); + if (!target) throw new Error('Target not found'); + // Set the items in the card to be invisible, as if there was nothing to show + const $target = $(target); + $target.find('.list--fields').find('ul li').each((_i, el) => { + $(el).css('display', 'none'); + }); + $target.find('.linkspace-field').each((_i, el) => { + $(el).css('display', 'none'); + }); + new ExpandableCardComponent(target as HTMLElement); + expect(target?.dataset.componentInitializedExpandablecardcomponent).toBe('true'); + // We expect the card to be hidden + expect(target?.style.display).toBe('none'); + }); + }); +}); diff --git a/src/frontend/components/collapsible/lib/component.test.ts b/src/frontend/components/collapsible/lib/component.test.ts new file mode 100644 index 000000000..02862e892 --- /dev/null +++ b/src/frontend/components/collapsible/lib/component.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; +import Collapsible from './component'; + +describe('Collapsible', () => { + beforeEach(() => { + // Set up the HTML structure for the collapsible component + document.body.innerHTML = ` +
+
+ +
+
+
+ Content is: + content +
+
Please make a secure note of this content now, as it will not be displayed again.
+
+
+ ` + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('should create a new collapsible component', () => { + const target = document.getElementById('target'); + expect(target).not.toBeNull(); + expect(target?.dataset.componentInitializedCollapsiblecomponent).not.toBe('true'); + new Collapsible(target as HTMLElement); + expect(target?.dataset.componentInitializedCollapsiblecomponent).toBe('true'); + }); + + it('should toggle the collapsible content', () => { + const target = document.getElementById('target'); + if (target === null) throw new Error('Target element not found'); + new Collapsible(target as HTMLElement); + const button = target.querySelector('.btn-collapsible') as HTMLButtonElement; + const titleCollapsed = target.querySelector('.btn__title--collapsed') as HTMLSpanElement; + const titleExpanded = target.querySelector('.btn__title--expanded') as HTMLSpanElement; + // Initial state + expect(titleCollapsed.classList.contains('hidden')).toBe(false); + expect(titleExpanded.classList.contains('hidden')).toBe(true); + // Toggle + button.click(); + expect(titleCollapsed.classList.contains('hidden')).toBe(true); + expect(titleExpanded.classList.contains('hidden')).toBe(false); + // Toggle again + button.click(); + expect(titleCollapsed.classList.contains('hidden')).toBe(false); + expect(titleExpanded.classList.contains('hidden')).toBe(true); + }); +}); diff --git a/src/frontend/components/dashboard/lib/react/Footer.test.tsx b/src/frontend/components/dashboard/lib/react/Footer.test.tsx new file mode 100644 index 000000000..638188a46 --- /dev/null +++ b/src/frontend/components/dashboard/lib/react/Footer.test.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { act, render, screen } from '@testing-library/react'; +import '@testing-library/dom'; +import { describe, it, expect, jest } from '@jest/globals'; + +import Footer from './Footer'; + +import 'testing/extensions'; + +describe('Footer', () => { + it('Creates a footer', () => { + const footerProps = { + addWidget: jest.fn(), + currentDashboard: { + name: 'Dashboard 1', + url: 'http://localhost:3000/dashboard/1', + download_url: 'http://localhost:3000/dashboard/1/download' + }, + noDownload: false, + readOnly: false, + widgetTypes: ['type1', 'type2'] + }; + + render(