diff options
| author | Leyla Jähnig <[email protected]> | 2022-08-29 13:10:45 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-08-29 11:10:45 +0000 |
| commit | 7f8b8716ba69e24800e26d6c072c3076c01bfccf (patch) | |
| tree | 909b28019e2d461ba5257ac91ea98ca1220b9a34 | |
| parent | c2108fa5db889bb1455a5735934776bcf91fabac (diff) | |
| download | faker-7f8b8716ba69e24800e26d6c072c3076c01bfccf.tar.xz faker-7f8b8716ba69e24800e26d6c072c3076c01bfccf.zip | |
refactor(unique): move to helpers (#1298)
| -rw-r--r-- | src/faker.ts | 2 | ||||
| -rw-r--r-- | src/modules/helpers/index.ts | 44 | ||||
| -rw-r--r-- | src/modules/helpers/unique.ts (renamed from src/modules/unique/unique.ts) | 4 | ||||
| -rw-r--r-- | src/modules/unique/index.ts | 26 | ||||
| -rw-r--r-- | test/__snapshots__/helpers.spec.ts.snap | 24 | ||||
| -rw-r--r-- | test/helpers.spec.ts | 159 | ||||
| -rw-r--r-- | test/unique.spec.ts | 84 |
7 files changed, 249 insertions, 94 deletions
diff --git a/src/faker.ts b/src/faker.ts index c84d77a4..9534e373 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -77,7 +77,7 @@ export class Faker { readonly definitions: LocaleDefinition = this.initDefinitions(); readonly fake: Fake['fake'] = new Fake(this).fake; - readonly unique: Unique['unique'] = new Unique().unique; + readonly unique: Unique['unique'] = new Unique(this).unique; readonly mersenne: Mersenne = new Mersenne(); readonly random: Random = new Random(this); diff --git a/src/modules/helpers/index.ts b/src/modules/helpers/index.ts index fac7e3e2..5228c605 100644 --- a/src/modules/helpers/index.ts +++ b/src/modules/helpers/index.ts @@ -2,6 +2,8 @@ import type { Faker } from '../..'; import { FakerError } from '../../errors/faker-error'; import { deprecated } from '../../internal/deprecated'; import { luhnCheckValue } from './luhn-check'; +import type { RecordKey } from './unique'; +import * as uniqueExec from './unique'; /** * Module with various helper methods that transform the method input rather than returning values from locales. @@ -590,4 +592,46 @@ export class Helpers { // return the response recursively until we are done finding all tags return this.fake(res); } + + /** + * Generates a unique result using the results of the given method. + * Used unique entries will be stored internally and filtered from subsequent calls. + * + * @template Method 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 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. + * + * @example + * faker.helpers.unique(faker.name.firstName) // 'Corbin' + */ + unique<Method extends (...parameters) => RecordKey>( + method: Method, + args?: Parameters<Method>, + options: { + startTime?: number; + maxTime?: number; + maxRetries?: number; + currentIterations?: number; + exclude?: RecordKey | RecordKey[]; + compare?: (obj: Record<RecordKey, RecordKey>, key: RecordKey) => 0 | -1; + store?: Record<RecordKey, RecordKey>; + } = {} + ): ReturnType<Method> { + const { maxTime = 50, maxRetries = 50 } = options; + return uniqueExec.exec(method, args, { + ...options, + startTime: new Date().getTime(), + maxTime, + maxRetries, + currentIterations: 0, + }); + } } diff --git a/src/modules/unique/unique.ts b/src/modules/helpers/unique.ts index 70408faf..31fddec8 100644 --- a/src/modules/unique/unique.ts +++ b/src/modules/helpers/unique.ts @@ -4,7 +4,7 @@ export type RecordKey = string | number | symbol; /** * Global store of unique values. - * This means that faker should *never* return duplicate values across all API methods when using `Faker.unique` without passing `options.store`. + * This means that faker should *never* return duplicate values across all API methods when using `Faker.helpers.unique` without passing `options.store`. */ const GLOBAL_UNIQUE_STORE: Record<RecordKey, RecordKey> = {}; @@ -60,7 +60,7 @@ total time: ${now - startTime}ms` `${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.unique().` +Try adjusting maxTime or maxRetries parameters for faker.helpers.unique().` ); } diff --git a/src/modules/unique/index.ts b/src/modules/unique/index.ts index d77004e4..6c02d6d8 100644 --- a/src/modules/unique/index.ts +++ b/src/modules/unique/index.ts @@ -1,11 +1,14 @@ -import type { RecordKey } from './unique'; -import * as uniqueExec from './unique'; +import type { Faker } from '../..'; +import { deprecated } from '../../internal/deprecated'; +import type { RecordKey } from '../helpers/unique'; /** * Module to generate unique entries. + * + * @deprecated */ export class Unique { - constructor() { + constructor(private readonly faker: Faker) { // Bind `this` so namespaced is working correctly for (const name of Object.getOwnPropertyNames(Unique.prototype)) { if ( @@ -36,8 +39,12 @@ export class Unique { * @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 faker.helpers.unique() + * * @example * faker.unique(faker.name.firstName) // 'Corbin' + * + * @deprecated Use faker.helpers.unique() instead. */ unique<Method extends (...parameters) => RecordKey>( method: Method, @@ -52,13 +59,12 @@ export class Unique { store?: Record<RecordKey, RecordKey>; } = {} ): ReturnType<Method> { - const { maxTime = 50, maxRetries = 50 } = options; - return uniqueExec.exec(method, args, { - ...options, - startTime: new Date().getTime(), - maxTime, - maxRetries, - currentIterations: 0, + deprecated({ + deprecated: 'faker.unique()', + proposed: 'faker.helpers.unique()', + since: '7.5', + until: '8.0', }); + return this.faker.helpers.unique(method, args, options); } } diff --git a/test/__snapshots__/helpers.spec.ts.snap b/test/__snapshots__/helpers.spec.ts.snap index c79f19c8..0fa011fa 100644 --- a/test/__snapshots__/helpers.spec.ts.snap +++ b/test/__snapshots__/helpers.spec.ts.snap @@ -98,6 +98,14 @@ exports[`helpers > 42 > slugify > noArgs 1`] = `""`; exports[`helpers > 42 > slugify > some string 1`] = `"hello-world"`; +exports[`helpers > 42 > unique > with () => number 1`] = `37454`; + +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", @@ -212,6 +220,14 @@ exports[`helpers > 1211 > slugify > noArgs 1`] = `""`; exports[`helpers > 1211 > slugify > some string 1`] = `"hello-world"`; +exports[`helpers > 1211 > unique > with () => number 1`] = `92852`; + +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", @@ -316,6 +332,14 @@ exports[`helpers > 1337 > slugify > noArgs 1`] = `""`; exports[`helpers > 1337 > slugify > some string 1`] = `"hello-world"`; +exports[`helpers > 1337 > unique > with () => number 1`] = `26202`; + +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/helpers.spec.ts b/test/helpers.spec.ts index 61d742a7..ce8fb733 100644 --- a/test/helpers.spec.ts +++ b/test/helpers.spec.ts @@ -5,6 +5,13 @@ import { seededTests } from './support/seededRuns'; 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', () => { afterEach(() => { faker.locale = 'en'; @@ -93,6 +100,13 @@ describe('helpers', () => { 'my string: {{datatype.string}}' ); }); + + t.describe('unique', (t) => { + t.it('with customMethod', customUniqueMethod) + .it('with customMethod and args', customUniqueMethod, ['prefix-1-']) + .it('with () => number', faker.datatype.number) + .it('with () => number and args', faker.datatype.number, [50]); + }); }); describe(`random seeded tests for seed ${faker.seed()}`, () => { @@ -608,6 +622,151 @@ describe('helpers', () => { delete (faker.random as any).special; }); }); + + 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'], + }); + }).toThrowError( + 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'], + }); + }).toThrowError( + 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'], + }); + }).toThrowError( + 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().`) + ); + }); + }); } + + // 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, + }) + ).toThrowError( + 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 = new Date().getTime(); + const maxTime = 49; + const maxRetries = 49; + const currentIterations = 0; + const exclude = []; + const compare = (obj, key) => (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 = {}; + + 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/unique.spec.ts b/test/unique.spec.ts index 9644cd67..e603939d 100644 --- a/test/unique.spec.ts +++ b/test/unique.spec.ts @@ -86,7 +86,7 @@ describe('unique', () => { 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.unique().`) +Try adjusting maxTime or maxRetries parameters for faker.helpers.unique().`) ); }); @@ -101,7 +101,7 @@ Try adjusting maxTime or maxRetries parameters for faker.unique().`) 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.unique().`) +Try adjusting maxTime or maxRetries parameters for faker.helpers.unique().`) ); }); @@ -116,88 +116,10 @@ Try adjusting maxTime or maxRetries parameters for faker.unique().`) 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.unique().`) +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 - it('should be possible to exclude results as array', () => { - const internetProtocol = () => - faker.helpers.arrayElement(['https', 'http']); - const result = faker.unique(internetProtocol, [], { - exclude: ['https'], - }); - expect(result).toBe('http'); - }); - - it('no conflict', () => { - let i = 0; - const method = () => `no conflict: ${i++}`; - expect(faker.unique(method)).toBe('no conflict: 0'); - expect(faker.unique(method)).toBe('no conflict: 1'); - }); - - it('with conflict', () => { - const method = () => 'with conflict: 0'; - expect(faker.unique(method)).toBe('with conflict: 0'); - expect(() => - faker.unique(method, [], { - maxRetries: 1, - }) - ).toThrowError( - 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.unique().`) - ); - }); - - it('should not mutate most of the input option properties', () => { - const method = () => 'options-mutate-test'; - - const startTime = new Date().getTime(); - const maxTime = 49; - const maxRetries = 49; - const currentIterations = 0; - const exclude = []; - const compare = (obj, key) => (obj[key] === undefined ? -1 : 0); - - const options = { - startTime, - maxTime, - maxRetries, - currentIterations, - exclude, - compare, - }; - - faker.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.unique` function. - expect(options.exclude).toBe(exclude); - expect(options.compare).toBe(compare); - }); - - it('should be possible to pass a user-specific store', () => { - const store = {}; - - const method = () => 'with conflict: 0'; - - expect(faker.unique(method, [], { store })).toBe('with conflict: 0'); - expect(store).toEqual({ 'with conflict: 0': 'with conflict: 0' }); - - expect(() => faker.unique(method, [], { store })).toThrow(); - - delete store['with conflict: 0']; - - expect(faker.unique(method, [], { store })).toBe('with conflict: 0'); - expect(store).toEqual({ 'with conflict: 0': 'with conflict: 0' }); - }); }); |
