diff --git a/src/modules/company/index.ts b/src/modules/company/index.ts index e9e9bf09b31..d4b181f6be7 100644 --- a/src/modules/company/index.ts +++ b/src/modules/company/index.ts @@ -36,11 +36,9 @@ export class CompanyModule { * @since 7.4.0 */ name(): string { - const pattern = this.faker.helpers.arrayElement( + return this.faker.helpers.fake( this.faker.definitions.company.name_patterns ); - - return this.faker.helpers.fake(pattern); } /** diff --git a/src/modules/helpers/index.ts b/src/modules/helpers/index.ts index 4cf9ad38b45..cb4f768074b 100644 --- a/src/modules/helpers/index.ts +++ b/src/modules/helpers/index.ts @@ -506,7 +506,7 @@ export class HelpersModule { * It checks the given string for placeholders and replaces them by calling faker methods: * * ```js - * const hello = faker.helpers.fake('Hi, my name is {{person.firstName}} {{person.lastName}}!') + * const hello = faker.helpers.fake('Hi, my name is {{person.firstName}} {{person.lastName}}!'); * ``` * * This would use the `faker.person.firstName()` and `faker.person.lastName()` method to resolve the placeholders respectively. @@ -515,18 +515,18 @@ export class HelpersModule { * and if that isn't possible, we will fall back to string: * * ```js - * const message = faker.helpers.fake('You can call me at {{phone.number(+!# !## #### #####!)}}.') + * const message = faker.helpers.fake('You can call me at {{phone.number(+!# !## #### #####!)}}.'); * ``` * * It is also possible to use multiple parameters (comma separated). * * ```js - * const message = faker.helpers.fake('Your pin is {{string.numeric(4, {"allowLeadingZeros": true})}}.') + * const message = faker.helpers.fake('Your pin is {{string.numeric(4, {"allowLeadingZeros": true})}}.'); * ``` * - * It is also NOT possible to use any non-faker methods or plain javascript in such templates. + * It is also NOT possible to use any non-faker methods or plain javascript in such patterns. * - * @param str The template string that will get interpolated. Must not be empty. + * @param pattern The pattern string that will get interpolated. Must not be empty. * * @see faker.helpers.mustache() to use custom functions for resolution. * @@ -541,24 +541,127 @@ export class HelpersModule { * * @since 7.4.0 */ - fake(str: string): string { + fake(pattern: string): string; + /** + * Generator for combining faker methods based on an array containing static string inputs. + * + * Note: We recommend using string template literals instead of `fake()`, + * which are faster and strongly typed (if you are using TypeScript), + * e.g. ``const address = `${faker.location.zipCode()} ${faker.location.city()}`;`` + * + * This method is useful if you have to build a random string from a static, non-executable source + * (e.g. string coming from a user, stored in a database or a file). + * + * It checks the given string for placeholders and replaces them by calling faker methods: + * + * ```js + * const hello = faker.helpers.fake(['Hi, my name is {{person.firstName}} {{person.lastName}}!']); + * ``` + * + * This would use the `faker.person.firstName()` and `faker.person.lastName()` method to resolve the placeholders respectively. + * + * It is also possible to provide parameters. At first, they will be parsed as json, + * and if that isn't possible, it will fall back to string: + * + * ```js + * const message = faker.helpers.fake([ + * 'You can call me at {{phone.number(+!# !## #### #####!)}}.', + * 'My email is {{internet.email}}.', + * ]); + * ``` + * + * It is also possible to use multiple parameters (comma separated). + * + * ```js + * const message = faker.helpers.fake(['Your pin is {{string.numeric(4, {"allowLeadingZeros": true})}}.']); + * ``` + * + * It is also NOT possible to use any non-faker methods or plain javascript in such patterns. + * + * @param patterns The array to select a pattern from, that will then get interpolated. Must not be empty. + * + * @see faker.helpers.mustache() to use custom functions for resolution. + * + * @example + * faker.helpers.fake(['A: {{person.firstName}}', 'B: {{person.lastName}}']) // 'A: Barry' + * + * @since 8.0.0 + */ + fake(patterns: string[]): string; + /** + * Generator for combining faker methods based on a static string input or an array of static string inputs. + * + * Note: We recommend using string template literals instead of `fake()`, + * which are faster and strongly typed (if you are using TypeScript), + * e.g. ``const address = `${faker.location.zipCode()} ${faker.location.city()}`;`` + * + * This method is useful if you have to build a random string from a static, non-executable source + * (e.g. string coming from a user, stored in a database or a file). + * + * It checks the given string for placeholders and replaces them by calling faker methods: + * + * ```js + * const hello = faker.helpers.fake('Hi, my name is {{person.firstName}} {{person.lastName}}!'); + * ``` + * + * This would use the `faker.person.firstName()` and `faker.person.lastName()` method to resolve the placeholders respectively. + * + * It is also possible to provide parameters. At first, they will be parsed as json, + * and if that isn't possible, it will fall back to string: + * + * ```js + * const message = faker.helpers.fake('You can call me at {{phone.number(+!# !## #### #####!)}}.'); + * ``` + * + * It is also possible to use multiple parameters (comma separated). + * + * ```js + * const message = faker.helpers.fake('Your pin is {{string.numeric(4, {"allowLeadingZeros": true})}}.'); + * ``` + * + * It is also NOT possible to use any non-faker methods or plain javascript in such patterns. + * + * @param pattern The pattern string that will get interpolated. Must not be empty. If an array is passed, a random element will be picked and interpolated. + * + * @see faker.helpers.mustache() to use custom functions for resolution. + * + * @example + * faker.helpers.fake('{{person.lastName}}') // 'Barrows' + * faker.helpers.fake('{{person.lastName}}, {{person.firstName}} {{person.suffix}}') // 'Durgan, Noe MD' + * faker.helpers.fake('This is static test.') // 'This is static test.' + * faker.helpers.fake('Good Morning {{person.firstName}}!') // 'Good Morning Estelle!' + * faker.helpers.fake('You can call me at {{phone.number(!## ### #####!)}}.') // 'You can call me at 202 555 973722.' + * faker.helpers.fake('I flipped the coin and got: {{helpers.arrayElement(["heads", "tails"])}}') // 'I flipped the coin and got: tails' + * faker.helpers.fake(['A: {{person.firstName}}', 'B: {{person.lastName}}']) // 'A: Barry' + * + * @since 7.4.0 + */ + fake(pattern: string | string[]): string; + fake(pattern: string | string[]): string { + if (Array.isArray(pattern)) { + pattern = this.arrayElement(pattern); + // TODO @ST-DDT 2022-10-15: Remove this check after we fail in `arrayElement` when the array is empty + if (pattern == null) { + throw new FakerError('Array of pattern strings cannot be empty.'); + } + } // if incoming str parameter is not provided, return error message - if (typeof str !== 'string' || str.length === 0) { - throw new FakerError('string parameter is required!'); + if (pattern.length === 0) { + throw new FakerError('Pattern string cannot be empty.'); } // find first matching {{ and }} - const start = str.search(/{{[a-z]/); - const end = str.indexOf('}}', start); + const start = pattern.search(/{{[a-z]/); + const end = pattern.indexOf('}}', start); // if no {{ and }} is found, we are done if (start === -1 || end === -1) { - return str; + return pattern; } // extract method name from between the {{ }} that we found // for example: {{person.firstName}} - const token = str.substring(start + 2, end + 2); + const token = pattern.substring(start + 2, end + 2); let method = token.replace('}}', '').replace('{{', ''); // extract method parameters @@ -615,7 +718,8 @@ export class HelpersModule { // Replace the found tag with the returned fake value // We cannot use string.replace here because the result might contain evaluated characters - const res = str.substring(0, start) + result + str.substring(end + 2); + const res = + pattern.substring(0, start) + result + pattern.substring(end + 2); if (res === '') { return ''; diff --git a/src/modules/location/index.ts b/src/modules/location/index.ts index a4238241008..a53c11d7abc 100644 --- a/src/modules/location/index.ts +++ b/src/modules/location/index.ts @@ -75,10 +75,7 @@ export class LocationModule { * @since 8.0.0 */ city(): string { - const pattern = this.faker.helpers.arrayElement( - this.faker.definitions.location.city - ); - return this.faker.helpers.fake(pattern); + return this.faker.helpers.fake(this.faker.definitions.location.city); } /** @@ -121,10 +118,7 @@ export class LocationModule { * @since 8.0.0 */ street(): string { - const format = this.faker.helpers.arrayElement( - this.faker.definitions.location.street - ); - return this.faker.helpers.fake(format); + return this.faker.helpers.fake(this.faker.definitions.location.street); } /** diff --git a/test/__snapshots__/helpers.spec.ts.snap b/test/__snapshots__/helpers.spec.ts.snap index 06e0bc43a32..4ce3183266e 100644 --- a/test/__snapshots__/helpers.spec.ts.snap +++ b/test/__snapshots__/helpers.spec.ts.snap @@ -29,9 +29,13 @@ exports[`helpers > 42 > arrayElements > with array and count 1`] = ` ] `; -exports[`helpers > 42 > fake > with args 1`] = `"my string: Cky2eiXX/J"`; +exports[`helpers > 42 > fake > with a dynamic template 1`] = `"my string: Cky2eiXX/J"`; -exports[`helpers > 42 > fake > with plain string 1`] = `"my test string"`; +exports[`helpers > 42 > fake > with a static template 1`] = `"my test string"`; + +exports[`helpers > 42 > fake > with multiple dynamic templates 1`] = `"Sandy"`; + +exports[`helpers > 42 > fake > with multiple static templates 1`] = `"B"`; exports[`helpers > 42 > maybe > with only value 1`] = `"Hello World!"`; @@ -208,9 +212,13 @@ exports[`helpers > 1211 > arrayElements > with array and count 1`] = ` ] `; -exports[`helpers > 1211 > fake > with args 1`] = `"my string: wKti5-}$_/"`; +exports[`helpers > 1211 > fake > with a dynamic template 1`] = `"my string: wKti5-}$_/"`; + +exports[`helpers > 1211 > fake > with a static template 1`] = `"my test string"`; -exports[`helpers > 1211 > fake > with plain string 1`] = `"my test string"`; +exports[`helpers > 1211 > fake > with multiple dynamic templates 1`] = `"La Crosse"`; + +exports[`helpers > 1211 > fake > with multiple static templates 1`] = `"C"`; exports[`helpers > 1211 > maybe > with only value 1`] = `undefined`; @@ -383,9 +391,13 @@ exports[`helpers > 1337 > arrayElements > with array and count 1`] = ` ] `; -exports[`helpers > 1337 > fake > with args 1`] = `"my string: 9U/4:SK$>6"`; +exports[`helpers > 1337 > fake > with a dynamic template 1`] = `"my string: 9U/4:SK$>6"`; + +exports[`helpers > 1337 > fake > with a static template 1`] = `"my test string"`; + +exports[`helpers > 1337 > fake > with multiple dynamic templates 1`] = `"U/4:SK$>6Q"`; -exports[`helpers > 1337 > fake > with plain string 1`] = `"my test string"`; +exports[`helpers > 1337 > fake > with multiple static templates 1`] = `"A"`; exports[`helpers > 1337 > maybe > with only value 1`] = `"Hello World!"`; diff --git a/test/helpers.spec.ts b/test/helpers.spec.ts index 5829ddd875e..37a1bd02693 100644 --- a/test/helpers.spec.ts +++ b/test/helpers.spec.ts @@ -96,10 +96,14 @@ describe('helpers', () => { }); t.describe('fake', (t) => { - t.it('with plain string', 'my test string').it( - 'with args', - 'my string: {{datatype.string}}' - ); + t.it('with a static template', 'my test string') + .it('with a dynamic template', 'my string: {{string.sample}}') + .it('with multiple static templates', ['A', 'B', 'C']) + .it('with multiple dynamic templates', [ + '{{string.sample}}', + '{{location.city_name}}', + '{{location.cityName}}', + ]); }); t.describe('rangeToNumber', (t) => { @@ -598,11 +602,16 @@ describe('helpers', () => { expect(actual).toMatch(/^\d{5}$/); }); - it('does not allow undefined parameters', () => { - expect(() => - // @ts-expect-error: The parameter is required - faker.helpers.fake() - ).toThrowError(new FakerError('string parameter is required!')); + it('does not allow empty string parameters', () => { + expect(() => faker.helpers.fake('')).toThrowError( + new FakerError('Pattern string cannot be empty.') + ); + }); + + it('does not allow empty array parameters', () => { + expect(() => faker.helpers.fake([])).toThrowError( + new FakerError('Array of pattern strings cannot be empty.') + ); }); it('does not allow invalid module name', () => { @@ -655,6 +664,21 @@ describe('helpers', () => { ); }); + it('should be able to pass multiple static templates', () => { + expect(['A', 'B', 'C']).toContain( + faker.helpers.fake(['A', 'B', 'C']) + ); + }); + + it('should be able to pass multiple dynamic templates', () => { + expect(faker.definitions.location.city_name).toContain( + faker.helpers.fake([ + '{{location.city_name}}', + '{{location.cityName}}', + ]) + ); + }); + it('should be able to handle only {{ brackets', () => { expect(faker.helpers.fake('{{hello')).toBe('{{hello'); expect(faker.helpers.fake('hello{{')).toBe('hello{{');