diff --git a/src/random.ts b/src/random.ts index 96128dd5c40..c349c92fab4 100644 --- a/src/random.ts +++ b/src/random.ts @@ -527,6 +527,64 @@ export class Random { return wholeString; } + /** + * Generates a given length string of digits. + * + * @param length The number of digits to generate. Defaults to `1`. + * @param options The options to use. Defaults to `{}`. + * @param options.allowLeadingZeros If true, leading zeros will be allowed. Defaults to `false`. + * @param options.bannedDigits An array of digits which should be banned in the generated string. Defaults to `[]`. + * + * @example + * faker.random.numeric() // '2' + * faker.random.numeric(5) // '31507' + * faker.random.numeric(42) // '56434563150765416546479875435481513188548' + * faker.random.numeric(42, { allowLeadingZeros: true }) // '00564846278453876543517840713421451546115' + * faker.random.numeric(6, { bannedDigits: ['0'] }) // '943228' + */ + numeric( + length: number = 1, + options: { + allowLeadingZeros?: boolean; + bannedDigits?: readonly string[]; + } = {} + ): string { + if (length <= 0) { + return ''; + } + + const { allowLeadingZeros = false, bannedDigits = [] } = options; + + const allowedDigits = '0123456789' + .split('') + .filter((digit) => !bannedDigits.includes(digit)); + + if ( + allowedDigits.length === 0 || + (allowedDigits.length === 1 && + !allowLeadingZeros && + allowedDigits[0] === '0') + ) { + throw new FakerError( + 'Unable to generate numeric string, because all possible digits are banned.' + ); + } + + let result = ''; + + if (!allowLeadingZeros && !bannedDigits.includes('0')) { + result += this.arrayElement( + allowedDigits.filter((digit) => digit !== '0') + ); + } + + while (result.length < length) { + result += this.arrayElement(allowedDigits); + } + + return result; + } + /** * Returns a hexadecimal number. * diff --git a/test/random.spec.ts b/test/random.spec.ts index 784e12cea9e..3e17aaef738 100644 --- a/test/random.spec.ts +++ b/test/random.spec.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { faker } from '../src'; +import { faker, FakerError } from '../src'; import { times } from './support/times'; describe('random', () => { @@ -304,6 +304,82 @@ describe('random', () => { }); }); + describe('numeric', () => { + it('should return single digit when no length provided', () => { + const actual = faker.random.numeric(); + + expect(actual).toHaveLength(1); + expect(actual).toMatch(/^[1-9]$/); + }); + + it.each(times(100))( + 'should generate random value with a length of %s', + (length) => { + const actual = faker.random.numeric(length); + + expect(actual).toHaveLength(length); + expect(actual).toMatch(/^[1-9][0-9]*$/); + } + ); + + it('should return empty string with a length of 0', () => { + const actual = faker.random.numeric(0); + + expect(actual).toHaveLength(0); + }); + + it('should return empty string with a negative length', () => { + const actual = faker.random.numeric(-10); + + expect(actual).toHaveLength(0); + }); + + it('should return a valid numeric string with provided length', () => { + const actual = faker.random.numeric(1000); + + expect(actual).toBeTypeOf('string'); + expect(actual).toHaveLength(1000); + expect(actual).toMatch(/^[1-9][0-9]+$/); + }); + + it('should allow leading zeros via option', () => { + const actual = faker.random.numeric(15, { allowLeadingZeros: true }); + + expect(actual).toMatch(/^[0-9]+$/); + }); + + it('should allow leading zeros via option and all other digits banned', () => { + const actual = faker.random.numeric(4, { + allowLeadingZeros: true, + bannedDigits: '123456789'.split(''), + }); + + expect(actual).toBe('0000'); + }); + + it('should fail on leading zeros via option and all other digits banned', () => { + expect(() => + faker.random.numeric(4, { + allowLeadingZeros: false, + bannedDigits: '123456789'.split(''), + }) + ).toThrowError( + new FakerError( + 'Unable to generate numeric string, because all possible digits are banned.' + ) + ); + }); + + it('should ban all digits passed via bannedDigits', () => { + const actual = faker.random.numeric(1000, { + bannedDigits: 'c84U1'.split(''), + }); + + expect(actual).toHaveLength(1000); + expect(actual).toMatch(/^[0235679]{1000}$/); + }); + }); + describe('deprecation warnings', () => { it.each([ ['number', 'datatype.number'],