diff --git a/docs/.vitepress/api-pages.ts b/docs/.vitepress/api-pages.ts index 575ec823897..a4446a22ffe 100644 --- a/docs/.vitepress/api-pages.ts +++ b/docs/.vitepress/api-pages.ts @@ -22,6 +22,7 @@ export const apiPages = [ { text: 'Phone', link: '/api/phone.html' }, { text: 'Random', link: '/api/random.html' }, { text: 'Science', link: '/api/science.html' }, + { text: 'String', link: '/api/string.html' }, { text: 'System', link: '/api/system.html' }, { text: 'Vehicle', link: '/api/vehicle.html' }, { text: 'Word', link: '/api/word.html' }, diff --git a/src/faker.ts b/src/faker.ts index 5c2f7e79058..3452ae49866 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -24,6 +24,7 @@ import { PersonModule } from './modules/person'; import { PhoneModule } from './modules/phone'; import { RandomModule } from './modules/random'; import { ScienceModule } from './modules/science'; +import { StringModule } from './modules/string'; import { SystemModule } from './modules/system'; import { VehicleModule } from './modules/vehicle'; import { WordModule } from './modules/word'; @@ -102,6 +103,7 @@ export class Faker { readonly person: PersonModule = new PersonModule(this); readonly phone: PhoneModule = new PhoneModule(this); readonly science: ScienceModule = new ScienceModule(this); + readonly string: StringModule = new StringModule(this); readonly system: SystemModule = new SystemModule(this); readonly vehicle: VehicleModule = new VehicleModule(this); readonly word: WordModule = new WordModule(this); diff --git a/src/index.ts b/src/index.ts index bbd419bd97f..f0b87953a2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -67,6 +67,7 @@ export type { export type { PhoneModule } from './modules/phone'; export type { RandomModule } from './modules/random'; export type { ChemicalElement, ScienceModule, Unit } from './modules/science'; +export type { StringModule } from './modules/string'; export type { SystemModule } from './modules/system'; export type { VehicleModule } from './modules/vehicle'; export type { WordModule } from './modules/word'; diff --git a/src/modules/color/index.ts b/src/modules/color/index.ts index c6dbb0a476e..4fd79a063e9 100644 --- a/src/modules/color/index.ts +++ b/src/modules/color/index.ts @@ -313,7 +313,7 @@ export class ColorModule { let color: string | number[]; let cssFunction: CSSFunction = 'rgb'; if (format === 'hex') { - color = this.faker.datatype.hexadecimal({ + color = this.faker.string.hexadecimal({ length: includeAlpha ? 8 : 6, prefix: '', }); diff --git a/src/modules/database/index.ts b/src/modules/database/index.ts index 8a4291f9ceb..d0985c53f4f 100644 --- a/src/modules/database/index.ts +++ b/src/modules/database/index.ts @@ -79,9 +79,9 @@ export class DatabaseModule { * @since 6.2.0 */ mongodbObjectId(): string { - return this.faker.datatype.hexadecimal({ + return this.faker.string.hexadecimal({ length: 24, - case: 'lower', + casing: 'lower', prefix: '', }); } diff --git a/src/modules/datatype/index.ts b/src/modules/datatype/index.ts index 4c522d53b75..41c5c3e4eb2 100644 --- a/src/modules/datatype/index.ts +++ b/src/modules/datatype/index.ts @@ -1,5 +1,6 @@ import type { Faker } from '../..'; import { FakerError } from '../../errors/faker-error'; +import { deprecated } from '../../internal/deprecated'; import type { MersenneModule } from '../../internal/mersenne/mersenne'; /** @@ -145,48 +146,46 @@ export class DatatypeModule { * * @param length Length of the generated string. Max length is `2^20`. Defaults to `10`. * + * @see faker.string.sample() + * * @example * faker.datatype.string() // 'Zo!.:*e>wR' * faker.datatype.string(5) // '6Bye8' * * @since 5.5.0 + * + * @deprecated Use faker.string.sample() instead. */ string(length = 10): string { - const maxLength = Math.pow(2, 20); - if (length >= maxLength) { - length = maxLength; - } - - const charCodeOption = { - min: 33, - max: 125, - }; - - let returnString = ''; - - for (let i = 0; i < length; i++) { - returnString += String.fromCharCode(this.number(charCodeOption)); - } - - return returnString; + deprecated({ + deprecated: 'faker.datatype.string()', + proposed: 'faker.string.sample()', + since: '8.0', + until: '9.0', + }); + return this.faker.string.sample(length); } /** * Returns a UUID v4 ([Universally Unique Identifier](https://en.wikipedia.org/wiki/Universally_unique_identifier)). * + * @see faker.string.uuid() + * * @example * faker.datatype.uuid() // '4136cd0b-d90b-4af7-b485-5d1ded8db252' * * @since 5.5.0 + * + * @deprecated Use faker.string.uuid() instead. */ uuid(): string { - const RFC4122_TEMPLATE = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; - const replacePlaceholders = (placeholder) => { - const random = this.number({ min: 0, max: 15 }); - const value = placeholder === 'x' ? random : (random & 0x3) | 0x8; - return value.toString(16); - }; - return RFC4122_TEMPLATE.replace(/[xy]/g, replacePlaceholders); + deprecated({ + deprecated: 'faker.datatype.uuid()', + proposed: 'faker.string.uuid()', + since: '8.0', + until: '9.0', + }); + return this.faker.string.uuid(); } /** @@ -209,6 +208,8 @@ export class DatatypeModule { * @param options.prefix Prefix for the generated number. Defaults to `'0x'`. * @param options.case Case of the generated number. Defaults to `'mixed'`. * + * @see faker.string.hexadecimal() + * * @example * faker.datatype.hexadecimal() // '0xB' * faker.datatype.hexadecimal({ length: 10 }) // '0xaE13d044cB' @@ -220,6 +221,8 @@ export class DatatypeModule { * faker.datatype.hexadecimal({ length: 10, prefix: '0x', case: 'mixed' }) // '0xAdE330a4D1' * * @since 6.1.2 + * + * @deprecated Use `faker.string.hexadecimal()` instead. */ hexadecimal( options: { @@ -228,44 +231,13 @@ export class DatatypeModule { case?: 'lower' | 'upper' | 'mixed'; } = {} ): string { - const { length = 1, prefix = '0x', case: letterCase = 'mixed' } = options; - - let wholeString = ''; - - for (let i = 0; i < length; i++) { - wholeString += this.faker.helpers.arrayElement([ - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - ]); - } - - if (letterCase === 'upper') { - wholeString = wholeString.toUpperCase(); - } else if (letterCase === 'lower') { - wholeString = wholeString.toLowerCase(); - } - - return `${prefix}${wholeString}`; + deprecated({ + deprecated: 'faker.datatype.hexadecimal()', + proposed: 'faker.string.hexadecimal()', + since: '8.0', + until: '9.0', + }); + return this.faker.string.hexadecimal({ ...options, casing: options.case }); } /** @@ -281,7 +253,9 @@ export class DatatypeModule { const returnObject: Record = {}; properties.forEach((prop) => { - returnObject[prop] = this.boolean() ? this.string() : this.number(); + returnObject[prop] = this.boolean() + ? this.faker.string.sample() + : this.number(); }); return JSON.stringify(returnObject); @@ -300,7 +274,7 @@ export class DatatypeModule { */ array(length = 10): Array { return Array.from({ length }).map(() => - this.boolean() ? this.string() : this.number() + this.boolean() ? this.faker.string.sample() : this.number() ); } @@ -356,7 +330,8 @@ export class DatatypeModule { const offset = BigInt( - this.faker.random.numeric(delta.toString(10).length, { + this.faker.string.numeric({ + length: delta.toString(10).length, allowLeadingZeros: true, }) ) % diff --git a/src/modules/finance/index.ts b/src/modules/finance/index.ts index 7612eedb261..3c60ec41d06 100644 --- a/src/modules/finance/index.ts +++ b/src/modules/finance/index.ts @@ -235,7 +235,8 @@ export class FinanceModule { let address = this.faker.helpers.arrayElement(['1', '3']); - address += this.faker.random.alphaNumeric(addressLength, { + address += this.faker.string.alphanumeric({ + length: addressLength, casing: 'mixed', bannedChars: '0OIl', }); @@ -353,9 +354,9 @@ export class FinanceModule { * @since 5.0.0 */ ethereumAddress(): string { - const address = this.faker.datatype.hexadecimal({ + const address = this.faker.string.hexadecimal({ length: 40, - case: 'lower', + casing: 'lower', }); return address; } @@ -446,15 +447,18 @@ export class FinanceModule { ): string { const { includeBranchCode = this.faker.datatype.boolean() } = options; - const bankIdentifier = this.faker.random.alpha({ - count: 4, + const bankIdentifier = this.faker.string.alpha({ + length: 4, casing: 'upper', }); const countryCode = this.faker.helpers.arrayElement(iban.iso3166); - const locationCode = this.faker.random.alphaNumeric(2, { casing: 'upper' }); + const locationCode = this.faker.string.alphanumeric({ + length: 2, + casing: 'upper', + }); const branchCode = includeBranchCode ? this.faker.datatype.boolean() - ? this.faker.random.alphaNumeric(3, { casing: 'upper' }) + ? this.faker.string.alphanumeric({ length: 3, casing: 'upper' }) : 'XXX' : ''; diff --git a/src/modules/git/index.ts b/src/modules/git/index.ts index cd9e7de01fa..78cf0c684b6 100644 --- a/src/modules/git/index.ts +++ b/src/modules/git/index.ts @@ -100,9 +100,9 @@ export class GitModule { * @since 5.0.0 */ commitSha(): string { - return this.faker.datatype.hexadecimal({ + return this.faker.string.hexadecimal({ length: 40, - case: 'lower', + casing: 'lower', prefix: '', }); } @@ -116,9 +116,9 @@ export class GitModule { * @since 5.0.0 */ shortSha(): string { - return this.faker.datatype.hexadecimal({ + return this.faker.string.hexadecimal({ length: 7, - case: 'lower', + casing: 'lower', prefix: '', }); } diff --git a/src/modules/random/index.ts b/src/modules/random/index.ts index e79a2e70bf2..7f567edf2e1 100644 --- a/src/modules/random/index.ts +++ b/src/modules/random/index.ts @@ -1,97 +1,12 @@ import type { Faker } from '../..'; -import { FakerError } from '../../errors/faker-error'; +import { deprecated } from '../../internal/deprecated'; import type { LiteralUnion } from '../../utils/types'; - -export type Casing = 'upper' | 'lower' | 'mixed'; - -const UPPER_CHARS: readonly string[] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); -const LOWER_CHARS: readonly string[] = 'abcdefghijklmnopqrstuvwxyz'.split(''); -const DIGIT_CHARS: readonly string[] = '0123456789'.split(''); - -export type LowerAlphaChar = - | 'a' - | 'b' - | 'c' - | 'd' - | 'e' - | 'f' - | 'g' - | 'h' - | 'i' - | 'j' - | 'k' - | 'l' - | 'm' - | 'n' - | 'o' - | 'p' - | 'q' - | 'r' - | 's' - | 't' - | 'u' - | 'v' - | 'w' - | 'x' - | 'y' - | 'z'; - -export type UpperAlphaChar = - | 'A' - | 'B' - | 'C' - | 'D' - | 'E' - | 'F' - | 'G' - | 'H' - | 'I' - | 'J' - | 'K' - | 'L' - | 'M' - | 'N' - | 'O' - | 'P' - | 'Q' - | 'R' - | 'S' - | 'T' - | 'U' - | 'V' - | 'W' - | 'X' - | 'Y' - | 'Z'; - -export type NumericChar = - | '0' - | '1' - | '2' - | '3' - | '4' - | '5' - | '6' - | '7' - | '8' - | '9'; - -export type AlphaChar = LowerAlphaChar | UpperAlphaChar; -export type AlphaNumericChar = AlphaChar | NumericChar; - -/** - * Method to reduce array of characters. - * - * @param arr existing array of characters - * @param values array of characters which should be removed - * @returns new array without banned characters - */ -function arrayRemove(arr: T[], values: readonly T[]): T[] { - values.forEach((value) => { - arr = arr.filter((ele) => ele !== value); - }); - return arr; -} +import type { + AlphaChar, + AlphaNumericChar, + Casing, + NumericChar, +} from '../string'; /** * Generates random values of different kinds. @@ -267,12 +182,16 @@ export class RandomModule { * @param options.casing The casing of the characters. Defaults to `'mixed'`. * @param options.bannedChars An array with characters to exclude. Defaults to `[]`. * + * @see faker.string.alpha() + * * @example * faker.random.alpha() // 'b' * faker.random.alpha(10) // 'qccrabobaf' * faker.random.alpha({ count: 5, casing: 'upper', bannedChars: ['A'] }) // 'DTCIC' * * @since 5.0.0 + * + * @deprecated Use faker.string.alpha() instead. */ alpha( options: @@ -283,50 +202,16 @@ export class RandomModule { bannedChars?: readonly LiteralUnion[] | string; } = {} ): string { + deprecated({ + deprecated: 'faker.random.alpha()', + proposed: 'faker.string.alpha()', + since: '8.0', + until: '9.0', + }); if (typeof options === 'number') { - options = { - count: options, - }; - } - - const { count = 1 } = options; - let { bannedChars = [] } = options; - - if (typeof bannedChars === 'string') { - bannedChars = bannedChars.split(''); - } - - if (count <= 0) { - return ''; - } - - const { casing = 'mixed' } = options; - - let charsArray: string[]; - switch (casing) { - case 'upper': - charsArray = [...UPPER_CHARS]; - break; - case 'lower': - charsArray = [...LOWER_CHARS]; - break; - case 'mixed': - default: - charsArray = [...LOWER_CHARS, ...UPPER_CHARS]; - break; + return this.faker.string.alpha(options); } - - charsArray = arrayRemove(charsArray, bannedChars); - - if (charsArray.length === 0) { - throw new FakerError( - 'Unable to generate string, because all possible characters are banned.' - ); - } - - return Array.from({ length: count }, () => - this.faker.helpers.arrayElement(charsArray) - ).join(''); + return this.faker.string.alpha({ ...options, length: options.count }); } /** @@ -337,12 +222,16 @@ export class RandomModule { * @param options.casing The casing of the characters. Defaults to `'lower'`. * @param options.bannedChars An array of characters and digits which should be banned in the generated string. Defaults to `[]`. * + * @see faker.string.alphanumeric() + * * @example * faker.random.alphaNumeric() // '2' * faker.random.alphaNumeric(5) // '3e5v7' * faker.random.alphaNumeric(5, { bannedChars: ["a"] }) // 'xszlm' * * @since 3.1.0 + * + * @deprecated Use faker.string.alphanumeric() instead. */ alphaNumeric( count: number = 1, @@ -351,46 +240,17 @@ export class RandomModule { bannedChars?: readonly LiteralUnion[] | string; } = {} ): string { - if (count <= 0) { - return ''; - } - - const { - // Switch to 'mixed' with v8.0 - casing = 'lower', - } = options; - let { bannedChars = [] } = options; - - if (typeof bannedChars === 'string') { - bannedChars = bannedChars.split(''); - } - - let charsArray = [...DIGIT_CHARS]; - - switch (casing) { - case 'upper': - charsArray.push(...UPPER_CHARS); - break; - case 'lower': - charsArray.push(...LOWER_CHARS); - break; - case 'mixed': - default: - charsArray.push(...LOWER_CHARS, ...UPPER_CHARS); - break; - } - - charsArray = arrayRemove(charsArray, bannedChars); - - if (charsArray.length === 0) { - throw new FakerError( - 'Unable to generate string, because all possible characters are banned.' - ); - } - - return Array.from({ length: count }, () => - this.faker.helpers.arrayElement(charsArray) - ).join(''); + deprecated({ + deprecated: 'faker.random.alphaNumeric()', + proposed: 'faker.string.alphanumeric()', + since: '8.0', + until: '9.0', + }); + return this.faker.string.alphanumeric({ + length: count, + bannedChars: options.bannedChars, + casing: options.casing, + }); } /** @@ -401,6 +261,8 @@ export class RandomModule { * @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 `[]`. * + * @see faker.string.numeric() + * * @example * faker.random.numeric() // '2' * faker.random.numeric(5) // '31507' @@ -409,6 +271,8 @@ export class RandomModule { * faker.random.numeric(6, { bannedDigits: ['0'] }) // '943228' * * @since 6.3.0 + * + * @deprecated Use faker.string.numeric() instead. */ numeric( length: number = 1, @@ -417,44 +281,16 @@ export class RandomModule { bannedDigits?: readonly LiteralUnion[] | string; } = {} ): string { - if (length <= 0) { - return ''; - } - - const { allowLeadingZeros = false } = options; - let { bannedDigits = [] } = options; - - if (typeof bannedDigits === 'string') { - bannedDigits = bannedDigits.split(''); - } - - const allowedDigits = DIGIT_CHARS.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.faker.helpers.arrayElement( - allowedDigits.filter((digit) => digit !== '0') - ); - } - - while (result.length < length) { - result += this.faker.helpers.arrayElement(allowedDigits); - } - - return result; + deprecated({ + deprecated: 'faker.random.numeric()', + proposed: 'faker.string.numeric()', + since: '8.0', + until: '9.0', + }); + return this.faker.string.numeric({ + length, + allowLeadingZeros: options.allowLeadingZeros, + bannedDigits: options.bannedDigits, + }); } } diff --git a/src/modules/string/index.ts b/src/modules/string/index.ts new file mode 100644 index 00000000000..a02fea0f33a --- /dev/null +++ b/src/modules/string/index.ts @@ -0,0 +1,425 @@ +import type { Faker } from '../..'; +import { FakerError } from '../../errors/faker-error'; +import type { LiteralUnion } from '../../utils/types'; + +export type Casing = 'upper' | 'lower' | 'mixed'; + +const UPPER_CHARS: readonly string[] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); +const LOWER_CHARS: readonly string[] = 'abcdefghijklmnopqrstuvwxyz'.split(''); +const DIGIT_CHARS: readonly string[] = '0123456789'.split(''); + +export type LowerAlphaChar = + | 'a' + | 'b' + | 'c' + | 'd' + | 'e' + | 'f' + | 'g' + | 'h' + | 'i' + | 'j' + | 'k' + | 'l' + | 'm' + | 'n' + | 'o' + | 'p' + | 'q' + | 'r' + | 's' + | 't' + | 'u' + | 'v' + | 'w' + | 'x' + | 'y' + | 'z'; + +export type UpperAlphaChar = + | 'A' + | 'B' + | 'C' + | 'D' + | 'E' + | 'F' + | 'G' + | 'H' + | 'I' + | 'J' + | 'K' + | 'L' + | 'M' + | 'N' + | 'O' + | 'P' + | 'Q' + | 'R' + | 'S' + | 'T' + | 'U' + | 'V' + | 'W' + | 'X' + | 'Y' + | 'Z'; + +export type NumericChar = + | '0' + | '1' + | '2' + | '3' + | '4' + | '5' + | '6' + | '7' + | '8' + | '9'; + +export type AlphaChar = LowerAlphaChar | UpperAlphaChar; +export type AlphaNumericChar = AlphaChar | NumericChar; + +const SAMPLE_MAX_LENGTH = Math.pow(2, 20); + +/** + * Module to generate string related entries. + */ +export class StringModule { + constructor(private readonly faker: Faker) { + // Bind `this` so namespaced is working correctly + for (const name of Object.getOwnPropertyNames(StringModule.prototype)) { + if (name === 'constructor' || typeof this[name] !== 'function') { + continue; + } + this[name] = this[name].bind(this); + } + } + + /** + * Generating a string consisting of letters in the English alphabet. + * + * @param options Either the number of characters or an options instance. Defaults to `{ length: 1, casing: 'lower', bannedChars: [] }`. + * @param options.length The number of characters to generate. Defaults to `1`. + * @param options.casing The casing of the characters. Defaults to `'lower'`. + * @param options.bannedChars An array with characters to exclude. Defaults to `[]`. + * + * @example + * faker.string.alpha() // 'b' + * faker.string.alpha(10) // 'qccrabobaf' + * faker.string.alpha({ length: 5, casing: 'upper', bannedChars: ['A'] }) // 'DTCIC' + * + * @since 8.0.0 + */ + alpha( + options: + | number + | { + length?: number; + casing?: Casing; + bannedChars?: readonly LiteralUnion[] | string; + } = {} + ): string { + if (typeof options === 'number') { + options = { + length: options, + }; + } + + const { length = 1, casing = 'mixed' } = options; + let { bannedChars = [] } = options; + + if (typeof bannedChars === 'string') { + bannedChars = bannedChars.split(''); + } + + if (length <= 0) { + return ''; + } + + let charsArray: string[]; + switch (casing) { + case 'upper': + charsArray = [...UPPER_CHARS]; + break; + case 'lower': + charsArray = [...LOWER_CHARS]; + break; + case 'mixed': + default: + charsArray = [...LOWER_CHARS, ...UPPER_CHARS]; + break; + } + + charsArray = charsArray.filter((elem) => !bannedChars.includes(elem)); + + if (charsArray.length === 0) { + throw new FakerError( + 'Unable to generate string, because all possible characters are banned.' + ); + } + + return Array.from({ length }, () => + this.faker.helpers.arrayElement(charsArray) + ).join(''); + } + + /** + * Generating a string consisting of alpha characters and digits. + * + * @param options Either the number of characters or an options instance. Defaults to `{ length: 1, casing: 'mixed', bannedChars: [] }`. + * @param options.length The number of characters and digits to generate. Defaults to `1`. + * @param options.casing The casing of the characters. Defaults to `'mixed'`. + * @param options.bannedChars An array of characters and digits which should be banned in the generated string. Defaults to `[]`. + * + * @example + * faker.string.alphanumeric() // '2' + * faker.string.alphanumeric(5) // '3e5v7' + * faker.string.alphanumeric({ length: 5, bannedChars: ["a"] }) // 'xszlm' + * + * @since 8.0.0 + */ + alphanumeric( + options: + | number + | { + length?: number; + casing?: Casing; + bannedChars?: readonly LiteralUnion[] | string; + } = {} + ): string { + if (typeof options === 'number') { + options = { + length: options, + }; + } + + const { length = 1, casing = 'mixed' } = options; + + if (length <= 0) { + return ''; + } + + let { bannedChars = [] } = options; + + if (typeof bannedChars === 'string') { + bannedChars = bannedChars.split(''); + } + + let charsArray = [...DIGIT_CHARS]; + + switch (casing) { + case 'upper': + charsArray.push(...UPPER_CHARS); + break; + case 'lower': + charsArray.push(...LOWER_CHARS); + break; + case 'mixed': + default: + charsArray.push(...LOWER_CHARS, ...UPPER_CHARS); + break; + } + + charsArray = charsArray.filter((elem) => !bannedChars.includes(elem)); + + if (charsArray.length === 0) { + throw new FakerError( + 'Unable to generate string, because all possible characters are banned.' + ); + } + + return Array.from({ length }, () => + this.faker.helpers.arrayElement(charsArray) + ).join(''); + } + + /** + * Returns a [hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) string. + * + * @param options The optional options object. + * @param options.length Length of the generated number. Defaults to `1`. + * @param options.casing Casing of the generated number. Defaults to `'mixed'`. + * @param options.prefix Prefix for the generated number. Defaults to `'0x'`. + * + * @example + * faker.string.hexadecimal() // '0xB' + * faker.string.hexadecimal({ length: 10 }) // '0xaE13d044cB' + * faker.string.hexadecimal({ prefix: '0x' }) // '0xE' + * faker.string.hexadecimal({ casing: 'lower' }) // '0xf' + * faker.string.hexadecimal({ length: 10, prefix: '#' }) // '#f12a974eB1' + * faker.string.hexadecimal({ length: 10, casing: 'upper' }) // '0xE3F38014FB' + * faker.string.hexadecimal({ casing: 'lower', prefix: '' }) // 'd' + * faker.string.hexadecimal({ length: 10, casing: 'mixed', prefix: '0x' }) // '0xAdE330a4D1' + * + * @since 8.0.0 + */ + hexadecimal( + options: { + length?: number; + casing?: Casing; + prefix?: string; + } = {} + ): string { + const { length = 1, casing = 'mixed', prefix = '0x' } = options; + + let wholeString = ''; + + for (let i = 0; i < length; i++) { + wholeString += this.faker.helpers.arrayElement([ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + ]); + } + + if (casing === 'upper') { + wholeString = wholeString.toUpperCase(); + } else if (casing === 'lower') { + wholeString = wholeString.toLowerCase(); + } + + return `${prefix}${wholeString}`; + } + + /** + * Generates a given length string of digits. + * + * @param options Either the number of characters or the options to use. Defaults to `{ length: 1, allowLeadingZeros = false, bannedDigits = [] }`. + * @param options.length The number of digits to generate. Defaults to `1`. + * @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.string.numeric() // '2' + * faker.string.numeric(5) // '31507' + * faker.string.numeric(42) // '56434563150765416546479875435481513188548' + * faker.string.numeric({ length: 42, allowLeadingZeros: true }) // '00564846278453876543517840713421451546115' + * faker.string.numeric({ length: 6, bannedDigits: ['0'] }) // '943228' + * + * @since 8.0.0 + */ + numeric( + options: + | number + | { + length?: number; + allowLeadingZeros?: boolean; + bannedDigits?: readonly LiteralUnion[] | string; + } = {} + ): string { + if (typeof options === 'number') { + options = { + length: options, + }; + } + + const { length = 1, allowLeadingZeros = false } = options; + if (length <= 0) { + return ''; + } + + let { bannedDigits = [] } = options; + + if (typeof bannedDigits === 'string') { + bannedDigits = bannedDigits.split(''); + } + + const allowedDigits = DIGIT_CHARS.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.faker.helpers.arrayElement( + allowedDigits.filter((digit) => digit !== '0') + ); + } + + while (result.length < length) { + result += this.faker.helpers.arrayElement(allowedDigits); + } + + return result; + } + + /** + * Returns a string containing UTF-16 chars between 33 and 125 (`!` to `}`). + * + * @param length Length of the generated string. Max length is `2^20`. Defaults to `10`. + * + * @example + * faker.string.sample() // 'Zo!.:*e>wR' + * faker.string.sample(5) // '6Bye8' + * + * @since 8.0.0 + */ + sample(length = 10): string { + if (length >= SAMPLE_MAX_LENGTH) { + length = SAMPLE_MAX_LENGTH; + } + + const charCodeOption = { + min: 33, + max: 125, + }; + + let returnString = ''; + + while (returnString.length < length) { + returnString += String.fromCharCode( + this.faker.datatype.number(charCodeOption) + ); + } + + return returnString; + } + + /** + * Returns a UUID v4 ([Universally Unique Identifier](https://en.wikipedia.org/wiki/Universally_unique_identifier)). + * + * @example + * faker.string.uuid() // '4136cd0b-d90b-4af7-b485-5d1ded8db252' + * + * @since 8.0.0 + */ + uuid(): string { + const RFC4122_TEMPLATE = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; + const replacePlaceholders = (placeholder: string) => { + const random = this.faker.datatype.number({ min: 0, max: 15 }); + const value = placeholder === 'x' ? random : (random & 0x3) | 0x8; + return value.toString(16); + }; + return RFC4122_TEMPLATE.replace(/[xy]/g, replacePlaceholders); + } +} diff --git a/src/modules/vehicle/index.ts b/src/modules/vehicle/index.ts index cd01ece30c9..3ef8f748d32 100644 --- a/src/modules/vehicle/index.ts +++ b/src/modules/vehicle/index.ts @@ -88,14 +88,16 @@ export class VehicleModule { */ vin(): string { const bannedChars = ['o', 'i', 'q', 'O', 'I', 'Q']; - return `${this.faker.random.alphaNumeric(10, { + return `${this.faker.string.alphanumeric({ + length: 10, casing: 'upper', bannedChars, - })}${this.faker.random.alpha({ - count: 1, + })}${this.faker.string.alpha({ + length: 1, casing: 'upper', bannedChars, - })}${this.faker.random.alphaNumeric(1, { + })}${this.faker.string.alphanumeric({ + length: 1, casing: 'upper', bannedChars, })}${this.faker.datatype.number({ min: 10000, max: 99999 })}` // return five digit # @@ -123,8 +125,8 @@ export class VehicleModule { * @since 5.4.0 */ vrm(): string { - return `${this.faker.random.alpha({ - count: 2, + return `${this.faker.string.alpha({ + length: 2, casing: 'upper', })}${this.faker.datatype.number({ min: 0, @@ -132,7 +134,10 @@ export class VehicleModule { })}${this.faker.datatype.number({ min: 0, max: 9, - })}${this.faker.random.alpha({ count: 3, casing: 'upper' })}`.toUpperCase(); + })}${this.faker.string.alpha({ + length: 3, + casing: 'upper', + })}`; } /** diff --git a/test/__snapshots__/random.spec.ts.snap b/test/__snapshots__/random.spec.ts.snap index 1a9b2dff10a..a55a07747c1 100644 --- a/test/__snapshots__/random.spec.ts.snap +++ b/test/__snapshots__/random.spec.ts.snap @@ -4,9 +4,9 @@ exports[`random > 42 > alpha > noArgs 1`] = `"t"`; exports[`random > 42 > alpha > withLength 1`] = `"tPXjM"`; -exports[`random > 42 > alphaNumeric > noArgs 1`] = `"d"`; +exports[`random > 42 > alphaNumeric > noArgs 1`] = `"n"`; -exports[`random > 42 > alphaNumeric > withLength 1`] = `"dsy6q"`; +exports[`random > 42 > alphaNumeric > withLength 1`] = `"nNWbJ"`; exports[`random > 42 > locale 1`] = `"es_MX"`; @@ -24,9 +24,9 @@ exports[`random > 1211 > alpha > noArgs 1`] = `"W"`; exports[`random > 1211 > alpha > withLength 1`] = `"WxUOl"`; -exports[`random > 1211 > alphaNumeric > noArgs 1`] = `"x"`; +exports[`random > 1211 > alphaNumeric > noArgs 1`] = `"V"`; -exports[`random > 1211 > alphaNumeric > withLength 1`] = `"xgws8"`; +exports[`random > 1211 > alphaNumeric > withLength 1`] = `"VsTMd"`; exports[`random > 1211 > locale 1`] = `"ur"`; @@ -44,9 +44,9 @@ exports[`random > 1337 > alpha > noArgs 1`] = `"n"`; exports[`random > 1337 > alpha > withLength 1`] = `"nDilo"`; -exports[`random > 1337 > alphaNumeric > noArgs 1`] = `"9"`; +exports[`random > 1337 > alphaNumeric > noArgs 1`] = `"g"`; -exports[`random > 1337 > alphaNumeric > withLength 1`] = `"9k57a"`; +exports[`random > 1337 > alphaNumeric > withLength 1`] = `"gy9dh"`; exports[`random > 1337 > locale 1`] = `"en_GH"`; diff --git a/test/__snapshots__/string.spec.ts.snap b/test/__snapshots__/string.spec.ts.snap new file mode 100644 index 00000000000..f4a607b4888 --- /dev/null +++ b/test/__snapshots__/string.spec.ts.snap @@ -0,0 +1,313 @@ +// Vitest Snapshot v1 + +exports[`string > 42 > alpha > noArgs 1`] = `"t"`; + +exports[`string > 42 > alpha > with bannedChars 1`] = `"A"`; + +exports[`string > 42 > alpha > with casing = lower 1`] = `"j"`; + +exports[`string > 42 > alpha > with casing = mixed 1`] = `"t"`; + +exports[`string > 42 > alpha > with casing = upper 1`] = `"J"`; + +exports[`string > 42 > alpha > with length 1`] = `"tPXjMO"`; + +exports[`string > 42 > alpha > with length parameter 1`] = `"tPXjM"`; + +exports[`string > 42 > alpha > with length parameter 2`] = `"OFFix"`; + +exports[`string > 42 > alpha > with length parameter 3`] = `"ifdxT"`; + +exports[`string > 42 > alpha > with length parameter 4`] = `"rFhKH"`; + +exports[`string > 42 > alpha > with length parameter 5`] = `"bcYLR"`; + +exports[`string > 42 > alpha > with length, casing and bannedChars 1`] = `"fwzcvwj"`; + +exports[`string > 42 > alphanumeric > noArgs 1`] = `"n"`; + +exports[`string > 42 > alphanumeric > with bannedChars 1`] = `"x"`; + +exports[`string > 42 > alphanumeric > with casing = lower 1`] = `"d"`; + +exports[`string > 42 > alphanumeric > with casing = mixed 1`] = `"n"`; + +exports[`string > 42 > alphanumeric > with casing = upper 1`] = `"D"`; + +exports[`string > 42 > alphanumeric > with length 1`] = `"nNWbJM"`; + +exports[`string > 42 > alphanumeric > with length parameter 1`] = `"nNWbJ"`; + +exports[`string > 42 > alphanumeric > with length parameter 2`] = `"MBB9r"`; + +exports[`string > 42 > alphanumeric > with length parameter 3`] = `"963sR"`; + +exports[`string > 42 > alphanumeric > with length parameter 4`] = `"kB8HE"`; + +exports[`string > 42 > alphanumeric > with length parameter 5`] = `"13YIP"`; + +exports[`string > 42 > alphanumeric > with length, casing and bannedChars 1`] = `"cvy4kvh"`; + +exports[`string > 42 > hexadecimal > noArgs 1`] = `"0x8"`; + +exports[`string > 42 > hexadecimal > with casing = lower 1`] = `"0x8"`; + +exports[`string > 42 > hexadecimal > with casing = mixed 1`] = `"0x8"`; + +exports[`string > 42 > hexadecimal > with casing = upper 1`] = `"0x8"`; + +exports[`string > 42 > hexadecimal > with custom prefix 1`] = `"hex_8"`; + +exports[`string > 42 > hexadecimal > with length 1`] = `"0x8BE4AB"`; + +exports[`string > 42 > hexadecimal > with length, casing and empty prefix 1`] = `"8be4abd"`; + +exports[`string > 42 > numeric > noArgs 1`] = `"4"`; + +exports[`string > 42 > numeric > with allowLeadingZeros 1`] = `"3"`; + +exports[`string > 42 > numeric > with bannedDigits 1`] = `"7"`; + +exports[`string > 42 > numeric > with length 1`] = `"479177"`; + +exports[`string > 42 > numeric > with length parameter 1`] = `"47917"`; + +exports[`string > 42 > numeric > with length parameter 2`] = `"85514"`; + +exports[`string > 42 > numeric > with length parameter 3`] = `"20048"`; + +exports[`string > 42 > numeric > with length parameter 4`] = `"46176"`; + +exports[`string > 42 > numeric > with length parameter 5`] = `"10978"`; + +exports[`string > 42 > numeric > with length, allowLeadingZeros and bannedDigits 1`] = `"6890887"`; + +exports[`string > 42 > sample > noArgs 1`] = `"Cky2eiXX/J"`; + +exports[`string > 42 > sample > with length parameter 1`] = `"Cky2e"`; + +exports[`string > 42 > sample > with length parameter 2`] = `"iXX/J"`; + +exports[`string > 42 > sample > with length parameter 3`] = `"/*&Kq"`; + +exports[`string > 42 > sample > with length parameter 4`] = `"@X.b]"`; + +exports[`string > 42 > sample > with length parameter 5`] = `"\\"&{dn"`; + +exports[`string > 42 > uuid 1`] = `"5cf2bc99-2721-407d-992b-a00fbdf302f2"`; + +exports[`string > 42 > uuid 2`] = `"94980604-8962-404f-9371-c9368f970d9a"`; + +exports[`string > 42 > uuid 3`] = `"2710fff9-c640-413a-b7a1-97d02e642ac4"`; + +exports[`string > 42 > uuid 4`] = `"6838920f-dc7f-46ee-9be5-19380f5d6b48"`; + +exports[`string > 42 > uuid 5`] = `"d95f4984-24c2-410f-ac63-400d3bbbcc91"`; + +exports[`string > 1211 > alpha > noArgs 1`] = `"W"`; + +exports[`string > 1211 > alpha > with bannedChars 1`] = `"X"`; + +exports[`string > 1211 > alpha > with casing = lower 1`] = `"y"`; + +exports[`string > 1211 > alpha > with casing = mixed 1`] = `"W"`; + +exports[`string > 1211 > alpha > with casing = upper 1`] = `"Y"`; + +exports[`string > 1211 > alpha > with length 1`] = `"WxUOlg"`; + +exports[`string > 1211 > alpha > with length parameter 1`] = `"WxUOl"`; + +exports[`string > 1211 > alpha > with length parameter 2`] = `"gZbIi"`; + +exports[`string > 1211 > alpha > with length parameter 3`] = `"JkNvs"`; + +exports[`string > 1211 > alpha > with length parameter 4`] = `"iKMQa"`; + +exports[`string > 1211 > alpha > with length parameter 5`] = `"NIILR"`; + +exports[`string > 1211 > alpha > with length, casing and bannedChars 1`] = `"yhywdcz"`; + +exports[`string > 1211 > alphanumeric > noArgs 1`] = `"V"`; + +exports[`string > 1211 > alphanumeric > with bannedChars 1`] = `"W"`; + +exports[`string > 1211 > alphanumeric > with casing = lower 1`] = `"x"`; + +exports[`string > 1211 > alphanumeric > with casing = mixed 1`] = `"V"`; + +exports[`string > 1211 > alphanumeric > with casing = upper 1`] = `"X"`; + +exports[`string > 1211 > alphanumeric > with length 1`] = `"VsTMd8"`; + +exports[`string > 1211 > alphanumeric > with length parameter 1`] = `"VsTMd"`; + +exports[`string > 1211 > alphanumeric > with length parameter 2`] = `"8Z2F9"`; + +exports[`string > 1211 > alphanumeric > with length parameter 3`] = `"GdLql"`; + +exports[`string > 1211 > alphanumeric > with length parameter 4`] = `"aHKO0"`; + +exports[`string > 1211 > alphanumeric > with length parameter 5`] = `"LFEJP"`; + +exports[`string > 1211 > alphanumeric > with length, casing and bannedChars 1`] = `"yexv53z"`; + +exports[`string > 1211 > hexadecimal > noArgs 1`] = `"0xE"`; + +exports[`string > 1211 > hexadecimal > with casing = lower 1`] = `"0xe"`; + +exports[`string > 1211 > hexadecimal > with casing = mixed 1`] = `"0xE"`; + +exports[`string > 1211 > hexadecimal > with casing = upper 1`] = `"0xE"`; + +exports[`string > 1211 > hexadecimal > with custom prefix 1`] = `"hex_E"`; + +exports[`string > 1211 > hexadecimal > with length 1`] = `"0xEaDB42"`; + +exports[`string > 1211 > hexadecimal > with length, casing and empty prefix 1`] = `"eadb42f"`; + +exports[`string > 1211 > numeric > noArgs 1`] = `"9"`; + +exports[`string > 1211 > numeric > with allowLeadingZeros 1`] = `"9"`; + +exports[`string > 1211 > numeric > with bannedDigits 1`] = `"9"`; + +exports[`string > 1211 > numeric > with length 1`] = `"948721"`; + +exports[`string > 1211 > numeric > with length parameter 1`] = `"94872"`; + +exports[`string > 1211 > numeric > with length parameter 2`] = `"29061"`; + +exports[`string > 1211 > numeric > with length parameter 3`] = `"72743"`; + +exports[`string > 1211 > numeric > with length parameter 4`] = `"26780"`; + +exports[`string > 1211 > numeric > with length parameter 5`] = `"76678"`; + +exports[`string > 1211 > numeric > with length, allowLeadingZeros and bannedDigits 1`] = `"9798609"`; + +exports[`string > 1211 > sample > noArgs 1`] = `"wKti5-}$_/"`; + +exports[`string > 1211 > sample > with length parameter 1`] = `"wKti5"`; + +exports[`string > 1211 > sample > with length parameter 2`] = `"-}$_/"`; + +exports[`string > 1211 > sample > with length parameter 3`] = `"\`4hHA"`; + +exports[`string > 1211 > sample > with length parameter 4`] = `"0afl\\""`; + +exports[`string > 1211 > sample > with length parameter 5`] = `"h^]dn"`; + +exports[`string > 1211 > uuid 1`] = `"e7ec32f0-a2a3-4c65-abbd-0caabde64dfd"`; + +exports[`string > 1211 > uuid 2`] = `"f379e325-9f7c-4064-a086-f23942b68e5f"`; + +exports[`string > 1211 > uuid 3`] = `"d4694649-2183-4b32-8bd7-a336639d6997"`; + +exports[`string > 1211 > uuid 4`] = `"10ab829b-742c-4a8b-b732-98d718d77069"`; + +exports[`string > 1211 > uuid 5`] = `"7b91ce88-effb-4d1d-93bb-ad759e00b86c"`; + +exports[`string > 1337 > alpha > noArgs 1`] = `"n"`; + +exports[`string > 1337 > alpha > with bannedChars 1`] = `"v"`; + +exports[`string > 1337 > alpha > with casing = lower 1`] = `"g"`; + +exports[`string > 1337 > alpha > with casing = mixed 1`] = `"n"`; + +exports[`string > 1337 > alpha > with casing = upper 1`] = `"G"`; + +exports[`string > 1337 > alpha > with length 1`] = `"nDiloC"`; + +exports[`string > 1337 > alpha > with length parameter 1`] = `"nDilo"`; + +exports[`string > 1337 > alpha > with length parameter 2`] = `"Cxbqm"`; + +exports[`string > 1337 > alpha > with length parameter 3`] = `"AEnrY"`; + +exports[`string > 1337 > alpha > with length parameter 4`] = `"oMpfP"`; + +exports[`string > 1337 > alpha > with length parameter 5`] = `"ueGsg"`; + +exports[`string > 1337 > alpha > with length, casing and bannedChars 1`] = `"eicdeih"`; + +exports[`string > 1337 > alphanumeric > noArgs 1`] = `"g"`; + +exports[`string > 1337 > alphanumeric > with bannedChars 1`] = `"s"`; + +exports[`string > 1337 > alphanumeric > with casing = lower 1`] = `"9"`; + +exports[`string > 1337 > alphanumeric > with casing = mixed 1`] = `"g"`; + +exports[`string > 1337 > alphanumeric > with casing = upper 1`] = `"9"`; + +exports[`string > 1337 > alphanumeric > with length 1`] = `"gy9dhx"`; + +exports[`string > 1337 > alphanumeric > with length parameter 1`] = `"gy9dh"`; + +exports[`string > 1337 > alphanumeric > with length parameter 2`] = `"xs2je"`; + +exports[`string > 1337 > alphanumeric > with length parameter 3`] = `"wAgkY"`; + +exports[`string > 1337 > alphanumeric > with length parameter 4`] = `"gJj7N"`; + +exports[`string > 1337 > alphanumeric > with length parameter 5`] = `"n5Cm7"`; + +exports[`string > 1337 > alphanumeric > with length, casing and bannedChars 1`] = `"ag45age"`; + +exports[`string > 1337 > hexadecimal > noArgs 1`] = `"0x5"`; + +exports[`string > 1337 > hexadecimal > with casing = lower 1`] = `"0x5"`; + +exports[`string > 1337 > hexadecimal > with casing = mixed 1`] = `"0x5"`; + +exports[`string > 1337 > hexadecimal > with casing = upper 1`] = `"0x5"`; + +exports[`string > 1337 > hexadecimal > with custom prefix 1`] = `"hex_5"`; + +exports[`string > 1337 > hexadecimal > with length 1`] = `"0x5c346b"`; + +exports[`string > 1337 > hexadecimal > with length, casing and empty prefix 1`] = `"5c346ba"`; + +exports[`string > 1337 > numeric > noArgs 1`] = `"3"`; + +exports[`string > 1337 > numeric > with allowLeadingZeros 1`] = `"2"`; + +exports[`string > 1337 > numeric > with bannedDigits 1`] = `"7"`; + +exports[`string > 1337 > numeric > with length 1`] = `"351225"`; + +exports[`string > 1337 > numeric > with length parameter 1`] = `"35122"`; + +exports[`string > 1337 > numeric > with length parameter 2`] = `"54032"`; + +exports[`string > 1337 > numeric > with length parameter 3`] = `"55239"`; + +exports[`string > 1337 > numeric > with length parameter 4`] = `"37318"`; + +exports[`string > 1337 > numeric > with length parameter 5`] = `"40631"`; + +exports[`string > 1337 > numeric > with length, allowLeadingZeros and bannedDigits 1`] = `"6706677"`; + +exports[`string > 1337 > sample > noArgs 1`] = `"9U/4:SK$>6"`; + +exports[`string > 1337 > sample > with length parameter 1`] = `"9U/4:"`; + +exports[`string > 1337 > sample > with length parameter 2`] = `"SK$>6"`; + +exports[`string > 1337 > sample > with length parameter 3`] = `"QX9@{"`; + +exports[`string > 1337 > sample > with length parameter 4`] = `":e=+k"`; + +exports[`string > 1337 > sample > with length parameter 5`] = `"D)[B,"`; + +exports[`string > 1337 > uuid 1`] = `"48234870-5389-445f-8b41-c61a52bf27dc"`; + +exports[`string > 1337 > uuid 2`] = `"cc057669-8c53-474d-a677-226d3e8ed92f"`; + +exports[`string > 1337 > uuid 3`] = `"fe6d8b8b-0db9-4fa2-b265-abc0a5d0ccf5"`; + +exports[`string > 1337 > uuid 4`] = `"0b87afbd-8949-4dfb-8d04-19f4fe7458b2"`; + +exports[`string > 1337 > uuid 5`] = `"0bcea83c-a7ea-428e-85db-d448f2b777a6"`; diff --git a/test/color.spec.ts b/test/color.spec.ts index 66db2620096..091d7ff0e37 100644 --- a/test/color.spec.ts +++ b/test/color.spec.ts @@ -73,15 +73,15 @@ describe('color', () => { }); }); - describe(`rgbHex({ prefix: '0x', case: 'lower' })`, () => { - it('should return a random rgb hex color with # prefix and lower case only', () => { + describe(`rgbHex({ prefix: '0x', casing: 'lower' })`, () => { + it('should return a random rgb hex color with # prefix and lower casing only', () => { const color = faker.color.rgb({ prefix: '0x', casing: 'lower' }); expect(color).match(/^(0x[a-f0-9]{6})$/); }); }); - describe(`rgb({ prefix: '0x', case: 'upper' })`, () => { - it('should return a random rgb hex color with # prefix and upper case only', () => { + describe(`rgb({ prefix: '0x', casing: 'upper' })`, () => { + it('should return a random rgb hex color with # prefix and upper casing only', () => { const color = faker.color.rgb({ prefix: '0x', casing: 'upper' }); expect(color).match(/^(0x[A-F0-9]{6})$/); }); diff --git a/test/helpers.spec.ts b/test/helpers.spec.ts index 42fcd61f7f4..44316c1d2e2 100644 --- a/test/helpers.spec.ts +++ b/test/helpers.spec.ts @@ -396,7 +396,7 @@ describe('helpers', () => { it('supports function replace values faker values', () => { const actual = faker.helpers.mustache('1{{value}}3', { - value: faker.datatype.string(2), + value: faker.string.sample(2), }); expect(actual).toHaveLength(4); @@ -404,7 +404,7 @@ describe('helpers', () => { it('supports function replace values faker function', () => { const actual = faker.helpers.mustache('1{{value}}3', { - value: () => faker.datatype.string(3), + value: () => faker.string.sample(3), }); expect(actual).toHaveLength(5); @@ -557,7 +557,7 @@ describe('helpers', () => { }); it('should be able to return empty strings', () => { - expect(faker.helpers.fake('{{random.alphaNumeric(0)}}')).toBe(''); + expect(faker.helpers.fake('{{string.alphanumeric(0)}}')).toBe(''); }); it('should be able to return locale definition strings', () => { @@ -587,13 +587,13 @@ describe('helpers', () => { }); it('should be able to handle random }} brackets', () => { - expect(faker.helpers.fake('}}hello{{random.alpha}}')).toMatch( + expect(faker.helpers.fake('}}hello{{string.alpha}}')).toMatch( /^}}hello[a-zA-Z]$/ ); }); it('should be able to handle connected brackets', () => { - expect(faker.helpers.fake('{{{random.alpha}}}')).toMatch( + expect(faker.helpers.fake('{{{string.alpha}}}')).toMatch( /^{[a-zA-Z]}$/ ); }); @@ -604,12 +604,12 @@ describe('helpers', () => { it('should be able to handle special replacement patterns', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any - (faker.random as any).special = () => '$&'; + (faker.string as any).special = () => '$&'; - expect(faker.helpers.fake('{{random.special}}')).toBe('$&'); + expect(faker.helpers.fake('{{string.special}}')).toBe('$&'); // eslint-disable-next-line @typescript-eslint/no-explicit-any - delete (faker.random as any).special; + delete (faker.string as any).special; }); it('should support deprecated aliases', () => { diff --git a/test/random.spec.ts b/test/random.spec.ts index b387d000a3c..2cdfd2c033c 100644 --- a/test/random.spec.ts +++ b/test/random.spec.ts @@ -127,7 +127,7 @@ describe('random', () => { expect(actual).toHaveLength(1); }); - it('should return lowercase letter when no upcase option provided', () => { + it('should return mixed letter when no option provided', () => { const actual = faker.random.alpha(); expect(actual).toMatch(/^[a-z]$/i); @@ -300,6 +300,7 @@ describe('random', () => { it('should be able to handle mistake in banned characters array', () => { const alphaText = faker.random.alphaNumeric(5, { bannedChars: ['a', 'p', 'a'], + casing: 'lower', }); expect(alphaText).toHaveLength(5); @@ -311,6 +312,7 @@ describe('random', () => { expect(() => faker.random.alphaNumeric(5, { bannedChars, + casing: 'lower', }) ).toThrowError( new FakerError( @@ -324,6 +326,7 @@ describe('random', () => { expect(() => faker.random.alphaNumeric(5, { bannedChars, + casing: 'lower', }) ).toThrowError(); }); diff --git a/test/string.spec.ts b/test/string.spec.ts new file mode 100644 index 00000000000..66c6bc59dca --- /dev/null +++ b/test/string.spec.ts @@ -0,0 +1,483 @@ +import { describe, expect, it } from 'vitest'; +import { faker, FakerError } from '../src'; +import { seededTests } from './support/seededRuns'; +import { times } from './support/times'; + +const NON_SEEDED_BASED_RUN = 5; + +describe('string', () => { + seededTests(faker, 'string', (t) => { + t.describe('alpha', (t) => { + t.it('noArgs') + .itRepeated('with length parameter', 5, 5) + .it('with length', { length: 6 }) + .it('with casing = lower', { casing: 'lower' }) + .it('with casing = upper', { casing: 'upper' }) + .it('with casing = mixed', { casing: 'mixed' }) + .it('with bannedChars', { bannedChars: 'abcdefghijk' }) + .it('with length, casing and bannedChars', { + length: 7, + casing: 'lower', + bannedChars: 'lmnopqrstu', + }); + }); + + t.describe('alphanumeric', (t) => { + t.it('noArgs') + .itRepeated('with length parameter', 5, 5) + .it('with length', { length: 6 }) + .it('with casing = lower', { casing: 'lower' }) + .it('with casing = upper', { casing: 'upper' }) + .it('with casing = mixed', { casing: 'mixed' }) + .it('with bannedChars', { bannedChars: 'abcdefghijk12345' }) + .it('with length, casing and bannedChars', { + length: 7, + casing: 'lower', + bannedChars: 'lmnopqrstu67890', + }); + }); + + t.describe('hexadecimal', (t) => { + t.it('noArgs') + .it('with length', { length: 6 }) + .it('with casing = lower', { casing: 'lower' }) + .it('with casing = upper', { casing: 'upper' }) + .it('with casing = mixed', { casing: 'mixed' }) + .it('with custom prefix', { prefix: 'hex_' }) + .it('with length, casing and empty prefix', { + length: 7, + casing: 'lower', + prefix: '', + }); + }); + + t.describe('numeric', (t) => { + t.it('noArgs') + .itRepeated('with length parameter', 5, 5) + .it('with length', { length: 6 }) + .it('with allowLeadingZeros', { allowLeadingZeros: true }) + .it('with bannedDigits', { bannedDigits: '12345' }) + .it('with length, allowLeadingZeros and bannedDigits', { + length: 7, + allowLeadingZeros: true, + bannedDigits: '12345', + }); + }); + + t.describe('sample', (t) => { + t.it('noArgs').itRepeated('with length parameter', 5, 5); + }); + + t.itRepeated('uuid', 5); + }); + + describe(`random seeded tests for seed ${faker.seed()}`, () => { + for (let i = 1; i <= NON_SEEDED_BASED_RUN; i++) { + describe('alpha', () => { + it('should return single letter when no length provided', () => { + const actual = faker.string.alpha(); + + expect(actual).toHaveLength(1); + }); + + it('should return any letters when no option is provided', () => { + const actual = faker.string.alpha(); + + expect(actual).toMatch(/^[a-zA-Z]$/); + }); + + it.each([ + ['upper', /^[A-Z]{250}$/], + ['lower', /^[a-z]{250}$/], + ['mixed', /^[a-zA-Z]{250}$/], + ] as const)('should return %s-case', (casing, pattern) => { + const actual = faker.string.alpha({ length: 250, casing }); + expect(actual).toMatch(pattern); + }); + + it('should generate many random letters', () => { + const actual = faker.string.alpha(5); + + expect(actual).toHaveLength(5); + }); + + it.each([0, -1, -100])( + 'should return empty string when length is <= 0', + (length) => { + const actual = faker.string.alpha(length); + + expect(actual).toBe(''); + } + ); + + it('should be able to ban some characters', () => { + const actual = faker.string.alpha({ + length: 5, + casing: 'lower', + bannedChars: ['a', 'p'], + }); + + expect(actual).toHaveLength(5); + expect(actual).toMatch(/^[b-oq-z]{5}$/); + }); + + it('should be able to ban some characters via string', () => { + const actual = faker.string.alpha({ + length: 5, + casing: 'lower', + bannedChars: 'ap', + }); + + expect(actual).toHaveLength(5); + expect(actual).toMatch(/^[b-oq-z]{5}$/); + }); + + it('should be able handle mistake in banned characters array', () => { + const alphaText = faker.string.alpha({ + length: 5, + casing: 'lower', + bannedChars: ['a', 'a', 'p'], + }); + + expect(alphaText).toHaveLength(5); + expect(alphaText).toMatch(/^[b-oq-z]{5}$/); + }); + + it('should throw if all possible characters being banned', () => { + const bannedChars = 'abcdefghijklmnopqrstuvwxyz'.split(''); + expect(() => + faker.string.alpha({ + length: 5, + casing: 'lower', + bannedChars, + }) + ).toThrowError( + new FakerError( + 'Unable to generate string, because all possible characters are banned.' + ) + ); + }); + + it('should not mutate the input object', () => { + const input: { + length: number; + casing: 'mixed'; + bannedChars: string[]; + } = Object.freeze({ + length: 5, + casing: 'mixed', + bannedChars: ['a', '%'], + }); + + expect(() => faker.string.alpha(input)).not.toThrow(); + expect(input.bannedChars).toEqual(['a', '%']); + }); + }); + + describe('alphaNumeric', () => { + it('should generate single character when no additional argument was provided', () => { + const actual = faker.string.alphanumeric(); + + expect(actual).toHaveLength(1); + }); + + it.each([ + ['upper', /^[A-Z0-9]{250}$/], + ['lower', /^[a-z0-9]{250}$/], + ['mixed', /^[a-zA-Z0-9]{250}$/], + ] as const)('should return %s-case', (casing, pattern) => { + const actual = faker.string.alphanumeric({ length: 250, casing }); + expect(actual).toMatch(pattern); + }); + + it('should generate many random characters', () => { + const actual = faker.string.alphanumeric(5); + + expect(actual).toHaveLength(5); + }); + + it.each([0, -1, -100])( + 'should return empty string when length is <= 0', + (length) => { + const actual = faker.string.alphanumeric(length); + + expect(actual).toBe(''); + } + ); + + it('should be able to ban all alphabetic characters', () => { + const bannedChars = 'abcdefghijklmnopqrstuvwxyz'.split(''); + const alphaText = faker.string.alphanumeric({ + length: 5, + casing: 'lower', + bannedChars, + }); + + expect(alphaText).toHaveLength(5); + for (const bannedChar of bannedChars) { + expect(alphaText).not.includes(bannedChar); + } + }); + + it('should be able to ban all alphabetic characters via string', () => { + const bannedChars = 'abcdefghijklmnopqrstuvwxyz'; + const alphaText = faker.string.alphanumeric({ + length: 5, + casing: 'lower', + bannedChars, + }); + + expect(alphaText).toHaveLength(5); + for (const bannedChar of bannedChars) { + expect(alphaText).not.includes(bannedChar); + } + }); + + it('should be able to ban all numeric characters', () => { + const bannedChars = '0123456789'.split(''); + const alphaText = faker.string.alphanumeric({ + length: 5, + bannedChars, + }); + + expect(alphaText).toHaveLength(5); + for (const bannedChar of bannedChars) { + expect(alphaText).not.includes(bannedChar); + } + }); + + it('should be able to ban all numeric characters via string', () => { + const bannedChars = '0123456789'; + const alphaText = faker.string.alphanumeric({ + length: 5, + bannedChars, + }); + + expect(alphaText).toHaveLength(5); + for (const bannedChar of bannedChars) { + expect(alphaText).not.includes(bannedChar); + } + }); + + it('should be able to handle mistake in banned characters array', () => { + const alphaText = faker.string.alphanumeric({ + length: 5, + casing: 'lower', + bannedChars: ['a', 'p', 'a'], + }); + + expect(alphaText).toHaveLength(5); + expect(alphaText).toMatch(/^[0-9b-oq-z]{5}$/); + }); + + it('should throw if all possible characters being banned', () => { + const bannedChars = 'abcdefghijklmnopqrstuvwxyz0123456789'.split(''); + expect(() => + faker.string.alphanumeric({ + length: 5, + casing: 'lower', + bannedChars, + }) + ).toThrowError( + new FakerError( + 'Unable to generate string, because all possible characters are banned.' + ) + ); + }); + + it('should throw if all possible characters being banned via string', () => { + const bannedChars = 'abcdefghijklmnopqrstuvwxyz0123456789'; + expect(() => + faker.string.alphanumeric({ + length: 5, + casing: 'lower', + bannedChars, + }) + ).toThrowError(); + }); + + it('should not mutate the input object', () => { + const input: { + length: number; + bannedChars: string[]; + } = Object.freeze({ + length: 5, + bannedChars: ['a', '0', '%'], + }); + + expect(() => faker.string.alphanumeric(input)).not.toThrow(); + expect(input.bannedChars).toEqual(['a', '0', '%']); + }); + }); + + describe(`hexadecimal`, () => { + it('generates single hex character when no additional argument was provided', () => { + const hex = faker.string.hexadecimal(); + expect(hex).toMatch(/^0x[0-9a-f]*$/i); + expect(hex).toHaveLength(3); + }); + + it('generates a random hex string', () => { + const hex = faker.string.hexadecimal({ + length: 5, + prefix: '', + }); + expect(hex).toMatch(/^[0-9a-f]*$/i); + expect(hex).toHaveLength(5); + }); + }); + + describe('numeric', () => { + it('should return single digit when no length provided', () => { + const actual = faker.string.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.string.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.string.numeric(0); + + expect(actual).toHaveLength(0); + }); + + it('should return empty string with a negative length', () => { + const actual = faker.string.numeric(-10); + + expect(actual).toHaveLength(0); + }); + + it('should return a valid numeric string with provided length', () => { + const actual = faker.string.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.string.numeric({ + length: 15, + allowLeadingZeros: true, + }); + + expect(actual).toMatch(/^[0-9]+$/); + }); + + it('should allow leading zeros via option and all other digits banned', () => { + const actual = faker.string.numeric({ + length: 4, + allowLeadingZeros: true, + bannedDigits: '123456789'.split(''), + }); + + expect(actual).toBe('0000'); + }); + + it('should allow leading zeros via option and all other digits banned via string', () => { + const actual = faker.string.numeric({ + length: 4, + allowLeadingZeros: true, + bannedDigits: '123456789', + }); + + expect(actual).toBe('0000'); + }); + + it('should fail on leading zeros via option and all other digits banned', () => { + expect(() => + faker.string.numeric({ + length: 4, + allowLeadingZeros: false, + bannedDigits: '123456789'.split(''), + }) + ).toThrowError( + new FakerError( + 'Unable to generate numeric string, because all possible digits are banned.' + ) + ); + }); + + it('should fail on leading zeros via option and all other digits banned via string', () => { + expect(() => + faker.string.numeric({ + length: 4, + allowLeadingZeros: false, + bannedDigits: '123456789', + }) + ).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.string.numeric({ + length: 1000, + bannedDigits: 'c84U1'.split(''), + }); + + expect(actual).toHaveLength(1000); + expect(actual).toMatch(/^[0235679]{1000}$/); + }); + + it('should ban all digits passed via bannedDigits via string', () => { + const actual = faker.string.numeric({ + length: 1000, + bannedDigits: 'c84U1', + }); + + expect(actual).toHaveLength(1000); + expect(actual).toMatch(/^[0235679]{1000}$/); + }); + }); + + describe('sample', () => { + it('should generate a string value', () => { + const generatedString = faker.string.sample(); + expect(generatedString).toBeTypeOf('string'); + expect(generatedString).toHaveLength(10); + }); + + it('should return empty string if negative length is passed', () => { + const negativeValue = faker.datatype.number({ min: -1000, max: -1 }); + const generatedString = faker.string.sample(negativeValue); + expect(generatedString).toBe(''); + expect(generatedString).toHaveLength(0); + }); + + it('should return string with length of 2^20 if bigger length value is passed', () => { + const overMaxValue = Math.pow(2, 28); + const generatedString = faker.string.sample(overMaxValue); + expect(generatedString).toHaveLength(Math.pow(2, 20)); + }); + + it('should return string with a specific length', () => { + const length = 1337; + const generatedString = faker.string.sample(length); + expect(generatedString).toHaveLength(length); + }); + }); + + describe(`uuid`, () => { + it('generates a valid UUID', () => { + const UUID = faker.string.uuid(); + const RFC4122 = + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; + expect(UUID).toMatch(RFC4122); + }); + }); + } + }); +});