12 Commits

Author SHA1 Message Date
35d2ef8580 Remove package lock npm file
Some checks failed
JavaScriptUtilsVitest / ci-tests (push) Has been cancelled
2025-11-27 18:01:12 +09:00
5b69cecf9f Update isObject and isIterable, isArray
Some checks failed
JavaScriptUtilsVitest / ci-tests (push) Has been cancelled
isObject only returns true for objects and nothing else.
Update cel/ael in the HTML builder to use isArray for array check
2025-11-27 17:29:03 +09:00
ef8b4e04ce Add more URL parse helper functions, update Readme file
All checks were successful
JavaScriptUtilsVitest / ci-tests (push) Successful in 22s
`npm build` is deprecated it is now `npm run`

Add more url parser
- set/remove entry from URL
- get single entry from URL alt version
- check if url has key entry
2025-05-29 11:21:12 +09:00
082cb761c0 Action box update with change from keyInObject to objectKeyExists
All checks were successful
JavaScriptUtilsVitest / ci-tests (push) Successful in 18s
2025-05-09 15:19:51 +09:00
0968085084 Add jsdom and set environment to jsdom for future browser based testing
All checks were successful
JavaScriptUtilsVitest / ci-tests (push) Successful in 18s
2025-03-10 20:05:26 +09:00
a629482a20 Change the ci names
All checks were successful
JavaScriptUtilsVitest / ci-tests (push) Successful in 15s
2025-03-10 19:53:26 +09:00
c818e56f3e Test fixes for UrlParser, HtmlElementCreator
All checks were successful
CI / ci-tests (push) Successful in 15s
Also fix old parseQueryString and alias it to getQueryStringParam
2025-03-10 19:33:44 +09:00
41e794b35d Node vesion 23 for github actions
All checks were successful
CI / ci-tests (push) Successful in 23s
2025-03-10 18:56:43 +09:00
1a3faad442 Github actions
All checks were successful
CI / ci-tests (push) Successful in 1m13s
2025-03-10 18:54:51 +09:00
f9d771f079 Add coverage report and basic vitest settings
Set tests folder and coverage settings, update run commands for tests
2025-03-10 17:29:07 +09:00
df94f864cf Readme update with covered tests 2025-03-10 17:07:27 +09:00
2f9333da54 Add testing for HtmlElementCreator and fix ...args calls
All args passthrough class where missing the leading "..."

Added Testing to HtmlElementCreator
2025-03-10 17:02:17 +09:00
27 changed files with 1251 additions and 2564 deletions

28
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: JavaScriptUtilsVitest
run-name: ${{ github.actor}} runs vitest checks
# on:
# push:
# branches:
# - main
# pull_request:
# branches:
# - main
# - staging
# - development
on: [push]
jobs:
ci-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 23
- name: Run `npm install`
run: |
npm install
- name: Run vitest and report issues
run: npm run test:run

2
.gitignore vendored
View File

