Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(commerce): return fractional prices #2458

Merged
merged 28 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d4b6bc7
fix: return fractional prices from commerce.price
ejcheng Oct 9, 2023
6b74642
docs: edit jsdoc to reflect decimal change
ejcheng Oct 9, 2023
0f0286b
feat: weighted last digits
ejcheng Oct 18, 2023
2bc5188
Merge branch 'next' into fix/fract_currency_prices
ejcheng Oct 19, 2023
46b64da
chore: run preflight
ejcheng Oct 19, 2023
3a983fd
Merge branch 'next' into fix/fract_currency_prices
ST-DDT Oct 30, 2023
6437b31
fix: ensure returned value is between min and max
ejcheng Nov 1, 2023
3f4f01b
Merge branch 'next' into fix/fract_currency_prices
ejcheng Nov 1, 2023
09c1c6c
chore: apply review suggestions
ejcheng Nov 1, 2023
d7f4174
chore: new var
ejcheng Nov 1, 2023
df51738
Merge branch 'next' into fix/fract_currency_prices
ejcheng Nov 8, 2023
d00f619
chore: apply review suggestions
ejcheng Nov 8, 2023
f01fbfe
Update index.ts
ejcheng Nov 9, 2023
2809ad2
Update index.ts
ejcheng Nov 9, 2023
4537896
chore: jsdoc code blocks
ejcheng Nov 9, 2023
d8a64a7
Merge branch 'next' into fix/fract_currency_prices
ejcheng Nov 30, 2023
a1790d9
chore: apply review suggestions
ejcheng Dec 22, 2023
777db84
Merge branch 'next' into fix/fract_currency_prices
ejcheng Dec 22, 2023
20586fe
chore: apply review suggestions
ejcheng Jan 14, 2024
5c89bca
Merge branch 'next' into fix/fract_currency_prices
ejcheng Jan 14, 2024
a682a99
Merge branch 'next' into fix/fract_currency_prices
ejcheng Feb 6, 2024
1dfe91d
Merge branch 'next' into fix/fract_currency_prices
ST-DDT Feb 9, 2024
8357168
Merge branch 'next' into fix/fract_currency_prices
ST-DDT Feb 29, 2024
baf71df
chore: fix test
ST-DDT Feb 29, 2024
2802e3c
chore: improve readability
ST-DDT Feb 29, 2024
5689c92
docs: add upgrading guide
ST-DDT Feb 29, 2024
7cc2e59
Merge branch 'next' into fix/fract_currency_prices
ST-DDT Mar 1, 2024
ae6343d
Merge branch 'next' into fix/fract_currency_prices
ST-DDT Mar 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/guide/upgrading_v9/2458.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### Prices now return more price-like values

The `faker.commerce.price` method now produces values, that also return fractional values.

Old price: 828.00
New price: 828.59

The last digit of the price will adjusted to be more price-like:

- 50% of the time: `9`
- 30% of the time: `5`
- 10% of the time: `0`
- 10% of the time: a random digit from `0` to `9`

