diff --git a/classes/Modules/Address/www/js/address_create.js b/classes/Modules/Address/www/js/address_create.js index aec7149ed..099b7f110 100644 --- a/classes/Modules/Address/www/js/address_create.js +++ b/classes/Modules/Address/www/js/address_create.js @@ -10,6 +10,7 @@ var AddressDuplicates = (function ($) { }, init: function () { + me.registerZipcodeSanitizer(); me.registerEvents(); }, @@ -33,6 +34,40 @@ var AddressDuplicates = (function ($) { }); }, + registerZipcodeSanitizer: function () { + var $zipcodeFields = $('#plz, #rechnung_plz'); + + if(!$zipcodeFields.length){ + return; + } + + me.sanitizeZipcodeField($zipcodeFields); + + $zipcodeFields.on('blur change', function () { + me.sanitizeZipcodeField($(this)); + }); + }, + + sanitizeZipcodeField: function ($field) { + if(!$field || !$field.length){ + return; + } + + $field.each(function () { + var $currentField = $(this); + var currentValue = $currentField.val(); + if(typeof currentValue !== 'string'){ + return; + } + + // Entfernt Leerzeichen am Anfang oder Ende, die keine eigentlichen Zeichen flankieren + var sanitizedValue = currentValue.replace(/^\s+|\s+$/g, ''); + if(sanitizedValue !== currentValue){ + $currentField.val(sanitizedValue); + } + }); + }, + checkDuplicate: function () { var nameValue = $('#name').val(); var streetValue = $('#strasse').val(); @@ -58,4 +93,4 @@ var AddressDuplicates = (function ($) { $(document).ready(function () { AddressDuplicates.init(); -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index 869f8afc8..53d2cb3a6 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "api-docs:build": "./node_modules/raml2html/bin/raml2html --validate --theme raml2html-werk-theme --input www/api/docs.raml --output www/api/docs-tmp.html && cat www/api/docs-tmp.html | sed -e 's/<\\/head>/<\\/head>/' > www/api/docs.html && rm www/api/docs-tmp.html", "dev": "vite -d --cors --host ::", "build": "vite build", + "test:zipcode": "node --test tests/address_zipcode_sanitizer.test.js", "preview": "vite preview" }, "devDependencies": { diff --git a/tests/address_zipcode_sanitizer.test.js b/tests/address_zipcode_sanitizer.test.js new file mode 100644 index 000000000..1ec3f8e0c --- /dev/null +++ b/tests/address_zipcode_sanitizer.test.js @@ -0,0 +1,172 @@ +import assert from 'node:assert'; +import fs from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import vm from 'node:vm'; +import test from 'node:test'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const scriptPath = path.resolve(__dirname, '..', 'classes', 'Modules', 'Address', 'www', 'js', 'address_create.js'); + +const createElement = (id, value = '') => ({ + id, + value, + handlers: {}, + classes: new Set(), +}); + +const createJQueryStub = (elements, documentElement) => { + function JQueryCollection(resolvedElements) { + this.elements = resolvedElements; + this.length = resolvedElements.length; + } + + JQueryCollection.prototype.on = function (events, handler) { + const eventList = events.split(/\s+/); + this.elements.forEach((element) => { + eventList.forEach((evt) => { + if (!element.handlers[evt]) { + element.handlers[evt] = []; + } + element.handlers[evt].push(handler); + }); + }); + return this; + }; + + JQueryCollection.prototype.ready = function (handler) { + if (typeof handler === 'function') { + handler(); + } + return this; + }; + + JQueryCollection.prototype.val = function (newValue) { + if (newValue === undefined) { + return this.elements[0]?.value; + } + this.elements.forEach((element) => { + element.value = newValue; + }); + return this; + }; + + JQueryCollection.prototype.each = function (callback) { + this.elements.forEach((element, index) => { + callback.call(element, index, element); + }); + return this; + }; + + JQueryCollection.prototype.trigger = function (eventName) { + this.elements.forEach((element) => { + const handlers = element.handlers[eventName] || []; + handlers.forEach((handler) => handler.call(element, {type: eventName})); + }); + return this; + }; + + JQueryCollection.prototype.addClass = function (className) { + this.elements.forEach((element) => element.classes.add(className)); + return this; + }; + + JQueryCollection.prototype.removeClass = function (className) { + this.elements.forEach((element) => element.classes.delete(className)); + return this; + }; + + JQueryCollection.prototype.hasClass = function (className) { + return this.elements.some((element) => element.classes.has(className)); + }; + + JQueryCollection.prototype.next = function () { + return { + remove: () => undefined, + }; + }; + + JQueryCollection.prototype.insertAfter = function () { + return this; + }; + + JQueryCollection.prototype.html = function () { + return this; + }; + + const resolveSelector = (selector) => { + if (selector === document || selector === 'document') { + return [documentElement]; + } + + if (typeof selector === 'string') { + if (selector.startsWith('<')) { + return [createElement('generated')]; + } + const ids = selector.split(',').map((part) => part.trim()); + return ids + .map((id) => (id.startsWith('#') ? id : `#${id}`)) + .map((id) => elements[id]) + .filter(Boolean); + } + + if (selector && selector.id && elements[`#${selector.id}`]) { + return [elements[`#${selector.id}`]]; + } + + return []; + }; + + const jQuery = function (selector) { + return new JQueryCollection(resolveSelector(selector)); + }; + + jQuery.ajax = () => ({ + done: (handler) => { + handler(false); + return this; + }, + }); + + return jQuery; +}; + +const setupEnvironment = (plzValue, rechnungPlzValue) => { + const elements = { + '#plz': createElement('plz', plzValue), + '#rechnung_plz': createElement('rechnung_plz', rechnungPlzValue), + '#name': createElement('name'), + '#strasse': createElement('strasse'), + '#ort': createElement('ort'), + }; + + const documentElement = createElement('document'); + const $ = createJQueryStub(elements, documentElement); + + globalThis.$ = $; + globalThis.jQuery = $; + globalThis.document = documentElement; + + const scriptContent = fs.readFileSync(scriptPath, 'utf8'); + vm.runInThisContext(scriptContent, {filename: scriptPath}); + + return {elements, $}; +}; + +test('sanitizes zipcode fields on init', () => { + const {elements} = setupEnvironment(' 12345 ', ' 98765 '); + + assert.equal(elements['#plz'].value, '12345'); + assert.equal(elements['#rechnung_plz'].value, '98765'); +}); + +test('sanitizes zipcode fields on blur', () => { + const {elements, $} = setupEnvironment('12345 ', '98765 '); + + $(elements['#plz']).val(' 54321 ').trigger('blur'); + $(elements['#rechnung_plz']).val(' 56789 ').trigger('blur'); + + assert.equal(elements['#plz'].value, '54321'); + assert.equal(elements['#rechnung_plz'].value, '56789'); +});