Skip to content

Commit

Permalink
feat: add casing option (#955)
Browse files Browse the repository at this point in the history
Co-authored-by: Eric Cheng <ericcheng9316@gmail.com>
  • Loading branch information
Shinigami92 and import-brain committed May 21, 2022
1 parent 5af79f4 commit 4c0e418
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 99 deletions.
181 changes: 92 additions & 89 deletions src/modules/random/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import type { Faker } from '../..';
import { FakerError } from '../../errors/faker-error';
import { deprecated } from '../../internal/deprecated';

export type Casing = 'upper' | 'lower' | 'mixed';

const UPPER_CHARS: readonly string[] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
const LOWER_CHARS: readonly string[] = 'abcdefghijklmnopqrstuvwxyz'.split('');
const DIGIT_CHARS: readonly string[] = '0123456789'.split('');

/**
* Method to reduce array of characters.
Expand Down Expand Up @@ -141,25 +148,30 @@ export class Random {
}

/**
* Generating a string consisting of lower/upper alpha characters based on count and upcase options.
* Generating a string consisting of letters in the English alphabet.
*
* @param options Either the number of characters or an options instance. Defaults to `{ count: 1, upcase: false, bannedChars: [] }`.
* @param options Either the number of characters or an options instance. Defaults to `{ count: 1, casing: 'lower', bannedChars: [] }`.
* @param options.count The number of characters to generate. Defaults to `1`.
* @param options.upcase If true, the result will be uppercase. If false, it will be lowercase. Defaults to `false`.
* @param options.casing The casing of the characters. Defaults to `'lower'`.
* @param options.upcase Deprecated, use `casing: 'upper'` instead.
* @param options.bannedChars An array with characters to exclude. Defaults to `[]`.
*
* @example
* faker.random.alpha() // 'b'
* faker.random.alpha(10) // 'qccrabobaf'
* faker.random.alpha({ count: 5, upcase: true, bannedChars: ['a'] }) // 'DTCIC'
* faker.random.alpha({ count: 5, casing: 'upper', bannedChars: ['A'] }) // 'DTCIC'
*/
// TODO @Shinigami92 2022-02-14: Tests covered `(count, options)`, but they were never typed like that
alpha(
options:
| number
| {
count?: number;
/**
* @deprecated Use `casing` instead.
*/
upcase?: boolean;
casing?: Casing;
bannedChars?: readonly string[];
} = {}
): string {
Expand All @@ -168,52 +180,59 @@ export class Random {
count: options,
};
}
const { count = 1, upcase = false, bannedChars = [] } = options;

let charsArray = [
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
];
const { count = 1, upcase, bannedChars = [] } = options;

if (count <= 0) {
return '';
}

const {
// Switch to 'mixed' with v8.0
casing = upcase ? 'upper' : 'lower',
} = options;

if (upcase != null) {
deprecated({
deprecated: 'faker.random.alpha({ upcase: true })',
proposed: "faker.random.alpha({ casing: 'upper' })",
since: 'v7.0',
until: 'v8.0',
});
}

let charsArray: string[];
switch (casing) {
case 'upper':
charsArray = [...UPPER_CHARS];
break;
case 'lower':
charsArray = [...LOWER_CHARS];
break;
case 'mixed':
default:
charsArray = [...LOWER_CHARS, ...UPPER_CHARS];
break;
}

charsArray = arrayRemove(charsArray, bannedChars);

let wholeString = '';
for (let i = 0; i < count; i++) {
wholeString += this.faker.helpers.arrayElement(charsArray);
if (charsArray.length === 0) {
throw new FakerError(
'Unable to generate string, because all possible characters are banned.'
);
}

return upcase ? wholeString.toUpperCase() : wholeString;
return Array.from({ length: count }, () =>
this.faker.helpers.arrayElement(charsArray)
).join('');
}

/**
* Generating a string consisting of lower/upper alpha characters and digits based on count and upcase options.
* 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. Defaults to `{ bannedChars: [] }`.
* @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 `[]`.
*
* @example
Expand All @@ -223,48 +242,35 @@ export class Random {
*/
alphaNumeric(
count: number = 1,
options: { bannedChars?: readonly string[] } = {}
options: {
casing?: Casing;
bannedChars?: readonly string[];
} = {}
): string {
const { bannedChars = [] } = options;

let charsArray = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
];
if (count <= 0) {
return '';
}

const {
// Switch to 'mixed' with v8.0
casing = 'lower',
bannedChars = [],
} = options;

let charsArray = [...DIGIT_CHARS];

switch (casing) {
case 'upper':
charsArray.push(...UPPER_CHARS);
break;
case 'lower':
charsArray.push(...LOWER_CHARS);
break;
case 'mixed':
default:
charsArray.push(...LOWER_CHARS, ...UPPER_CHARS);
break;
}

charsArray = arrayRemove(charsArray, bannedChars);

Expand All @@ -274,12 +280,9 @@ export class Random {
);
}

