Skip to content
Merged

Dev #190

Show file tree
Hide file tree
Changes from 6 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
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,27 +87,27 @@
"devDependencies": {
"@commitlint/config-conventional": "^19.8.1",
"@commitlint/types": "^19.8.1",
"@types/node": "^22.15.18",
"@vitest/coverage-v8": "^3.1.3",
"@types/node": "^22.15.29",
"@vitest/coverage-v8": "^3.1.4",
"@webdeveric/eslint-config-ts": "^0.11.0",
"@webdeveric/prettier-config": "^0.3.0",
"commitlint": "^19.8.1",
"commitlint-plugin-cspell": "^0.2.0",
"conventional-changelog-conventionalcommits": "^8.0.0",
"cspell": "^9.0.1",
"conventional-changelog-conventionalcommits": "^9.0.0",
"cspell": "^9.0.2",
"eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.5",
"eslint-import-resolver-typescript": "^4.3.4",
"eslint-import-resolver-typescript": "^4.4.2",
"eslint-plugin-import": "^2.31.0",
"husky": "^9.1.7",
"jsdom": "^26.1.0",
"lint-staged": "^16.0.0",
"lint-staged": "^16.1.0",
"prettier": "^3.5.3",
"rimraf": "^6.0.1",
"semantic-release": "^24.2.3",
"semantic-release": "^24.2.5",
"typescript": "^5.8.3",
"validate-package-exports": "^0.9.0",
"vitest": "^3.1.3"
"vitest": "^3.1.4"
},
"pnpm": {
"onlyBuiltDependencies": [
Expand Down
1,471 changes: 753 additions & 718 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/counter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function* counter(start = 0, end = Infinity, step = 1): Generator<number> {
export function* counter(start = 0, end = Infinity, step = 1): Generator<number, number, number | undefined> {
if (step === 0) {
throw new TypeError('step cannot be zero');
}
Expand All @@ -10,4 +10,6 @@ export function* counter(start = 0, end = Infinity, step = 1): Generator<number>

count += step;
} while (step > 0 ? count <= end : count >= end);

return count - step;
}
16 changes: 16 additions & 0 deletions src/getRandomItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { randomInt } from './randomInt.js';

export function getRandomItem(input: []): undefined;

export function getRandomItem<T>(input: [T, ...T[]]): T;

export function getRandomItem<T>(input: T[]): T | undefined {
switch (input.length) {
case 0:
return;
case 1:
return input[0];
default:
return input[randomInt(0, input.length)];
}
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export * from './getMinValue.js';
export * from './getOwnKeys.js';
export * from './getOwnPaths.js';
export * from './getOwnProperties.js';
export * from './getPackageName.js';
export * from './getPaths.js';
export * from './getRandomItem.js';
export * from './getType.js';
export * from './inRange.js';
export * from './isEmpty.js';
Expand All @@ -41,6 +43,7 @@ export * from './looksLikeURL.js';
export * from './normalize.js';
export * from './parseNumber.js';
export * from './prefix.js';
export * from './randomInt.js';
export * from './redactCredentialsInURL.js';
export * from './resultify.js';
export * from './secToString.js';
Expand Down
37 changes: 37 additions & 0 deletions src/randomInt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { assertIsInteger } from './assertion/assertIsInteger.js';
import { isObject } from './predicate/isObject.js';

import type { RequireAtLeastOne } from './types/records.js';
import type { Pretty } from './types/utils.js';

export type RandomIntOptions = Pretty<
RequireAtLeastOne<{
// Inclusive
min: number;
// Exclusive
max: number;
}>
>;

export function randomInt(options?: RandomIntOptions): number;

export function randomInt(min: number, max?: number): number;

export function randomInt(arg1?: number | RandomIntOptions, arg2?: number): number {
const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = isObject(arg1)
? arg1
: { min: arg1, max: arg2 };

assertIsInteger(min, 'min must be an integer');
assertIsInteger(max, 'max must be an integer');

if (min === max) {
return min;
}

if (min > max) {
throw new RangeError('min must be less than max');
}

return Math.floor(Math.random() * (max - min)) + min;
}
15 changes: 15 additions & 0 deletions test/counter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,19 @@ describe('counter()', () => {
it('Throws when given a zero step', () => {
expect(() => [...counter(0, 10, 0)]).toThrow();
});

it('next() returns a number', () => {
const generator = counter();

expect(generator.next().value).toBe(0);
expect(generator.next().value).toBe(1);
expect(generator.next().value).toBe(2);
});

it('Can return()', () => {
const generator = counter();

expect(generator.return(10).value).toBe(10);
expect(generator.next()).toEqual({ done: true, value: undefined });
});
});
15 changes: 15 additions & 0 deletions test/getRandomItem.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, expect, it } from 'vitest';

import { getRandomItem } from '../src/getRandomItem.js';

describe('getRandomItem()', () => {
it('Returns a random item from an array', () => {
expect(getRandomItem([1])).toEqual(1);

expect(getRandomItem([1, 2, 3])).toBeTypeOf('number');
});

it('Returns undefined when input is empty', () => {
expect(getRandomItem([])).toBeUndefined();
});
});
42 changes: 42 additions & 0 deletions test/randomInt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest';

import { randomInt } from '../src/randomInt.js';

describe('randomInt()', () => {
it('Returns early when min and max are the same', () => {
expect(randomInt(0, 0)).toEqual(0);
});

it('Returns a random integer', () => {
const value = randomInt(100, 200);

expect(value).greaterThanOrEqual(100);
expect(value).lessThan(200);
});

it('The returns value is inclusive of min and exclusive of max', () => {
expect(randomInt(0, 1)).toEqual(0);
expect(randomInt(-2, -1)).toEqual(-2);
expect(randomInt(10, 11)).toEqual(10);
});

it('Takes an options object', () => {
const value = randomInt({ max: 1000 });

expect(value).greaterThanOrEqual(Number.MIN_SAFE_INTEGER);
expect(value).lessThan(1000);
});

it('Defaults to SAFE_INTEGER range', () => {
const value = randomInt();

expect(value).greaterThanOrEqual(Number.MIN_SAFE_INTEGER);
expect(value).lessThan(Number.MAX_SAFE_INTEGER);
});

it('Throws when min > max', () => {
expect(() => {
randomInt(100, 0);
}).toThrow();
});
});