aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAmaan Shaikh <[email protected]>2024-10-31 15:30:16 +0530
committerGitHub <[email protected]>2024-10-31 10:00:16 +0000
commit72937de55c892c011846bc2b67dc0df61fbdf5a2 (patch)
treea14debb5d77f03f8a712f401b02544b06718e03f
parentc02beeadd49e48656a0307451517e9751e3118c3 (diff)
downloadfaker-72937de55c892c011846bc2b67dc0df61fbdf5a2.tar.xz
faker-72937de55c892c011846bc2b67dc0df61fbdf5a2.zip
feat(number): add romanNumeral method (#3070)
-rw-r--r--src/modules/number/index.ts91
-rw-r--r--test/modules/__snapshots__/number.spec.ts.snap42
-rw-r--r--test/modules/number.spec.ts74
3 files changed, 206 insertions, 1 deletions
diff --git a/src/modules/number/index.ts b/src/modules/number/index.ts
index be5e213f..f5f5c27f 100644
--- a/src/modules/number/index.ts
+++ b/src/modules/number/index.ts
@@ -443,4 +443,95 @@ export class NumberModule extends SimpleModuleBase {
return min + offset;
}
+
+ /**
+ * Returns a roman numeral in String format.
+ * The bounds are inclusive.
+ *
+ * @param options Maximum value or options object.
+ * @param options.min Lower bound for generated roman numerals. Defaults to `1`.
+ * @param options.max Upper bound for generated roman numerals. Defaults to `3999`.
+ *
+ * @throws When `min` is greater than `max`.
+ * @throws When `min`, `max` is not a number.
+ * @throws When `min` is less than `1`.
+ * @throws When `max` is greater than `3999`.
+ *
+ * @example
+ * faker.number.romanNumeral() // "CMXCIII"
+ * faker.number.romanNumeral(5) // "III"
+ * faker.number.romanNumeral({ min: 10 }) // "XCIX"
+ * faker.number.romanNumeral({ max: 20 }) // "XVII"
+ * faker.number.romanNumeral({ min: 5, max: 10 }) // "VII"
+ *
+ * @since 9.2.0
+ */
+ romanNumeral(
+ options:
+ | number
+ | {
+ /**
+ * Lower bound for generated number.
+ *
+ * @default 1
+ */
+ min?: number;
+ /**
+ * Upper bound for generated number.
+ *
+ * @default 3999
+ */
+ max?: number;
+ } = {}
+ ): string {
+ const DEFAULT_MIN = 1;
+ const DEFAULT_MAX = 3999;
+
+ if (typeof options === 'number') {
+ options = {
+ max: options,
+ };
+ }
+
+ const { min = DEFAULT_MIN, max = DEFAULT_MAX } = options;
+
+ if (min < DEFAULT_MIN) {
+ throw new FakerError(
+ `Min value ${min} should be ${DEFAULT_MIN} or greater.`
+ );
+ }
+
+ if (max > DEFAULT_MAX) {
+ throw new FakerError(
+ `Max value ${max} should be ${DEFAULT_MAX} or less.`
+ );
+ }
+
+ let num = this.int({ min, max });
+
+ const lookup: Array<[string, number]> = [
+ ['M', 1000],
+ ['CM', 900],
+ ['D', 500],
+ ['CD', 400],
+ ['C', 100],
+ ['XC', 90],
+ ['L', 50],
+ ['XL', 40],
+ ['X', 10],
+ ['IX', 9],
+ ['V', 5],
+ ['IV', 4],
+ ['I', 1],
+ ];
+
+ let result = '';
+
+ for (const [k, v] of lookup) {
+ result += k.repeat(Math.floor(num / v));
+ num %= v;
+ }
+
+ return result;
+ }
}
diff --git a/test/modules/__snapshots__/number.spec.ts.snap b/test/modules/__snapshots__/number.spec.ts.snap
index 8c3f5ccc..516abc9f 100644
--- a/test/modules/__snapshots__/number.spec.ts.snap
+++ b/test/modules/__snapshots__/number.spec.ts.snap
@@ -50,6 +50,20 @@ exports[`number > 42 > octal > with options 1`] = `"4"`;
exports[`number > 42 > octal > with value 1`] = `"0"`;
+exports[`number > 42 > romanNumeral > noArgs 1`] = `"MCDXCVIII"`;
+
+exports[`number > 42 > romanNumeral > with max as 3999 1`] = `"MCDXCVIII"`;
+
+exports[`number > 42 > romanNumeral > with min and max 1`] = `"CCL"`;
+
+exports[`number > 42 > romanNumeral > with min as 1 1`] = `"MCDXCVIII"`;
+
+exports[`number > 42 > romanNumeral > with number value 1`] = `"CCCLXXV"`;
+
+exports[`number > 42 > romanNumeral > with only max 1`] = `"LXII"`;
+
+exports[`number > 42 > romanNumeral > with only min 1`] = `"MDI"`;
+
exports[`number > 1211 > bigInt > noArgs 1`] = `982966736876848n`;
exports[`number > 1211 > bigInt > with big options 1`] = `25442250580110979794946298n`;
@@ -100,6 +114,20 @@ exports[`number > 1211 > octal > with options 1`] = `"12"`;
exports[`number > 1211 > octal > with value 1`] = `"1"`;
+exports[`number > 1211 > romanNumeral > noArgs 1`] = `"MMMDCCXIV"`;
+
+exports[`number > 1211 > romanNumeral > with max as 3999 1`] = `"MMMDCCXIV"`;
+
+exports[`number > 1211 > romanNumeral > with min and max 1`] = `"CDLXXIV"`;
+
+exports[`number > 1211 > romanNumeral > with min as 1 1`] = `"MMMDCCXIV"`;
+
+exports[`number > 1211 > romanNumeral > with number value 1`] = `"CMXXIX"`;
+
+exports[`number > 1211 > romanNumeral > with only max 1`] = `"CLIV"`;
+
+exports[`number > 1211 > romanNumeral > with only min 1`] = `"MMMDCCXIV"`;
+
exports[`number > 1337 > bigInt > noArgs 1`] = `212435297136194n`;
exports[`number > 1337 > bigInt > with big options 1`] = `27379244885156992800029992n`;
@@ -149,3 +177,17 @@ exports[`number > 1337 > octal > noArgs 1`] = `"2"`;
exports[`number > 1337 > octal > with options 1`] = `"2"`;
exports[`number > 1337 > octal > with value 1`] = `"0"`;
+
+exports[`number > 1337 > romanNumeral > noArgs 1`] = `"MXLVIII"`;
+
+exports[`number > 1337 > romanNumeral > with max as 3999 1`] = `"MXLVIII"`;
+
+exports[`number > 1337 > romanNumeral > with min and max 1`] = `"CCV"`;
+
+exports[`number > 1337 > romanNumeral > with min as 1 1`] = `"MXLVIII"`;
+
+exports[`number > 1337 > romanNumeral > with number value 1`] = `"CCLXIII"`;
+
+exports[`number > 1337 > romanNumeral > with only max 1`] = `"XLIV"`;
+
+exports[`number > 1337 > romanNumeral > with only min 1`] = `"MLI"`;
diff --git a/test/modules/number.spec.ts b/test/modules/number.spec.ts
index f9adf9ff..cfd50b20 100644
--- a/test/modules/number.spec.ts
+++ b/test/modules/number.spec.ts
@@ -1,5 +1,5 @@
import validator from 'validator';
-import { describe, expect, it } from 'vitest';
+import { describe, expect, it, vi } from 'vitest';
import { FakerError, SimpleFaker, faker } from '../../src';
import { seededTests } from '../support/seeded-runs';
import { MERSENNE_MAX_VALUE } from '../utils/mersenne-test-utils';
@@ -47,6 +47,16 @@ describe('number', () => {
max: 32465761264574654845432354n,
});
});
+
+ t.describe('romanNumeral', (t) => {
+ t.it('noArgs')
+ .it('with number value', 1000)
+ .it('with only min', { min: 5 })
+ .it('with only max', { max: 165 })
+ .it('with min as 1', { min: 1 })
+ .it('with max as 3999', { max: 3999 })
+ .it('with min and max', { min: 100, max: 502 });
+ });
});
describe(`random seeded tests for seed ${faker.seed()}`, () => {
@@ -625,6 +635,68 @@ describe('number', () => {
);
});
});
+
+ describe('romanNumeral', () => {
+ it('should generate a Roman numeral within default range', () => {
+ const roman = faker.number.romanNumeral();
+ expect(roman).toBeTypeOf('string');
+ expect(roman).toMatch(/^[IVXLCDM]+$/);
+ });
+
+ it('should generate a Roman numeral with max value of 1000', () => {
+ const roman = faker.number.romanNumeral(1000);
+ expect(roman).toMatch(/^[IVXLCDM]+$/);
+ });
+
+ it.each(
+ Object.entries({
+ I: 1,
+ IV: 4,
+ IX: 9,
+ X: 10,
+ XXVII: 27,
+ XC: 90,
+ XCIX: 99,
+ CCLXIII: 263,
+ DXXXVI: 536,
+ DCCXIX: 719,
+ MDCCCLI: 1851,
+ MDCCCXCII: 1892,
+ MMCLXXXIII: 2183,
+ MMCMXLIII: 2943,
+ MMMDCCLXVI: 3766,
+ MMMDCCLXXIV: 3774,
+ MMMCMXCIX: 3999,
+ })
+ )(
+ 'should generate a Roman numeral %s for value %d',
+ (expected: string, value: number) => {
+ const mock = vi.spyOn(faker.number, 'int');
+ mock.mockReturnValue(value);
+ const actual = faker.number.romanNumeral();
+ mock.mockRestore();
+ expect(actual).toBe(expected);
+ }
+ );
+
+ it('should throw when min value is less than 1', () => {
+ expect(() => {
+ faker.number.romanNumeral({ min: 0 });
+ }).toThrow(new FakerError('Min value 0 should be 1 or greater.'));
+ });
+
+ it('should throw when max value is greater than 3999', () => {
+ expect(() => {
+ faker.number.romanNumeral({ max: 4000 });
+ }).toThrow(new FakerError('Max value 4000 should be 3999 or less.'));
+ });
+
+ it('should throw when max value is less than min value', () => {
+ expect(() => {
+ faker.number.romanNumeral({ min: 500, max: 100 });
+ }).toThrow(new FakerError('Max 100 should be greater than min 500.'));
+ });
+ });
});
describe('value range tests', () => {