From ef69cea268f1e42685213502a8c49190e9b4a398 Mon Sep 17 00:00:00 2001 From: Ignatius Bagus Date: Tue, 25 Apr 2023 08:29:31 +0700 Subject: [PATCH 1/7] chore: packages housekeeping (#225) --- package.json | 8 ++++---- pnpm-lock.yaml | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 27f299b8..7d2f69aa 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "clean": "git add * && git clean -dfx -e node_modules", "watch": "pnpm pub:build --watch", "check": "pnpm check:code && pnpm check:style", - "check:code": "tsc --noEmit --target ES2020", + "check:code": "pnpm pub:build --noEmit", "check:style": "prettier -c \"src/**/*.ts\"", "test": "pnpm test:unit", "test:unit": "uvu -r tsm src \"(spec\\.ts)\"", @@ -52,10 +52,10 @@ "settings" ], "devDependencies": { - "@types/node": "^18.15.11", - "prettier": "^2.8.7", + "@types/node": "^18.16.0", + "prettier": "^2.8.8", "tsm": "^2.3.0", - "typescript": "^5.0.2", + "typescript": "^5.0.4", "uvu": "^0.5.6" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d6c1268..1c3e4b54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,17 +2,17 @@ lockfileVersion: '6.0' devDependencies: '@types/node': - specifier: ^18.15.11 - version: 18.15.11 + specifier: ^18.16.0 + version: 18.16.0 prettier: - specifier: ^2.8.7 - version: 2.8.7 + specifier: ^2.8.8 + version: 2.8.8 tsm: specifier: ^2.3.0 version: 2.3.0 typescript: - specifier: ^5.0.2 - version: 5.0.2 + specifier: ^5.0.4 + version: 5.0.4 uvu: specifier: ^0.5.6 version: 0.5.6 @@ -37,8 +37,8 @@ packages: dev: true optional: true - /@types/node@18.15.11: - resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==} + /@types/node@18.16.0: + resolution: {integrity: sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==} dev: true /dequal@2.0.3: @@ -271,8 +271,8 @@ packages: engines: {node: '>=4'} dev: true - /prettier@2.8.7: - resolution: {integrity: sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==} + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true dev: true @@ -292,8 +292,8 @@ packages: esbuild: 0.15.18 dev: true - /typescript@5.0.2: - resolution: {integrity: sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==} + /typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} engines: {node: '>=12.20'} hasBin: true dev: true From 3ea7363843b3e7dbc681dfddced159f40f9f00a3 Mon Sep 17 00:00:00 2001 From: Ignatius Bagus Date: Tue, 2 May 2023 12:09:47 +0700 Subject: [PATCH 2/7] feat: implement `tsf` to `/std` (#221) --- CHANGELOG.md | 4 +++ src/std/README.md | 32 ++++++++++++++++++++++++ src/std/index.ts | 1 + src/std/tsf/index.spec.ts | 50 ++++++++++++++++++++++++++++++++++++++ src/std/tsf/index.test.ts | 51 +++++++++++++++++++++++++++++++++++++++ src/std/tsf/index.ts | 47 ++++++++++++++++++++++++++++++++++++ 6 files changed, 185 insertions(+) create mode 100644 src/std/tsf/index.spec.ts create mode 100644 src/std/tsf/index.test.ts create mode 100644 src/std/tsf/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e627575a..7e0f00e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # mauss changelog +## Unreleased + +- ([#221](https://github.com/alchemauss/mauss/pull/221)) add `tsf` function to `/std` module + ## 0.4.11 - 2023/03/30 - ([#216](https://github.com/alchemauss/mauss/pull/216)) remove deprecated option for TS 5.0 diff --git a/src/std/README.md b/src/std/README.md index 30ff3e78..241046dd 100644 --- a/src/std/README.md +++ b/src/std/README.md @@ -90,3 +90,35 @@ Original function, aggregates elements from each of the arrays and returns a sin ```typescript export function zip>(...arrays: T[]): Record[]; ``` + +## `tsf` + +A template string function. This takes a template string and returns a function that takes an object of functions, which is used to manipulate the name of the braces in the template string. + +This assumes the braces inside the template string are balanced and not nested. The function will not throw an error if the braces are not balanced, but the result will be unexpected. If you're using TypeScript and are passing a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types), it will point out any unbalanced braces by throwing an error from the compiler. + +```typescript +export function tsf( + template: string +): (table: { + [key: string]: string | false | Nullish | ((key: string) => string | false | Nullish); +}) => string; +``` + +The template string is parsed into an array of strings, which are then executed with the provided table of functions, which is an object with the key being the name of the braces from the template string, and the value being the function to manipulate the name of the braces. + +```javascript +import { tsf } from 'mauss/std'; + +const render = tsf('https://api.example.com/v1/{category}/{id}'); + +function publish({ category, id }) { + const prefix = // ... + const url = render({ + category: () => category !== 'new' && category, + id: (v) => prefix + uuid(`${v}-${id}`), + }); + + return fetch(url); +} +``` diff --git a/src/std/index.ts b/src/std/index.ts index faa5fe32..75b7c80f 100644 --- a/src/std/index.ts +++ b/src/std/index.ts @@ -1,2 +1,3 @@ export * as csv from './csv/index.js'; export * as ntv from './ntv/index.js'; +export { tsf } from './tsf/index.js'; diff --git a/src/std/tsf/index.spec.ts b/src/std/tsf/index.spec.ts new file mode 100644 index 00000000..a3544243 --- /dev/null +++ b/src/std/tsf/index.spec.ts @@ -0,0 +1,50 @@ +import { test } from 'uvu'; +import * as assert from 'uvu/assert'; +import { tsf } from './index.js'; + +test.skip('throws on nested braces', () => { + assert.throws(() => tsf('/{foo/{bar}}' as string)); + assert.throws(() => tsf('/{nested-{}-braces}' as string)); +}); + +test('parses template without braces', () => { + assert.equal(tsf('')({}), ''); + assert.equal(tsf('/')({}), '/'); + assert.equal(tsf('/foo')({}), '/foo'); +}); + +test('parses template correctly', () => { + const r1 = tsf('/{foo}/{bar}'); + + assert.equal( + r1({ + foo: 'hello', + bar: 'world', + }), + '/hello/world' + ); + assert.equal( + r1({ + foo: (v) => v, + bar: (v) => v, + }), + '/foo/bar' + ); + assert.equal( + r1({ + foo: (v) => [...v].reverse().join(''), + bar: (v) => [...v].reverse().join(''), + }), + '/oof/rab' + ); +}); + +test.skip('parses template with nested braces', () => { + const r1 = tsf('/{foo/{bar}}' as string); + assert.equal(r1({ 'foo/{bar}': (v) => v }), '/foo/{bar}'); + + const r2 = tsf('/{nested-{}-braces}' as string); + assert.equal(r2({ 'nested-{}-braces': (v) => v }), '/nested-{}-braces'); +}); + +test.run(); diff --git a/src/std/tsf/index.test.ts b/src/std/tsf/index.test.ts new file mode 100644 index 00000000..72a5ad10 --- /dev/null +++ b/src/std/tsf/index.test.ts @@ -0,0 +1,51 @@ +import { tsf } from './index.js'; + +tsf(''); +tsf('/'); +tsf('/')({}); +tsf('/{foo}')({ foo: (v) => v }); +tsf('/{foo}/{bar}')({ foo: 'hello', bar: () => 'world' }); +tsf('/{foo}/{bar}')({ foo: (v) => v, bar: (v) => v }); +tsf('/{foo}')({ foo: (v) => v.length > 1 && v.replace('o', 'u') }); +tsf('' as string)({ boo: (v) => v }); +tsf('' as `${string}/v1/posts/{id}/comments`)({ id: (v) => v }); + +// ---- errors ---- + +// @ts-expect-error +tsf('{}'); +// @ts-expect-error +tsf('/{}'); +// @ts-expect-error +tsf('{}/'); +// @ts-expect-error +tsf('/{{}}'); +// @ts-expect-error +tsf('/{}}'); +// @ts-expect-error +tsf('/{{}'); +// @ts-expect-error +tsf('/{{}{'); +// @ts-expect-error +tsf('/{{foo}}'); +// @ts-expect-error +tsf('/{{foo}{'); +// @ts-expect-error +tsf('/{}/{bar}'); +// @ts-expect-error +tsf('/{foo}/{{}}'); + +// @ts-expect-error +tsf('/')(); +// @ts-expect-error +tsf('/{foo}')(); +// @ts-expect-error +tsf('/{foo}/{bar}')({}); +// @ts-expect-error +tsf('/{foo}/{bar}')({ foo: (v) => v }); +// @ts-expect-error +tsf('/{foo}')({ foo: (v) => v, bar: (v) => v }); +// @ts-expect-error +tsf('' as `${string}/v1/posts/{id}/comments`)({}); + +tsf('{hello-world}')({ 'hello-world': (v) => v }); diff --git a/src/std/tsf/index.ts b/src/std/tsf/index.ts new file mode 100644 index 00000000..a34f0b66 --- /dev/null +++ b/src/std/tsf/index.ts @@ -0,0 +1,47 @@ +import { Nullish } from '../../typings/aliases.js'; +import { UnaryFunction } from '../../typings/helpers.js'; + +type Parse = T extends `${string}{${infer P}}${infer R}` ? P | Parse : never; +export function tsf( + template: Input extends + | `${string}{}${string}` + | `${string}{{${string}}}${string}` + | `${string}{{${string}}${string}` + | `${string}{${string}}}${string}` + ? never + : Input +) { + const parts: string[] = []; + for (let i = 0, start = 0; i < template.length; i += 1) { + if (template[i] === '{') { + if (i > start) parts.push(template.slice(start, i)); + + const end = template.indexOf('}', i); + if (end === -1 /** missing closing */) { + parts.push(template.slice(i)); + break; + } + + parts.push(template.slice(i + 1, end)); + start = (i = end) + 1; + } else if (i === template.length - 1) { + parts.push(template.slice(start)); + } + } + + type ConditionalString = string | false | Nullish; + type Replacer = ConditionalString | UnaryFunction; + type ExpectedProperties = string extends Input ? string : Parse; + return function render(table: { [K in ExpectedProperties]: Replacer }) { + let transformed = ''; + for (let i = 0; i < parts.length; i += 1) { + const replace = table[parts[i] as ExpectedProperties]; + if (typeof replace === 'function') { + transformed += replace(parts[i]) || ''; + } else { + transformed += replace || parts[i]; + } + } + return transformed; + }; +} From 96cd19bb508989bd1d2028c5b6aa1b8056b4b947 Mon Sep 17 00:00:00 2001 From: Ignatius Bagus Date: Tue, 2 May 2023 16:37:33 +0700 Subject: [PATCH 3/7] feat: add string guards (#224) --- CHANGELOG.md | 1 + src/guards/README.md | 8 ++++++++ src/guards/index.spec.ts | 11 +++++++++++ src/guards/index.ts | 10 ++++++++++ 4 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e0f00e6..59543b8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- ([#224](https://github.com/alchemauss/mauss/pull/224)) add string guards - ([#221](https://github.com/alchemauss/mauss/pull/221)) add `tsf` function to `/std` module ## 0.4.11 - 2023/03/30 diff --git a/src/guards/README.md b/src/guards/README.md index 5b4c058f..4aaab558 100644 --- a/src/guards/README.md +++ b/src/guards/README.md @@ -49,3 +49,11 @@ A utility guard that takes in any guards above and negates the result. For examp - `not(exists)` will return `true` if the input is nullish or an empty string - `not(natural)` will return `true` if the input exists or is a number less than or equal to 0. + +## `lowercase` + +A string guard that returns `true` if the input is a string with all lowercase characters. + +## `uppercase` + +A string guard that returns `true` if the input is a string with all uppercase characters. diff --git a/src/guards/index.spec.ts b/src/guards/index.spec.ts index a581af6a..3c405179 100644 --- a/src/guards/index.spec.ts +++ b/src/guards/index.spec.ts @@ -5,6 +5,7 @@ import * as guards from './index.js'; // checked based on https://developer.mozilla.org/en-US/docs/Glossary/Falsy const data = [true, false, 'a', 'b', 0, 1, 2, '', null, undefined, NaN]; const numbers = [-2, -1, 0, 1, 2, 3]; +const strings = ['a', 'A', 'b', 'B', 'c', 'C']; const basics = { guards: suite('guards'), @@ -36,6 +37,16 @@ basics.guards('filters numbers that are whole', () => { assert.equal(filtered, [0, 1, 2, 3]); }); +basics.guards('filters strings that are lowercase', () => { + const filtered = strings.filter(guards.lowercase); + assert.equal(filtered, ['a', 'b', 'c']); +}); + +basics.guards('filters strings that are uppercase', () => { + const filtered = strings.filter(guards.uppercase); + assert.equal(filtered, ['A', 'B', 'C']); +}); + // ---- not() suite ---- basics.inverse('filters values that does not exists', () => { diff --git a/src/guards/index.ts b/src/guards/index.ts index 4f21be4c..d8e6d738 100644 --- a/src/guards/index.ts +++ b/src/guards/index.ts @@ -36,3 +36,13 @@ export function not(fn: F) { type D = F extends typeof exists ? Primitives : F extends typeof nullish ? Nullish : Empty; return (i: T | D): i is T => !fn(i); } + +// string guards +/** @returns true if string input is all lowercase letters */ +export function lowercase(s: string): boolean { + return s === s.toLowerCase(); +} +/** @returns true if string input is all uppercase letters */ +export function uppercase(s: string): boolean { + return s === s.toUpperCase(); +} From 464c58d4934445aed0568d5aeb0df6740e3dd9a8 Mon Sep 17 00:00:00 2001 From: Ignatius Bagus Date: Tue, 2 May 2023 16:38:16 +0700 Subject: [PATCH 4/7] ~ v0.4.12 --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59543b8d..ee6bc247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # mauss changelog -## Unreleased +## 0.4.12 - 2023/05/02 - ([#224](https://github.com/alchemauss/mauss/pull/224)) add string guards - ([#221](https://github.com/alchemauss/mauss/pull/221)) add `tsf` function to `/std` module diff --git a/package.json b/package.json index 7d2f69aa..8cad66a1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mauss", "author": "Ignatius Bagus", "description": "fast and efficient type-safe SDK", - "version": "0.4.11", + "version": "0.4.12", "license": "MIT", "type": "module", "types": "./core/index.d.ts", From 7202af25ec88f7cae8169b2034397ee528e6bd27 Mon Sep 17 00:00:00 2001 From: Ignatius Bagus Date: Thu, 4 May 2023 16:11:18 +0800 Subject: [PATCH 5/7] feat: remove `create` from `ntv` (#223) --- CHANGELOG.md | 8 ++++++++ src/std/README.md | 8 -------- src/std/ntv/object.spec.ts | 8 -------- src/std/ntv/object.ts | 6 ------ 4 files changed, 8 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee6bc247..affa45f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # mauss changelog +## 0.5.0 - Unreleased + +- ([#223](https://github.com/alchemauss/mauss/pull/223)) remove `create` from `ntv` namespace + +### Breaking Changes + +- [#223](https://github.com/alchemauss/mauss/pull/223) | Removed `create` from `ntv` namespace, use array `.reduce` instead + ## 0.4.12 - 2023/05/02 - ([#224](https://github.com/alchemauss/mauss/pull/224)) add string guards diff --git a/src/std/README.md b/src/std/README.md index 241046dd..a27de2fb 100644 --- a/src/std/README.md +++ b/src/std/README.md @@ -35,14 +35,6 @@ export function clone(i: T): T; Creating a copy of a data type, especially an object, is useful for removing the reference to the original object, keeping it clean from unexpected changes and side effects. This is possible because we are creating a new instance, making sure that any mutation or changes that are applied won't affect one or the other. -### `ntv.create` - -```typescript -export function create(array: T[], i: any): { [K in T]: typeof i }; -``` - -Create an object with keys from an array of strings with the option to specify the initial value, defaulting to `null`. - ### `ntv.freeze` Augmented `Object.freeze()`, deep freezes and strongly-typed. diff --git a/src/std/ntv/object.spec.ts b/src/std/ntv/object.spec.ts index ea865e89..6499cb27 100644 --- a/src/std/ntv/object.spec.ts +++ b/src/std/ntv/object.spec.ts @@ -5,7 +5,6 @@ import * as ntv from './object.js'; const basics = { clone: suite('obj:clone'), - create: suite('obj:create'), entries: suite('obj:entries'), freeze: suite('obj:freeze'), iterate: suite('obj:iterate'), @@ -28,13 +27,6 @@ basics.clone('clone any possible data type', () => { assert.equal(base.obj.now, cloned.obj.now); }); -basics.create('create object from an array', () => { - const numbers = ntv.create([1, 2, 0]); - assert.equal(numbers, { 1: null, 2: null, 0: null }); - const vowels = ntv.create(['a', 'i', 'u', 'e', 'o'], ''); - assert.equal(vowels, { a: '', i: '', u: '', e: '', o: '' }); -}); - basics.entries('return object entries', () => { assert.equal(ntv.entries({ hello: 'world', foo: 0, bar: { baz: 1 } }), [ ['hello', 'world'], diff --git a/src/std/ntv/object.ts b/src/std/ntv/object.ts index eeca7158..5406d542 100644 --- a/src/std/ntv/object.ts +++ b/src/std/ntv/object.ts @@ -10,12 +10,6 @@ export function clone(i: T): T { return iterate(i) as T; } -export function create(array: T[], i: any = null) { - const object = {} as { [K in T]: typeof i }; - for (const key of array) object[key] = i; - return object; -} - export function entries(o: T) { return Object.entries(o) as Entries; } From b98f88380d2ad6ecc02e88199bc9c1f92bf26ca8 Mon Sep 17 00:00:00 2001 From: Ignatius Bagus Date: Fri, 5 May 2023 13:08:31 +0800 Subject: [PATCH 6/7] feat: merge `/utils` to `/core` (#222) --- CHANGELOG.md | 5 ++ src/core/README.md | 31 ++++++++ src/core/index.ts | 3 + src/{utils => core}/random/index.spec.ts | 0 src/{utils => core}/random/index.ts | 0 src/core/standard/index.spec.ts | 8 ++ src/core/standard/index.ts | 12 +++ src/{utils => core}/temporal/index.spec.ts | 0 src/{utils => core}/temporal/index.ts | 0 src/utils/README.md | 92 ---------------------- src/utils/index.spec.ts | 46 ----------- src/utils/index.test.ts | 26 ------ src/utils/index.ts | 36 --------- src/web/query/decoder.ts | 4 +- 14 files changed, 61 insertions(+), 202 deletions(-) rename src/{utils => core}/random/index.spec.ts (100%) rename src/{utils => core}/random/index.ts (100%) rename src/{utils => core}/temporal/index.spec.ts (100%) rename src/{utils => core}/temporal/index.ts (100%) delete mode 100644 src/utils/README.md delete mode 100644 src/utils/index.spec.ts delete mode 100644 src/utils/index.test.ts delete mode 100644 src/utils/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index affa45f9..803528dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,15 @@ ## 0.5.0 - Unreleased - ([#223](https://github.com/alchemauss/mauss/pull/223)) remove `create` from `ntv` namespace +- ([#222](https://github.com/alchemauss/mauss/pull/222)) remove `/utils` module ### Breaking Changes - [#223](https://github.com/alchemauss/mauss/pull/223) | Removed `create` from `ntv` namespace, use array `.reduce` instead +- [#222](https://github.com/alchemauss/mauss/pull/222) | Removed `/utils` namespace + - `dt` has been moved to core module + - `random` has been moved to core module + - `tryNumber` has been removed, use `Number.isNaN(Number(s)) ? s : Number(s)` ## 0.4.12 - 2023/05/02 diff --git a/src/core/README.md b/src/core/README.md index 0ae77dbb..7522f106 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -90,6 +90,37 @@ dSearch('mauss'); // execute after 500ms tSearch('mauss'); // execute every 500ms ``` +## `dt` + +Simple `date/time` (`dt`) utility namespace. + +```ts +type DateValue = string | number | Date; + +interface BuildOptions { + base?: 'UTC'; +} + +interface TravelOptions { + /** relative point of reference to travel */ + from?: DateValue; + /** relative days to travel in number */ + to: number; +} + +export const dt: { + current(d?: DateValue): Date; + build(options: BuildOptions): (date?: DateValue) => (mask?: string) => string; + format: ReturnType; + travel({ from, to }: TravelOptions): Date; +} +``` + +- `dt.current` is a function `(d?: DateValue) => Date` that optionally takes in a `DateValue` to be converted into a `Date` object, `new Date()` will be used if nothing is passed +- `dt.build` is a function that accepts `BuildOptions` and builds a formatter, a convenience export is included with all the default options as `dt.format` +- `dt.format` is a function that takes in a `DateValue` and returns a renderer that accepts a string mask to format the date in, defaults to `'DDDD, DD MMMM YYYY'` +- `dt.travel` is a function `({ from, to }) => Date` that takes in a `{ from, to }` object with `from` property being optional + ## `execute` ```ts diff --git a/src/core/index.ts b/src/core/index.ts index fa5dd649..cb2c5fce 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -2,6 +2,9 @@ export * from './processor/index.js'; export * from './standard/index.js'; export * as compare from './compare/index.js'; +export * as random from './random/index.js'; +export * as dt from './temporal/index.js'; + export { default as curry } from './lambda/curry.js'; export { default as pipe } from './lambda/pipe.js'; export { default as unique } from './standard/unique.js'; diff --git a/src/utils/random/index.spec.ts b/src/core/random/index.spec.ts similarity index 100% rename from src/utils/random/index.spec.ts rename to src/core/random/index.spec.ts diff --git a/src/utils/random/index.ts b/src/core/random/index.ts similarity index 100% rename from src/utils/random/index.ts rename to src/core/random/index.ts diff --git a/src/core/standard/index.spec.ts b/src/core/standard/index.spec.ts index 5bfe1696..689f7192 100644 --- a/src/core/standard/index.spec.ts +++ b/src/core/standard/index.spec.ts @@ -3,9 +3,17 @@ import * as assert from 'uvu/assert'; import * as std from './index.js'; const basics = { + capitalize: suite('std:capitalize'), identical: suite('std:identical'), }; +basics.capitalize('change one letter for one word', () => { + assert.equal(std.capitalize('hello'), 'Hello'); +}); +basics.capitalize('change two letter for two words', () => { + assert.equal(std.capitalize('hello world'), 'Hello World'); +}); + basics.identical('identical primitive checks', () => { // boolean assert.ok(std.identical(true, true)); diff --git a/src/core/standard/index.ts b/src/core/standard/index.ts index b9274490..60ac1dd7 100644 --- a/src/core/standard/index.ts +++ b/src/core/standard/index.ts @@ -1,6 +1,18 @@ import type { AlsoPromise } from '../../typings/extenders.js'; import type { AnyFunction, Reverse } from '../../typings/helpers.js'; +interface CapitalizeOptions { + /** only capitalize the very first letter */ + cap?: boolean; + /** convert the remaining word to lowercase */ + normalize?: boolean; +} +export function capitalize(text: string, { cap, normalize }: CapitalizeOptions = {}): string { + if (normalize) text = text.toLowerCase(); + if (cap) return `${text[0].toUpperCase()}${text.slice(1)}`; + return text.replace(/(?:^|\s)\S/g, (a) => a.toUpperCase()); +} + export function execute( condition: boolean, correct: () => AlsoPromise | AnyFunction<[]>, diff --git a/src/utils/temporal/index.spec.ts b/src/core/temporal/index.spec.ts similarity index 100% rename from src/utils/temporal/index.spec.ts rename to src/core/temporal/index.spec.ts diff --git a/src/utils/temporal/index.ts b/src/core/temporal/index.ts similarity index 100% rename from src/utils/temporal/index.ts rename to src/core/temporal/index.ts diff --git a/src/utils/README.md b/src/utils/README.md deleted file mode 100644 index 6d58ee3b..00000000 --- a/src/utils/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# mauss/utils - -```js -import { :util } from 'mauss/utils'; -``` - -## `capitalize` - -```ts -interface CapitalizeOptions { - /** only capitalize the very first letter */ - cap?: boolean; - /** convert the remaining word to lowercase */ - normalize?: boolean; -} - -export function capitalize(text: string, options?: CapitalizeOptions): string; -``` - -```js -capitalize('hi there'); // 'Hi There' -capitalize('hI thErE'); // 'HI ThErE' -capitalize('hI thErE', { cap: true }); // 'HI thErE' -capitalize('hI thErE', { normalize: true }); // 'Hi There' -capitalize('hI thErE', { cap: true, normalize: true }); // 'Hi there' -``` - -## `dt` - -Simple `date/time` (`dt`) utility namespace. - -```ts -type DateValue = string | number | Date; - -interface BuildOptions { - base?: 'UTC'; -} - -interface TravelOptions { - /** relative point of reference to travel */ - from?: DateValue; - /** relative days to travel in number */ - to: number; -} - -export const dt: { - current(d?: DateValue): Date; - build(options: BuildOptions): (date?: DateValue) => (mask?: string) => string; - format: ReturnType; - travel({ from, to }: TravelOptions): Date; -} -``` - -- `dt.current` is a function `(d?: DateValue) => Date` that optionally takes in a `DateValue` to be converted into a `Date` object, `new Date()` will be used if nothing is passed -- `dt.build` is a function that accepts `BuildOptions` and builds a formatter, a convenience export is included with all the default options as `dt.format` -- `dt.format` is a function that takes in a `DateValue` and returns a renderer that accepts a string mask to format the date in, defaults to `'DDDD, DD MMMM YYYY'` -- `dt.travel` is a function `({ from, to }) => Date` that takes in a `{ from, to }` object with `from` property being optional - -## `tryNumber` - -will check an input and convert to number when applicable, otherwise it will return the input as is. - -```ts -type Possibilities = string | number | null | undefined; - -export function tryNumber( - input: Input, - fallback?: Fallback -): Input is number ? number : Fallback | Input; -``` - -Example inputs and outputs - -```js -tryNumber('0'); // 0 -tryNumber(0); // 0 -tryNumber('1H'); // '1H' -``` - -## `random` - -```js -/** random number from [min, max) */ -random.int(2); // 0 - 1 -random.int(1000); // 0 - 999 -random.int(9, 1); // 1 - 8 - -/** random key from any object */ -const data = { a: {}, b: 1, c: [3] }; -// returns a random value from an object -random.key(data); // a || 1 || [3] -``` diff --git a/src/utils/index.spec.ts b/src/utils/index.spec.ts deleted file mode 100644 index 97d69140..00000000 --- a/src/utils/index.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { suite } from 'uvu'; -import * as assert from 'uvu/assert'; -import * as utils from './index.js'; - -const basics = { - capitalize: suite('capitalize'), - tryNumber: suite('tryNumber'), -}; - -// ---- capitalize ---- - -basics.capitalize('change one letter for one word', () => { - assert.equal(utils.capitalize('hello'), 'Hello'); -}); -basics.capitalize('change two letter for two words', () => { - assert.equal(utils.capitalize('hello world'), 'Hello World'); -}); - -// ---- tryNumber ---- - -basics.tryNumber('convert to numbers', () => { - assert.equal(utils.tryNumber(null), 0); - assert.equal(utils.tryNumber('0'), 0); - assert.equal(utils.tryNumber('1'), 1); - assert.equal(utils.tryNumber('-1'), -1); - assert.equal(utils.tryNumber('1e3'), 1e3); -}); - -basics.tryNumber('fallback to original value as-is', () => { - assert.equal(utils.tryNumber('a'), 'a'); - assert.equal(utils.tryNumber('a1a'), 'a1a'); - assert.equal(utils.tryNumber('1a1'), '1a1'); - assert.equal(utils.tryNumber('-1a'), '-1a'); - assert.equal(utils.tryNumber('-1e'), '-1e'); - - const dyn: string = 'dynamic'; - assert.equal(utils.tryNumber(dyn), 'dynamic'); - assert.equal(utils.tryNumber(undefined), undefined); -}); - -basics.tryNumber('fallback to provided as expected', () => { - assert.equal(utils.tryNumber('a', 0), 0); - assert.equal(utils.tryNumber('a', 1), 1); -}); - -Object.values(basics).forEach((v) => v.run()); diff --git a/src/utils/index.test.ts b/src/utils/index.test.ts deleted file mode 100644 index 020874b5..00000000 --- a/src/utils/index.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { tryNumber } from './index.js'; - -declare function expect(v: T): void; - -expect<''>(tryNumber('')); -expect(tryNumber(null)); -expect(tryNumber(0)); -expect(tryNumber(1)); -expect(tryNumber('0')); -expect(tryNumber('1')); -expect(tryNumber('12')); -expect(tryNumber('123')); -expect(tryNumber('123456')); -expect<'asd'>(tryNumber('asd')); -expect<'123asd'>(tryNumber('123asd')); -expect<'asd123'>(tryNumber('asd123')); -expect<'123asd123'>(tryNumber('123asd123')); -expect<'asd123asd'>(tryNumber('asd123asd')); -expect<'12as'>(tryNumber('12as')); - -// @ts-expect-error -expect(tryNumber('nan')); -// @ts-expect-error -expect(tryNumber('000nope')); -// @ts-expect-error -expect(tryNumber('123')); diff --git a/src/utils/index.ts b/src/utils/index.ts deleted file mode 100644 index 91b03ee7..00000000 --- a/src/utils/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -export * as random from './random/index.js'; -export * as dt from './temporal/index.js'; - -interface CapitalizeOptions { - /** only capitalize the very first letter */ - cap?: boolean; - /** convert the remaining word to lowercase */ - normalize?: boolean; -} -export function capitalize(text: string, { cap, normalize }: CapitalizeOptions = {}): string { - if (normalize) text = text.toLowerCase(); - if (cap) return `${text[0].toUpperCase()}${text.slice(1)}`; - return text.replace(/(?:^|\s)\S/g, (a) => a.toUpperCase()); -} - -type Numeric = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; -type TryValidator = Text extends `${infer Character}${infer Rest}` - ? [Character, TryValidator] extends [Numeric, infer Validated] - ? [Validated] extends [''] - ? number - : Validated extends string - ? Text - : number - : Text - : Text; - -type Possibilities = string | number | null | undefined; -export function tryNumber( - input: Input, - fallback: Fallback = input as unknown as Fallback -) { - type Narrow = Other extends number | null ? number : Fallback; - type TryReturned = Input extends string ? TryValidator : Narrow; - const converted = Number(input); - return (Number.isNaN(converted) ? fallback : converted) as TryReturned; -} diff --git a/src/web/query/decoder.ts b/src/web/query/decoder.ts index 4873c02e..33313a92 100644 --- a/src/web/query/decoder.ts +++ b/src/web/query/decoder.ts @@ -2,7 +2,6 @@ import type { IndexSignature, Primitives } from '../../typings/aliases.js'; import type { AlsoArray } from '../../typings/extenders.js'; import type { Intersection } from '../../typings/helpers.js'; import type { Flatten } from '../../typings/prototypes.js'; -import { tryNumber } from '../../utils/index.js'; type CombineExisting< A extends Record, @@ -37,7 +36,8 @@ export default function qsd(qs: Q) { const dec = (s: string) => { if (!s.trim()) return ''; s = decodeURIComponent(s); - return ['true', 'false'].includes(s) ? s[0] === 't' : tryNumber(s); + if (['true', 'false'].includes(s)) return s[0] === 't'; + return Number.isNaN(Number(s)) ? s : Number(s); }; const dqs: Record> = {}; From 75b45c6ebe989725d10ad52249912203218c2d2c Mon Sep 17 00:00:00 2001 From: Ignatius Bagus Date: Fri, 19 May 2023 11:19:54 +0800 Subject: [PATCH 7/7] chore: organize test suites (#226) --- src/core/compare/index.spec.ts | 58 ++++++++++++++------------------ src/core/lambda/index.spec.ts | 12 +++---- src/core/random/index.spec.ts | 38 ++++++++++----------- src/core/standard/index.spec.ts | 20 +++++------ src/core/standard/unique.spec.ts | 12 +++---- src/core/temporal/index.spec.ts | 24 +++++-------- src/guards/index.spec.ts | 34 +++++++++---------- src/math/set/index.spec.ts | 34 ++++++++----------- src/std/ntv/array.spec.ts | 12 +++---- src/std/ntv/object.spec.ts | 38 ++++++++++----------- src/web/cookies/index.spec.ts | 58 +++++++++++++------------------- src/web/query/index.spec.ts | 18 ++++------ 12 files changed, 160 insertions(+), 198 deletions(-) diff --git a/src/core/compare/index.spec.ts b/src/core/compare/index.spec.ts index aa2dbb14..51eaa01c 100644 --- a/src/core/compare/index.spec.ts +++ b/src/core/compare/index.spec.ts @@ -2,31 +2,26 @@ import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as compare from './index.js'; -const basics = { - inspect: suite('compare:inspect'), - wildcard: suite('compare:wildcard'), +const suites = { + 'inspect/': suite('compare/inspect'), + 'wildcard/': suite('compare/wildcard'), - undefined: suite('compare:undefined'), - boolean: suite('compare:boolean'), - number: suite('compare:number'), - bigint: suite('compare:bigint'), - symbol: suite('compare:symbol'), - string: suite('compare:string'), - object: suite('compare:object'), + 'undefined/': suite('compare/undefined'), + 'boolean/': suite('compare/boolean'), + 'number/': suite('compare/number'), + 'bigint/': suite('compare/bigint'), + 'symbol/': suite('compare/symbol'), + 'string/': suite('compare/string'), + 'object/': suite('compare/object'), - date: suite('compare:date'), - time: suite('compare:time'), + 'date/': suite('compare/date'), + 'time/': suite('compare/time'), - order: suite('compare:order'), -}; + 'order/': suite('compare/order'), + 'order/key': suite('compare/order:key'), +} as const; -const composite = { - order: suite('compare:key+order'), -}; - -// ---- standard ---- - -basics.inspect('inspect', () => { +suites['inspect/']('inspect', () => { assert.type(compare.inspect, 'function'); const data = [{ id: 0, name: 'B' }, { name: 'A' }, { id: 1, name: 'C' }]; @@ -37,25 +32,28 @@ basics.inspect('inspect', () => { ]); }); -basics.undefined('sort undefined values with null values above', () => { +suites['undefined/']('sort undefined values with null values above', () => { assert.equal( [undefined, 3, 0, null, 1, -1, undefined, -2, undefined, null].sort(compare.undefined), [3, 0, 1, -1, -2, null, null, undefined, undefined, undefined] ); }); -basics.boolean('sort boolean values with true above', () => { + +suites['boolean/']('sort boolean values with true above', () => { assert.equal( [true, false, true, false, true, false, true, false, true, false].sort(compare.boolean), [true, true, true, true, true, false, false, false, false, false] ); }); -basics.number('sort number in descending order', () => { + +suites['number/']('sort number in descending order', () => { assert.equal( [5, 3, 9, 6, 0, 2, 1, -1, 4, -2].sort(compare.number), [9, 6, 5, 4, 3, 2, 1, 0, -1, -2] ); }); -basics.string('sort string in alphabetical order', () => { + +suites['string/']('sort string in alphabetical order', () => { assert.equal( ['k', 'h', 'g', 'f', 'e', 'l', 'd', 'm', 'c', 'b', 'j', 'i', 'a'].sort(compare.string), ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'] @@ -66,18 +64,14 @@ basics.string('sort string in alphabetical order', () => { ); }); -basics.order('customized compare with order', () => { +suites['order/']('customized compare with order', () => { const months = ['January', 'February', 'March', 'April', 'May', 'June']; const list = ['March', 'June', 'May', 'April', 'January', 'June', 'February']; const result = ['January', 'February', 'March', 'April', 'May', 'June', 'June']; assert.equal(list.sort(compare.order(months)), result); }); -Object.values(basics).forEach((v) => v.run()); - -// ---- composite ---- - -composite.order('nested keyed compare with order', () => { +suites['order/key']('nested keyed compare with order', () => { const months = ['January', 'February', 'March', 'April', 'May', 'June']; const posts = [ { date: { pub: { month: 'March' } } }, @@ -99,4 +93,4 @@ composite.order('nested keyed compare with order', () => { ]); }); -Object.values(composite).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/core/lambda/index.spec.ts b/src/core/lambda/index.spec.ts index e51f52fc..3ab044d4 100644 --- a/src/core/lambda/index.spec.ts +++ b/src/core/lambda/index.spec.ts @@ -4,12 +4,12 @@ import * as assert from 'uvu/assert'; import curry from './curry.js'; import pipe from './pipe.js'; -const basics = { - curry: suite('lambda:curry'), - pipe: suite('lambda:pipe'), +const suites = { + 'curry/': suite('lambda/curry'), + 'pipe/': suite('lambda/pipe'), }; -basics.curry('properly curry a function', () => { +suites['curry/']('properly curry a function', () => { const sum = (a: number, b: number, c: number) => a + b + c; const curried = curry(sum); @@ -20,7 +20,7 @@ basics.curry('properly curry a function', () => { assert.equal(curried(1)(1)(1), 3); }); -basics.pipe('properly apply functions in ltr order', () => { +suites['pipe/']('properly apply functions in ltr order', () => { const cap = (v: string) => v.toUpperCase(); const name = (v: T) => v.name; const split = (v: string) => v.split(''); @@ -29,4 +29,4 @@ basics.pipe('properly apply functions in ltr order', () => { assert.equal(pipeline({ name: 'mom' }), ['M', 'O', 'M']); }); -Object.values(basics).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/core/random/index.spec.ts b/src/core/random/index.spec.ts index 7fb01ffe..2002e1a2 100644 --- a/src/core/random/index.spec.ts +++ b/src/core/random/index.spec.ts @@ -3,55 +3,53 @@ import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as random from './index.js'; -const basics = { - float: suite('random:float'), - int: suite('random:int'), - bool: suite('random:bool'), - array: suite('random:array'), - key: suite('random:key'), - val: suite('random:val'), - // hex: suite('random:hex'), - // ipv4: suite('random:ipv4'), - uuid: suite('random:uuid'), +const suites = { + 'float/': suite('random/float'), + 'int/': suite('random/int'), + 'bool/': suite('random/bool'), + 'array/': suite('random/array'), + 'key/': suite('random/key'), + 'val/': suite('random/val'), + // 'hex/': suite('random/hex'), + // 'ipv4/': suite('random/ipv4'), + 'uuid/': suite('random/uuid'), }; -// ---- standard ---- - -basics.float('generate random float', () => { +suites['float/']('generate random float', () => { const number = random.float(); assert.type(number, 'number'); assert.ok(number >= 0 && number <= 1); }); -basics.int('generate random integer', () => { +suites['int/']('generate random integer', () => { const number = random.int(); assert.type(number, 'number'); assert.ok(number === 0 || number === 1); }); -basics.bool('generate random bool', () => { +suites['bool/']('generate random bool', () => { assert.type(random.bool(), 'boolean'); }); -basics.array('generate array with random values', () => { +suites['array/']('generate array with random values', () => { const array = random.array(5, 3); assert.type(array, 'object'); assert.equal(array.length, 5); }); -basics.key('get random key from object', () => { +suites['key/']('get random key from object', () => { const key = random.key({ foo: 0, bar: 1 }); assert.type(key, 'string'); assert.ok(key === 'foo' || key === 'bar'); }); -basics.val('get random value from object', () => { +suites['val/']('get random value from object', () => { const val = random.val({ foo: 0, bar: 1 }); assert.type(val, 'number'); assert.ok(val === 0 || val === 1); }); -basics.uuid('generate random uuid', () => { +suites['uuid/']('generate random uuid', () => { const floating = random.uuid(); assert.equal(floating.length, 36); assert.equal(floating.split('-').length, 5); @@ -61,4 +59,4 @@ basics.uuid('generate random uuid', () => { assert.equal(secure.split('-').length, 5); }); -Object.values(basics).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/core/standard/index.spec.ts b/src/core/standard/index.spec.ts index 689f7192..e2ed5eb7 100644 --- a/src/core/standard/index.spec.ts +++ b/src/core/standard/index.spec.ts @@ -2,19 +2,19 @@ import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as std from './index.js'; -const basics = { - capitalize: suite('std:capitalize'), - identical: suite('std:identical'), +const suites = { + 'capitalize/': suite('std/capitalize'), + 'identical/': suite('std/identical'), }; -basics.capitalize('change one letter for one word', () => { +suites['capitalize/']('change one letter for one word', () => { assert.equal(std.capitalize('hello'), 'Hello'); }); -basics.capitalize('change two letter for two words', () => { +suites['capitalize/']('change two letter for two words', () => { assert.equal(std.capitalize('hello world'), 'Hello World'); }); -basics.identical('identical primitive checks', () => { +suites['identical/']('identical primitive checks', () => { // boolean assert.ok(std.identical(true, true)); assert.ok(!std.identical(true, false)); @@ -55,22 +55,22 @@ basics.identical('identical primitive checks', () => { ) ); }); -basics.identical('identical array checks', () => { +suites['identical/']('identical array checks', () => { assert.ok(std.identical([], [])); assert.ok(std.identical(['', 1, !0], ['', 1, !0])); assert.ok(std.identical([{ x: [] }], [{ x: [] }])); assert.ok(!std.identical(['', 0, !0], ['', 1, !1])); assert.ok(!std.identical([{ x: [] }], [{ y: [] }])); }); -basics.identical('identical object checks', () => { +suites['identical/']('identical object checks', () => { assert.ok(std.identical({}, {})); assert.ok(std.identical({ a: '', b: 1, c: !0 }, { a: '', b: 1, c: !0 })); assert.ok(std.identical({ x: [{}], y: { a: 0 } }, { x: [{}], y: { a: 0 } })); }); -basics.identical('identical clone', async () => { +suites['identical/']('identical clone', async () => { const { clone } = await import('../../std/ntv/object.js'); const data = { a: [1, '', {}], o: { now: new Date() } }; assert.ok(std.identical(data, clone(data))); }); -Object.values(basics).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/core/standard/unique.spec.ts b/src/core/standard/unique.spec.ts index bc5fb713..77561bbd 100644 --- a/src/core/standard/unique.spec.ts +++ b/src/core/standard/unique.spec.ts @@ -3,12 +3,12 @@ import * as assert from 'uvu/assert'; import unique from './unique.js'; -const basics = { - simple: suite('unique:simple'), - object: suite('unique:object'), +const suites = { + 'simple/': suite('unique/simple'), + 'object/': suite('unique/object'), }; -basics.simple('make array items unique', () => { +suites['simple/']('make array items unique', () => { assert.equal(unique([true, false, !0, !1]), [true, false]); assert.equal(unique([1, 1, 2, 3, 2, 4, 5]), [1, 2, 3, 4, 5]); assert.equal(unique(['a', 'a', 'b', 'c', 'b']), ['a', 'b', 'c']); @@ -17,7 +17,7 @@ basics.simple('make array items unique', () => { assert.equal(unique(months), ['jan', 'feb', 'mar']); }); -basics.object('make array of object unique', () => { +suites['object/']('make array of object unique', () => { assert.equal( unique( [ @@ -53,4 +53,4 @@ basics.object('make array of object unique', () => { ); }); -Object.values(basics).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/core/temporal/index.spec.ts b/src/core/temporal/index.spec.ts index b71b566b..789edd0e 100644 --- a/src/core/temporal/index.spec.ts +++ b/src/core/temporal/index.spec.ts @@ -2,17 +2,15 @@ import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as dt from './index.js'; -const basics = { - build: suite('temporal:build'), - format: suite('temporal:format'), - travel: suite('temporal:travel'), +const suites = { + 'build/': suite('temporal/build'), + 'format/': suite('temporal/format'), + 'travel/': suite('temporal/travel'), }; const fixed = new Date('2017/09/08, 13:02:03'); -// ---- build ---- - -basics.build('basic formatter builder', () => { +suites['build/']('basic formatter builder', () => { const format = dt.build({ base: 'UTC' }); assert.type(format, 'function'); @@ -22,9 +20,7 @@ basics.build('basic formatter builder', () => { assert.equal(renderer('DD/MM/YYYY (Z)'), '08/09/2017 (+0)'); }); -// ---- format ---- - -basics.format('basic rendering', () => { +suites['format/']('basic rendering', () => { const renderer = dt.format(fixed); assert.equal(renderer('foo'), 'foo'); @@ -55,12 +51,8 @@ basics.format('basic rendering', () => { 'Valid from: [2017-09-08 ~ 13:02:03]' ); }); -basics.format('throw on invalid date', () => { +suites['format/']('throw on invalid date', () => { assert.throws(() => dt.format('invalid')); }); -// ---- travel ---- - -// basics.travel('basic travelling', () => {}); - -Object.values(basics).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/guards/index.spec.ts b/src/guards/index.spec.ts index 3c405179..775b4af6 100644 --- a/src/guards/index.spec.ts +++ b/src/guards/index.spec.ts @@ -7,71 +7,69 @@ const data = [true, false, 'a', 'b', 0, 1, 2, '', null, undefined, NaN]; const numbers = [-2, -1, 0, 1, 2, 3]; const strings = ['a', 'A', 'b', 'B', 'c', 'C']; -const basics = { - guards: suite('guards'), - inverse: suite('not()'), +const suites = { + 'guards/': suite('guards/core'), + 'inverse/': suite('guards/not'), }; -basics.guards('filters values that exists', () => { +suites['guards/']('filters values that exists', () => { const filtered = data.filter(guards.exists); assert.equal(filtered, [true, false, 'a', 'b', 0, 1, 2, NaN]); }); -basics.guards('filters values that are nullish', () => { +suites['guards/']('filters values that are nullish', () => { const filtered = data.filter(guards.nullish); assert.equal(filtered, [null, undefined]); }); -basics.guards('filters values that are truthy', () => { +suites['guards/']('filters values that are truthy', () => { const filtered = data.filter(guards.truthy); assert.equal(filtered, [true, 'a', 'b', 1, 2]); }); -basics.guards('filters numbers that are natural', () => { +suites['guards/']('filters numbers that are natural', () => { const filtered = numbers.filter(guards.natural); assert.equal(filtered, [1, 2, 3]); }); -basics.guards('filters numbers that are whole', () => { +suites['guards/']('filters numbers that are whole', () => { const filtered = numbers.filter(guards.whole); assert.equal(filtered, [0, 1, 2, 3]); }); -basics.guards('filters strings that are lowercase', () => { +suites['guards/']('filters strings that are lowercase', () => { const filtered = strings.filter(guards.lowercase); assert.equal(filtered, ['a', 'b', 'c']); }); -basics.guards('filters strings that are uppercase', () => { +suites['guards/']('filters strings that are uppercase', () => { const filtered = strings.filter(guards.uppercase); assert.equal(filtered, ['A', 'B', 'C']); }); -// ---- not() suite ---- - -basics.inverse('filters values that does not exists', () => { +suites['inverse/']('filters values that does not exists', () => { const filtered = data.filter(guards.not(guards.exists)); assert.equal(filtered, ['', null, undefined]); }); -basics.inverse('filters values that are not nullish', () => { +suites['inverse/']('filters values that are not nullish', () => { const filtered = data.filter(guards.not(guards.nullish)); assert.equal(filtered, [true, false, 'a', 'b', 0, 1, 2, '', NaN]); }); -basics.inverse('filters values that are falsy', () => { +suites['inverse/']('filters values that are falsy', () => { const filtered = data.filter(guards.not(guards.truthy)); assert.equal(filtered, [false, 0, '', null, undefined, NaN]); }); -basics.inverse('filters numbers that are not natural', () => { +suites['inverse/']('filters numbers that are not natural', () => { const filtered = numbers.filter(guards.not(guards.natural)); assert.equal(filtered, [-2, -1, 0]); }); -basics.inverse('filters numbers that are not whole', () => { +suites['inverse/']('filters numbers that are not whole', () => { const filtered = numbers.filter(guards.not(guards.whole)); assert.equal(filtered, [-2, -1]); }); -Object.values(basics).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/math/set/index.spec.ts b/src/math/set/index.spec.ts index e11b5124..9dc7d671 100644 --- a/src/math/set/index.spec.ts +++ b/src/math/set/index.spec.ts @@ -2,23 +2,24 @@ import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as set from './index.js'; -const basics = { - permutation: suite('permutation'), +const suites = { + 'permutation/': suite('set/permutation'), + 'permutation/+': suite('set/permutation:+'), }; -basics.permutation('returns array with empty array for empty array', () => { +suites['permutation/']('returns array with empty array for empty array', () => { assert.equal(set.permutation([]), [[]]); }); -basics.permutation('returns immediate wrapped input for one length array', () => { +suites['permutation/']('returns immediate wrapped input for one length array', () => { assert.equal(set.permutation(['a']), [['a']]); }); -basics.permutation('correctly permute 2 words and returns array of results', () => { +suites['permutation/']('correctly permute 2 words and returns array of results', () => { assert.equal(set.permutation(['a', 'b']), [ ['a', 'b'], ['b', 'a'], ]); }); -basics.permutation('correctly permute 3 words and returns array of results', () => { +suites['permutation/']('correctly permute 3 words and returns array of results', () => { assert.equal(set.permutation(['a', 'b', 'c']), [ ['a', 'b', 'c'], ['a', 'c', 'b'], @@ -28,7 +29,7 @@ basics.permutation('correctly permute 3 words and returns array of results', () ['c', 'b', 'a'], ]); }); -basics.permutation('correctly permute 4 words and returns array of results', () => { +suites['permutation/']('correctly permute 4 words and returns array of results', () => { assert.equal(set.permutation(['a', 'b', 'c', 'd']), [ ['a', 'b', 'c', 'd'], ['a', 'b', 'd', 'c'], @@ -57,23 +58,17 @@ basics.permutation('correctly permute 4 words and returns array of results', () ]); }); -// ---- mutated suite ---- - -const advanced = { - permutation: suite('permutation+'), -}; - const dashed = (i: string[]) => i.join('-'); -advanced.permutation('returns array with empty string for empty array', () => { +suites['permutation/+']('returns array with empty string for empty array', () => { assert.equal(set.permutation([], dashed), ['']); }); -advanced.permutation('returns immediate input for one length array', () => { +suites['permutation/+']('returns immediate input for one length array', () => { assert.equal(set.permutation(['a'], dashed), ['a']); }); -advanced.permutation('correctly permute and mutate 2 words', () => { +suites['permutation/+']('correctly permute and mutate 2 words', () => { assert.equal(set.permutation(['a', 'b'], dashed), ['a-b', 'b-a']); }); -advanced.permutation('correctly permute and mutate 3 words', () => { +suites['permutation/+']('correctly permute and mutate 3 words', () => { assert.equal(set.permutation(['a', 'b', 'c'], dashed), [ 'a-b-c', 'a-c-b', @@ -83,7 +78,7 @@ advanced.permutation('correctly permute and mutate 3 words', () => { 'c-b-a', ]); }); -advanced.permutation('correctly permute and mutate 4 words', () => { +suites['permutation/+']('correctly permute and mutate 4 words', () => { assert.equal(set.permutation(['a', 'b', 'c', 'd'], dashed), [ 'a-b-c-d', 'a-b-d-c', @@ -112,5 +107,4 @@ advanced.permutation('correctly permute and mutate 4 words', () => { ]); }); -Object.values(basics).forEach((v) => v.run()); -Object.values(advanced).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/std/ntv/array.spec.ts b/src/std/ntv/array.spec.ts index 372271ea..8ca8a010 100644 --- a/src/std/ntv/array.spec.ts +++ b/src/std/ntv/array.spec.ts @@ -3,11 +3,11 @@ import * as assert from 'uvu/assert'; import * as ntv from './array.js'; -const basics = { - zip: suite('obj:zip'), +const suites = { + 'arr/zip': suite('arr/zip'), }; -basics.zip('zip multiple arrays of objects', () => { +suites['arr/zip']('zip multiple arrays of objects', () => { const zipped = ntv.zip( [{ a: 0 }, { x: 0 }], [{ b: 0 }, { y: 0 }], @@ -20,7 +20,7 @@ basics.zip('zip multiple arrays of objects', () => { { x: 1, y: 0, z: 0 }, ]); }); -basics.zip('zip multiple uneven arrays', () => { +suites['arr/zip']('zip multiple uneven arrays', () => { const zipped = ntv.zip( [{ a: 0 }], [{ a: 1 }, { x: 0 }], @@ -39,7 +39,7 @@ basics.zip('zip multiple uneven arrays', () => { { w: 0, x: 0, y: 0 }, ]); }); -basics.zip('zip remove all nullish index', () => { +suites['arr/zip']('zip remove all nullish index', () => { const zipped = ntv.zip( [{ a: 0 }, null, { x: 0 }, null, { a: 0 }, undefined], [{ b: 0 }, null, { y: 0 }, undefined, { b: 0 }, null], @@ -54,4 +54,4 @@ basics.zip('zip remove all nullish index', () => { ]); }); -Object.values(basics).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/std/ntv/object.spec.ts b/src/std/ntv/object.spec.ts index 6499cb27..cb9ae108 100644 --- a/src/std/ntv/object.spec.ts +++ b/src/std/ntv/object.spec.ts @@ -3,17 +3,17 @@ import * as assert from 'uvu/assert'; import * as ntv from './object.js'; -const basics = { - clone: suite('obj:clone'), - entries: suite('obj:entries'), - freeze: suite('obj:freeze'), - iterate: suite('obj:iterate'), - keys: suite('obj:keys'), - pick: suite('obj:pick'), - size: suite('obj:size'), +const suites = { + 'obj/clone': suite('obj/clone'), + 'obj/entries': suite('obj/entries'), + 'obj/freeze': suite('obj/freeze'), + 'obj/iterate': suite('obj/iterate'), + 'obj/keys': suite('obj/keys'), + 'obj/pick': suite('obj/pick'), + 'obj/size': suite('obj/size'), }; -basics.clone('clone any possible data type', () => { +suites['obj/clone']('clone any possible data type', () => { const base = { arr: [0, 'hi', /wut/], obj: { now: new Date() } }; const cloned = ntv.clone(base); @@ -27,7 +27,7 @@ basics.clone('clone any possible data type', () => { assert.equal(base.obj.now, cloned.obj.now); }); -basics.entries('return object entries', () => { +suites['obj/entries']('return object entries', () => { assert.equal(ntv.entries({ hello: 'world', foo: 0, bar: { baz: 1 } }), [ ['hello', 'world'], ['foo', 0], @@ -35,7 +35,7 @@ basics.entries('return object entries', () => { ]); }); -basics.freeze('deep freezes nested objects', () => { +suites['obj/freeze']('deep freezes nested objects', () => { const nested = ntv.freeze({ foo: { a: 0 }, bar: { b: 1 }, @@ -45,7 +45,7 @@ basics.freeze('deep freezes nested objects', () => { assert.ok(Object.isFrozen(nested.foo)); assert.ok(Object.isFrozen(nested.bar)); }); -basics.freeze('deep freeze ignore function', () => { +suites['obj/freeze']('deep freeze ignore function', () => { const nested = ntv.freeze({ identity: (v: any) => v, namespace: { a() {} }, @@ -56,7 +56,7 @@ basics.freeze('deep freeze ignore function', () => { assert.ok(!Object.isFrozen(nested.namespace.a)); }); -basics.iterate('iterate over nested objects', () => { +suites['obj/iterate']('iterate over nested objects', () => { const months = 'jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec'.split(','); const currencies = 'usd,eur,sgd,gbp,aud,jpy'.split(','); @@ -90,7 +90,7 @@ basics.iterate('iterate over nested objects', () => { }, {}) ); }); -basics.iterate('iterate with empty/falsy return', () => { +suites['obj/iterate']('iterate with empty/falsy return', () => { assert.equal( ntv.iterate({}, ([]) => {}), {} @@ -114,25 +114,25 @@ basics.iterate('iterate with empty/falsy return', () => { }); }); }); -basics.iterate('iterate creates deep copy', () => { +suites['obj/iterate']('iterate creates deep copy', () => { const original = { x: 1, y: { z: 'foo' } }; const copy = ntv.iterate(original); assert.ok(original !== copy); assert.ok(original.y !== copy.y); }); -basics.keys('return object keys', () => { +suites['obj/keys']('return object keys', () => { assert.equal(ntv.keys({ a: 0, b: 1, c: 2 }), ['a', 'b', 'c']); }); -basics.pick('pick properties from an object', () => { +suites['obj/pick']('pick properties from an object', () => { const unwrap = ntv.pick(['a', 'b', 'c', 'z']); const picked = unwrap({ a: 0, c: 'b', y: undefined, z: null }); assert.equal(picked, { a: 0, c: 'b', z: null }); }); -basics.size('return size of an object', () => { +suites['obj/size']('return size of an object', () => { assert.equal(ntv.size({ a: 0, b: 1, c: 2 }), 3); }); -Object.values(basics).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/web/cookies/index.spec.ts b/src/web/cookies/index.spec.ts index 394812ff..78d162f0 100644 --- a/src/web/cookies/index.spec.ts +++ b/src/web/cookies/index.spec.ts @@ -2,23 +2,21 @@ import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as cookies from './index.js'; -const basics = { - parse: suite('cookie:parse'), - create: suite('cookie:create'), - remove: suite('cookie:remove'), - bulk: suite('cookie:bulk'), - raw: suite('cookie:raw'), +const suites = { + 'parse/': suite('cookie/parse'), + 'create/': suite('cookie/create'), + 'remove/': suite('cookie/remove'), + 'bulk/': suite('cookie/bulk'), + 'raw/': suite('cookie/raw'), }; -// ---- parse ---- - -basics.parse('parse basic input', () => { +suites['parse/']('parse basic input', () => { const jar = cookies.parse('foo=bar;hi=mom;hello=world'); assert.equal(jar.get('foo'), 'bar'); assert.equal(jar.get('hi'), 'mom'); assert.equal(jar.get('hello'), 'world'); }); -basics.parse('parse nullish header', () => { +suites['parse/']('parse nullish header', () => { let header: null | undefined = null; const jar = cookies.parse(header); assert.ok(!jar.has('foo')); @@ -28,92 +26,84 @@ basics.parse('parse nullish header', () => { assert.equal(jar.get('hi'), undefined); assert.equal(jar.get('hello'), undefined); }); -basics.parse('parse ignore spaces', () => { +suites['parse/']('parse ignore spaces', () => { const jar = cookies.parse('foo = bar; hi= mom'); assert.equal(jar.get('foo'), 'bar'); assert.equal(jar.get('hi'), 'mom'); }); -basics.parse('parse handle quoted', () => { +suites['parse/']('parse handle quoted', () => { const jar = cookies.parse('foo="bar=123&hi=mom"'); assert.equal(jar.get('foo'), 'bar=123&hi=mom'); }); -basics.parse('parse escaped values', () => { +suites['parse/']('parse escaped values', () => { const jar = cookies.parse('foo=%20%22%2c%2f%3b'); assert.equal(jar.get('foo'), ' ",/;'); }); -basics.parse('parse ignore errors', () => { +suites['parse/']('parse ignore errors', () => { const jar = cookies.parse('foo=%1;bar=baz;huh;'); assert.equal(jar.get('foo'), '%1'); assert.equal(jar.get('bar'), 'baz'); assert.ok(!jar.has('huh')); }); -basics.parse('parse ignore missing values', () => { +suites['parse/']('parse ignore missing values', () => { const jar = cookies.parse('foo=;bar= ;huh'); assert.ok(!jar.has('foo')); assert.ok(!jar.has('bar')); assert.ok(!jar.has('huh')); }); -// ---- create ---- - -basics.create('generate Set-Cookie value to set cookie', () => { +suites['create/']('generate Set-Cookie value to set cookie', () => { const value = cookies.create()('foo', 'bar'); assert.match(value, /foo=bar; Expires=(.*); Path=\/; SameSite=Lax; HttpOnly/); }); -basics.create('set Secure attribute for SameSite=None', () => { +suites['create/']('set Secure attribute for SameSite=None', () => { const printer = cookies.create({ sameSite: 'None' }); const value = printer('foo', 'bar'); assert.match(value, /foo=bar; Expires=(.*); Path=\/; SameSite=None; HttpOnly; Secure/); }); -// ---- remove ---- - -basics.remove('generate Set-Cookie value to remove cookie', () => { +suites['remove/']('generate Set-Cookie value to remove cookie', () => { const value = cookies.remove('foo'); assert.match(value, /foo=; Path=\/; Expires=Thu, 01 Jan 1970 00:00:01 GMT/); }); -// ---- bulk ---- - -basics.bulk('bulk generate Set-Cookie values', () => { +suites['bulk/']('bulk generate Set-Cookie values', () => { const data = { foo: 'bar' }; for (const value of cookies.bulk(data)) { assert.match(value, /(.*)=(.*); Expires=(.*); Path=\/; SameSite=Lax; HttpOnly/); } }); -// ---- raw ---- - -basics.raw('raw basic input', () => { +suites['raw/']('raw basic input', () => { const header = 'foo=bar;hi=mom;hello=world'; assert.equal(cookies.raw(header, 'foo'), 'bar'); assert.equal(cookies.raw(header, 'hi'), 'mom'); assert.equal(cookies.raw(header, 'hello'), 'world'); }); -basics.raw('raw nullish header', () => { +suites['raw/']('raw nullish header', () => { let header: null | undefined = null; assert.ok(!cookies.raw(header, 'foo')); assert.ok(!cookies.raw(header, 'hi')); assert.ok(!cookies.raw(header, 'hello')); }); -basics.raw('raw ignore spaces', () => { +suites['raw/']('raw ignore spaces', () => { const header = 'foo = bar; hi= mom'; assert.equal(cookies.raw(header, 'foo'), ' bar'); assert.equal(cookies.raw(header, 'hi'), ' mom'); }); -basics.raw('raw handle quoted', () => { +suites['raw/']('raw handle quoted', () => { const header = 'foo="bar=123&hi=mom"'; assert.equal(cookies.raw(header, 'foo'), '"bar=123&hi=mom"'); }); -basics.raw('raw escaped values', () => { +suites['raw/']('raw escaped values', () => { const header = 'foo=%20%22%2c%2f%3b'; assert.equal(cookies.raw(header, 'foo'), '%20%22%2c%2f%3b'); }); -basics.raw('raw return empty values', () => { +suites['raw/']('raw return empty values', () => { const header = 'foo=;bar= ;huh'; assert.ok(!cookies.raw(header, 'foo')); assert.ok(cookies.raw(header, 'bar')); assert.ok(!cookies.raw(header, 'huh')); }); -Object.values(basics).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run()); diff --git a/src/web/query/index.spec.ts b/src/web/query/index.spec.ts index b9c348c0..d8de9033 100644 --- a/src/web/query/index.spec.ts +++ b/src/web/query/index.spec.ts @@ -4,14 +4,12 @@ import * as assert from 'uvu/assert'; import qsd from './decoder.js'; import qse from './encoder.js'; -const basics = { - decoder: suite('query:decoder'), - encoder: suite('query:encoder'), +const suites = { + 'decoder/': suite('query/decoder'), + 'encoder/': suite('query/encoder'), }; -// ---- decoder ---- - -basics.decoder('decode query string to object', () => { +suites['decoder/']('decode query string to object', () => { const pairs = [ ['?hi=mom&hello=world', { hi: 'mom', hello: 'world' }], ['fam=mom&fam=dad&fam=sis', { fam: ['mom', 'dad', 'sis'] }], @@ -23,9 +21,7 @@ basics.decoder('decode query string to object', () => { } }); -// ---- encoder ---- - -basics.encoder('encode object to query string', () => { +suites['encoder/']('encode object to query string', () => { let payload: string = 'dynamic'; const pairs = [ [{ hi: 'mom', hello: 'world' }, '?hi=mom&hello=world'], @@ -40,7 +36,7 @@ basics.encoder('encode object to query string', () => { assert.equal(qse(input), output); } }); -basics.encoder('transform final string if it exists', () => { +suites['encoder/']('transform final string if it exists', () => { const bound = { q: '' }; assert.equal(qse(bound), ''); @@ -49,4 +45,4 @@ basics.encoder('transform final string if it exists', () => { assert.equal(qse(bound), '?q=hi'); }); -Object.values(basics).forEach((v) => v.run()); +Object.values(suites).forEach((v) => v.run());