let wholeString = '';
for (let i = 0; i < count; i++) {
wholeString += this.faker.helpers.arrayElement(charsArray);
}

return wholeString;
return Array.from({ length: count }, () =>
this.faker.helpers.arrayElement(charsArray)
).join('');
}

/**
Expand Down Expand Up @@ -310,9 +313,9 @@ export class Random {

const { allowLeadingZeros = false, bannedDigits = [] } = options;

const allowedDigits = '0123456789'
.split('')
.filter((digit) => !bannedDigits.includes(digit));
const allowedDigits = DIGIT_CHARS.filter(
(digit) => !bannedDigits.includes(digit)
);

if (
allowedDigits.length === 0 ||
Expand Down
10 changes: 6 additions & 4 deletions src/modules/vehicle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,16 @@ export class Vehicle {
* faker.vehicle.vin() // 'YV1MH682762184654'
*/
vin(): string {
const bannedChars = ['o', 'i', 'q'];
const bannedChars = ['o', 'i', 'q', 'O', 'I', 'Q'];
return `${this.faker.random.alphaNumeric(10, {
casing: 'upper',
bannedChars,
})}${this.faker.random.alpha({
count: 1,
upcase: true,
casing: 'upper',
bannedChars,
})}${this.faker.random.alphaNumeric(1, {
casing: 'upper',
bannedChars,
})}${this.faker.datatype.number({ min: 10000, max: 99999 })}` // return five digit #
.toUpperCase();
Expand All @@ -107,14 +109,14 @@ export class Vehicle {
vrm(): string {
return `${this.faker.random.alpha({
count: 2,
upcase: true,
casing: 'upper',
})}${this.faker.datatype.number({
min: 0,
max: 9,
})}${this.faker.datatype.number({
min: 0,
max: 9,
})}${this.faker.random.alpha({ count: 3, upcase: true })}`.toUpperCase();
})}${this.faker.random.alpha({ count: 3, casing: 'upper' })}`.toUpperCase();
}

/**
Expand Down
61 changes: 55 additions & 6 deletions test/random.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,13 @@ describe('random', () => {
expect(actual).toMatch(/^[a-z]$/);
});

it('should return uppercase when upcase option is true', () => {
const actual = faker.random.alpha({ upcase: true });
expect(actual).toMatch(/^[A-Z]$/);
it.each([
['upper', /^[A-Z]{250}$/],
['lower', /^[a-z]{250}$/],
['mixed', /^[a-zA-Z]{250}$/],
] as const)('should return %s-case', (casing, pattern) => {
const actual = faker.random.alpha({ count: 250, casing });
expect(actual).toMatch(pattern);
});

it('should generate many random letters', () => {
Expand All @@ -190,6 +194,15 @@ describe('random', () => {
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,
Expand All @@ -210,14 +223,28 @@ describe('random', () => {
expect(alphaText).toMatch(/^[b-oq-z]{5}$/);
});

it('should throw if all possible characters being banned', () => {
const bannedChars = 'abcdefghijklmnopqrstuvwxyz'.split('');
expect(() =>
faker.random.alpha({
count: 5,
bannedChars,
})
).toThrowError(
new FakerError(
'Unable to generate string, because all possible characters are banned.'
)
);
});

it('should not mutate the input object', () => {
const input: {
count: number;
upcase: boolean;
casing: 'mixed';
bannedChars: string[];
} = Object.freeze({
count: 5,
upcase: true,
casing: 'mixed',
bannedChars: ['a', '%'],
});

Expand All @@ -233,12 +260,30 @@ describe('random', () => {
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'.split('');
const alphaText = faker.random.alphaNumeric(5, {
Expand Down Expand Up @@ -278,7 +323,11 @@ describe('random', () => {
faker.random.alphaNumeric(5, {
bannedChars,
})
).toThrowError();
).toThrowError(
new FakerError(
'Unable to generate string, because all possible characters are banned.'
)
);
});

it('should not mutate the input object', () => {
Expand Down

0 comments on commit 4c0e418

Please sign in to comment.