Skip to content

Commit

Permalink
refactor: toBoolean does more; narrower predicates
Browse files Browse the repository at this point in the history
the toBoolean function centralizes the logic for coercing a value and allows
the predicate functions to be more specific in what they validate rather than
them simply being alais names to broad validation.

closes #122
  • Loading branch information
kalisjoshua committed Dec 4, 2024
1 parent 9674cae commit 7b8e49a
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 104 deletions.
5 changes: 1 addition & 4 deletions packages/converters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
"private": false,
"license": "Apache-2.0",
"type": "module",
"files": [
"dist/**"
],
"files": ["dist/**"],
"types": "./dist/index.d.ts",
"exports": {
".": {
Expand Down Expand Up @@ -39,7 +37,6 @@
},
"dependencies": {
"@accelint/constants": "workspace:0.1.3",
"@accelint/predicates": "workspace:0.1.3",
"typescript": "^5.6.3"
},
"$schema": "https://json.schemastore.org/package",
Expand Down
62 changes: 43 additions & 19 deletions packages/converters/src/to-boolean/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,55 @@
import { expect, it, describe } from 'vitest';
import { toBoolean } from './';

const truthy = [1, '1', 'on', 'true', 'yes', true, 'ON', 'YES', 'TRUE'];
// biome-ignore lint/style/useNumberNamespace: testing value
const INFINITY = Infinity;

const truthy = [
// 'on', 'yes', 'ON', 'YES'
[],
1,
'1',
true,
'true',
'Yes',
{},
'anything at all',
' on',
'off ',
'no',
'Y',
INFINITY,
-INFINITY,
Number.POSITIVE_INFINITY,
Number.NEGATIVE_INFINITY,
/abc/,
new Date(),
new Error('Fun times.'),
() => void 0,
];
const falsey = [
// 'off', 'no', 'OFF', 'NO',
'',
0,
0.0,
'0',
'off',
'false',
'no',
'0.000',
'0000.000',
false,
[],
{},
'OFF',
'NO',
'FALSE',
'false',
' FaLsE ',
void 0,
Number.NaN,
null,
undefined,
];

describe('toBoolean', () => {
for (const item of truthy) {
it(`should return true for ${item}`, () => {
expect(toBoolean(item)).toBeTruthy();
});
}
it.each(falsey)('%s', (val) => {
expect(toBoolean(val)).toBe(false);
});

for (const item of falsey) {
it(`should return false for ${item}`, () => {
expect(toBoolean(item)).not.toBeTruthy();
});
}
it.each(truthy)('%s', (val) => {
expect(toBoolean(val)).toBe(true);
});
});
29 changes: 16 additions & 13 deletions packages/converters/src/to-boolean/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,33 @@
* governing permissions and limitations under the License.
*/

import { isTrue } from '@accelint/predicates';

/**
* Compare the given value against a custom list of `truthy` values.
*
* String values are not case sensitive.
* Will consider any non "false-value" to be true. String values are not case sensitive.
*
* _1, '1', 'on', 'true', 'yes', true_
* **"false-values"**
* - inherently false values: '' (empty string), 0, false, undefined, null, NaN
* - numeric zero: '0.000' - any number of leading or trailing zeros
* - string literal: 'false' - any capitalizations or space-padding
*
* @pure
*
* @example
* toBoolean('on');
* // true
*
* toBoolean('yes');
* toBoolean(1);
* // true
*
* toBoolean('off');
* toBoolean(' FaLsE ');
* // false
*
* toBoolean('no');
* toBoolean(' true');
* // true
*
* toBoolean('000.000');
* // false
*/
export function toBoolean(val: unknown) {
return isTrue(val);
return !(
!val ||
`${val}`.trim().toLowerCase() === 'false' ||
Number.parseFloat(val as string) === 0
);
}
1 change: 1 addition & 0 deletions packages/predicates/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"vitest": "^2.1.3"
},
"dependencies": {
"@accelint/converters": "workspace:^0.1.3",
"@accelint/core": "workspace:0.1.3",
"typescript": "^5.6.3"
},
Expand Down
39 changes: 22 additions & 17 deletions packages/predicates/src/is-noyes/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,31 @@
* governing permissions and limitations under the License.
*/

import { describe, it, expect } from 'vitest';
import { isTrue, isYes, isFalse, isNo, isOn, isOff } from './';
import { describe, expect, it } from 'vitest';
import { isFalse, isNo, isOff, isOn, isTrue, isYes } from './';

const truthy = [1, '1', 'on', 'true', 'yes', true, 'ON', 'YES', 'TRUE'];
const falsey = [0, '0', 'off', 'false', 'no', false, 'OFF', 'NO', 'FALSE'];
type Config = {
negative: unknown[];
positive: unknown[];
predicate: (a: unknown) => boolean;
};