@@ -1 +1,3 @@
node_modules/
coverage/*
package-lock.json

View File

@@ -100,19 +100,19 @@ Build with the following commands, the output will be stored in "build/js/output
For full not minified version
```sh
npm build utils-build
npm run utils-build
```
For minified build (but keeps the function names as is)
```sh
npm build utils-min-build
npm run utils-min-build
```
Or build both at the same time
```sh
npm build utils-build-all
npm run utils-build-all
```
## Develop
@@ -141,6 +141,18 @@ vitest is used for all usage tests
npx vitest
```
for one stop tests
```sh
npm run test:run
```
To show the full coverate (of all files)
```sh
npm run test:coverage
```
Currently covered:
- FormatBytes
@@ -149,6 +161,7 @@ Currently covered:
- UniqIdGenerators (Part)
- UrlParser
- JavaScriptHelpers
- HtmlElementCreator
TODO:

View File

@@ -4,12 +4,17 @@
<script type="text/javascript" src="js/general/jquery.min.js"></script>
<!-- <script type="text/javascript" src="js/general/translateTest-ja_JP.UTF-8.js"></script> -->
<script type="text/javascript" src="js/output/utils.min.js"></script>
<!-- <script type="text/javascript" src="js/output/utils.js"></script> -->
</head>
<body>
<div>
<h1>JavaScript Utils Test</h1>
<div id="test-div">
</div>
<div><hr></div>
<div id="build-test">
</div>
<div><hr></div>
</div>
</body>
<script languagae="JavaScript">
@@ -17,6 +22,7 @@
document.addEventListener('DOMContentLoaded', function() {
console.log('JavaScript Utils Test');
let el = document.getElementById('test-div');
let build_test = document.getElementById('build-test');
if (el === null) {
throw new Error("element test-div not found");
}
@@ -30,6 +36,27 @@ document.addEventListener('DOMContentLoaded', function() {
el.innerHTML += '<div>formatBytes: ' + formatBytes(bytes) + '</div>';
el.innerHTML += '<div>formatBytesLong: ' + formatBytesLong(bytes) + '</div>';
let cel_test = cel('div', 'sample-id', 'Some text', ['css-sample']);
el.innerHTML += '<div>cel: ' + JSON.stringify(cel_test) + '</div>';
let cel_out = phfo(cel_test);
console.log('CEL OUT: %o', cel_out);
build_test.innerHTML = cel_out;
//
let aelx_test = aelx(
cel('div', 'container-id', '', ['container-css']),
cel('div', 'entry-a-id', 'Entry block: A'),
cel('div', 'entry-b-id', 'Entry block: B')
);
el.innerHTML += '<div>aelx: ' + JSON.stringify(aelx_test) + '</div>';
let aelx_out = phfo(aelx_test);
console.log('AELX OUT: %o', aelx_out);
build_test.innerHTML += aelx_out;
// console.log("TEST URL: %o", parseQueryString("http://foor.org/?key=value"));
console.log("TEST URL: %o", getQueryStringParam('', "http://foor.org/?param=foo&param=other", true));
console.log("TEST URL: %o", parseQueryString("http://foor.org/?param=foo&param=other", ''));
// console.log("TEST URL: %o", getQueryStringParam('', "http://foor.org/?key=value&key=other&key=value&bar="));
// console.log('TR: %s', l10n.__('Original'));
// console.log('TR: %s', l10n.__('Not exists'));
});

2443
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,11 +4,13 @@
"main": "",
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"utils-min-build": "node_modules/esbuild/bin/esbuild utils.min=src/utils.mjs --outdir=build/js/output/ --format=esm --bundle --charset=utf8 --tree-shaking=false --minify-whitespace --minify-syntax --sourcemap",
"utils-build": "node_modules/esbuild/bin/esbuild utils=src/utils.mjs --outdir=build/js/output/ --format=esm --bundle --charset=utf8 --tree-shaking=false",
"utils-build-all": "npm run utils-min-build && npm run utils-build",
"utils-develop": "node_modules/esbuild/bin/esbuild utils=src/utils.mjs --outdir=build/js/output/ --format=esm --bundle --charset=utf8 --tree-shaking=false --watch"
"utils-develop": "node_modules/esbuild/bin/esbuild utils=src/utils.mjs --outdir=build/js/output/ --format=esm --bundle --charset=utf8 --tree-shaking=false --watch",
"utils-min-develop": "node_modules/esbuild/bin/esbuild utils.min=src/utils.mjs --outdir=build/js/output/ --format=esm --bundle --charset=utf8 --tree-shaking=false --minify-whitespace --minify-syntax --sourcemap --watch"
},
"author": "Clemens Schwaighofer",
"license": "",
@@ -16,9 +18,11 @@
"devDependencies": {
"@eslint/js": "^9.20.0",
"@types/jquery": "^3.5.32",
"@vitest/coverage-v8": "^3.0.8",
"esbuild": "^0.25.0",
"eslint": "^9.20.1",
"globals": "^15.15.0",
"jsdom": "^26.0.0",
"vitest": "^3.0.8"
}
}

View File

@@ -10,6 +10,8 @@ import {
isFunction as _isFunction,
executeFunctionByName as _executeFunctionByName,
isObject as _isObject,
isArray as _isArray,
isIterable as _isIterable,
getObjectCount as _getObjectCount,
keyInObject as _keyInObject,
getKeyByValue as _getKeyByValue,
@@ -61,7 +63,11 @@ import {
} from './utils/FormatBytes.mjs';
import {
parseQueryString as _parseQueryString,
getQueryStringParam as _getQueryStringParam
getQueryStringParam as _getQueryStringParam,
hasUrlParameter as _hasUrlParameter,
getUrlParameter as _getUrlParameter,
updateUrlParameter as _updateUrlParameter,
removeUrlParameter as _removeUrlParameter,
} from './utils/UrlParser.mjs';
import {
loginLogout as _loginLogout,
@@ -81,6 +87,11 @@ import { l10nTranslation } from './utils/l10nTranslation.mjs';
import { HtmlElementCreator } from './utils/HtmlElementCreator.mjs';
import { ActionBox } from './utils/ActionBox.mjs';
import { LoginNavMenu } from './utils/LoginNavMenu.mjs';
import {
isWebkit as _isWebkit,
isMobileWebKit as _isMobileWebKit,
isDesktopWebKit as _isDesktopWebKit
} from './utils/BrowserDetect.mjs';
let aiob = new ActionIndicatorOverlayBox();
let hec = new HtmlElementCreator();
@@ -197,7 +208,7 @@ function roundPrecision(number, prec) // eslint-disable-line no-unused-vars
// @ts-ignore
function formatString(string, ...args) // eslint-disable-line no-unused-vars
{
return _formatString(string, args);
return _formatString(string, ...args);
}
/**
@@ -433,8 +444,8 @@ function executeFunctionByName(functionName, context) // eslint-disable-line no-
/**
* checks if a variable is an object
* @param {any} val possible object
* @return {Boolean} true/false if it is an object or not
* @param {any} val possible object
* @return {Boolean} true/false if it is an object or not
*/
// @ts-ignore
function isObject(val) // eslint-disable-line no-unused-vars
@@ -442,6 +453,28 @@ function isObject(val) // eslint-disable-line no-unused-vars
return _isObject(val);
}
/**
* Check if variable is array
* @param {any} val possible array
* @returns {Boolean} true/false if it an array
*/
// @ts-ignore
function isArray(val) // eslint-disable-line no-unused-vars
{
return _isArray(val);
}
/**
* checks if a variable is an iterable
* @param {any} val possible iterable
* @return {Boolean} true/false if it is an object or not
*/
// @ts-ignore
function isIterable(val) // eslint-disable-line no-unused-vars
{
return _isIterable(val);
}
/**
* get the length of an object (entries)
* @param {Object} object object to check
@@ -775,7 +808,7 @@ function ael(base, attach, id = '') // eslint-disable-line no-unused-vars
// @ts-ignore
function aelx(base, ...attach) // eslint-disable-line no-unused-vars
{
return hec.aelx(base, attach);
return hec.aelx(base, ...attach);
}
/**
@@ -937,16 +970,14 @@ function html_options_refill(name, data, sort = '') // eslint-disable-line no-un
* ALTERNATIVE CODE
* var url = new URL(window.location.href);
* param_uid = url.searchParams.get('uid');
* @param {String} [query=''] the query string to parse
* if not set will auto fill
* @param {String} [return_key=''] if set only returns this key entry
* or empty for none
* @return {Object|String} parameter entry list
* @param {String} [query=''] the query string to parse, if not set will auto fill
* @param {String} [return_key=''] if set only returns this key entry or empty for none
* @param {Boolean} [single=false] if set to true then only the first found will be returned
*/
// @ts-ignore
function parseQueryString(query = '', return_key = '') // eslint-disable-line no-unused-vars
function parseQueryString(query = '', return_key = '', single = false) // eslint-disable-line no-unused-vars
{
return _parseQueryString(query, return_key);
return _parseQueryString(query, return_key, single);
}
/**
@@ -955,15 +986,12 @@ function parseQueryString(query = '', return_key = '') // eslint-disable-line no
* if a parameter is set several times it will be returned as an array
* if search parameter set and nothing found and empty string is returned
* if no parametes exist and no serach is set and empty object is returned
* @param {String} [search=''] if set searches for this entry, if empty
* all parameters are returned
* @param {String} [query=''] different query string to parse, if not
* set (default) the current window href is used
* @param {Boolean} [single=false] if set to true then only the first found
* will be returned
* @return {Object|Array|String} if search is empty, object, if search is set
* and only one entry, then string, else array
* unless single is true
* @param {String} [search=''] if set searches for this entry, if empty all parameters are returned
* @param {String} [query=''] different query string to parse, if not set (default) the current window href is used
* @param {Boolean} [single=false] if set to true then only the first found will be returned
* @return {Object|Array|String} if search is empty, object, if search is set
* and only one entry, then string, else array
* unless single is true
*/
// @ts-ignore
function getQueryStringParam(search = '', query = '', single = false) // eslint-disable-line no-unused-vars
@@ -971,6 +999,51 @@ function getQueryStringParam(search = '', query = '', single = false) // eslint-
return _getQueryStringParam(search, query, single);
}
/**
* Add or update a query parameter in the current URL and update the browser's address bar
* @param {string} key - The parameter name to add or update
* @param {string} value - The value to set for the parameter
* @param {boolean} [reload=false] - Whether to reload the page after updating the URL
*/
// @ts-ignore
function updateUrlParameter(key, value, reload = false) // eslint-disable-line no-unused-vars
{
return _updateUrlParameter(key, value, reload);
}
/**
* Remove a parameter from the current URL and update the browser's address bar
* @param {string} key - The parameter name to remove
* @param {boolean} [reload=false] - Whether to reload the page after updating the URL
*/
// @ts-ignore
function removeUrlParameter(key, reload = false) // eslint-disable-line no-unused-vars
{
return _removeUrlParameter(key, reload);
}
/**
* Check if key exists as URL parameter
* @param {String} key URL parameter to search
* @returns {Boolean} True if key exists
*/
// @ts-ignore
function hasUrlParameter(key) // eslint-disable-line no-unused-vars
{
return _hasUrlParameter(key);
}
/**
* Return one value for a URL paramter or null if not found
* @param {String} key Which URL parameter to get
* @returns {String|Null} URL parameter content
*/
// @ts-ignore
function getUrlParameter(key) // eslint-disable-line no-unused-vars
{
return _getUrlParameter(key);
}
// MARK: ACL LOGIN
// *** MASTER logout call
/**
@@ -1156,4 +1229,20 @@ function adjustActionBoxHeight(target_id = 'actionBox', override = 0, content_ov
ab.adjustActionBoxHeight(target_id, override, content_override);
}
// MARK: BROWSER DETECTION
function isWebkit() // eslint-disable-line no-unused-vars
{
return _isWebkit();
}
function isMobileWebKit() // eslint-disable-line no-unused-vars
{
return _isMobileWebKit();
}
function isDesktopWebKit() // eslint-disable-line no-unused-vars
{
return _isDesktopWebKit();
}
/* END */

