Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: toBoolean does more; narrower predicates #125

Merged
merged 1 commit into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/young-kiwis-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@accelint/converters": minor
"@accelint/predicates": minor
---

The `toBoolean` function (packages/converters) centralizes the logic for coercing a value
to a boolean which enables the predicate functions (packages/predicates/src/is-noyes) to
be more specific in what they compare against rather than them simply being alias names
to broad validation. The available predicates are now:

- `isAnyFalsy`
- `isAnyTruthy`
- `isFalse`
- `isTrue`
- `isOn`
- `isOff`
- `isNo`
- `isYes`
1 change: 0 additions & 1 deletion packages/converters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,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: 44 additions & 18 deletions packages/converters/src/to-boolean/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,57 @@
import { expect, it, describe } from 'vitest';
import { toBoolean } from './';

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

const falsy = [
'',
0,
0.0,
'0',
'off',
'false',
'no',
'0.000',
'0000.000',
false,
'false',
' FaLsE ',
void 0,
Number.NaN,
null,
undefined,
];
const truthy = [
[],
1,
'1',
true,
'true',
{},
'OFF',
'NO',
'FALSE',
'any non-empty string',
// 'Yes',
// 'yes',
// 'No',
// 'no',
// 'off',
// 'Off',
// 'OFF',
// 'On',
// 'on',
INFINITY,
-INFINITY,
Number.POSITIVE_INFINITY,
Number.NEGATIVE_INFINITY,
/abc/,
new Date(),
new Error('Fun times.'),
() => void 0,
];

describe('toBoolean', () => {
for (const item of truthy) {
it(`should return true for ${item}`, () => {
expect(toBoolean(item)).toBeTruthy();
});
}
it.each(falsy)('%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);
});
});
33 changes: 16 additions & 17 deletions packages/converters/src/to-boolean/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,29 @@
* governing permissions and limitations under the License.
*/

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

/**
* Compare the given value against a custom list of `truthy` values.
* Returns true for any value not found to be a "false" value.
*
* String values are not case sensitive.
* **"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
*
* _1, '1', 'on', 'true', 'yes', true_
* For more restrictive comparisons against: true, false, on, off, yes, no; see
* the predicates package (\@accelint/predicates).
*
* @pure
*
* @example
* toBoolean('on');
* // true
*
* toBoolean('yes');
* // true
*
* toBoolean('off');
* // false
*
* toBoolean('no');
* // false
* toBoolean(1); // true
* toBoolean(' FaLsE '); // false
* toBoolean(' true'); // true
* toBoolean('000.000'); // false
*/
export function toBoolean(val: unknown) {
return isTrue(val);
return !(
brandonjpierce marked this conversation as resolved.
Show resolved Hide resolved
!val ||
`${val}`.trim().toLowerCase() === 'false' ||
Number.parseFloat(`${val}`) === 0
);
}
11 changes: 10 additions & 1 deletion packages/predicates/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ export { isBbox } from './is-bbox';
export { isLatitude } from './is-latitude';
export { isLongitude } from './is-longitude';
export { isNothing } from './is-nothing';
export { isFalse, isNo, isOff, isOn, isTrue, isYes } from './is-noyes';
export {
isAnyFalsy,
isAnyTruthy,
isFalse,
isNo,
isOff,
isOn,
isTrue,
isYes,
} from './is-noyes';
export {
isFiniteNumber,
isFiniteNumeric,
Expand Down
53 changes: 35 additions & 18 deletions packages/predicates/src/is-noyes/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,43 @@
* 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 {
isAnyFalsy,
isAnyTruthy,
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();
});
}
const falsy = [0, '', false, ' false ', null, undefined, Number.NaN];
const truthy = [1, true, ' true '];

for (const item of falsey) {
it(`should return false for ${item}`, () => {
expect(isFalse(item)).toBeTruthy();
expect(isOff(item)).toBeTruthy();
expect(isNo(item)).toBeTruthy();
describe('boolean predicates', () => {
describe.each`
predicate | positive | negative
${isAnyFalsy} | ${[...falsy, 'no', 'off']} | ${[...truthy, 'on', 'yes']}
${isAnyTruthy} | ${[...truthy, 'on', 'yes']} | ${[...falsy, 'no', 'off']}
${isFalse} | ${[...falsy, ' false', '0']} | ${[...truthy, 'o', 'O', 'true', 'string with false', '0.00']}
${isNo} | ${[...falsy, ' n', 'N ', 'no', 'NO']} | ${[...truthy, 'yes', 'string with no']}
${isOff} | ${[...falsy, ' off', 'OFF ']} | ${[...truthy, 'on', 'string with off', 'of']}
${isOn} | ${[...truthy, ' on', 'ON ']} | ${[...falsy, 'of', 'string with on', 'o']}
${isTrue} | ${[...truthy, ' true']} | ${[...falsy, 'any string', {}, [], /abc/]}
${isYes} | ${[...truthy, ' yes', 'YeS ', 'y']} | ${[...falsy, 'no', 'string with yes', 2]}
Copy link
Contributor

@orteth01 orteth01 Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious: does 'YeS ' reflect a value we actually see in the real world?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YeS, and also yES, and also YES πŸ˜†

`('$predicate.name', ({ predicate, ...lists }: Config) => {
describe.each(['positive', 'negative'])('%s matches', (list) => {
it.each(lists[list] as unknown[])('%j', (val) => {
expect(predicate(val)).toBe(list === 'positive');
});
});
}
});
});
Loading
Loading