describe('boolean validators', () => {
for (const item of truthy) {
it(`should return true for ${item}`, () => {
expect(isOn(item)).toBeTruthy();
expect(isTrue(item)).toBeTruthy();
expect(isYes(item)).toBeTruthy();
describe('boolean predicates', () => {
describe.each`
predicate | positive | negative
${isFalse} | ${[false, ' false', '0', '0.00']} | ${[true, 'o', 'O', 'true', 'string with false']}
${isNo} | ${[false, ' n', 'N ', 'no', 'NO']} | ${[true, 'yes', 'string with no']}
${isOff} | ${[false, ' off', 'OFF ']} | ${[true, 'on', 'string with off', 'of']}
${isOn} | ${[true, ' on', 'ON ']} | ${[false, 'of', 'string with on', 'o']}
${isTrue} | ${[true, ' true', 'any string', {}, [], /abc/]} | ${[false, '']}
${isYes} | ${[true, ' yes', 'YeS ', 'y']} | ${[false, 'no', 'string with yes']}
`('$predicate.name', ({ negative, positive, predicate }: Config) => {
it.each(positive)('%s', (val) => {
expect(predicate(val)).toBe(true);
});
}

for (const item of falsey) {
it(`should return false for ${item}`, () => {
expect(isFalse(item)).toBeTruthy();
expect(isOff(item)).toBeTruthy();
expect(isNo(item)).toBeTruthy();
it.each(negative)('%s', (val) => {
expect(predicate(val)).toBe(false);
});
}
});
});
91 changes: 43 additions & 48 deletions packages/predicates/src/is-noyes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,21 @@
* governing permissions and limitations under the License.
*/

// const trueRegex = /^(?:y|yes|true|1|on)$/i;
// const falseRegex = /^(?:n|no|false|0|off)$/i;

// const test = (r: RegExp, val: unknown) => r.test(`${val}`.trim());

const falseValues = ['0', 'false', 'n', 'no', 'off'];
const trueValues = ['1', 'true', 'y', 'yes', 'on'];
import { toBoolean } from '@accelint/converters';

const test = (list: string[], val: unknown) =>
list.includes(`${val}`.trim().toLowerCase());

/**
* Compare the given value against a custom list of `falsey` values.
* Check the given value against "false-values" and returns true if found to be
* false. String values are not case sensitive.
*
* String values are not case sensitive.
*
* _0, '0', 'n', 'no', 'off', 'false', false_
* @see toBoolean
*
* @pure
*
* @example
* isFalse('on');
* isFalse(' falSE');
* // false
*
* isFalse('yes');
Expand All @@ -41,15 +34,13 @@ const test = (list: string[], val: unknown) =>
* // true
*
* isFalse('no');
* // false
*/
export const isFalse = (val: unknown) => test(falseValues, val);
export const isFalse = (val: unknown) => toBoolean(val) === false;

/**
* Compare the given value against a custom list of `falsey` values.
*
* String values are not case sensitive.
*
* _0, '0', 'n', 'no', 'off', 'false', false_
* Compare the given value against a custom list of 'No' values. String values
* are not case sensitive.
*
* @pure
*
Expand All @@ -61,18 +52,19 @@ export const isFalse = (val: unknown) => test(falseValues, val);
* // false
*
* isNo('off');
* // true
* // false
*
* isNo('no');
* // true
*
* isNo('N');
* // true
*/
export const isNo = isFalse;
export const isNo = (val: unknown) => !val || test(['n', 'no'], val);

/**
* Compare the given value against a custom list of `falsey` values.
*
* String values are not case sensitive.
*
* _0, '0', 'n', 'no', 'off', 'false', false_
* Compare the given value against a custom list of 'Off' values. String values
* are not case sensitive.
*
* @pure
*
Expand All @@ -87,39 +79,42 @@ export const isNo = isFalse;
* // true
*
* isOff('no');
* // false
*/
export const isOff = isFalse;
export const isOff = (val: unknown) => !val || test(['off'], val);

/**
* Compare the given value against a custom list of `truthy` values.
*
* String values are not case sensitive.
* Check the given value against "false-values" and returns true if found to
* be ***NOT*** false. String values are not case sensitive.
*
* _1, '1', 'y', 'yes', 'on', 'true', true_
* @see toBoolean
*
* @pure
*
* @example
* isTrue('on');
* isTrue(' TrUe');
* // true
*
* isTrue('yes');
* // true
*
* isTrue('off');
* isTrue('any string');
* // true
*
* isTrue({});
* // true
*
* isTrue(false);
* // false
*
* isTrue('no');
* isTrue('');
* // false
*/
export const isTrue = (val: unknown) => test(trueValues, val);
export const isTrue = (val: unknown) => !isFalse(val);

/**
* Compare the given value against a custom list of `truthy` values.
*
* String values are not case sensitive.
*
* _1, '1', 'y', 'yes', 'on', 'true', true_
* Compare the given value against a custom list of 'On' values. String values
* are not case sensitive.
*
* @pure
*
Expand All @@ -128,36 +123,36 @@ export const isTrue = (val: unknown) => test(trueValues, val);
* // true
*
* isOn('yes');
* // true
* // false
*
* isOn('off');
* // false
*
* isOn('no');
* // false
*/
export const isOn = isTrue;
export const isOn = (val: unknown) => val === true || test(['on'], val);

/**
* Compare the given value against a custom list of `truthy` values.
*
* String values are not case sensitive.
*
* _1, '1', 'y', 'yes', 'on', 'true', true_
* Compare the given value against a custom list of 'Yes' values. String values
* are not case sensitive.
*
* @pure
*
* @example
* isYes('on');
* // true
* // false
*
* isYes('yes');
* // true
*
* isYes('Y');
* // true
*
* isYes('off');
* // false
*
* isYes('no');
* // false
*/
export const isYes = isTrue;
export const isYes = (val: unknown) => val === true || test(['y', 'yes'], val);
Loading

0 comments on commit 7b8e49a

Please sign in to comment.