View File

@@ -5,7 +5,7 @@ Creator: Clemens Schwaighofer
*/
export { ActionBox };
import { keyInObject, getObjectCount } from './JavaScriptHelpers.mjs';
import { objectKeyExists, getObjectCount } from './JavaScriptHelpers.mjs';
import { exists } from './DomHelpers.mjs';
import { setCenter, getWindowSize } from './ResizingAndMove.mjs';
@@ -155,7 +155,7 @@ class ActionBox {
}
// adjust zIndex so its above all other and set action box zindex +1
$('#overlayBox').show();
if (!keyInObject(target_id, this.zIndex.boxes)) {
if (!objectKeyExists(this.zIndex.boxes, target_id)) {
this.zIndex.boxes[target_id] = this.zIndex.max;
// increase by ten
this.zIndex.max += 10;
@@ -197,7 +197,7 @@ class ActionBox {
}
// clear storage object
if (
keyInObject(target_id, this.action_box_storage) && clean === true
objectKeyExists(this.action_box_storage, target_id) && clean === true
) {
this.action_box_storage[target_id] = {};
}
@@ -249,18 +249,18 @@ class ActionBox {
settings = {},
show_close = true
) {
if (!keyInObject(target_id, this.action_box_storage)) {
if (!objectKeyExists(this.action_box_storage, target_id)) {
this.action_box_storage[target_id] = {};
}
// settings can have the following
// : header_css:[]
// : action_box_css:[]
let header_css = [];
if (keyInObject('header_css', settings)) {
if (objectKeyExists(settings, 'header_css')) {
header_css = settings.header_css;
}
let action_box_css = [];
if (keyInObject('action_box_css', settings)) {
if (objectKeyExists(settings, 'action_box_css')) {
action_box_css = settings.action_box_css;
}
let elements = [];
@@ -288,7 +288,7 @@ class ActionBox {
// if we have header content, add that here
if (getObjectCount(headers) > 0) {
// if the element has an entry called "raw_string" then this does not need to be converted
if (keyInObject('raw_string', headers)) {
if (objectKeyExists(headers, 'raw_string')) {
elements.push(headers.raw_string);
} else {
elements.push(this.hec.phfo(headers));
@@ -297,7 +297,7 @@ class ActionBox {
// main content part (this should NOT be empty), if empty, add empty _content block
if (getObjectCount(contents) > 0) {
// if the element has an entry called "raw_string" then this does not need to be converted
if (keyInObject('raw_string', contents)) {
if (objectKeyExists(contents, 'raw_string')) {
elements.push(contents.raw_string);
} else {
elements.push(this.hec.phfo(contents));

View File

@@ -0,0 +1,31 @@
/*
Description: Browser detect
Date: 2025/11.25
Creator: Clemens Schwaighofer
*/
export { isWebkit, isMobileWebKit, isDesktopWebKit };
// Desktop Safari, all mobile WebKit browsers on iOS and webview iOS
function isWebkit()
{
return "GestureEvent" in window;
}
// all mobile webkit browsers and webview iOS
function isMobileWebKit()
{
return "ongesturechange" in window;
}
// Desktop Safari
function isDesktopWebKit()
{
return (
typeof window !== 'undefined' &&
'safari' in window &&
'pushNotification' in window.safari
);
}
// __END__

View File

@@ -1,6 +1,6 @@
/*
Description: Date Time functions
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/

View File

@@ -1,6 +1,6 @@
/*
Description: DOM Helpers
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/

View File

@@ -1,6 +1,6 @@
/*
Description: Byte string formatting
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/
@@ -19,6 +19,9 @@ function formatBytes(bytes)
if (typeof bytes === "bigint") {
bytes = Number(bytes);
}
if (isNaN(bytes)) {
return bytes.toString();
}
do {
bytes = bytes / 1024;
i++;

View File

@@ -1,6 +1,6 @@
/*
Description: DOM Management and HTML builder
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/
@@ -9,7 +9,7 @@ export {
// deprecated name
HtmlElementCreator as DomManagement
};
import { deepCopyFunction, isObject } from './JavaScriptHelpers.mjs';
import { deepCopyFunction, isObject, isArray } from './JavaScriptHelpers.mjs';
class HtmlElementCreator {
/**
@@ -26,7 +26,8 @@ class HtmlElementCreator {
return {
tag: tag,
id: id,
name: options.name, // override name if set [name gets ignored in tree build anyway]
// override name if set, else id is used. Only for input/button
name: options.name,
content: content,
css: css,
options: options,
@@ -46,11 +47,10 @@ class HtmlElementCreator {
if (id) {
// base id match already
if (base.id == id) {
// base.sub.push(Object.assign({}, attach));
base.sub.push(deepCopyFunction(attach));
} else {
// sub check
if (isObject(base.sub) && base.sub.length > 0) {
if (isArray(base.sub) && base.sub.length > 0) {
for (var i = 0; i < base.sub.length; i ++) {
// recursive call to sub element
this.ael(base.sub[i], attach, id);
@@ -58,7 +58,6 @@ class HtmlElementCreator {
}
}
} else {
// base.sub.push(Object.assign({}, attach));
base.sub.push(deepCopyFunction(attach));
}
return base;
@@ -74,7 +73,6 @@ class HtmlElementCreator {
aelx(base, ...attach)
{
for (var i = 0; i < attach.length; i ++) {
// base.sub.push(Object.assign({}, attach[i]));
base.sub.push(deepCopyFunction(attach[i]));
}
return base;
@@ -90,7 +88,6 @@ class HtmlElementCreator {
aelxar(base, attach)
{
for (var i = 0; i < attach.length; i ++) {
// base.sub.push(Object.assign({}, attach[i]));
base.sub.push(deepCopyFunction(attach[i]));
}
return base;
@@ -149,6 +146,7 @@ class HtmlElementCreator {
{
this.rcssel(_element, rcss);
this.acssel(_element, acss);
return _element;
}
/**
@@ -211,7 +209,7 @@ class HtmlElementCreator {
}
}
// second CSS
if (isObject(tree.css) && tree.css.length > 0) {
if (isArray(tree.css) && tree.css.length > 0) {
line += ' class="';
for (i = 0; i < tree.css.length; i ++) {
line += tree.css[i] + ' ';
@@ -236,7 +234,7 @@ class HtmlElementCreator {
// dive into sub tree to attach sub nodes
// NOTES: we can have content (text) AND sub nodes at the same level
// CONTENT (TEXT) takes preference over SUB NODE in order
if (isObject(tree.sub) && tree.sub.length > 0) {
if (isArray(tree.sub) && tree.sub.length > 0) {
if (tree.content) {
content.push(tree.content);
}

View File

@@ -1,6 +1,6 @@
/*
Description: HTML Helpers
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/

View File

@@ -1,12 +1,13 @@
/*
Description: JavaScript Helpers
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/
export {
errorCatch, isFunction, executeFunctionByName,
isObject, getObjectCount,
errorCatch, isFunction,
executeFunctionByName, runFunction, runFunctionArgsArray,
isObject, isIterable, isArray, getObjectCount,
keyInObject, objectKeyExists,
getKeyByValue, valueInObject, objectValueExists,
deepCopyFunction
@@ -77,6 +78,35 @@ function executeFunctionByName(functionName, context /*, args */)
return context[func].apply(context, args);
}
/**
* call a function by string
* call runFunctionArgArray
* @param {string} name Function name to call
* @param {Array} arguments all next function arguments are passed on as argument to the function
* @returns void
*/
function runFunction(name)
{
var args = Array.prototype.slice.call(arguments, 1);
runFunctionArgsArray(name, args);
}
/**
* call a function with a string, argumens as array
* @param {string} name Function name to call
* @param {array} args function arguments as arry
* @returns void
*/
function runFunctionArgsArray(name, args)
{
var fn = window[name];
if(typeof fn !== 'function') {
return;
}
fn.apply(window, args);
}
/**
* checks if a variable is an object
* @param {any} val possible object
@@ -84,10 +114,33 @@ function executeFunctionByName(functionName, context /*, args */)
*/
function isObject(val)
{
if (val === null) {
return false;
}
return ((typeof val === 'function') || (typeof val === 'object'));
return val !== null && typeof val === 'object' && !Array.isArray(val);
}
/**
* Check if variable is array
* @param {any} val possible array
* @returns {Boolean} true/false if it an array
*/
function isArray(val)
{
return val !== null && Array.isArray(val);
}
/**
* Check if any iterable type
* @param {any} val possible iteratable object/array
* @returns {Boolean} true/false if it is iterable
*/
function isIterable(val)
{
if (val == null) return false;
// Check if it's iterable, but excldue strings
if (typeof val[Symbol.iterator] === 'function' && typeof val !== 'string') return true;
// Check if it's a plain object
return typeof val === 'object' && val.constructor === Object;
}
/**

View File

@@ -1,6 +1,6 @@
/*
Description: Login access and menu
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/

View File

@@ -1,6 +1,6 @@
/*
Description: Login access and menu
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/

View File

@@ -1,6 +1,6 @@
/*
Description: Resize and Move Javascript
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/

View File

@@ -1,6 +1,6 @@
/*
Description: String Helpers
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/

View File

@@ -1,59 +1,26 @@
/*
Description: HTML Helpers
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/
export { parseQueryString, getQueryStringParam };
import { keyInObject } from './JavaScriptHelpers.mjs';
export { parseQueryString, getQueryStringParam, hasUrlParameter, getUrlParameter, updateUrlParameter, removeUrlParameter };
/**
* NOTE: this original code was wrong, now using URL and parsing through
* getQueryStringParam
* parses a query string from window.location.search.substring(1)
* ALTERNATIVE CODE
* var url = new URL(window.location.href);
* param_uid = url.searchParams.get('uid');
* @param {String} [query=''] the query string to parse
* if not set will auto fill
* @param {String} [return_key=''] if set only returns this key entry
* or empty for none
* @return {Object|String} parameter entry list
* @param {String} [query=''] the query string to parse, if not set will auto fill
* @param {String} [return_key=''] if set only returns this key entry or empty for none
* @param {Boolean} [single=false] if set to true then only the first found will be returned
* @return {Object|String} parameter entry list
*/
function parseQueryString(query = '', return_key = '')
function parseQueryString(query = '', return_key = '', single = false)
{
if (!query) {
query = window.location.search.substring(1);
}
var vars = query.split('&');
var query_string = {};
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
var key = decodeURIComponent(pair[0]);
var value = decodeURIComponent(pair[1]);
// skip over run if there is nothing
if (!key || value === 'undefined') {
continue;
}
// If first entry with this name
if (typeof query_string[key] === 'undefined') {
query_string[key] = decodeURIComponent(value);
// If second entry with this name
} else if (typeof query_string[key] === 'string') {
var arr = [query_string[key], decodeURIComponent(value)];
query_string[key] = arr;
// If third or later entry with this name
} else {
query_string[key].push(decodeURIComponent(value));
}
}
if (return_key) {
if (keyInObject(return_key, query_string)) {
return query_string[return_key];
} else {
return '';
}
} else {
return query_string;
}
return getQueryStringParam(return_key, query, single);
}
/**
@@ -62,15 +29,12 @@ function parseQueryString(query = '', return_key = '')
* if a parameter is set several times it will be returned as an array
* if search parameter set and nothing found and empty string is returned
* if no parametes exist and no serach is set and empty object is returned
* @param {String} [search=''] if set searches for this entry, if empty
* all parameters are returned
* @param {String} [query=''] different query string to parse, if not
* set (default) the current window href is used
* @param {Boolean} [single=false] if set to true then only the first found
* will be returned
* @return {Object|Array|String} if search is empty, object, if search is set
* and only one entry, then string, else array
* unless single is true
* @param {String} [search=''] if set searches for this entry, if empty all parameters are returned
* @param {String} [query=''] different query string to parse, if not set (default) the current window href is used
* @param {Boolean} [single=false] if set to true then only the first found will be returned
* @return {Object|Array|String} if search is empty, object, if search is set
* and only one entry, then string, else array
* unless single is true
*/
function getQueryStringParam(search = '', query = '', single = false)
{
@@ -105,4 +69,74 @@ function getQueryStringParam(search = '', query = '', single = false)
return param;
}
/**
* Check if key exists as URL parameter
* @param {String} key URL parameter to search
* @returns {Boolean} True if key exists
*/
function hasUrlParameter(key)
{
var urlParams = new URLSearchParams(window.location.search);
return urlParams.has(key);
}
/**
* Return one value for a URL paramter or null if not found
* @param {String} key Which URL parameter to get
* @returns {String|Null} URL parameter content
*/
function getUrlParameter(key)
{
var urlParams = new URLSearchParams(window.location.search);
return urlParams.get(key);
}
/**
* Add or update a query parameter in the current URL and update the browser's address bar
* @param {string} key - The parameter name to add or update
* @param {string} value - The value to set for the parameter
* @param {boolean} [reload=false] - Whether to reload the page after updating the URL
*/
function updateUrlParameter(key, value, reload = false)
{
// Create a URL object from the current URL
const url = new URL(window.location.href);
// Set or update the parameter
url.searchParams.set(key, value);
const newUrl = url.toString();
// Update the browser's address bar without reloading the page
window.history.pushState({ path: newUrl }, '', newUrl);
// Optionally reload the page
if (reload) {
// window.location.href = newUrl;
window.location.reload();
}
}
/**
* Remove a parameter from the current URL and update the browser's address bar
* @param {string} key - The parameter name to remove
* @param {boolean} [reload=false] - Whether to reload the page after updating the URL
*/
function removeUrlParameter(key, reload = false)
{
// Create a URL object from the current URL
const url = new URL(window.location.href);
// Remove the parameter if it exists
url.searchParams.delete(key);
// Update the browser's address bar without reloading the page
window.history.pushState({}, '', url.toString());
// Optionally reload the page
if (reload) {
window.location.reload();
}
}
// __EMD__

View File

@@ -1,6 +1,6 @@
/*
Description: Translation call
Date: 2025//3/6
Date: 2025/3/6
Creator: Clemens Schwaighofer
*/

View File

@@ -7,6 +7,11 @@ import {
} from '../src/utils/FormatBytes.mjs';
let bytes_map = [
{
"in": "120MB",
"out": "120MB",
"out_l": "120MB",
},
{
"in": -123123123,
"out": "-120237.42kB",
@@ -57,6 +62,7 @@ describe("formatBytes", () => {
it('convert bytes to human readable, round up to next set', () => {
// expect(formatBytes(1021152)).toBe('0.97MB');
for (const bytes of bytes_map) {
// @ts-ignore
expect(formatBytes(bytes.in)).toBe(bytes.out);
}
});
@@ -66,6 +72,7 @@ describe("formatBytesLong", () => {
it('convert bytes to human readable, keep on current set', () => {
expect(formatBytesLong(1021152)).toBe('997.22 KB');
for (const bytes of bytes_map) {
// @ts-ignore
expect(formatBytesLong(bytes.in)).toBe(bytes.out_l);
}
});

View File

@@ -0,0 +1,693 @@
import { describe, it, expect } from "vitest";
import {
HtmlElementCreator
} from '../src/utils/HtmlElementCreator.mjs';
let hec = new HtmlElementCreator();
/*
A cel object
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": ""
};
*/
/**
* build cel block
* auto set default if undefined in the in object
* @param {Object} cel_in array for creating one entry
* @returns {Object}
*/
function celCreate(cel_in)
{
let cel_out;
if (cel_in.id === undefined) {
cel_out = hec.cel(cel_in.tag);
} else if (cel_in.content === undefined) {
cel_out = hec.cel(
cel_in.tag,
cel_in.id,
);
} else if (cel_in.css === undefined) {
cel_out = cel_out = hec.cel(
cel_in.tag,
cel_in.id,
cel_in.content,
);
} else if (cel_in.options === undefined) {
cel_out = cel_out = hec.cel(
cel_in.tag,
cel_in.id,
cel_in.content,
cel_in.css,
);
} else {
cel_out = hec.cel(
cel_in.tag,
cel_in.id,
cel_in.content,
cel_in.css,
cel_in.options
);
}
return cel_out;
}
describe("cel", () => {
it('should create HTML Element block and return an Object', () => {
// test list for various calls
let cel_list = [
{
"id": "tag only",
"in": {
"tag": "div",
},
"out": {
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
},
{
"id": "tag+id only",
"in": {
"tag": "div",
"id": "div-id",
},
"out": {
"content": "",
"css": [],
"id": "div-id",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
},
{
"id": "tag+id+content only",
"in": {
"tag": "div",
"id": "div-id",
"content": "text entry",
},
"out": {
"content": "text entry",
"css": [],
"id": "div-id",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
},
{
"id": "tag+id+content+css only",
"in": {
"tag": "div",
"id": "div-id",
"content": "text entry",
"css": ['css-a', 'css-b'],
},
"out": {
"content": "text entry",
"css": ['css-a', 'css-b'],
"id": "div-id",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
},
{
"id": "tag+id+content+css+options only",
"in": {
"tag": "div",
"id": "div-id",
"content": "text entry",
"css": ['css-a', 'css-b'],
"options": {"onclick": "doThis();"}
},
"out": {
"content": "text entry",
"css": ['css-a', 'css-b'],
"id": "div-id",
"name": undefined,
"options": {"onclick": "doThis();"},
"sub": [],
"tag": "div"
}
},
// various in random
{
"id": "only css",
"in": {
"tag": "div",
"id": "",
"content": "",
"css": ["abc"],
"options": undefined,
},
"out": {
"content": "",
"css": ['abc'],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
},
{
"id": "set name",
"in": {
"tag": "div",
"id": "foo-bar",
"content": "",
"css": [],
"options": {
"name": "baz"
},
},
"out": {
"content": "",
"css": [],
"id": "foo-bar",
"name": "baz",
"options": {"name": "baz"},
"sub": [],
"tag": "div"
}
},
];
for (const cel_entry of cel_list) {
expect(celCreate(cel_entry.in)).toEqual(cel_entry.out);
}
});
});
describe("ael", () => {
it('should attach one HTML Element block to another and return an Object', () => {
let ael_block = hec.ael(
hec.cel('div'),
hec.cel('div')
);
expect(ael_block).toEqual(
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
],
"tag": "div"
}
);
ael_block = hec.ael(
ael_block,
hec.cel('div')
);
expect(ael_block).toEqual(
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
},
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
],
"tag": "div"
}
);
let test_cel = hec.ael(
hec.cel('div', 'block-id'),
hec.cel('div', 'sub-id'),
);
expect(hec.ael(test_cel, hec.cel('div', 'new-block'), 'sub-id')).toEqual(
{
"content": "",
"css": [],
"id": "block-id",
"name": undefined,
"options": {},
"sub": [
{
"content": "",
"css": [],
"id": "sub-id",
"name": undefined,
"options": {},
"sub": [
{
"content": "",
"css": [],
"id": "new-block",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
],
"tag": "div"
}
],
"tag": "div"
}
);
});
});
describe("aelx", () => {
it('should attach one or more HTML Element block to another and return an Object', () => {
let ael_block = hec.aelx(
hec.cel('div'),
hec.cel('div')
);
expect(ael_block).toEqual(
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
],
"tag": "div"
}
);
ael_block = hec.aelx(
ael_block,
hec.cel('div'),
hec.cel('div'),
hec.cel('div')
);
expect(ael_block).toEqual(
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
},
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
},
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
},
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
},
],
"tag": "div"
}
);
});
});
describe("aelxar", () => {
it('should attach one or more HTML Element block arrays to another and return an Object', () => {
let ael_block = hec.aelxar(
hec.cel('div'),
[hec.cel('div')]
);
expect(ael_block).toEqual(
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
],
"tag": "div"
}
);
ael_block = hec.aelxar(
ael_block,
[
hec.cel('div'),
hec.cel('div'),
hec.cel('div')
]
);
expect(ael_block).toEqual(
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
},
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
},
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
},
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
},
],
"tag": "div"
}
);
});
});
describe("rel", () => {
it('should reset the sub entry array in an element and return an Object', () => {
let ael_block = hec.ael(
hec.cel('div'),
hec.cel('div')
);
expect(ael_block).toEqual(
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
],
"tag": "div"
}
);
let ael_removed = hec.rel(ael_block);
expect(ael_removed).toEqual(
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
);
});
});
// grouped CSS together
describe("rcssel/acssel/scssel", () => {
it('should remove/add/replace css entries in an element and return an object', () => {
let cel_entry = hec.cel('div');
let css_test = hec.acssel(cel_entry, 'foo');
expect(css_test).toEqual(
{
"content": "",
"css": ['foo'],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
);
css_test = hec.rcssel(cel_entry, 'foo');
expect(css_test).toEqual(
{
"content": "",
"css": [],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
);
css_test = hec.acssel(cel_entry, 'foo');
expect(css_test).toEqual(
{
"content": "",
"css": ['foo'],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
);
cel_entry = hec.scssel(cel_entry, 'foo', 'bar');
expect(css_test).toEqual(
{
"content": "",
"css": ['bar'],
"id": "",
"name": undefined,
"options": {},
"sub": [],
"tag": "div"
}
);
});
});
// render
describe("phfo", () => {
it('should render an object tree out to HTML', () => {
let cel_list = [
{
"id": "tag only",
"in": {
"tag": "div",
},
"out": '<div></div>'
},
{
"id": "tag+id only",
"in": {
"tag": "div",
"id": "div-id",
},
"out": '<div id="div-id"></div>'
},
{
"id": "tag+id+content only",
"in": {
"tag": "div",
"id": "div-id",
"content": "text entry",
},
"out": '<div id="div-id">text entry</div>'
},
{
"id": "tag+id+content+css only",
"in": {
"tag": "div",
"id": "div-id",
"content": "text entry",
"css": ['css-a', 'css-b'],
},
"out": '<div id="div-id" class="css-a css-b">text entry</div>'
},
{
"id": "tag+id+content+css+options only",
"in": {
"tag": "div",
"id": "div-id",
"content": "text entry",
"css": ['css-a', 'css-b'],
"options": {"onclick": "doThis();"}
},
"out": '<div id="div-id" class="css-a css-b" onclick="doThis();">text entry</div>'
},
// various in random
{
"id": "first",
"in": {
"tag": "div",
"id": "",
"content": "",
"css": ["abc"],
"options": undefined,
},
"out": '<div class="abc"></div>'
}
];
for (const cel_entry of cel_list) {
expect(hec.phfo(
celCreate(cel_entry.in)
)).toEqual(cel_entry.out);
}
// nested
expect(hec.phfo(
hec.ael(hec.cel('div', 'B1'),
hec.aelx(
hec.cel('div', 'A'),
hec.cel('div', 'T1'),
hec.cel('div', 'T2')
)
)
)).toEqual(
'<div id="B1"><div id="A"><div id="T1"></div><div id="T2"></div></div></div>'
);
});
});
describe("phfa", () => {
it('should render an array tree out to HTML', () => {
let el_list = [
hec.cel('div', 'A'),
hec.cel('div', 'B'),
hec.cel('div', 'C'),
];
expect(hec.phfa(el_list)).toEqual(
'<div id="A"></div><div id="B"></div><div id="C"></div>'
);
});
});
// build check
describe("phfo", () => {
it('should render an object tree out to HTML, extended', () => {
let cel_list = [
{
"id": "text input",
"in": hec.cel("input", 'text-id', '', ['abc']),
"out": '<input id="text-id" name="text-id" class="abc">'
},
{
"id": "text input, different name",
"in": hec.cel("input", 'text-id', '', ['abc'], {"name": "other-name"}),
"out": '<input id="text-id" name="other-name" class="abc">'
},
{
"id": "block test",
"in": hec.aelx(hec.cel('div', 'outer-id'),
hec.aelx(hec.cel('div', 'inner-id'),
hec.cel('input', 'some-id', '', ['abc'], {"type": "button", "onclick": "send()"}),
hec.cel('div', '', 'content')
)
),
"out": '<div id="outer-id"><div id="inner-id"><input id="some-id" name="some-id" class="abc" type="button" onclick="send()"><div>content</div></div></div>'
}
];
for (const cel_entry of cel_list) {
expect(hec.phfo(
cel_entry.in
)).toEqual(cel_entry.out);
}
});
});
// __END__

View File

@@ -5,6 +5,8 @@ import {
// isFunction,
// executeFunctionByName,
isObject,
isArray,
isIterable,
getObjectCount,
keyInObject,
objectKeyExists,
@@ -30,15 +32,57 @@ describe("isObject", () => {
let is_object = {
"a": 1
};
let empty_array = [];
let is_array = [1, 2, 3];
let is_string = "";
let is_number = 1;
expect(isObject(empty_object)).toEqual(true);
expect(isObject(is_object)).toEqual(true);
expect(isObject(empty_array)).toEqual(false);
expect(isObject(is_array)).toEqual(false);
expect(isObject(is_string)).toEqual(false);
expect(isObject(is_number)).toEqual(false);
});
});
describe("isArray", () => {
it('should return bool if array', () => {
let empty_object = {};
let is_object = {
"a": 1
};
let empty_array = [];
let is_array = [1, 2, 3];
let is_string = "";
let is_number = 1;
expect(isArray(empty_object)).toEqual(false);
expect(isArray(is_object)).toEqual(false);
expect(isArray(empty_array)).toEqual(true);
expect(isArray(is_array)).toEqual(true);
expect(isArray(is_string)).toEqual(false);
expect(isArray(is_number)).toEqual(false);
});
});
describe("isIterable", () => {
it('should return bool if iterable', () => {
let empty_object = {};
let is_object = {
"a": 1
};
let empty_array = [];
let is_array = [1, 2, 3];
let is_string = "";
let is_number = 1;
expect(isIterable(empty_object)).toEqual(true);
expect(isIterable(is_object)).toEqual(true);
expect(isIterable(empty_array)).toEqual(true);
expect(isIterable(is_array)).toEqual(true);
expect(isIterable(is_string)).toEqual(false);
expect(isIterable(is_number)).toEqual(false);
});
});
describe("getObjectCount", () => {
it('should return count of objects', () => {
let zero = {};

View File

@@ -24,6 +24,8 @@ describe("roundPrecision", () => {
it('should round numbers to a given precision', () => {
let val = roundPrecision(10.1234, 2);
expect(val).toBe(10.12);
// @ts-ignore
expect(roundPrecision("abc", 2)).toBe("abc");
});
});

View File

@@ -5,17 +5,99 @@ import {
getQueryStringParam,
} from '../src/utils/UrlParser.mjs';
describe("parseQueryString", () => {
it('Should parse query string for key', () => {
let kv = parseQueryString("http://foor.org?key=value");
expect(kv).toEqual({"http://foor.org?key": "value"});
});
});
let url_list = [
{
"id": "no params",
"in": {
"query": "http://foor.org",
"search": "",
"single": false
},
"out": {}
},
{
"id": "no params",
"in": {
"query": "http://foor.org/?param",
"search": "",
"single": false
},
"out": {"param": ""}
},
{
"id": "one params",
"in": {
"query": "http://foor.org/?param=foo",
"search": "",
"single": false
},
"out": {"param": "foo"}
},
{
"id": "two params",
"in": {
"query": "http://foor.org/?param=foo&bar=other",
"search": "",
"single": false
},
"out": {"param": "foo", "bar": "other"}
},
{
"id": "two params, select",
"in": {
"query": "http://foor.org/?param=foo&bar=other",
"search": "bar",
"single": false
},
"out": "other"
},
{
"id": "two params, same",
"in": {
"query": "http://foor.org/?param=foo&param=other",
"search": "",
"single": false
},
"out": {"param": ["foo", "other"]}
},
{
"id": "two params, same, single but no search",
"in": {
"query": "http://foor.org/?param=foo&param=other",
"search": "",
"single": true
},
// "out": {"param": ["foo", "other"]}
"out": {"param": "foo"}
},
{
"id": "three params, same, search",
"in": {
"query": "http://foor.org/?param=foo&param=other&different=1",
"search": "param",
"single": false
},
"out": ["foo", "other"]
},
];
describe("getQueryStringParam", () => {
it('Should parse query string for key', () => {
let kv = getQueryStringParam("key", "http://foor.org?key=value");
expect(kv).toEqual("value");
for (const url of url_list) {
expect(
getQueryStringParam(url.in.search, url.in.query, url.in.single)
).toEqual(url.out);
}
});
});
describe("parseQueryString", () => {
it('Should parse query string for key', () => {
// let kv = getQueryStringParam("key", "http://foor.org?key=value");
// expect(kv).toEqual("value");
for (const url of url_list) {
expect(
parseQueryString(url.in.query, url.in.search, url.in.single)
).toEqual(url.out);
}
});
});

20
vitest.config.js Normal file
View File

@@ -0,0 +1,20 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
include: [
'tests/**/*.{test,spec}.?(c|m)[jt]s?(x)'
],
// exclude: [],
coverage: {
include: [
'src/',
],
exclude: [
'src/utils.mjs',
]
}
},
});