Compare commits

...

9 Commits

Author SHA1 Message Date
028b9c0f4c + Caching manually expire
All checks were successful
Build / Build NPM Project (push) Successful in 41s
Build / Tag Version (push) Successful in 8s
Build / Publish Documentation (push) Successful in 36s
2025-05-12 20:29:29 -04:00
d938996a66 + Caching expiry strategies
Some checks failed
Build / Build NPM Project (push) Failing after 34s
Build / Publish Documentation (push) Has been skipped
Build / Tag Version (push) Has been skipped
+ Prefix PathEvents
2025-05-12 19:46:23 -04:00
cdcaeda67c + Added writable
All checks were successful
Build / Build NPM Project (push) Successful in 40s
Build / Tag Version (push) Successful in 8s
Build / Publish Documentation (push) Successful in 35s
2025-05-12 18:00:12 -04:00
482c90b53b + Added writable
All checks were successful
Build / Build NPM Project (push) Successful in 1m13s
Build / Tag Version (push) Successful in 15s
Build / Publish Documentation (push) Successful in 49s
2025-05-12 16:28:10 -04:00
7500ba502f + Added clear function to ASet
All checks were successful
Build / Build NPM Project (push) Successful in 44s
Build / Tag Version (push) Successful in 8s
Build / Publish Documentation (push) Successful in 37s
* fixed pathedEvent fullPath casing
2025-05-11 14:13:01 -04:00
edc059d17d Added var-persist
All checks were successful
Build / Build NPM Project (push) Successful in 37s
Build / Tag Version (push) Successful in 8s
Build / Publish Documentation (push) Successful in 35s
2025-05-11 11:55:40 -04:00
48cfbee46e Removed test
All checks were successful
Build / Build NPM Project (push) Successful in 1m8s
Build / Tag Version (push) Successful in 14s
Build / Publish Documentation (push) Successful in 53s
2025-05-11 11:50:08 -04:00
26cc18ffb3 Fixed path event, renamed testCondition to logicTest & fixed some tests
Some checks failed
Build / Build NPM Project (push) Failing after 44s
Build / Publish Documentation (push) Has been skipped
Build / Tag Version (push) Has been skipped
2025-05-11 11:46:03 -04:00
3fd5c5ed57 Added sync function runner
All checks were successful
Build / Build NPM Project (push) Successful in 42s
Build / Tag Version (push) Successful in 7s
Build / Publish Documentation (push) Successful in 41s
2025-05-06 19:52:32 -04:00
13 changed files with 203 additions and 185 deletions

View File

@ -1,104 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>About Us | OurTrainingRoom</title>
<style>
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #fdfdfd;
color: #333;
line-height: 1.6;
}
header {
background: #004080;
color: #fff;
padding: 2rem 1rem;
text-align: center;
}
header h1 {
margin: 0;
font-size: 2.5rem;
}
main {
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}
section {
margin-bottom: 2rem;
}
h2 {
color: #004080;
margin-bottom: 0.5rem;
}
ul {
padding-left: 1.25rem;
}
footer {
text-align: center;
font-size: 0.9rem;
color: #777;
padding: 2rem 1rem;
background: #f1f1f1;
margin-top: 4rem;
}
</style>
</head>
<body>
<header>
<h1>About Us</h1>
<p>Empowering Learning Through Innovation</p>
</header>
<main>
<section>
<p>
E-learning has evolved significantly since its inception. Today, there's a shift towards
blended learning services, integrating online activities with practical, real-world applications.
</p>
</section>
<section>
<h2>What We Do</h2>
<p>At <strong>OurTrainingRoom.com</strong>, we specialize in content management and professional development training tailored for:</p>
<ul>
<li>School Boards</li>
<li>Municipalities</li>
<li>Hospitals</li>
<li>Large Corporations</li>
</ul>
</section>
<section>
<h2>Our Roots</h2>
<p>
Our parent company, <strong>The Auxilium Group</strong>, is a leader in online data management.
The formation of OurTrainingRoom.com was a natural progression to deliver state-of-the-art front-end e-learning programs.
</p>
</section>
<section>
<h2>Our Approach</h2>
<p>
Built on principles of quality and continuous improvement, our diverse delivery range continues to grow.
We set new trends by enhancing our existing products and attentively listening to our clients and their employees.
This unique approach has solidified our position in the industry, making a substantial impact for our clients.
</p>
</section>
<section>
<h2>Have a Question?</h2>
<p>
We value your inquiries and are here to assist you. Please reach out with any questions or feedback.
</p>
</section>
</main>
<footer>
&copy; 2025 OurTrainingRoom.com. All rights reserved.
</footer>
</body>
</html>

