diff --git a/.github/renovate.json5 b/.github/renovate.json5 index a4dfb985ca8..db37c6ec152 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -8,6 +8,9 @@ "helpers:pinGitHubActionDigests" ], "labels": ["c: dependencies"], + "lockFileMaintenance": { + "enabled": true + }, "reviewersFromCodeOwners": true, "rangeStrategy": "bump", "packageRules": [ diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6b9138306e2..006918667dc 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -41,12 +41,15 @@ jobs: pnpm run build pnpm run test -u continue-on-error: true + env: + CI_PREFLIGHT: true - name: Check diff id: diff run: | git add . - git diff --cached --exit-code + git diff --cached --name-only | sed -E 's/^(.*)$/::error file=\1,title=Diff detected::Please run `pnpm run preflight` and commit the changes./' + git diff --cached --name-only --exit-code continue-on-error: true - name: Transpile ts diff --git a/docs/.vitepress/api-pages.ts b/docs/.vitepress/api-pages.ts index 60cd553d98b..7df42281564 100644 --- a/docs/.vitepress/api-pages.ts +++ b/docs/.vitepress/api-pages.ts @@ -30,7 +30,6 @@ export const apiPages = [ { text: 'Number', link: '/api/number.html' }, { text: 'Person', link: '/api/person.html' }, { 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' }, diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 17d7d84d74c..22937a8d357 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -213,6 +213,10 @@ const config: UserConfig = { text: 'Randomizer', link: '/guide/randomizer', }, + { + text: 'Unique Values', + link: '/guide/unique', + }, { text: 'Upgrading to v9', link: '/guide/upgrading', diff --git a/docs/guide/unique.md b/docs/guide/unique.md new file mode 100644 index 00000000000..235e33ac52b --- /dev/null +++ b/docs/guide/unique.md @@ -0,0 +1,58 @@ +# Unique Values + +In general, Faker methods do not return unique values. + +```ts +faker.seed(55); +faker.animal.type(); //'cat' +faker.animal.type(); //'bird' +faker.animal.type(); //'horse' +faker.animal.type(); //'horse' +``` + +Some methods and locales use much smaller data sets than others. For example, `faker.animal.type` has only 13 possible animals to choose from. In contrast, `faker.person.fullName()` pulls from a list of hundreds of first names, surnames, and prefixes/suffixes, so it can generate hundreds of thousands of unique names. Even then, the [birthday paradox](https://en.wikipedia.org/wiki/Birthday_Paradox) means that duplicate values will quickly be generated. + +Sometimes, you want to generate unique values. For example, you may wish to have unique values in a database email column. +There are a few possible strategies for this: + +1. Use `faker.helpers.uniqueArray()` if you want to generate all the values at one time. For example: + +```ts +faker.helpers.uniqueArray(faker.internet.email, 1000); // will generate 1000 unique email addresses +``` + +2. If there are insufficient values for your needs, consider prefixing or suffixing values with your own sequential values, for example you could prefix `1.`, `2.` to each generated email in turn. + +3. Build your own logic to keep track of a set of previously generated values and regenerate values as necessary if a duplicate is generated + +4. Use a third party package to enforce uniqueness such as [enforce-unique](https://github.com/MansurAliKoroglu/enforce-unique) + +Note you can supply a maximum time (in milliseconds) or maximum number of retries. + +```js +import { EnforceUniqueError, UniqueEnforcer } from 'enforce-unique'; + +const uniqueEnforcerEmail = new UniqueEnforcer(); + +function createRandomUser() { + const firstName = faker.person.firstName(); + const lastName = faker.person.lastName(); + const email = uniqueEnforcerEmail.enforce( + () => + faker.internet.email({ + firstName, + lastName, + }), + { + maxTime: 50, + maxRetries: 50, + } + ); + + return { + firstName, + lastName, + email, + }; +} +``` diff --git a/docs/guide/upgrading_v9/2661.md b/docs/guide/upgrading_v9/2661.md new file mode 100644 index 00000000000..f3303a651c2 --- /dev/null +++ b/docs/guide/upgrading_v9/2661.md @@ -0,0 +1,65 @@ +### Remove helpers.unique + +Prior to v9, Faker provided a [`faker.helpers.unique()`](https://v8.fakerjs.dev/api/helpers.html#unique) method which had a global store to keep track of duplicates. This was removed in v9. + +Please see the [unique values guide](/guide/unique) for alternatives. + +For example, many simple use cases can use [`faker.helpers.uniqueArray`](https://v8.fakerjs.dev/api/helpers.html#uniqueArray). Or you can migrate to a third party package such as `enforce-unique`: + +Basic example: + +```js +// OLD +const name = faker.helpers.unique(faker.person.firstName); + +// NEW +import { UniqueEnforcer } from 'enforce-unique'; +//const { UniqueEnforcer } = require("enforce-unique") // CJS + +const enforcerName = new UniqueEnforcer(); +const name = enforcerName.enforce(faker.person.firstName); +``` + +With parameters: + +```js +// OLD +const stateCode = faker.helpers.unique(faker.location.state, [ + { + abbreviated: true, + }, +]); + +// NEW +import { UniqueEnforcer } from 'enforce-unique'; + +const enforcerState = new UniqueEnforcer(); +const stateCode = enforcerState.enforce(() => + faker.location.state({ + abbreviated: true, + }) +); +``` + +With options: + +```js +// OLD +const city = faker.helpers.unique(faker.location.city, [], { + maxRetries: 100, + maxTime: 1000, +}); + +// NEW +import { UniqueEnforcer } from 'enforce-unique'; + +const enforcer = new UniqueEnforcer(); +const city = enforcer.enforce(faker.location.city, { + maxRetries: 100, + maxTime: 1000, +}); +``` + +::: tip Note +`enforce-unique` does not support the `exclude` or `store` options. If you were previously using these, you may wish to build your own unique logic instead. +::: diff --git a/docs/guide/upgrading_v9/2678.md b/docs/guide/upgrading_v9/2678.md new file mode 100644 index 00000000000..ce34e1f7b0d --- /dev/null +++ b/docs/guide/upgrading_v9/2678.md @@ -0,0 +1,12 @@ +### Remove deprecated random module + +Removed deprecated random module + +| old | replacement | +| ----------------------------- | ----------------------------------------------- | +| `faker.random.alpha()` | `faker.string.alpha()` | +| `faker.random.alphaNumeric()` | `faker.string.alphanumeric()` | +| `faker.random.locale()` | `faker.helpers.objectKey(allLocales/allFakers)` | +| `faker.random.numeric()` | `faker.string.numeric()` | +| `faker.random.word()` | `faker.lorem.word()` or `faker.word.sample()` | +| `faker.random.words()` | `faker.lorem.words()` or `faker.word.words()` | diff --git a/docs/guide/upgrading_v9/2682.md b/docs/guide/upgrading_v9/2682.md new file mode 100644 index 00000000000..491b4cfe1d8 --- /dev/null +++ b/docs/guide/upgrading_v9/2682.md @@ -0,0 +1,22 @@ +### Remove deprecated faker constructor and deprecated JS backwards compatibility + +Removed deprecated faker constructor, so it is not possible anymore to just pass a locale string identifier. + +Also removed the accessors and method that were only for JS backwards compatibility. + +- `get/set locales` +- `get/set locale` +- `get/set localeFallback` +- `setLocale` + +To use the new constructor, you need to pass a locale object like: + +```ts +import { Faker, es, base } from '@faker-js/faker'; + +// A custom faker instance that does not have any fallbacks +const customEsFakerWithoutFallback = new Faker({ locale: es }); + +// A custom faker instance that has only base-data as fallback, but not english data +const customEsFakerWithFallback = new Faker({ locale: [es, base] }); +``` diff --git a/docs/guide/upgrading_v9/2685.md b/docs/guide/upgrading_v9/2685.md new file mode 100644 index 00000000000..7e05fac6e53 --- /dev/null +++ b/docs/guide/upgrading_v9/2685.md @@ -0,0 +1,16 @@ +### Usage of TypeScript 5 Features + +_This upgrade is an extension to_ [#1953](./1953.md) + +The helpers module now uses TS5 features, so if you are using Faker with TypeScript, you must use TS5. + +```ts +// v8 +faker.helpers.arrayElement([1, 2, 3]); // number +faker.helpers.arrayElement([1, 2, 3] as const); // 1 | 2 | 3 + +// v9 +faker.helpers.arrayElement([1, 2, 3]); // 1 | 2 | 3 +``` + +If you are unable to upgrade to TS5, you have to keep using Faker v8. diff --git a/docs/guide/usage.md b/docs/guide/usage.md index ed6bf0afd13..f19721c2569 100644 --- a/docs/guide/usage.md +++ b/docs/guide/usage.md @@ -233,45 +233,6 @@ Here, we could also pass in the `sex` value as argument, but in our use-case the By doing this first, we are able to pass both names into the `email` generation function. This allows the value to be more reasonable based on the provided arguments. -But we can take this even another step further. -Opposite to the `_id` property that uses an `uuid` implementation, which is unique by design, the `email` property potentially isn't. -But, in most use-cases, this would be desirable. - -Faker has your back, with another helper method: - -```ts {7-10} -import { faker } from '@faker-js/faker'; - -function createRandomUser(): User { - const sex = faker.person.sexType(); - const firstName = faker.person.firstName(sex); - const lastName = faker.person.lastName(); - const email = faker.helpers.unique(faker.internet.email, [ - firstName, - lastName, - ]); - - return { - _id: faker.string.uuid(), - avatar: faker.image.avatar(), - birthday: faker.date.birthdate(), - email, - firstName, - lastName, - sex, - subscriptionTier: faker.helpers.arrayElement(['free', 'basic', 'business']), - }; -} - -const user = createRandomUser(); -``` - -By wrapping Faker's `email` function with the [`unique`](../api/helpers.md#unique) helper function, we ensure that the return value of `email` is always unique. - -::: warning -The `faker.helpers.unique` is targeted to be removed from Faker in the future. -Please have a look at the issue [#1785](https://github.com/faker-js/faker/issues/1785). -We will update these docs once a replacement is available. -::: +Unlike the `_id` property that uses an `uuid` implementation, which has a low chance of duplicates, the `email` function is more likely to produce duplicates, especially if the call arguments are similar. We have a dedicated guide page on generating [unique values](unique). Congratulations, you should now be able to create any complex object you desire. Happy faking 🥳. diff --git a/package.json b/package.json index 30752acf7df..d38da9422ca 100644 --- a/package.json +++ b/package.json @@ -31,14 +31,7 @@ "type": "module", "main": "dist/index.cjs", "module": "dist/index.js", - "types": "index.d.ts", - "typesVersions": { - ">=4.0": { - "*": [ - "dist/types/*" - ] - } - }, + "types": "dist/types/index.d.ts", "exports": { ".": { "types": "./dist/types/index.d.ts", diff --git a/src/faker.ts b/src/faker.ts index ff54e2f3893..c78fe1de583 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -24,7 +24,6 @@ import { MusicModule } from './modules/music'; import type { PersonModule as NameModule } from './modules/person'; import { PersonModule } from './modules/person'; import { PhoneModule } from './modules/phone'; -import { RandomModule } from './modules/random'; import { ScienceModule } from './modules/science'; import { SystemModule } from './modules/system'; import { VehicleModule } from './modules/vehicle'; @@ -62,12 +61,6 @@ export class Faker extends SimpleFaker { readonly rawDefinitions: LocaleDefinition; readonly definitions: LocaleProxy; - /** - * @deprecated Use the modules specific to the type of data you want to generate instead. - */ - // eslint-disable-next-line deprecation/deprecation - readonly random: RandomModule = new RandomModule(this); - readonly airline: AirlineModule = new AirlineModule(this); readonly animal: AnimalModule = new AnimalModule(this); readonly color: ColorModule = new ColorModule(this); @@ -163,169 +156,13 @@ export class Faker extends SimpleFaker { */ randomizer?: Randomizer; }); - /** - * Creates a new instance of Faker. - * - * In most cases you should use one of the prebuilt Faker instances instead of the constructor, for example `fakerDE`, `fakerFR`, ... - * - * You only need to use the constructor if you need custom fallback logic or a custom locale. - * - * For more information see our [Localization Guide](https://fakerjs.dev/guide/localization.html). - * - * @param options The options to use. - * @param options.locales The locale data to use. - * @param options.locale The name of the main locale to use. - * @param options.localeFallback The name of the fallback locale to use. - * - * @example - * import { Faker, allLocales } from '@faker-js/faker'; - * // const { Faker, allLocales } = require('@faker-js/faker'); - * - * new Faker({ locales: allLocales }); - * - * @since 6.0.0 - * - * @deprecated Use `new Faker({ locale: [locale, localeFallback] })` instead. - */ - constructor(options: { - /** - * The locale data to use for this instance. - * - * @deprecated Use `new Faker({ locale: [locale, localeFallback] })` instead. - */ - locales: Record; - /** - * The name of the main locale to use. - * - * @default 'en' - * - * @deprecated Use `new Faker({ locale: [locale, localeFallback] })` instead. - */ - locale?: string; - /** - * The name of the fallback locale to use. - * - * @default 'en' - * - * @deprecated Use `new Faker({ locale: [locale, localeFallback] })` instead. - */ - localeFallback?: string; - }); - // This is somehow required for `ConstructorParameters[0]` to work - /** - * Creates a new instance of Faker. - * - * In most cases you should use one of the prebuilt Faker instances instead of the constructor, for example `fakerDE`, `fakerFR`, ... - * - * You only need to use the constructor if you need custom fallback logic or a custom locale. - * - * For more information see our [Localization Guide](https://fakerjs.dev/guide/localization.html). - * - * @param options The options to use. - * @param options.locale The locale data to use or the name of the main locale. - * @param options.locales The locale data to use. - * @param options.localeFallback The name of the fallback locale to use. - * @param options.randomizer The Randomizer to use. - * Specify this only if you want to use it to achieve a specific goal, - * such as sharing the same random generator with other instances/tools. - * Defaults to faker's Mersenne Twister based pseudo random number generator. - * - * @example - * import { Faker, es } from '@faker-js/faker'; - * // const { Faker, es } = require('@faker-js/faker'); - * - * // create a Faker instance with only es data and no en fallback (=> smaller bundle size) - * const customFaker = new Faker({ locale: [es] }); - * - * customFaker.person.firstName(); // 'Javier' - * customFaker.person.lastName(); // 'Ocampo Corrales' - * - * customFaker.music.genre(); // throws Error as this data is not available in `es` - * - * @since 8.0.0 - */ - constructor( - options: - | { - /** - * The locale data to use for this instance. - * If an array is provided, the first locale that has a definition for a given property will be used. - * - * @see mergeLocales(): For more information about how the locales are merged. - */ - locale: LocaleDefinition | LocaleDefinition[]; - /** - * The Randomizer to use. - * Specify this only if you want to use it to achieve a specific goal, - * such as sharing the same random generator with other instances/tools. - * - * @default generateMersenne32Randomizer() - */ - randomizer?: Randomizer; - } - | { - /** - * The locale data to use for this instance. - * - * @deprecated Use `new Faker({ locale: [locale, localeFallback] })` instead. - */ - locales: Record; - /** - * The name of the main locale to use. - * - * @default 'en' - * - * @deprecated Use `new Faker({ locale: [locale, localeFallback] })` instead. - */ - locale?: string; - /** - * The name of the fallback locale to use. - * - * @default 'en' - * - * @deprecated Use `new Faker({ locale: [locale, localeFallback] })` instead. - */ - localeFallback?: string; - } - ); - constructor( - options: - | { - locale: LocaleDefinition | LocaleDefinition[]; - randomizer?: Randomizer; - } - | { - locales: Record; - locale?: string; - localeFallback?: string; - randomizer?: Randomizer; - } - ) { + constructor(options: { + locale: LocaleDefinition | LocaleDefinition[]; + randomizer?: Randomizer; + }) { super({ randomizer: options.randomizer }); - const { locales } = options as { - locales: Record; - }; - - if (locales != null) { - deprecated({ - deprecated: - "new Faker({ locales: {a, b}, locale: 'a', localeFallback: 'b' })", - proposed: - 'new Faker({ locale: [a, b, ...] }) or new Faker({ locale: a })', - since: '8.0', - until: '9.0', - }); - const { locale = 'en', localeFallback = 'en' } = options as { - locale: string; - localeFallback: string; - }; - options = { - locale: [locales[locale], locales[localeFallback]], - }; - } - let { locale } = options; if (Array.isArray(locale)) { @@ -338,7 +175,7 @@ export class Faker extends SimpleFaker { locale = mergeLocales(locale); } - this.rawDefinitions = locale as LocaleDefinition; + this.rawDefinitions = locale; this.definitions = createLocaleProxy(this.rawDefinitions); } @@ -356,85 +193,6 @@ export class Faker extends SimpleFaker { getMetadata(): MetadataDefinition { return this.rawDefinitions.metadata ?? {}; } - - // Pure JS backwards compatibility - - /** - * Do NOT use. This property has been removed. - * - * @deprecated Use the constructor instead. - */ - private get locales(): never { - throw new FakerError( - 'The locales property has been removed. Please use the constructor instead.' - ); - } - - /** - * Do NOT use. This property has been removed. - * - * @deprecated Use the constructor instead. - */ - private set locales(value: never) { - throw new FakerError( - 'The locales property has been removed. Please use the constructor instead.' - ); - } - - /** - * Do NOT use. This property has been removed. - * - * @deprecated Use the constructor instead. - */ - private get locale(): never { - throw new FakerError( - 'The locale property has been removed. Please use the constructor instead.' - ); - } - - /** - * Do NOT use. This property has been removed. - * - * @deprecated Use the constructor instead. - */ - private set locale(value: never) { - throw new FakerError( - 'The locale property has been removed. Please use the constructor instead.' - ); - } - - /** - * Do NOT use. This property has been removed. - * - * @deprecated Use the constructor instead. - */ - private get localeFallback(): never { - throw new FakerError( - 'The localeFallback property has been removed. Please use the constructor instead.' - ); - } - - /** - * Do NOT use. This property has been removed. - * - * @deprecated Use the constructor instead. - */ - private set localeFallback(value: never) { - throw new FakerError( - 'The localeFallback property has been removed. Please use the constructor instead.' - ); - } - - /** - * Do NOT use. This property has been removed. - * - * @deprecated Use the constructor instead. - */ - private setLocale(): never { - throw new FakerError( - 'This method has been removed. Please use the constructor instead.' - ); - } } export type FakerOptions = ConstructorParameters[0]; diff --git a/src/index.ts b/src/index.ts index 8dd2982bbb2..1edb01f9953 100644 --- a/src/index.ts +++ b/src/index.ts @@ -124,8 +124,6 @@ export type { SexType, } from './modules/person'; export type { PhoneModule } from './modules/phone'; -// eslint-disable-next-line deprecation/deprecation -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'; diff --git a/src/modules/helpers/index.ts b/src/modules/helpers/index.ts index 36f050e86e4..593d1ede6a8 100644 --- a/src/modules/helpers/index.ts +++ b/src/modules/helpers/index.ts @@ -4,8 +4,6 @@ import { deprecated } from '../../internal/deprecated'; import { SimpleModuleBase } from '../../internal/module-base'; import { fakeEval } from './eval'; import { luhnCheckValue } from './luhn-check'; -import type { RecordKey } from './unique'; -import * as uniqueExec from './unique'; /** * Returns a number based on given RegEx-based quantifier symbol or quantifier values. @@ -204,14 +202,6 @@ export function legacyReplaceSymbolWithNumber( * Module with various helper methods providing basic (seed-dependent) operations useful for implementing faker methods (without methods requiring localized data). */ export class SimpleHelpersModule extends SimpleModuleBase { - /** - * Global store of unique values. - * This means that faker should *never* return duplicate values across all API methods when using `faker.helpers.unique` without passing `options.store`. - * - * @internal - */ - private readonly uniqueStore: Record = {}; - /** * Slugifies the given string. * For that all spaces (` `) are replaced by hyphens (`-`) @@ -643,7 +633,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 8.0.0 */ - shuffle( + shuffle( list: T[], options: { /** @@ -669,7 +659,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 2.0.1 */ - shuffle( + shuffle( list: ReadonlyArray, options?: { /** @@ -696,7 +686,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 2.0.1 */ - shuffle( + shuffle( list: T[], options?: { /** @@ -707,7 +697,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { inplace?: boolean; } ): T[]; - shuffle(list: T[], options: { inplace?: boolean } = {}): T[] { + shuffle(list: T[], options: { inplace?: boolean } = {}): T[] { const { inplace = false } = options; if (!inplace) { @@ -744,7 +734,10 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 6.0.0 */ - uniqueArray(source: ReadonlyArray | (() => T), length: number): T[] { + uniqueArray( + source: ReadonlyArray | (() => T), + length: number + ): T[] { if (Array.isArray(source)) { const set = new Set(source); const array = [...set]; @@ -823,7 +816,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 6.3.0 */ - maybe( + maybe( callback: () => TResult, options: { /** @@ -855,7 +848,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 6.3.0 */ - objectKey>(object: T): keyof T { + objectKey>(object: T): keyof T { const array: Array = Object.keys(object); return this.arrayElement(array); } @@ -874,7 +867,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 6.3.0 */ - objectValue>(object: T): T[keyof T] { + objectValue>(object: T): T[keyof T] { const key = this.faker.helpers.objectKey(object); return object[key]; } @@ -893,7 +886,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 8.0.0 */ - objectEntry>( + objectEntry>( object: T ): [keyof T, T[keyof T]] { const key = this.faker.helpers.objectKey(object); @@ -914,7 +907,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 6.3.0 */ - arrayElement(array: ReadonlyArray): T { + arrayElement(array: ReadonlyArray): T { // TODO @xDivisionByZerox 2023-04-20: Remove in v9 if (array == null) { throw new FakerError( @@ -951,7 +944,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 8.0.0 */ - weightedArrayElement( + weightedArrayElement( array: ReadonlyArray<{ /** * The weight of the value. @@ -1010,7 +1003,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 6.3.0 */ - arrayElements( + arrayElements( array: ReadonlyArray, count?: | number @@ -1084,6 +1077,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 8.0.0 */ + // This does not use `const T` because enums shouldn't be created on the spot. enumValue>( enumObject: T ): T[keyof T] { @@ -1129,114 +1123,6 @@ export class SimpleHelpersModule extends SimpleModuleBase { return this.faker.number.int(numberOrRange); } - /** - * Generates a unique result using the results of the given method. - * Used unique entries will be stored internally and filtered from subsequent calls. - * - * @template TMethod The type of the method to execute. - * - * @param method The method used to generate the values. - * @param args The arguments used to call the method. Defaults to `[]`. - * @param options The optional options used to configure this method. - * @param options.startTime This parameter does nothing. - * @param options.maxTime The time in milliseconds this method may take before throwing an error. Defaults to `50`. - * @param options.maxRetries The total number of attempts to try before throwing an error. Defaults to `50`. - * @param options.currentIterations This parameter does nothing. - * @param options.exclude The value or values that should be excluded/skipped. Defaults to `[]`. - * @param options.compare The function used to determine whether a value was already returned. Defaults to check the existence of the key. - * @param options.store The store of unique entries. Defaults to a global store. - * - * @see https://github.com/faker-js/faker/issues/1785#issuecomment-1407773744 - * - * @example - * faker.helpers.unique(faker.person.firstName) // 'Corbin' - * - * @since 7.5.0 - * - * @deprecated Please find a dedicated npm package instead, or even create one on your own if you want to. - * More info can be found in issue [faker-js/faker #1785](https://github.com/faker-js/faker/issues/1785). - */ - unique< - TMethod extends ( - // TODO @Shinigami92 2023-02-14: This `any` type can be fixed by anyone if they want to. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...parameters: any[] - ) => RecordKey, - >( - method: TMethod, - args: Parameters = [] as unknown as Parameters, - options: { - /** - * This parameter does nothing. - * - * @default new Date().getTime() - */ - startTime?: number; - /** - * The time in milliseconds this method may take before throwing an error. - * - * @default 50 - */ - maxTime?: number; - /** - * The total number of attempts to try before throwing an error. - * - * @default 50 - */ - maxRetries?: number; - /** - * This parameter does nothing. - * - * @default 0 - */ - currentIterations?: number; - /** - * The value or values that should be excluded/skipped. - * - * @default [] - */ - exclude?: RecordKey | RecordKey[]; - /** - * The function used to determine whether a value was already returned. - * - * Defaults to check the existence of the key. - * - * @default (obj, key) => (obj[key] === undefined ? -1 : 0) - */ - compare?: (obj: Record, key: RecordKey) => 0 | -1; - /** - * The store of unique entries. - * - * Defaults to a global store. - */ - store?: Record; - } = {} - ): ReturnType { - deprecated({ - deprecated: 'faker.helpers.unique', - proposed: - 'https://github.com/faker-js/faker/issues/1785#issuecomment-1407773744', - since: '8.0', - until: '9.0', - }); - - const { - maxTime = 50, - maxRetries = 50, - exclude = [], - store = this.uniqueStore, - } = options; - return uniqueExec.exec(method, args, { - ...options, - startTime: Date.now(), - maxTime, - maxRetries, - currentIterations: 0, - exclude, - store, - }); - } - /** * Generates an array containing values returned by the given method. * @@ -1252,7 +1138,7 @@ export class SimpleHelpersModule extends SimpleModuleBase { * * @since 8.0.0 */ - multiple( + multiple( method: () => TResult, options: { /** diff --git a/src/modules/helpers/unique.ts b/src/modules/helpers/unique.ts deleted file mode 100644 index 20805530478..00000000000 --- a/src/modules/helpers/unique.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { FakerError } from '../../errors/faker-error'; - -export type RecordKey = string | number | symbol; - -/** - * Uniqueness compare function. - * Default behavior is to check value as key against object hash. - * - * @param obj The object to check. - * @param key The key to check. - */ -function defaultCompare( - obj: Record, - key: RecordKey -): 0 | -1 { - if (obj[key] === undefined) { - return -1; - } - - return 0; -} - -/** - * Logs the given code as an error and throws it. - * Also logs a message for helping the user. - * - * @param startTime The time the execution started. - * @param now The current time. - * @param code The error code. - * @param store The store of unique entries. - * @param currentIterations Current iteration or retries of `unique.exec` (current loop depth). - * - * @throws The given error code with additional text. - */ -function errorMessage( - startTime: number, - now: number, - code: string, - store: Record, - currentIterations: number -): never { - console.error('Error', code); - console.log( - `Found ${Object.keys(store).length} unique entries before throwing error. -retried: ${currentIterations} -total time: ${now - startTime}ms` - ); - throw new FakerError( - `${code} for uniqueness check. - -May not be able to generate any more unique values with current settings. -Try adjusting maxTime or maxRetries parameters for faker.helpers.unique().` - ); -} - -/** - * Generates a unique result using the results of the given method. - * Used unique entries will be stored internally and filtered from subsequent calls. - * - * @template TMethod The type of the method to execute. - * - * @param method The method used to generate the values. - * @param args The arguments used to call the method. - * @param options The optional options used to configure this method. - * @param options.startTime The time this execution stared. Defaults to `new Date().getTime()`. - * @param options.maxTime The time in milliseconds this method may take before throwing an error. Defaults to `50`. - * @param options.maxRetries The total number of attempts to try before throwing an error. Defaults to `50`. - * @param options.currentIterations The current attempt. Defaults to `0`. - * @param options.exclude The value or values that should be excluded/skipped. Defaults to `[]`. - * @param options.compare The function used to determine whether a value was already returned. Defaults to check the existence of the key. - * @param options.store The store of unique entries. Defaults to `GLOBAL_UNIQUE_STORE`. - */ -export function exec< - TMethod extends ( - // TODO @Shinigami92 2023-02-14: This `any` type can be fixed by anyone if they want to. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...parameters: any[] - ) => RecordKey, ->( - method: TMethod, - args: Parameters, - options: { - startTime?: number; - maxTime?: number; - maxRetries?: number; - currentIterations?: number; - exclude?: RecordKey | RecordKey[]; - compare?: (obj: Record, key: RecordKey) => 0 | -1; - store?: Record; - } = {} -): ReturnType { - const now = Date.now(); - - const { - startTime = Date.now(), - maxTime = 50, - maxRetries = 50, - currentIterations = 0, - compare = defaultCompare, - store = {}, - } = options; - let { exclude = [] } = options; - options.currentIterations = currentIterations; - - // Support single exclude argument as string - if (!Array.isArray(exclude)) { - exclude = [exclude]; - } - - // If out of time -> throw error. - if (now - startTime >= maxTime) { - return errorMessage( - startTime, - now, - `Exceeded maxTime: ${maxTime}`, - store, - currentIterations - ); - } - - // If out of retries -> throw error. - if (currentIterations >= maxRetries) { - return errorMessage( - startTime, - now, - `Exceeded maxRetries: ${maxRetries}`, - store, - currentIterations - ); - } - - // Execute the provided method to find a potential satisfied value. - const result: ReturnType = method(...args) as ReturnType; - - // If the result has not been previously found, add it to the found array and return the value as it's unique. - if (compare(store, result) === -1 && !exclude.includes(result)) { - store[result] = result; - options.currentIterations = 0; - return result; - } - - // Conflict, try again. - options.currentIterations++; - return exec(method, args, { - ...options, - startTime, - maxTime, - maxRetries, - compare, - exclude, - }); -} diff --git a/src/modules/random/index.ts b/src/modules/random/index.ts deleted file mode 100644 index 371c810ec52..00000000000 --- a/src/modules/random/index.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { FakerError } from '../../errors/faker-error'; -import { deprecated } from '../../internal/deprecated'; -import { ModuleBase } from '../../internal/module-base'; -import type { LiteralUnion } from '../../utils/types'; -import type { - AlphaChar, - AlphaNumericChar, - Casing, - NumericChar, -} from '../string'; - -/** - * Generates random values of different kinds. - * - * @deprecated Use the modules specific to the type of data you want to generate instead. - */ -export class RandomModule extends ModuleBase { - /** - * Returns a random word. - * - * @see faker.lorem.word(): For generating a random placeholder word. - * @see faker.word.sample(): For generating a random real word. - * - * @example - * faker.random.word() // 'Seamless' - * - * @since 3.1.0 - * - * @deprecated Use `faker.lorem.word()` or `faker.word.sample()` instead. - */ - word(): string { - deprecated({ - deprecated: 'faker.random.word()', - proposed: 'faker.lorem.word() or faker.word.sample()', - since: '8.0', - until: '9.0', - }); - - const wordMethods = [ - () => this.faker.location.cardinalDirection(), - this.faker.location.country, - this.faker.location.county, - () => this.faker.location.direction(), - () => this.faker.location.ordinalDirection(), - this.faker.location.state, - this.faker.location.street, - - this.faker.color.human, - - this.faker.commerce.department, - this.faker.commerce.product, - this.faker.commerce.productAdjective, - this.faker.commerce.productMaterial, - this.faker.commerce.productName, - - this.faker.company.buzzAdjective, - this.faker.company.buzzNoun, - this.faker.company.buzzVerb, - this.faker.company.catchPhraseAdjective, - this.faker.company.catchPhraseDescriptor, - this.faker.company.catchPhraseNoun, - - this.faker.finance.accountName, - this.faker.finance.currencyName, - this.faker.finance.transactionType, - - this.faker.hacker.abbreviation, - this.faker.hacker.adjective, - this.faker.hacker.ingverb, - this.faker.hacker.noun, - this.faker.hacker.verb, - - this.faker.lorem.word, - - this.faker.music.genre, - - this.faker.person.gender, - this.faker.person.jobArea, - this.faker.person.jobDescriptor, - this.faker.person.jobTitle, - this.faker.person.jobType, - this.faker.person.sex, - - () => this.faker.science.chemicalElement().name, - () => this.faker.science.unit().name, - - this.faker.vehicle.bicycle, - this.faker.vehicle.color, - this.faker.vehicle.fuel, - this.faker.vehicle.manufacturer, - this.faker.vehicle.type, - - this.faker.word.adjective, - this.faker.word.adverb, - this.faker.word.conjunction, - this.faker.word.interjection, - this.faker.word.noun, - this.faker.word.preposition, - this.faker.word.verb, - ]; - - const bannedChars = [ - '!', - '#', - '%', - '&', - '*', - ')', - '(', - '+', - '=', - '.', - '<', - '>', - '{', - '}', - '[', - ']', - ':', - ';', - "'", - '"', - '_', - '-', - ]; - let result = ''; - - let iteration = 0; - - do { - // randomly pick from the many faker methods that can generate words - const randomWordMethod = this.faker.helpers.arrayElement(wordMethods); - - try { - result = randomWordMethod(); - } catch { - // catch missing locale data potentially required by randomWordMethod - iteration++; - - if (iteration > 100) { - throw new FakerError( - 'No matching word data available for the current locale' - ); - } - - continue; - } - } while (!result || bannedChars.some((char) => result.includes(char))); - - return this.faker.helpers.arrayElement(result.split(' ')); - } - - /** - * Returns a string with a given number of random words. - * - * @param count The number or range of words. Defaults to a random value between `1` and `3`. - * @param count.min The minimum number of words. Defaults to `1`. - * @param count.max The maximum number of words. Defaults to `3`. - * - * @see faker.lorem.words(): For generating a sequence of random placeholder words. - * @see faker.word.words(): For generating a sequence of random real words. - * - * @example - * faker.random.words() // 'neural' - * faker.random.words(5) // 'copy Handcrafted bus client-server Point' - * faker.random.words({ min: 3, max: 5 }) // 'cool sticky Borders' - * - * @since 3.1.0 - * - * @deprecated Use `faker.lorem.words()` or `faker.word.words()` instead. - */ - words( - count: - | number - | { - /** - * The minimum number of words. - */ - min: number; - /** - * The maximum number of words. - */ - max: number; - } = { min: 1, max: 3 } - ): string { - deprecated({ - deprecated: 'faker.random.words()', - proposed: 'faker.lorem.words() or faker.word.words()', - since: '8.0', - until: '9.0', - }); - - // eslint-disable-next-line deprecation/deprecation - return this.faker.helpers.multiple(this.word, { count }).join(' '); - } - - /** - * Do NOT use. This property has been removed. - * - * @example - * faker.helpers.objectKey(allLocales) - * faker.helpers.objectValue(allFakers) - * - * @since 3.1.0 - * - * @deprecated Use `faker.helpers.objectKey(allLocales/allFakers)` instead. - */ - private locale(): never { - // We cannot invoke this ourselves, because this would link to all locale data and increase the bundle size by a lot. - throw new FakerError( - 'This method has been removed. Please use `faker.helpers.objectKey(allLocales/allFakers)` instead.' - ); - } - - /** - * Generating a string consisting of letters in the English alphabet. - * - * @param options Either the number of characters or an options instance. - * @param options.count The number of characters to generate. Defaults to `1`. - * @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(): For the replacement method. - * - * @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: - | number - | { - /** - * The number of characters to generate. - * - * @default 1 - */ - count?: number; - /** - * The casing of the characters. - * - * @default 'mixed' - */ - casing?: Casing; - /** - * An array with characters to exclude. - * - * @default [] - */ - bannedChars?: ReadonlyArray> | string; - } = {} - ): string { - deprecated({ - deprecated: 'faker.random.alpha()', - proposed: 'faker.string.alpha()', - since: '8.0', - until: '9.0', - }); - if (typeof options === 'number') { - return this.faker.string.alpha(options); - } - - return this.faker.string.alpha({ - length: options.count, - casing: options.casing, - exclude: options.bannedChars, - }); - } - - /** - * Generating a string consisting of alpha characters and digits. - * - * @param count The number of characters and digits to generate. Defaults to `1`. - * @param options The options to use. - * @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(): For the replacement method. - * - * @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, - options: { - /** - * The casing of the characters. - * - * @default 'lower' - */ - casing?: Casing; - /** - * An array of characters and digits which should be banned in the generated string. - * - * @default [] - */ - bannedChars?: ReadonlyArray> | string; - } = {} - ): string { - deprecated({ - deprecated: 'faker.random.alphaNumeric()', - proposed: 'faker.string.alphanumeric()', - since: '8.0', - until: '9.0', - }); - return this.faker.string.alphanumeric({ - length: count, - exclude: options.bannedChars, - casing: options.casing, - }); - } - - /** - * Generates a given length string of digits. - * - * @param length The number of digits to generate. Defaults to `1`. - * @param options The options to use. - * @param options.allowLeadingZeros Whether leading zeros are allowed or not. Defaults to `true`. - * @param options.bannedDigits An array of digits which should be banned in the generated string. Defaults to `[]`. - * - * @see faker.string.numeric(): For the replacement method. - * - * @example - * faker.random.numeric() // '2' - * faker.random.numeric(5) // '31507' - * faker.random.numeric(42) // '00434563150765416546479875435481513188548' - * faker.random.numeric(42, { allowLeadingZeros: true }) // '00564846278453876543517840713421451546115' - * faker.random.numeric(6, { bannedDigits: ['0'] }) // '943228' - * - * @since 6.3.0 - * - * @deprecated Use `faker.string.numeric()` instead. - */ - numeric( - length: number = 1, - options: { - /** - * Whether leading zeros are allowed or not. - * - * @default true - */ - allowLeadingZeros?: boolean; - /** - * An array of digits which should be banned in the generated string. - * - * @default [] - */ - bannedDigits?: ReadonlyArray> | string; - } = {} - ): string { - deprecated({ - deprecated: 'faker.random.numeric()', - proposed: 'faker.string.numeric()', - since: '8.0', - until: '9.0', - }); - return this.faker.string.numeric({ - length, - allowLeadingZeros: options.allowLeadingZeros, - exclude: options.bannedDigits, - }); - } -} diff --git a/test/all-functional.spec.ts b/test/all-functional.spec.ts index 80b1fecef59..b1819428c44 100644 --- a/test/all-functional.spec.ts +++ b/test/all-functional.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { Faker, RandomModule, allLocales } from '../src'; +import type { Faker, allLocales } from '../src'; import { allFakers, fakerEN } from '../src'; import { keys } from '../src/internal/keys'; @@ -55,9 +55,6 @@ const BROKEN_LOCALE_METHODS = { zipCode: ['en_HK'], zipCodeByState: ['en_HK'], }, - random: { - locale: '*', // locale() has been pseudo removed - } as SkipConfig, string: { fromCharacters: '*', }, diff --git a/test/modules/__snapshots__/helpers.spec.ts.snap b/test/modules/__snapshots__/helpers.spec.ts.snap index 81f554399d5..04b416a2d18 100644 --- a/test/modules/__snapshots__/helpers.spec.ts.snap +++ b/test/modules/__snapshots__/helpers.spec.ts.snap @@ -204,14 +204,6 @@ exports[`helpers > 42 > slugify > noArgs 1`] = `""`; exports[`helpers > 42 > slugify > some string 1`] = `"hello-world"`; -exports[`helpers > 42 > unique > with () => number 1`] = `3373557438480384`; - -exports[`helpers > 42 > unique > with () => number and args 1`] = `19`; - -exports[`helpers > 42 > unique > with customMethod 1`] = `"Test-188"`; - -exports[`helpers > 42 > unique > with customMethod and args 1`] = `"prefix-1-Test-188"`; - exports[`helpers > 42 > uniqueArray > with array 1`] = ` [ "H", @@ -444,14 +436,6 @@ exports[`helpers > 1211 > slugify > noArgs 1`] = `""`; exports[`helpers > 1211 > slugify > some string 1`] = `"hello-world"`; -exports[`helpers > 1211 > unique > with () => number 1`] = `8363366036799488`; - -exports[`helpers > 1211 > unique > with () => number and args 1`] = `47`; - -exports[`helpers > 1211 > unique > with customMethod 1`] = `"Test-465"`; - -exports[`helpers > 1211 > unique > with customMethod and args 1`] = `"prefix-1-Test-465"`; - exports[`helpers > 1211 > uniqueArray > with array 1`] = ` [ "W", @@ -666,14 +650,6 @@ exports[`helpers > 1337 > slugify > noArgs 1`] = `""`; exports[`helpers > 1337 > slugify > some string 1`] = `"hello-world"`; -exports[`helpers > 1337 > unique > with () => number 1`] = `2360108468142080`; - -exports[`helpers > 1337 > unique > with () => number and args 1`] = `13`; - -exports[`helpers > 1337 > unique > with customMethod 1`] = `"Test-132"`; - -exports[`helpers > 1337 > unique > with customMethod and args 1`] = `"prefix-1-Test-132"`; - exports[`helpers > 1337 > uniqueArray > with array 1`] = ` [ "o", diff --git a/test/modules/helpers.spec-d.ts b/test/modules/helpers.spec-d.ts new file mode 100644 index 00000000000..11ce8c88df3 --- /dev/null +++ b/test/modules/helpers.spec-d.ts @@ -0,0 +1,127 @@ +import { describe, expectTypeOf, it } from 'vitest'; +import { faker } from '../../src'; + +describe('helpers', () => { + describe('shuffle', () => { + describe('inplace: true', () => { + it('const generic single element', () => { + const actual = faker.helpers.shuffle([1], { inplace: true }); + expectTypeOf(actual).toEqualTypeOf>(); + }); + + it('const generic multiple elements', () => { + const actual = faker.helpers.shuffle([1, 'a', false], { + inplace: true, + }); + expectTypeOf(actual).toEqualTypeOf>(); + }); + }); + + describe('inplace: false', () => { + it('const generic single element', () => { + const actual = faker.helpers.shuffle([1], { inplace: false }); + expectTypeOf(actual).toEqualTypeOf>(); + }); + + it('const generic multiple elements', () => { + const actual = faker.helpers.shuffle([1, 'a', false], { + inplace: false, + }); + expectTypeOf(actual).toEqualTypeOf>(); + }); + }); + }); + + describe('uniqueArray', () => { + it('const generic single element', () => { + const actual = faker.helpers.uniqueArray([1], 1); + expectTypeOf(actual).toEqualTypeOf>(); + }); + + it('const generic multiple elements', () => { + const actual = faker.helpers.uniqueArray([1, 'a', false], 3); + expectTypeOf(actual).toEqualTypeOf>(); + }); + }); + + describe('maybe', () => { + it('const generic single element', () => { + // TODO @ST-DDT 2024-02-25: Check why this is detected as `number` instead of `1` + const actual = faker.helpers.maybe(() => 1); + expectTypeOf(actual).toEqualTypeOf(); + }); + }); + + describe('objectKey', () => { + it('const generic single element', () => { + const actual = faker.helpers.objectKey({ a: 1 }); + expectTypeOf(actual).toEqualTypeOf<'a'>(); + }); + + it('const generic multiple elements', () => { + const actual = faker.helpers.objectKey({ a: 1, b: 'a', c: false }); + expectTypeOf(actual).toEqualTypeOf<'a' | 'b' | 'c'>(); + }); + }); + + describe('objectValue', () => { + it('const generic single element', () => { + const actual = faker.helpers.objectValue({ a: 1 }); + expectTypeOf(actual).toEqualTypeOf<1>(); + }); + + it('const generic multiple elements', () => { + const actual = faker.helpers.objectValue({ a: 1, b: 'a', c: false }); + expectTypeOf(actual).toEqualTypeOf<1 | 'a' | false>(); + }); + }); + + describe('objectEntry', () => { + it('const generic single element', () => { + const actual = faker.helpers.objectEntry({ a: 1 }); + expectTypeOf(actual).toEqualTypeOf<['a', 1]>(); + }); + + it('const generic multiple elements', () => { + const actual = faker.helpers.objectEntry({ a: 1, b: 'a', c: false }); + // TODO @ST-DDT 2024-02-25: Check whether we can infer the return type any better + expectTypeOf(actual).toEqualTypeOf<['a' | 'b' | 'c', false | 1 | 'a']>(); + }); + }); + + describe('arrayElement', () => { + it('const generic single element', () => { + const actual = faker.helpers.arrayElement([1]); + expectTypeOf(actual).toEqualTypeOf<1>(); + }); + + it('const generic multiple elements', () => { + const actual = faker.helpers.arrayElement([1, 'a', false]); + expectTypeOf(actual).toEqualTypeOf<1 | 'a' | false>(); + }); + }); + + describe('arrayElements', () => { + it('const generic single element', () => { + const actual = faker.helpers.arrayElements([1], 1); + expectTypeOf(actual).toEqualTypeOf>(); + }); + + it('const generic multiple elements', () => { + const actual = faker.helpers.arrayElements([1, 'a', false], 3); + expectTypeOf(actual).toEqualTypeOf>(); + }); + }); + + describe('multiple', () => { + it('const generic single element', () => { + const actual = faker.helpers.multiple(() => 1); + expectTypeOf(actual).toEqualTypeOf(); + }); + + it('const generic multiple elements', () => { + const actual = faker.helpers.multiple(() => 1, { count: 3 }); + expectTypeOf(actual).toEqualTypeOf(); + }); + }); +}); diff --git a/test/modules/helpers.spec.ts b/test/modules/helpers.spec.ts index 50bacda336f..6163a0d2fc5 100644 --- a/test/modules/helpers.spec.ts +++ b/test/modules/helpers.spec.ts @@ -1,20 +1,12 @@ import { describe, expect, it } from 'vitest'; import { FakerError, faker } from '../../src'; import { luhnCheck } from '../../src/modules/helpers/luhn-check'; -import type { RecordKey } from '../../src/modules/helpers/unique'; import { seededTests } from '../support/seeded-runs'; import { times } from './../support/times'; import './../vitest-extensions'; const NON_SEEDED_BASED_RUN = 5; -function customUniqueMethod(prefix: string = ''): string { - const element = faker.helpers.arrayElement( - Array.from({ length: 500 }, (_, index) => `Test-${index + 1}`) - ); - return `${prefix}${element}`; -} - describe('helpers', () => { seededTests(faker, 'helpers', (t) => { t.describe('slugify', (t) => { @@ -180,13 +172,6 @@ describe('helpers', () => { t.it('with number', 5).it('with range', { min: 1, max: 10 }); }); - t.describe('unique', (t) => { - t.it('with customMethod', customUniqueMethod) - .it('with customMethod and args', customUniqueMethod, ['prefix-1-']) - .it('with () => number', faker.number.int) - .it('with () => number and args', faker.number.int, [50]); - }); - t.describe('multiple', (t) => { t.it('with only method', faker.datatype.number) .it('with method and count', faker.datatype.number, { count: 5 }) @@ -1008,7 +993,7 @@ describe('helpers', () => { }); it('replaces a token with a random value for a method with an object parameter', () => { - const actual = faker.helpers.fake('{{random.alpha({"count": 3})}}'); + const actual = faker.helpers.fake('{{string.alpha({"length": 3})}}'); expect(actual).toMatch(/^[a-z]{3}$/i); }); @@ -1165,67 +1150,6 @@ describe('helpers', () => { }); }); - describe('unique()', () => { - it('should be possible to call a function with no arguments and return a result', () => { - const result = faker.helpers.unique(faker.internet.email); - expect(result).toBeTypeOf('string'); - }); - - it('should be possible to call a function with arguments and return a result', () => { - const result = faker.helpers.unique(faker.internet.email, [ - 'fName', - 'lName', - 'domain', - ]); // third argument is provider, or domain for email - expect(result).toMatch(/@domain/); - }); - - it('should be possible to limit unique call by maxTime in ms', () => { - expect(() => { - faker.helpers.unique(faker.internet.protocol, [], { - maxTime: 1, - maxRetries: 9999, - exclude: ['https', 'http'], - }); - }).toThrow( - new FakerError(`Exceeded maxTime: 1 for uniqueness check. - -May not be able to generate any more unique values with current settings. -Try adjusting maxTime or maxRetries parameters for faker.helpers.unique().`) - ); - }); - - it('should be possible to limit unique call by maxRetries', () => { - expect(() => { - faker.helpers.unique(faker.internet.protocol, [], { - maxTime: 5000, - maxRetries: 5, - exclude: ['https', 'http'], - }); - }).toThrow( - new FakerError(`Exceeded maxRetries: 5 for uniqueness check. - -May not be able to generate any more unique values with current settings. -Try adjusting maxTime or maxRetries parameters for faker.helpers.unique().`) - ); - }); - - it('should throw a FakerError instance on error', () => { - expect(() => { - faker.helpers.unique(faker.internet.protocol, [], { - maxTime: 5000, - maxRetries: 5, - exclude: ['https', 'http'], - }); - }).toThrow( - new FakerError(`Exceeded maxRetries: 5 for uniqueness check. - -May not be able to generate any more unique values with current settings. -Try adjusting maxTime or maxRetries parameters for faker.helpers.unique().`) - ); - }); - }); - describe('multiple()', () => { it('should generate values from the function with a default length of 3', () => { const result = faker.helpers.multiple(faker.person.firstName); @@ -1255,89 +1179,4 @@ Try adjusting maxTime or maxRetries parameters for faker.helpers.unique().`) }); } ); - - // This test can be only executed once, because the unique function has a global state. - // See: https://github.com/faker-js/faker/issues/371 - describe('global unique()', () => { - it('should be possible to exclude results as array', () => { - const internetProtocol = () => - faker.helpers.arrayElement(['https', 'http']); - const result = faker.helpers.unique(internetProtocol, [], { - exclude: ['https'], - }); - expect(result).toBe('http'); - }); - - it('no conflict', () => { - let i = 0; - const method = () => `no conflict: ${i++}`; - expect(faker.helpers.unique(method)).toBe('no conflict: 0'); - expect(faker.helpers.unique(method)).toBe('no conflict: 1'); - }); - - it('with conflict', () => { - const method = () => 'with conflict: 0'; - expect(faker.helpers.unique(method)).toBe('with conflict: 0'); - expect(() => - faker.helpers.unique(method, [], { - maxRetries: 1, - }) - ).toThrow( - new FakerError(`Exceeded maxRetries: 1 for uniqueness check. - -May not be able to generate any more unique values with current settings. -Try adjusting maxTime or maxRetries parameters for faker.helpers.unique().`) - ); - }); - - it('should not mutate most of the input option properties', () => { - const method = () => 'options-mutate-test'; - - const startTime = Date.now(); - const maxTime = 49; - const maxRetries = 49; - const currentIterations = 0; - const exclude: string[] = []; - const compare = (obj: Record, key: RecordKey) => - obj[key] === undefined ? -1 : 0; - - const options = { - startTime, - maxTime, - maxRetries, - currentIterations, - exclude, - compare, - }; - - faker.helpers.unique(method, [], options); - - expect(options.startTime).toBe(startTime); - expect(options.maxTime).toBe(maxTime); - expect(options.maxRetries).toBe(maxRetries); - // `options.currentIterations` is incremented in the `faker.helpers.unique` function. - expect(options.exclude).toBe(exclude); - expect(options.compare).toBe(compare); - }); - - it('should be possible to pass a user-specific store', () => { - const store: Record = {}; - - const method = () => 'with conflict: 0'; - - expect(faker.helpers.unique(method, [], { store })).toBe( - 'with conflict: 0' - ); - expect(store).toEqual({ 'with conflict: 0': 'with conflict: 0' }); - - expect(() => faker.helpers.unique(method, [], { store })).toThrow(); - - delete store['with conflict: 0']; - - expect(faker.helpers.unique(method, [], { store })).toBe( - 'with conflict: 0' - ); - expect(store).toEqual({ 'with conflict: 0': 'with conflict: 0' }); - }); - }); }); diff --git a/test/modules/random.spec.ts b/test/modules/random.spec.ts deleted file mode 100644 index 94de137ead3..00000000000 --- a/test/modules/random.spec.ts +++ /dev/null @@ -1,461 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { Faker, FakerError, faker, fakerZH_CN } from '../../src'; -import { seededTests } from '../support/seeded-runs'; -import { times } from './../support/times'; - -const NON_SEEDED_BASED_RUN = 5; - -describe('random', () => { - seededTests(faker, 'random', (t) => { - t.it('word'); - t.skip('locale' as 'word'); // locale() has been pseudo removed - - t.describeEach( - 'alpha', - 'alphaNumeric', - 'numeric' - )((t) => { - t.it('noArgs').it('with length', 5); - }); - - t.describe('words', (t) => { - t.it('noArgs') - .it('with length', 5) - .it('with length range', { min: 1, max: 5 }); - }); - }); - - describe.each(times(NON_SEEDED_BASED_RUN).map(() => faker.seed()))( - 'random seeded tests for seed %i', - () => { - describe('word', () => { - const bannedChars = [ - '!', - '#', - '%', - '&', - '*', - ')', - '(', - '+', - '=', - '.', - '<', - '>', - '{', - '}', - '[', - ']', - ':', - ';', - "'", - '"', - '_', - '-', - ]; - - it('should return a random word', () => { - const actual = faker.random.word(); - - expect(actual).toBeTruthy(); - expect(actual).toBeTypeOf('string'); - }); - - it.each(times(50))( - 'should only contain a word without undesirable non-alpha characters (run %i)', - () => { - const actual = faker.random.word(); - - expect(actual).not.satisfy((word: string) => - bannedChars.some((char) => word.includes(char)) - ); - } - ); - - it.each(times(50))( - 'should only contain a word without undesirable non-alpha characters, locale=zh_CN (run %i)', - () => { - const actual = fakerZH_CN.random.word(); - - expect(actual).not.satisfy((word: string) => - bannedChars.some((char) => word.includes(char)) - ); - } - ); - - it('should throw error if no data are available', () => { - const faker = new Faker({ - locale: [{ metadata: { title: 'custom' } }], - }); - - expect(() => faker.random.word()).toThrow(); - }); - }); - - describe('words', () => { - it('should return random words', () => { - const actual = faker.random.words(); - - expect(actual).toBeTruthy(); - expect(actual).toBeTypeOf('string'); - - const words = actual.split(' '); - expect(words.length).toBeGreaterThanOrEqual(1); - expect(words.length).toBeLessThanOrEqual(3); - }); - - it('should return 5 random words', () => { - const actual = faker.random.words(5); - - expect(actual).toBeTruthy(); - expect(actual).toBeTypeOf('string'); - - const words = actual.split(' '); - expect(words).toHaveLength(5); - }); - - it('should return 3-5 random words', () => { - const actual = faker.random.words({ min: 3, max: 5 }); - - expect(actual).toBeTruthy(); - expect(actual).toBeTypeOf('string'); - - const words = actual.split(' '); - expect(words.length).toBeGreaterThanOrEqual(3); - expect(words.length).toBeLessThanOrEqual(5); - }); - }); - - describe('alpha', () => { - it('should return single letter when no count provided', () => { - const actual = faker.random.alpha(); - - expect(actual).toHaveLength(1); - }); - - it('should return mixed letter when no option provided', () => { - const actual = faker.random.alpha(); - - expect(actual).toMatch(/^[a-z]$/i); - }); - - 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.random.alpha({ count: 250, casing }); - expect(actual).toMatch(pattern); - }); - - it('should generate many random letters', () => { - const actual = faker.random.alpha(5); - - expect(actual).toHaveLength(5); - }); - - it.each([0, -1, -100])( - 'should return empty string when length is <= 0', - (length) => { - const actual = faker.random.alpha(length); - - expect(actual).toBe(''); - } - ); - - it('should be able to ban some characters', () => { - const actual = faker.random.alpha({ - count: 5, - bannedChars: ['a', 'p', 'A', 'P'], - }); - - expect(actual).toHaveLength(5); - expect(actual).toMatch(/^[b-oq-z]{5}$/i); - }); - - it('should be able to ban some characters via string', () => { - const actual = faker.random.alpha({ - count: 5, - bannedChars: 'apAP', - }); - - expect(actual).toHaveLength(5); - expect(actual).toMatch(/^[b-oq-z]{5}$/i); - }); - - it('should be able handle mistake in banned characters array', () => { - const alphaText = faker.random.alpha({ - count: 5, - bannedChars: ['a', 'a', 'p', 'A', 'A', 'P'], - }); - - expect(alphaText).toHaveLength(5); - expect(alphaText).toMatch(/^[b-oq-z]{5}$/i); - }); - - it('should throw if all possible characters being banned', () => { - const bannedChars = [ - ...'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', - ]; - expect(() => - faker.random.alpha({ - count: 5, - bannedChars, - }) - ).toThrow( - new FakerError( - 'Unable to generate string: No characters to select from.' - ) - ); - }); - - it('should not mutate the input object', () => { - const input: { - count: number; - casing: 'mixed'; - bannedChars: string[]; - } = Object.freeze({ - count: 5, - casing: 'mixed', - bannedChars: ['a', '%'], - }); - - expect(() => faker.random.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.random.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.random.alphaNumeric(250, { casing }); - expect(actual).toMatch(pattern); - }); - - it('should generate many random characters', () => { - const actual = faker.random.alphaNumeric(5); - - expect(actual).toHaveLength(5); - }); - - it.each([0, -1, -100])( - 'should return empty string when length is <= 0', - (length) => { - const actual = faker.random.alphaNumeric(length); - - expect(actual).toBe(''); - } - ); - - it('should be able to ban all alphabetic characters', () => { - const bannedChars = [...'abcdefghijklmnopqrstuvwxyz']; - const alphaText = faker.random.alphaNumeric(5, { - 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.random.alphaNumeric(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', () => { - const bannedChars = [...'0123456789']; - const alphaText = faker.random.alphaNumeric(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.random.alphaNumeric(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.random.alphaNumeric(5, { - bannedChars: ['a', 'p', 'a'], - casing: 'lower', - }); - - expect(alphaText).toHaveLength(5); - expect(alphaText).toMatch(/^[0-9b-oq-z]{5}$/); - }); - - it('should throw if all possible characters being banned', () => { - const bannedChars = [...'abcdefghijklmnopqrstuvwxyz0123456789']; - expect(() => - faker.random.alphaNumeric(5, { - bannedChars, - casing: 'lower', - }) - ).toThrow( - new FakerError( - 'Unable to generate string: No characters to select from.' - ) - ); - }); - - it('should throw if all possible characters being banned via string', () => { - const bannedChars = 'abcdefghijklmnopqrstuvwxyz0123456789'; - expect(() => - faker.random.alphaNumeric(5, { - bannedChars, - casing: 'lower', - }) - ).toThrow(); - }); - - it('should not mutate the input object', () => { - const input: { - bannedChars: string[]; - } = Object.freeze({ - bannedChars: ['a', '0', '%'], - }); - - expect(() => faker.random.alphaNumeric(5, input)).not.toThrow(); - expect(input.bannedChars).toEqual(['a', '0', '%']); - }); - }); - - describe('numeric', () => { - it('should return single digit when no length provided', () => { - const actual = faker.random.numeric(); - - expect(actual).toHaveLength(1); - expect(actual).toMatch(/^[0-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(/^[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(/^[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'], - }); - - expect(actual).toBe('0000'); - }); - - it('should allow leading zeros via option and all other digits banned via string', () => { - const actual = faker.random.numeric(4, { - allowLeadingZeros: true, - bannedDigits: '123456789', - }); - - 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'], - }) - ).toThrow( - new FakerError( - 'Unable to generate numeric string, because all possible digits are excluded.' - ) - ); - }); - - it('should fail on leading zeros via option and all other digits banned via string', () => { - expect(() => - faker.random.numeric(4, { - allowLeadingZeros: false, - bannedDigits: '123456789', - }) - ).toThrow( - new FakerError( - 'Unable to generate numeric string, because all possible digits are excluded.' - ) - ); - }); - - it('should ban all digits passed via bannedDigits', () => { - const actual = faker.random.numeric(1000, { - bannedDigits: [...'c84U1'], - }); - - expect(actual).toHaveLength(1000); - expect(actual).toMatch(/^[0235679]{1000}$/); - }); - - it('should ban all digits passed via bannedDigits via string', () => { - const actual = faker.random.numeric(1000, { - bannedDigits: 'c84U1', - }); - - expect(actual).toHaveLength(1000); - expect(actual).toMatch(/^[0235679]{1000}$/); - }); - }); - } - ); -}); diff --git a/vitest.config.ts b/vitest.config.ts index e903e970670..1c73cfbb318 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -14,11 +14,17 @@ export default defineConfig({ reporter: ['clover', 'cobertura', 'lcov', 'text'], include: ['src'], }, - reporters: 'basic', + reporters: process.env.CI_PREFLIGHT + ? ['basic', 'github-actions'] + : ['basic'], sequence: { seed: VITEST_SEQUENCE_SEED, shuffle: true, }, + typecheck: { + enabled: true, + include: ['test/**/*.spec-d.ts'], + }, onConsoleLog(log, type) { if ( type === 'stderr' &&