We plan to rethink this method some more in the future: [#2579](https://github.com/faker-js/faker/issues/2579)
93 changes: 76 additions & 17 deletions src/modules/commerce/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,23 @@ export class CommerceModule extends ModuleBase {
/**
* Generates a price between min and max (inclusive).
*
* To better represent real-world prices, when `options.dec` is greater than `0`, the final decimal digit in the returned string will be generated as follows:
*
* - 50% of the time: `9`
* - 30% of the time: `5`
* - 10% of the time: `0`
* - 10% of the time: a random digit from `0` to `9`
*
* @param options An options object.
* @param options.min The minimum price. Defaults to `1`.
* @param options.max The maximum price. Defaults to `1000`.
* @param options.dec The number of decimal places. Defaults to `2`.
* @param options.symbol The currency value to use. Defaults to `''`.
*
* @example
* faker.commerce.price() // 828.00
* faker.commerce.price({ min: 100 }) // 904.00
* faker.commerce.price({ min: 100, max: 200 }) // 154.00
* faker.commerce.price() // 828.07
* faker.commerce.price({ min: 100 }) // 904.19
* faker.commerce.price({ min: 100, max: 200 }) // 154.55
* faker.commerce.price({ min: 100, max: 200, dec: 0 }) // 133
* faker.commerce.price({ min: 100, max: 200, dec: 0, symbol: '$' }) // $114
*
Expand Down Expand Up @@ -160,15 +167,22 @@ export class CommerceModule extends ModuleBase {
/**
* Generates a price between min and max (inclusive).
*
* To better represent real-world prices, when `options.dec` is greater than `0`, the final decimal digit in the returned string will be generated as follows:
*
* - 50% of the time: `9`
* - 30% of the time: `5`
* - 10% of the time: `0`
* - 10% of the time: a random digit from `0` to `9`
*
* @param min The minimum price. Defaults to `1`.
* @param max The maximum price. Defaults to `1000`.
* @param dec The number of decimal places. Defaults to `2`.
* @param symbol The currency value to use. Defaults to `''`.
*
* @example
* faker.commerce.price() // 828.00
* faker.commerce.price(100) // 904.00
* faker.commerce.price(100, 200) // 154.00
* faker.commerce.price() // 828.07
* faker.commerce.price(100) // 904.19
* faker.commerce.price(100, 200) // 154.55
* faker.commerce.price(100, 200, 0) // 133
* faker.commerce.price(100, 200, 0, '$') // $114
*
Expand All @@ -180,7 +194,14 @@ export class CommerceModule extends ModuleBase {
/**
* Generates a price between min and max (inclusive).
*
* @param options The minimum price or on options object.
* To better represent real-world prices, when `options.dec` is greater than `0`, the final decimal digit in the returned string will be generated as follows:
*
* - 50% of the time: `9`
* - 30% of the time: `5`
* - 10% of the time: `0`
* - 10% of the time: a random digit from `0` to `9`
*
* @param options The minimum price or an options object.
* @param options.min The minimum price. Defaults to `1`.
* @param options.max The maximum price. Defaults to `1000`.
* @param options.dec The number of decimal places. Defaults to `2`.
Expand All @@ -190,9 +211,9 @@ export class CommerceModule extends ModuleBase {
* @param legacySymbol The currency value to use. This argument is deprecated. Defaults to `''`.
*
* @example
* faker.commerce.price() // 828.00
* faker.commerce.price({ min: 100 }) // 904.00
* faker.commerce.price({ min: 100, max: 200 }) // 154.00
* faker.commerce.price() // 828.07
* faker.commerce.price({ min: 100 }) // 904.19
* faker.commerce.price({ min: 100, max: 200 }) // 154.55
* faker.commerce.price({ min: 100, max: 200, dec: 0 }) // 133
* faker.commerce.price({ min: 100, max: 200, dec: 0, symbol: '$' }) // $114
*
Expand Down Expand Up @@ -234,7 +255,14 @@ export class CommerceModule extends ModuleBase {
/**
* Generates a price between min and max (inclusive).
*
* @param options The minimum price or on options object.
* To better represent real-world prices, when `options.dec` is greater than `0`, the final decimal digit in the returned string will be generated as follows:
*
* - 50% of the time: `9`
* - 30% of the time: `5`
* - 10% of the time: `0`
* - 10% of the time: a random digit from `0` to `9`
*
* @param options The minimum price or an options object.
* @param options.min The minimum price. Defaults to `1`.
* @param options.max The maximum price. Defaults to `1000`.
* @param options.dec The number of decimal places. Defaults to `2`.
Expand All @@ -244,9 +272,9 @@ export class CommerceModule extends ModuleBase {
* @param legacySymbol The currency value to use. This argument is deprecated. Defaults to `''`.
*
* @example
* faker.commerce.price() // 828.00
* faker.commerce.price({ min: 100 }) // 904.00
* faker.commerce.price({ min: 100, max: 200 }) // 154.00
* faker.commerce.price() // 828.07
* faker.commerce.price({ min: 100 }) // 904.19
* faker.commerce.price({ min: 100, max: 200 }) // 154.55
* faker.commerce.price({ min: 100, max: 200, dec: 0 }) // 133
* faker.commerce.price({ min: 100, max: 200, dec: 0, symbol: '$' }) // $114
*
Expand Down Expand Up @@ -286,10 +314,41 @@ export class CommerceModule extends ModuleBase {
return `${symbol}0`;
}

// TODO @Shinigami92 2022-11-24: https://github.com/faker-js/faker/issues/350
const randValue = this.faker.number.int({ min, max });
if (min === max) {
return `${symbol}${min.toFixed(dec)}`;
}

const generated = this.faker.number.float({
min,
max,
fractionDigits: dec,
});

if (dec === 0) {
return `${symbol}${generated.toFixed(dec)}`;
}
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved

const oldLastDigit = (generated * 10 ** dec) % 10;
const newLastDigit = this.faker.helpers.weightedArrayElement([
{ weight: 5, value: 9 },
{ weight: 3, value: 5 },
{ weight: 1, value: 0 },
{
weight: 1,
value: this.faker.number.int({ min: 0, max: 9 }),
},
]);

const fraction = (1 / 10) ** dec;
const oldLastDigitValue = oldLastDigit * fraction;
const newLastDigitValue = newLastDigit * fraction;
const combined = generated - oldLastDigitValue + newLastDigitValue;

if (min <= combined && combined <= max) {
return `${symbol}${combined.toFixed(dec)}`;
}

return symbol + randValue.toFixed(dec);
return `${symbol}${generated.toFixed(dec)}`;
}

/**
Expand Down
72 changes: 39 additions & 33 deletions test/modules/__snapshots__/commerce.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,29 @@ exports[`commerce > 42 > isbn > with variant 10 and space separators 1`] = `"0 9

exports[`commerce > 42 > isbn > with variant 13 1`] = `"978-0-9751108-6-7"`;

exports[`commerce > 42 > price > noArgs 1`] = `"375.00"`;
exports[`commerce > 42 > price > noArgs 1`] = `"375.15"`;

exports[`commerce > 42 > price > with max 1`] = `"375.00"`;
exports[`commerce > 42 > price > with float min and float max option 1`] = `"1.05"`;

exports[`commerce > 42 > price > with max option 1`] = `"501.00"`;
exports[`commerce > 42 > price > with max 1`] = `"375.15"`;

exports[`commerce > 42 > price > with min 1`] = `"406.00"`;
exports[`commerce > 42 > price > with max option 1`] = `"501.35"`;

exports[`commerce > 42 > price > with min and max 1`] = `"69.00"`;
exports[`commerce > 42 > price > with min 1`] = `"405.85"`;

exports[`commerce > 42 > price > with min and max and decimals 1`] = `"69.0000"`;
exports[`commerce > 42 > price > with min and max 1`] = `"68.75"`;

exports[`commerce > 42 > price > with min and max and decimals and symbol 1`] = `"$69.0000"`;
exports[`commerce > 42 > price > with min and max and decimals 1`] = `"68.7275"`;

exports[`commerce > 42 > price > with min and max and decimals and symbol option 1`] = `"$69.0000"`;
exports[`commerce > 42 > price > with min and max and decimals and symbol 1`] = `"$68.7275"`;

exports[`commerce > 42 > price > with min and max and decimals option 1`] = `"69.0000"`;
exports[`commerce > 42 > price > with min and max and decimals and symbol option 1`] = `"$68.7275"`;

exports[`commerce > 42 > price > with min and max option 1`] = `"69.00"`;
exports[`commerce > 42 > price > with min and max and decimals option 1`] = `"68.7275"`;

exports[`commerce > 42 > price > with min option 1`] = `"401.00"`;
exports[`commerce > 42 > price > with min and max option 1`] = `"68.75"`;

exports[`commerce > 42 > price > with min option 1`] = `"400.85"`;

exports[`commerce > 42 > product 1`] = `"Pants"`;

Expand All @@ -56,27 +58,29 @@ exports[`commerce > 1211 > isbn > with variant 10 and space separators 1`] = `"1

exports[`commerce > 1211 > isbn > with variant 13 1`] = `"978-1-82966-736-0"`;

exports[`commerce > 1211 > price > noArgs 1`] = `"929.00"`;
exports[`commerce > 1211 > price > noArgs 1`] = `"928.69"`;

exports[`commerce > 1211 > price > with float min and float max option 1`] = `"1.10"`;

exports[`commerce > 1211 > price > with max 1`] = `"929.00"`;
exports[`commerce > 1211 > price > with max 1`] = `"928.69"`;

exports[`commerce > 1211 > price > with max option 1`] = `"1242.00"`;
exports[`commerce > 1211 > price > with max option 1`] = `"1241.59"`;

exports[`commerce > 1211 > price > with min 1`] = `"933.00"`;
exports[`commerce > 1211 > price > with min 1`] = `"932.19"`;

exports[`commerce > 1211 > price > with min and max 1`] = `"97.00"`;
exports[`commerce > 1211 > price > with min and max 1`] = `"96.49"`;

exports[`commerce > 1211 > price > with min and max and decimals 1`] = `"97.0000"`;
exports[`commerce > 1211 > price > with min and max and decimals 1`] = `"96.4269"`;

exports[`commerce > 1211 > price > with min and max and decimals and symbol 1`] = `"$97.0000"`;
exports[`commerce > 1211 > price > with min and max and decimals and symbol 1`] = `"$96.4269"`;

exports[`commerce > 1211 > price > with min and max and decimals and symbol option 1`] = `"$97.0000"`;
exports[`commerce > 1211 > price > with min and max and decimals and symbol option 1`] = `"$96.4269"`;

exports[`commerce > 1211 > price > with min and max and decimals option 1`] = `"97.0000"`;
exports[`commerce > 1211 > price > with min and max and decimals option 1`] = `"96.4269"`;

exports[`commerce > 1211 > price > with min and max option 1`] = `"97.00"`;
exports[`commerce > 1211 > price > with min and max option 1`] = `"96.49"`;

exports[`commerce > 1211 > price > with min option 1`] = `"932.00"`;
exports[`commerce > 1211 > price > with min option 1`] = `"931.59"`;

exports[`commerce > 1211 > product 1`] = `"Sausages"`;

Expand All @@ -100,27 +104,29 @@ exports[`commerce > 1337 > isbn > with variant 10 and space separators 1`] = `"0

exports[`commerce > 1337 > isbn > with variant 13 1`] = `"978-0-12-435297-1"`;

exports[`commerce > 1337 > price > noArgs 1`] = `"263.00"`;
exports[`commerce > 1337 > price > noArgs 1`] = `"262.79"`;

exports[`commerce > 1337 > price > with float min and float max option 1`] = `"1.09"`;

exports[`commerce > 1337 > price > with max 1`] = `"263.00"`;
exports[`commerce > 1337 > price > with max 1`] = `"262.79"`;

exports[`commerce > 1337 > price > with max option 1`] = `"351.00"`;
exports[`commerce > 1337 > price > with max option 1`] = `"351.09"`;

exports[`commerce > 1337 > price > with min 1`] = `"299.00"`;
exports[`commerce > 1337 > price > with min 1`] = `"298.99"`;

exports[`commerce > 1337 > price > with min and max 1`] = `"63.00"`;
exports[`commerce > 1337 > price > with min and max 1`] = `"63.19"`;

exports[`commerce > 1337 > price > with min and max and decimals 1`] = `"63.0000"`;
exports[`commerce > 1337 > price > with min and max and decimals 1`] = `"63.1019"`;

exports[`commerce > 1337 > price > with min and max and decimals and symbol 1`] = `"$63.0000"`;
exports[`commerce > 1337 > price > with min and max and decimals and symbol 1`] = `"$63.1019"`;

exports[`commerce > 1337 > price > with min and max and decimals and symbol option 1`] = `"$63.0000"`;
exports[`commerce > 1337 > price > with min and max and decimals and symbol option 1`] = `"$63.1019"`;

exports[`commerce > 1337 > price > with min and max and decimals option 1`] = `"63.0000"`;
exports[`commerce > 1337 > price > with min and max and decimals option 1`] = `"63.1019"`;

exports[`commerce > 1337 > price > with min and max option 1`] = `"63.00"`;
exports[`commerce > 1337 > price > with min and max option 1`] = `"63.19"`;

exports[`commerce > 1337 > price > with min option 1`] = `"293.00"`;
exports[`commerce > 1337 > price > with min option 1`] = `"293.09"`;

exports[`commerce > 1337 > product 1`] = `"Ball"`;

Expand Down
33 changes: 31 additions & 2 deletions test/modules/commerce.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe('commerce', () => {
.it('with min option', { min: 42 })
.it('with max option', { max: 1337 })
.it('with min and max option', { min: 50, max: 100 })
.it('with float min and float max option', { min: 1, max: 1.1 })
.it('with min and max and decimals option', {
min: 50,
max: 100,
Expand Down Expand Up @@ -124,14 +125,42 @@ describe('commerce', () => {
const price = faker.commerce.price(100, 100, 1);

expect(price).toBeTruthy();
expect(price, 'the price should be equal 100.0').toBe('100.0');
expect(price, 'the price should equal 100.0').toBe('100.0');
});

it('should handle argument dec = 0', () => {
const price = faker.commerce.price(100, 100, 0);

expect(price).toBeTruthy();
expect(price, 'the price should be equal 100').toBe('100');
expect(price, 'the price should equal 100').toBe('100');
});

it('should return decimal values between min and max', () => {
const result = faker.helpers.multiple(
() => faker.commerce.price(1, 1.1, 2),
ejcheng marked this conversation as resolved.
Show resolved Hide resolved
{ count: 50 }
);

for (const price of result) {
const parsedPrice = Number.parseFloat(price);

expect(parsedPrice).toBeLessThanOrEqual(1.1);
expect(parsedPrice).toBeGreaterThanOrEqual(1);
}
});

it('should return values with three decimal places between min and max', () => {
const result = faker.helpers.multiple(
() => faker.commerce.price({ min: 0.001, max: 0.009, dec: 3 }),
{ count: 50 }
);

for (const price of result) {
const parsedPrice = Number.parseFloat(price);

expect(parsedPrice).toBeLessThanOrEqual(0.009);
expect(parsedPrice).toBeGreaterThanOrEqual(0.001);
}
});
});

Expand Down