Skip to content

Commit a416ce2

Browse files
authored
feat: boolean (#5)
1 parent 4257756 commit a416ce2

5 files changed

Lines changed: 443 additions & 0 deletions

File tree

__tests__/boolean.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { describe, expect, it } from "vitest";
2+
import { toBoolean, isTruthy, isFalsy } from "../src/common/utils/boolean.js";
3+
4+
describe("toBoolean", () => {
5+
describe("strings — truthy set", () => {
6+
it.each(["true", "t", "yes", "y", "on", "1"])('returns true for "%s"', value => {
7+
expect(toBoolean(value)).toBe(true);
8+
});
9+
10+
it.each(["TRUE", "True", "YES", "Yes", "ON", "On", "T", "Y"])(
11+
'returns true for uppercase/mixed "%s"',
12+
value => {
13+
expect(toBoolean(value)).toBe(true);
14+
}
15+
);
16+
17+
it.each([" true ", " 1 ", " yes "])('returns true for whitespace-padded "%s"', value => {
18+
expect(toBoolean(value)).toBe(true);
19+
});
20+
});
21+
22+
describe("strings — falsy set", () => {
23+
it.each(["false", "f", "no", "n", "off", "0", "", "banana", "2", "null"])(
24+
'returns false for "%s"',
25+
value => {
26+
expect(toBoolean(value)).toBe(false);
27+
}
28+
);
29+
});
30+
31+
describe("numbers", () => {
32+
it("returns true for 1", () => {
33+
expect(toBoolean(1)).toBe(true);
34+
});
35+
36+
it.each([0, 2, -1, 100, NaN, Infinity, -Infinity])("returns false for %s", value => {
37+
expect(toBoolean(value)).toBe(false);
38+
});
39+
});
40+
41+
describe("booleans", () => {
42+
it("returns true for true", () => {
43+
expect(toBoolean(true)).toBe(true);
44+
});
45+
46+
it("returns false for false", () => {
47+
expect(toBoolean(false)).toBe(false);
48+
});
49+
});
50+
51+
describe("other types", () => {
52+
it.each([null, undefined, {}, [], () => {}, Symbol("x")])(
53+
"returns false for non-string/number/boolean values",
54+
value => {
55+
expect(toBoolean(value)).toBe(false);
56+
}
57+
);
58+
});
59+
});
60+
61+
describe("isTruthy", () => {
62+
it("mirrors toBoolean for a truthy value", () => {
63+
expect(isTruthy("true")).toBe(true);
64+
expect(isTruthy(1)).toBe(true);
65+
expect(isTruthy(true)).toBe(true);
66+
});
67+
68+
it("mirrors toBoolean for a falsy value", () => {
69+
expect(isTruthy("false")).toBe(false);
70+
expect(isTruthy(0)).toBe(false);
71+
expect(isTruthy(null)).toBe(false);
72+
});
73+
});
74+
75+
describe("isFalsy", () => {
76+
it("is the inverse of toBoolean for a truthy value", () => {
77+
expect(isFalsy("true")).toBe(false);
78+
expect(isFalsy(1)).toBe(false);
79+
expect(isFalsy(true)).toBe(false);
80+
});
81+
82+
it("is the inverse of toBoolean for a falsy value", () => {
83+
expect(isFalsy("false")).toBe(true);
84+
expect(isFalsy(0)).toBe(true);
85+
expect(isFalsy(null)).toBe(true);
86+
});
87+
});
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
# Boolean Utility Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Add `toBoolean`, `isTruthy`, and `isFalsy` pure utility functions to `@webiny/stdlib` as a drop-in replacement for the `boolean` npm package.
6+
7+
**Architecture:** Three plain exported functions live in a new `src/common/utils/boolean.ts` file. `toBoolean` implements exact parity with the `boolean` package using `Object.prototype.toString` dispatch. `isTruthy` and `isFalsy` delegate to it. All three are re-exported from the existing `src/common/index.ts` barrel.
8+
9+
**Tech Stack:** TypeScript (tsgo / `@typescript/native-preview`), Vitest for tests.
10+
11+
---
12+
13+
## File Map
14+
15+
| Action | Path | Responsibility |
16+
|---|---|---|
17+
| Create | `src/common/utils/boolean.ts` | The three utility functions |
18+
| Modify | `src/common/index.ts` | Re-export `toBoolean`, `isTruthy`, `isFalsy` |
19+
| Create | `__tests__/boolean.test.ts` | Full test coverage |
20+
21+
---
22+
23+
### Task 1: Write the failing tests
24+
25+
**Files:**
26+
- Create: `__tests__/boolean.test.ts`
27+
28+
- [ ] **Step 1: Create `__tests__/boolean.test.ts`** with the full test suite
29+
30+
```ts
31+
import { describe, expect, it } from "vitest";
32+
import { toBoolean, isTruthy, isFalsy } from "@webiny/stdlib";
33+
34+
describe("toBoolean", () => {
35+
describe("strings — truthy set", () => {
36+
it.each(["true", "t", "yes", "y", "on", "1"])(
37+
'returns true for "%s"',
38+
value => {
39+
expect(toBoolean(value)).toBe(true);
40+
}
41+
);
42+
43+
it.each(["TRUE", "True", "YES", "Yes", "ON", "On", "T", "Y"])(
44+
'returns true for uppercase/mixed "%s"',
45+
value => {
46+
expect(toBoolean(value)).toBe(true);
47+
}
48+
);
49+
50+
it.each([" true ", " 1 ", " yes "])(
51+
'returns true for whitespace-padded "%s"',
52+
value => {
53+
expect(toBoolean(value)).toBe(true);
54+
}
55+
);
56+
});
57+
58+
describe("strings — falsy set", () => {
59+
it.each(["false", "f", "no", "n", "off", "0", "", "banana", "2", "null"])(
60+
'returns false for "%s"',
61+
value => {
62+
expect(toBoolean(value)).toBe(false);
63+
}
64+
);
65+
});
66+
67+
describe("numbers", () => {
68+
it("returns true for 1", () => {
69+
expect(toBoolean(1)).toBe(true);
70+
});
71+
72+
it.each([0, 2, -1, 100, NaN])(
73+
"returns false for %s",
74+
value => {
75+
expect(toBoolean(value)).toBe(false);
76+
}
77+
);
78+
});
79+
80+
describe("booleans", () => {
81+
it("returns true for true", () => {
82+
expect(toBoolean(true)).toBe(true);
83+
});
84+
85+
it("returns false for false", () => {
86+
expect(toBoolean(false)).toBe(false);
87+
});
88+
});
89+
90+
describe("other types", () => {
91+
it.each([null, undefined, {}, [], () => {}, Symbol("x")])(
92+
"returns false for non-string/number/boolean values",
93+
value => {
94+
expect(toBoolean(value)).toBe(false);
95+
}
96+
);
97+
});
98+
});
99+
100+
describe("isTruthy", () => {
101+
it("mirrors toBoolean for a truthy value", () => {
102+
expect(isTruthy("true")).toBe(true);
103+
expect(isTruthy(1)).toBe(true);
104+
expect(isTruthy(true)).toBe(true);
105+
});
106+
107+
it("mirrors toBoolean for a falsy value", () => {
108+
expect(isTruthy("false")).toBe(false);
109+
expect(isTruthy(0)).toBe(false);
110+
expect(isTruthy(null)).toBe(false);
111+
});
112+
});
113+
114+
describe("isFalsy", () => {
115+
it("is the inverse of toBoolean for a truthy value", () => {
116+
expect(isFalsy("true")).toBe(false);
117+
expect(isFalsy(1)).toBe(false);
118+
expect(isFalsy(true)).toBe(false);
119+
});
120+
121+
it("is the inverse of toBoolean for a falsy value", () => {
122+
expect(isFalsy("false")).toBe(true);
123+
expect(isFalsy(0)).toBe(true);
124+
expect(isFalsy(null)).toBe(true);
125+
});
126+
});
127+
```
128+
129+
- [ ] **Step 2: Run the tests to verify they fail**
130+
131+
```sh
132+
yarn test --reporter=verbose __tests__/boolean.test.ts
133+
```
134+
135+
Expected: all tests fail with `toBoolean is not a function` (or similar import error).
136+
137+
---
138+
139+
### Task 2: Implement the utility functions
140+
141+
**Files:**
142+
- Create: `src/common/utils/boolean.ts`
143+
144+
- [ ] **Step 1: Create `src/common/utils/boolean.ts`**
145+
146+
```ts
147+
/**
148+
* Coerces a value to boolean with the same rules as the `boolean` npm package.
149+
*
150+
* Truthy strings (case-insensitive, trimmed): "true", "t", "yes", "y", "on", "1".
151+
* Truthy number: 1 only.
152+
* Booleans pass through.
153+
* Everything else returns false.
154+
*/
155+
export function toBoolean(value: unknown): boolean {
156+
switch (Object.prototype.toString.call(value)) {
157+
case "[object String]":
158+
return ["true", "t", "yes", "y", "on", "1"].includes(
159+
(value as string).trim().toLowerCase()
160+
);
161+
case "[object Number]":
162+
return (value as number).valueOf() === 1;
163+
case "[object Boolean]":
164+
return (value as boolean).valueOf();
165+
default:
166+
return false;
167+
}
168+
}
169+
170+
/** Returns `toBoolean(value)`. Readable alias for use in predicates. */
171+
export function isTruthy(value: unknown): boolean {
172+
return toBoolean(value);
173+
}
174+
175+
/** Returns `!toBoolean(value)`. Readable inverse of `isTruthy`. */
176+
export function isFalsy(value: unknown): boolean {
177+
return !toBoolean(value);
178+
}
179+
```
180+
181+
- [ ] **Step 2: Run the tests to verify they pass**
182+
183+
```sh
184+
yarn test --reporter=verbose __tests__/boolean.test.ts
185+
```
186+
187+
Expected: all tests pass.
188+
189+
---
190+
191+
### Task 3: Export from the common barrel
192+
193+
**Files:**
194+
- Modify: `src/common/index.ts`
195+
196+
- [ ] **Step 1: Add exports to `src/common/index.ts`**
197+
198+
Add this line at the end of the file:
199+
200+
```ts
201+
export { toBoolean, isTruthy, isFalsy } from "./utils/boolean.js";
202+
```
203+
204+
The full file should now look like:
205+
206+
```ts
207+
export { Result, ResultAsync, BaseError, createAbstraction, createFeature } from "./core/index.js";
208+
export type { ErrorInput } from "./core/index.js";
209+
export { Logger, type ILogger } from "./features/Logger/abstractions/Logger.js";
210+
export { ConsoleLoggerConfig } from "./features/Logger/abstractions/ConsoleLoggerConfig.js";
211+
export { ConsoleLoggerFeature } from "./features/Logger/feature.js";
212+
export { ConsoleLogger } from "./features/Logger/ConsoleLogger.js";
213+
export {
214+
Cache,
215+
AsyncCache,
216+
CacheError,
217+
MemoryCacheFeature,
218+
AsyncMemoryCacheFeature
219+
} from "./features/Cache/index.js";
220+
export type { ICache, IAsyncCache } from "./features/Cache/index.js";
221+
export { toBoolean, isTruthy, isFalsy } from "./utils/boolean.js";
222+
```
223+
224+
- [ ] **Step 2: Run the full test suite to confirm nothing regressed**
225+
226+
```sh
227+
yarn test
228+
```
229+
230+
Expected: all tests pass.
231+
232+
---
233+
234+
### Task 4: Run the full pre-commit chain and commit
235+
236+
**Files:** none new — validation only
237+
238+
- [ ] **Step 1: Run the full pre-commit chain**
239+
240+
```sh
241+
yarn format:fix && yarn lint:fix && yarn typecheck && yarn build && yarn test:coverage
242+
```
243+
244+
Expected: all five steps exit with code 0, zero errors, zero warnings.
245+
246+
- [ ] **Step 2: Stage and commit**
247+
248+
```sh
249+
git add src/common/utils/boolean.ts src/common/index.ts __tests__/boolean.test.ts docs/superpowers/specs/2026-05-09-boolean-utility-design.md docs/superpowers/plans/2026-05-09-boolean-utility.md
250+
git commit -m "$(cat <<'EOF'
251+
feat(stdlib/common): add toBoolean, isTruthy, isFalsy utilities
252+
253+
Replaces the `boolean` npm package with a native @webiny/stdlib
254+
implementation. Exact coercion parity: truthy strings are
255+
"true", "t", "yes", "y", "on", "1" (case-insensitive, trimmed);
256+
truthy number is 1 only; booleans pass through.
257+
isTruthy and isFalsy are readable wrappers on top.
258+
259+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
260+
EOF
261+
)"
262+
```

0 commit comments

Comments
 (0)