View File

@ -1,6 +1,6 @@
{ {
"name": "@ztimson/utils", "name": "@ztimson/utils",
"version": "0.24.1", "version": "0.24.9",
"description": "Utility library", "description": "Utility library",
"author": "Zak Timson", "author": "Zak Timson",
"license": "MIT", "license": "MIT",
@ -26,6 +26,9 @@
"test:coverage": "npx jest --coverage", "test:coverage": "npx jest --coverage",
"watch": "npx vite build --watch" "watch": "npx vite build --watch"
}, },
"dependencies": {
"var-persist": "^1.0.1"
},
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"jest": "^29.7.0", "jest": "^29.7.0",

View File

@ -29,6 +29,14 @@ export class ASet<T> extends Array {
return this; return this;
} }
/**
* Remove all elements
*/
clear() {
this.splice(0, this.length);
return this;
}
/** /**
* Delete elements from set * Delete elements from set
* @param items Elements that will be deleted * @param items Elements that will be deleted

View File

@ -7,16 +7,20 @@ export type CacheOptions = {
storage?: Storage; storage?: Storage;
/** Key cache will be stored under */ /** Key cache will be stored under */
storageKey?: string; storageKey?: string;
/** Keep or delete cached items once expired, defaults to delete */
expiryPolicy?: 'delete' | 'keep';
} }
export type CachedValue<T> = T | {_expired?: boolean};
/** /**
* Map of data which tracks whether it is a complete collection & offers optional expiry of cached values * Map of data which tracks whether it is a complete collection & offers optional expiry of cached values
*/ */
export class Cache<K extends string | number | symbol, T> { export class Cache<K extends string | number | symbol, T> {
private store = <Record<K, T>>{}; private store: Record<K, T> = <any>{};
/** Support index lookups */ /** Support index lookups */
[key: string | number | symbol]: T | any; [key: string | number | symbol]: CachedValue<T> | any;
/** Whether cache is complete */ /** Whether cache is complete */
complete = false; complete = false;
@ -38,7 +42,7 @@ export class Cache<K extends string | number | symbol, T> {
return new Proxy(this, { return new Proxy(this, {
get: (target: this, prop: string | symbol) => { get: (target: this, prop: string | symbol) => {
if(prop in target) return (target as any)[prop]; if(prop in target) return (target as any)[prop];
return deepCopy(target.store[prop as K]); return this.get(prop as K, true);
}, },
set: (target: this, prop: string | symbol, value: any) => { set: (target: this, prop: string | symbol, value: any) => {
if(prop in target) (target as any)[prop] = value; if(prop in target) (target as any)[prop] = value;
@ -53,12 +57,18 @@ export class Cache<K extends string | number | symbol, T> {
return <K>value[this.key]; return <K>value[this.key];
} }
private save() {
if(this.options.storageKey && this.options.storage)
this.options.storage.setItem(this.options.storageKey, JSON.stringify(this.store));
}
/** /**
* Get all cached items * Get all cached items
* @return {T[]} Array of items * @return {T[]} Array of items
*/ */
all(): T[] { all(expired?: boolean): CachedValue<T>[] {
return deepCopy(Object.values(this.store)); return deepCopy<any>(Object.values(this.store)
.filter((v: any) => expired || !v._expired));
} }
/** /**
@ -90,7 +100,8 @@ export class Cache<K extends string | number | symbol, T> {
* Remove all keys from cache * Remove all keys from cache
*/ */
clear() { clear() {
this.store = <Record<K, T>>{}; this.complete = false;
this.store = <any>{};
} }
/** /**
@ -99,16 +110,26 @@ export class Cache<K extends string | number | symbol, T> {
*/ */
delete(key: K) { delete(key: K) {
delete this.store[key]; delete this.store[key];
if(this.options.storageKey && this.options.storage) this.save();
this.options.storage.setItem(this.options.storageKey, JSON.stringify(this.store));
} }
/** /**
* Return cache as an array of key-value pairs * Return cache as an array of key-value pairs
* @return {[K, T][]} Key-value pairs array * @return {[K, T][]} Key-value pairs array
*/ */
entries(): [K, T][] { entries(expired?: boolean): [K, CachedValue<T>][] {
return <[K, T][]>Object.entries(this.store); return deepCopy<any>(Object.entries(this.store)
.filter((v: any) => expired || !v._expired));
}
/**
* Manually expire a cached item
* @param {K} key Key to expire
*/
expire(key: K) {
this.complete = false;
if(this.options.expiryPolicy == 'keep') (<any>this.store[key])._expired = true;
else this.delete(key);
} }
/** /**
@ -116,24 +137,31 @@ export class Cache<K extends string | number | symbol, T> {
* @param {K} key Key to lookup * @param {K} key Key to lookup
* @return {T} Cached item * @return {T} Cached item
*/ */
get(key: K): T { get(key: K, expired?: boolean): T | null {
return deepCopy(this.store[key]); const cached = deepCopy<any>(this.store[key] ?? null);
if(expired || !cached._expired) return cached;
return null;
} }
/** /**
* Get a list of cached keys * Get a list of cached keys
* @return {K[]} Array of keys * @return {K[]} Array of keys
*/ */
keys(): K[] { keys(expired?: boolean): K[] {
return <K[]>Object.keys(this.store); return <K[]>Object.keys(this.store)
.filter(k => expired || !(<any>this.store)[k]._expired);
} }
/** /**
* Get map of cached items * Get map of cached items
* @return {Record<K, T>} * @return {Record<K, T>}
*/ */
map(): Record<K, T> { map(expired?: boolean): Record<K, CachedValue<T>> {
return deepCopy(this.store); const copy: any = deepCopy(this.store);
if(!expired) Object.keys(copy).forEach(k => {
if(copy[k]._expired) delete copy[k]
});
return copy;
} }
/** /**
@ -144,12 +172,12 @@ export class Cache<K extends string | number | symbol, T> {
* @return {this} * @return {this}
*/ */
set(key: K, value: T, ttl = this.options.ttl): this { set(key: K, value: T, ttl = this.options.ttl): this {
if(this.options.expiryPolicy == 'keep') delete (<any>this.store[key])._expired;
this.store[key] = value; this.store[key] = value;
if(this.options.storageKey && this.options.storage) this.save();
this.options.storage.setItem(this.options.storageKey, JSON.stringify(this.store));
if(ttl) setTimeout(() => { if(ttl) setTimeout(() => {
this.complete = false; this.expire(key);
this.delete(key); this.save();
}, ttl * 1000); }, ttl * 1000);
return this; return this;
} }

View File

@ -19,3 +19,4 @@ export * from './search';
export * from './string'; export * from './string';
export * from './time'; export * from './time';
export * from './types'; export * from './types';
export * from 'var-persist';

View File

@ -5,11 +5,12 @@ import {md5} from './string';
* Run a stringified function with arguments asynchronously * Run a stringified function with arguments asynchronously
* @param {object} args Map of key/value arguments * @param {object} args Map of key/value arguments
* @param {string} fn Function as string * @param {string} fn Function as string
* @return {Promise<T>} Function string response * @param {boolean} async Run with async (returns a promise)
* @return {T | Promise<T>} Function return result
*/ */
export function asyncFunction<T>(args: object, fn: string): Promise<T> { export function fn<T>(args: object, fn: string, async: boolean = false): T {
const keys = Object.keys(args); const keys = Object.keys(args);
return new Function(...keys, `return (async (${keys.join(',')}) => { ${fn} })(${keys.join(',')})`)(...keys.map(k => (<any>args)[k])); return new Function(...keys, `return (${async ? 'async ' : ''}(${keys.join(',')}) => { ${fn} })(${keys.join(',')})`)(...keys.map(k => (<any>args)[k]));
} }
/** /**

View File

@ -31,7 +31,7 @@ export function PE(str: TemplateStringsArray, ...args: any[]) {
if(str[i]) combined.push(str[i]); if(str[i]) combined.push(str[i]);
if(args[i]) combined.push(args[i]); if(args[i]) combined.push(args[i]);
} }
return new PathEvent(combined.join('')); return new PathEvent(combined.join('/'));
} }
/** /**
@ -79,20 +79,20 @@ export class PathEvent {
set none(v: boolean) { v ? this.methods = new ASet<Method>(['n']) : this.methods.delete('n'); } set none(v: boolean) { v ? this.methods = new ASet<Method>(['n']) : this.methods.delete('n'); }
/** Create method specified */ /** Create method specified */
get create(): boolean { return !this.methods.has('n') && (this.methods.has('*') || this.methods.has('c')) } get create(): boolean { return !this.methods.has('n') && (this.methods.has('*') || this.methods.has('c')) }
set create(v: boolean) { v ? this.methods.delete('n').add('c') : this.methods.delete('c'); } set create(v: boolean) { v ? this.methods.delete('n').delete('*').add('c') : this.methods.delete('c'); }
/** Read method specified */ /** Read method specified */
get read(): boolean { return !this.methods.has('n') && (this.methods.has('*') || this.methods.has('r')) } get read(): boolean { return !this.methods.has('n') && (this.methods.has('*') || this.methods.has('r')) }
set read(v: boolean) { v ? this.methods.delete('n').add('r') : this.methods.delete('r'); } set read(v: boolean) { v ? this.methods.delete('n').delete('*').add('r') : this.methods.delete('r'); }
/** Update method specified */ /** Update method specified */
get update(): boolean { return !this.methods.has('n') && (this.methods.has('*') || this.methods.has('u')) } get update(): boolean { return !this.methods.has('n') && (this.methods.has('*') || this.methods.has('u')) }
set update(v: boolean) { v ? this.methods.delete('n').add('u') : this.methods.delete('u'); } set update(v: boolean) { v ? this.methods.delete('n').delete('*').add('u') : this.methods.delete('u'); }
/** Delete method specified */ /** Delete method specified */
get delete(): boolean { return !this.methods.has('n') && (this.methods.has('*') || this.methods.has('d')) } get delete(): boolean { return !this.methods.has('n') && (this.methods.has('*') || this.methods.has('d')) }
set delete(v: boolean) { v ? this.methods.delete('n').add('d') : this.methods.delete('d'); } set delete(v: boolean) { v ? this.methods.delete('n').delete('*').add('d') : this.methods.delete('d'); }
constructor(Event: string | PathEvent) { constructor(e: string | PathEvent) {
if(typeof Event == 'object') return Object.assign(this, Event); if(typeof e == 'object') return Object.assign(this, e);
let [p, scope, method] = Event.replaceAll(/\/{2,}/g, '/').split(':'); let [p, scope, method] = e.replaceAll(/\/{2,}/g, '/').split(':');
if(!method) method = scope || '*'; if(!method) method = scope || '*';
if(p == '*' || !p && method == '*') { if(p == '*' || !p && method == '*') {
p = ''; p = '';
@ -100,8 +100,8 @@ export class PathEvent {
} }
let temp = p.split('/').filter(p => !!p); let temp = p.split('/').filter(p => !!p);
this.module = temp.splice(0, 1)[0]?.toLowerCase() || ''; this.module = temp.splice(0, 1)[0]?.toLowerCase() || '';
this.fullPath = p;
this.path = temp.join('/'); this.path = temp.join('/');
this.fullPath = `${this.module}${this.module && this.path ? '/' : ''}${this.path}`;
this.name = temp.pop() || ''; this.name = temp.pop() || '';
this.methods = new ASet(<any>method.split('')); this.methods = new ASet(<any>method.split(''));
} }
@ -206,7 +206,7 @@ export class PathEvent {
* @return {string} String representation of Event * @return {string} String representation of Event
*/ */
static toString(path: string | string[], methods: Method | Method[]): string { static toString(path: string | string[], methods: Method | Method[]): string {
let p = makeArray(path).filter(p => p != null).join('/'); let p = makeArray(path).filter(p => !!p).join('/');
p = p?.trim().replaceAll(/\/{2,}/g, '/').replaceAll(/(^\/|\/$)/g, ''); p = p?.trim().replaceAll(/\/{2,}/g, '/').replaceAll(/(^\/|\/$)/g, '');
if(methods?.length) p += `:${makeArray(methods).map(m => m.toLowerCase()).join('')}`; if(methods?.length) p += `:${makeArray(methods).map(m => m.toLowerCase()).join('')}`;
return p; return p;
@ -235,11 +235,12 @@ export class PathEvent {
export type PathListener = (event: PathEvent, ...args: any[]) => any; export type PathListener = (event: PathEvent, ...args: any[]) => any;
export type PathUnsubscribe = () => void; export type PathUnsubscribe = () => void;
export type Event = string | PathEvent;
export interface IPathEventEmitter { export interface IPathEventEmitter {
emit(event: string, ...args: any[]): void; emit(event: Event, ...args: any[]): void;
off(listener: PathListener): void; off(listener: PathListener): void;
on(event: string, listener: PathListener): PathUnsubscribe; on(event: Event | Event[], listener: PathListener): PathUnsubscribe;
once(event: string, listener?: PathListener): Promise<any>; once(event: Event | Event[], listener?: PathListener): Promise<any>;
relayEvents(emitter: PathEventEmitter): void; relayEvents(emitter: PathEventEmitter): void;
} }
@ -249,8 +250,10 @@ export interface IPathEventEmitter {
export class PathEventEmitter implements IPathEventEmitter{ export class PathEventEmitter implements IPathEventEmitter{
private listeners: [PathEvent, PathListener][] = []; private listeners: [PathEvent, PathListener][] = [];
emit(event: string | PathEvent, ...args: any[]) { constructor(public readonly prefix: string = '') { }
const parsed = new PathEvent(event);
emit(event: Event, ...args: any[]) {
const parsed = new PathEvent(`${this.prefix}/${typeof event == 'string' ? event : event.toString()}`);
this.listeners.filter(l => PathEvent.has(l[0], event)) this.listeners.filter(l => PathEvent.has(l[0], event))
.forEach(async l => l[1](parsed, ...args)); .forEach(async l => l[1](parsed, ...args));
}; };
@ -259,12 +262,15 @@ export class PathEventEmitter implements IPathEventEmitter{
this.listeners = this.listeners.filter(l => l[1] != listener); this.listeners = this.listeners.filter(l => l[1] != listener);
} }
on(event: string | string[], listener: PathListener): PathUnsubscribe { on(event: Event | Event[], listener: PathListener): PathUnsubscribe {
makeArray(event).forEach(e => this.listeners.push([new PathEvent(e), listener])); makeArray(event).forEach(e => this.listeners.push([
new PathEvent(`${this.prefix}/${typeof e == 'string' ? event : event.toString()}`),
listener
]));
return () => this.off(listener); return () => this.off(listener);
} }
once(event: string | string[], listener?: PathListener): Promise<any> { once(event: Event | Event[], listener?: PathListener): Promise<any> {
return new Promise(res => { return new Promise(res => {
const unsubscribe = this.on(event, (event: PathEvent, ...args: any[]) => { const unsubscribe = this.on(event, (event: PathEvent, ...args: any[]) => {
res(args.length < 2 ? args[0] : args); res(args.length < 2 ? args[0] : args);

View File

@ -1,4 +1,4 @@
import {dotNotation, JSONAttemptParse} from './objects.ts'; import {dotNotation, JSONAttemptParse, JSONSerialize} from './objects.ts';
export function search(rows: any[], search: string, regex?: boolean, transform: Function = (r: any) => r) { export function search(rows: any[], search: string, regex?: boolean, transform: Function = (r: any) => r) {
if(!rows) return []; if(!rows) return [];
@ -13,12 +13,18 @@ export function search(rows: any[], search: string, regex?: boolean, transform:
catch { return false; } catch { return false; }
}).length }).length
} else { } else {
return testCondition(search, r); return logicTest(r, search);
} }
}); });
} }
export function testCondition(condition: string, row: any) { /**
* Test an object against a logic condition. By default values are checked
* @param {string} condition
* @param {object} target
* @return {boolean}
*/
export function logicTest(target: object, condition: string): boolean {
const evalBoolean = (a: any, op: string, b: any): boolean => { const evalBoolean = (a: any, op: string, b: any): boolean => {
switch(op) { switch(op) {
case '=': case '=':
@ -40,11 +46,11 @@ export function testCondition(condition: string, row: any) {
// Boolean operator // Boolean operator
const prop = /(\S+)\s*(==?|!=|>=|>|<=|<)\s*(\S+)/g.exec(p); const prop = /(\S+)\s*(==?|!=|>=|>|<=|<)\s*(\S+)/g.exec(p);
if(prop) { if(prop) {
const key = Object.keys(row).find(k => k.toLowerCase() == prop[1].toLowerCase()); const key = Object.keys(target).find(k => k.toLowerCase() == prop[1].toLowerCase());
return evalBoolean(dotNotation<any>(row, key || prop[1]), prop[2], JSONAttemptParse(prop[3])); return evalBoolean(dotNotation<any>(target, key || prop[1]), prop[2], JSONAttemptParse(prop[3]));
} }
// Case-sensitive // Case-sensitive
const v = Object.values(row).map(v => typeof v == 'object' && v != null ? JSON.stringify(v) : v).join(''); const v = Object.values(target).map(JSONSerialize).join('');
if(/[A-Z]/g.test(condition)) return v.includes(p); if(/[A-Z]/g.test(condition)) return v.includes(p);
// Case-insensitive // Case-insensitive
return v.toLowerCase().includes(p); return v.toLowerCase().includes(p);

View File

@ -18,3 +18,10 @@
export function typeKeys<T extends object>() { export function typeKeys<T extends object>() {
return Object.keys(<T>{}) as Array<keyof T>; return Object.keys(<T>{}) as Array<keyof T>;
} }
/**
* Mark all properties as writable
*/
export type Writable<T> = {
-readonly [P in keyof T]: T[P]
};

View File

@ -1,36 +1,18 @@
import {sleep, parseUrl} from '../src'; import {fn} from '../src';
describe('Miscellanies Utilities', () => { describe('Miscellanies Utilities', () => {
describe('sleep', () => { describe('fn', () => {
test('wait until', async () => { test('async', async () => {
const wait = ~~(Math.random() * 500); const test = {a: Math.random()};
const time = new Date().getTime(); const resp = fn(test, 'return a;', true);
await sleep(wait); expect(resp instanceof Promise).toBeTruthy();
expect(new Date().getTime()).toBeGreaterThanOrEqual(time + wait); expect(await resp).toEqual(test['a']);
});
}); });
describe('urlParser', () => { test('sync', async () => {
test('localhost w/ port', () => { const test = {a: Math.random()};
const parsed = parseUrl('http://localhost:4200/some/path?q1=test1&q2=test2#frag'); const resp = fn(test, 'return a;3');
expect(parsed.protocol).toStrictEqual('http'); expect(resp).toEqual(test['a']);
expect(parsed.host).toStrictEqual('localhost:4200');
expect(parsed.domain).toStrictEqual('localhost');
expect(parsed.port).toStrictEqual(4200);
expect(parsed.path).toStrictEqual('/some/path');
expect(parsed.query).toStrictEqual({q1: 'test1', q2: 'test2'});
expect(parsed.fragment).toStrictEqual('frag');
});
test('advanced URL', () => {
const parsed = parseUrl('https://sub.domain.example.com/some/path?q1=test1&q2=test2#frag');
expect(parsed.protocol).toStrictEqual('https');
expect(parsed.host).toStrictEqual('sub.domain.example.com');
expect(parsed.domain).toStrictEqual('example.com');
expect(parsed.subdomain).toStrictEqual('sub.domain');
expect(parsed.path).toStrictEqual('/some/path');
expect(parsed.query).toStrictEqual({q1: 'test1', q2: 'test2'});
expect(parsed.fragment).toStrictEqual('frag');
}); });
}); });
}); });

43
tests/path-events.spec.ts Normal file
View File

@ -0,0 +1,43 @@
import {PathEvent} from '../src';
describe('Path Events', () => {
describe('malformed', () => {
test('starting slash', async () =>
expect(new PathEvent('/module').toString()).toEqual('module:*'));
test('trailing slash', async () =>
expect(new PathEvent('module/').toString()).toEqual('module:*'));
test('double slash', async () =>
expect(new PathEvent('module////path').toString()).toEqual('module/path:*'));
});
describe('methods', () => {
test('custom', async () => {
expect(new PathEvent('module:t').methods.includes('t')).toBeTruthy();
expect(new PathEvent('module:t').methods.includes('z')).toBeFalsy();
});
test('create', async () =>
expect(new PathEvent('module:crud').create).toBeTruthy());
test('read', async () =>
expect(new PathEvent('module:crud').read).toBeTruthy());
test('update', async () =>
expect(new PathEvent('module:crud').update).toBeTruthy());
test('delete', async () =>
expect(new PathEvent('module:crud').delete).toBeTruthy());
test('none', async () => {
const event = new PathEvent('module:n');
expect(event.none).toBeTruthy();
expect(event.create).toBeFalsy();
expect(event.read).toBeFalsy();
expect(event.update).toBeFalsy();
expect(event.delete).toBeFalsy()
});
test('wildcard', async () => {
const event = new PathEvent('module:*');
expect(event.none).toBeFalsy();
expect(event.create).toBeTruthy();
expect(event.read).toBeTruthy();
expect(event.update).toBeTruthy();
expect(event.delete).toBeTruthy()
});
});
});

View File

@ -1,4 +1,5 @@
import {matchAll, randomString, randomStringBuilder} from "../src"; import {matchAll, parseUrl, randomString, randomStringBuilder} from "../src";
describe('String Utilities', () => { describe('String Utilities', () => {
describe('randomString', () => { describe('randomString', () => {
@ -47,4 +48,28 @@ describe('String Utilities', () => {
test('using regex', () => expect(matchAll('fooBar fooBar FooBar', /fooBar/g).length).toBe(2)); test('using regex', () => expect(matchAll('fooBar fooBar FooBar', /fooBar/g).length).toBe(2));
test('using malformed regex', () => expect(() => matchAll('fooBar fooBar FooBar', /fooBar/)).toThrow()); test('using malformed regex', () => expect(() => matchAll('fooBar fooBar FooBar', /fooBar/)).toThrow());
}); });
describe('urlParser', () => {
test('localhost', () => {
const parsed = parseUrl('http://localhost:4200/some/path?q1=test1&q2=test2#frag');
expect(parsed.protocol).toStrictEqual('http');
expect(parsed.host).toStrictEqual('localhost:4200');
expect(parsed.domain).toStrictEqual('localhost');
expect(parsed.port).toStrictEqual(4200);
expect(parsed.path).toStrictEqual('/some/path');
expect(parsed.query).toStrictEqual({q1: 'test1', q2: 'test2'});
expect(parsed.fragment).toStrictEqual('frag');
});
test('subdomains', () => {
const parsed = parseUrl('https://sub.domain.example.com/some/path?q1=test1&q2=test2#frag');
expect(parsed.protocol).toStrictEqual('https');
expect(parsed.host).toStrictEqual('sub.domain.example.com');
expect(parsed.domain).toStrictEqual('example.com');
expect(parsed.subdomain).toStrictEqual('sub.domain');
expect(parsed.path).toStrictEqual('/some/path');
expect(parsed.query).toStrictEqual({q1: 'test1', q2: 'test2'});
expect(parsed.fragment).toStrictEqual('frag');
});
});
}); });

12
tests/time.spec.ts Normal file
View File

@ -0,0 +1,12 @@
import {sleep} from '../src';
describe('Time Utilities', () => {
describe('sleep', () => {
test('wait until', async () => {
const wait = ~~(Math.random() * 500);
const time = new Date().getTime();
await sleep(wait);
expect(new Date().getTime()).toBeGreaterThanOrEqual(time + wait);
});
});
});