Skip to content

Commit

Permalink
feat: introduce compare function (#266)
Browse files Browse the repository at this point in the history
Co-authored-by: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com>
  • Loading branch information
danrivett and sarahdayan committed Jul 19, 2021
1 parent 107607d commit 53f84c2
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 0 deletions.
62 changes: 62 additions & 0 deletions packages/core/src/api/compare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* eslint-disable functional/no-expression-statement */
import { UNEQUAL_CURRENCIES_MESSAGE } from '../checks';
import { assert } from '../helpers';
import { compare as cmp } from '../utils';

import { haveSameCurrency } from './haveSameCurrency';
import { normalizeScale } from './normalizeScale';

import type { Dinero } from '../types';
import type { Dependencies } from './types';

export type CompareParams<TAmount> = readonly [
dineroObject: Dinero<TAmount>,
comparator: Dinero<TAmount>
];

export type UnsafeCompareDependencies<TAmount> = Dependencies<TAmount>;

export function unsafeCompare<TAmount>({
calculator,
}: UnsafeCompareDependencies<TAmount>) {
const compareFn = cmp(calculator);

return function compare(
...[dineroObject, comparator]: CompareParams<TAmount>
) {
const dineroObjects = [dineroObject, comparator];

const [subjectAmount, comparatorAmount] = dineroObjects.map((d) => {
const { amount } = d.toJSON();

return amount;
});

return compareFn(subjectAmount, comparatorAmount);
};
}

export type SafeCompareDependencies<TAmount> = Dependencies<TAmount>;

export function safeCompare<TAmount>({
calculator,
}: SafeCompareDependencies<TAmount>) {
const normalizeFn = normalizeScale({ calculator });
const compareFn = unsafeCompare({
calculator,
});

return function compare(
...[dineroObject, comparator]: CompareParams<TAmount>
) {
const condition = haveSameCurrency([dineroObject, comparator]);
assert(condition, UNEQUAL_CURRENCIES_MESSAGE);

const [subjectAmount, comparatorAmount] = normalizeFn([
dineroObject,
comparator,
]);

return compareFn(subjectAmount, comparatorAmount);
};
}
1 change: 1 addition & 0 deletions packages/core/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './add';
export * from './allocate';
export * from './compare';
export * from './convert';
export * from './equal';
export * from './greaterThan';
Expand Down
50 changes: 50 additions & 0 deletions packages/core/src/utils/__tests__/compare.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { compare as cmp } from '@dinero.js/calculator-number';

import { compare } from '../compare';

const compareFn = compare({ compare: cmp });

describe('compare', () => {
describe('inferiority', () => {
it('returns -1 when the first number is less than the other with positive numbers', () => {
expect(compareFn(1, 2)).toBe(-1);
});
it('returns -1 when the first number is less than the other with negative numbers', () => {
expect(compareFn(-3, -2)).toBe(-1);
});
it('returns -1 when the first number is less than the other with floats', () => {
expect(compareFn(1.2, 2.2)).toBe(-1);
});
it('returns -1 when the first number is less than the other with numbers in scientific notation', () => {
expect(compareFn(2e5, 3e5)).toBe(-1);
});
});
describe('equality', () => {
it('returns 0 when the first number is equal to the other with positive numbers', () => {
expect(compareFn(4, 4)).toBe(0);
});
it('returns 0 when the first number is equal to the other with negative numbers', () => {
expect(compareFn(-2, -2)).toBe(0);
});
it('returns 0 when the first number is equal to the other with floats', () => {
expect(compareFn(3.2, 3.2)).toBe(0);
});
it('returns 0 when the first number is equal to the other with numbers in scientific notation', () => {
expect(compareFn(3e5, 3e5)).toBe(0);
});
});
describe('superiority', () => {
it('returns 1 when the first number is greater than the other with positive numbers', () => {
expect(compareFn(4, 3)).toBe(1);
});
it('returns 1 when the first number is greater than the other with negative numbers', () => {
expect(compareFn(-2, -3)).toBe(1);
});
it('returns 1 when the first number is greater than the other with floats', () => {
expect(compareFn(3.2, 2.2)).toBe(1);
});
it('returns 1 when the first number is greater than the other with numbers in scientific notation', () => {
expect(compareFn(3e5, 2e5)).toBe(1);
});
});
});
16 changes: 16 additions & 0 deletions packages/core/src/utils/compare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Calculator } from '../types';

type ComparisonCalculator<TAmount> = Pick<Calculator<TAmount>, 'compare'>;

/**
* Returns a compare function.
*
* @param calculator - The calculator to use.
*
* @returns The compare function.
*/
export function compare<TAmount>(calculator: ComparisonCalculator<TAmount>) {
return (subject: TAmount, comparator: TAmount) => {
return calculator.compare(subject, comparator);
};
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './compare';
export * from './countTrailingZeros';
export * from './distribute';
export * from './equal';
Expand Down
40 changes: 40 additions & 0 deletions packages/dinero.js/src/api/__tests__/compare.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { EUR, USD } from '@dinero.js/currencies';

import { dinero, compare } from '../../..';

describe('compare', () => {
it('returns -1 when the first amount is less than the other', () => {
const d1 = dinero({ amount: 500, currency: USD });
const d2 = dinero({ amount: 800, currency: USD });

expect(compare(d1, d2)).toBe(-1);
});
it('returns 0 when amounts are equal', () => {
const d1 = dinero({ amount: 500, currency: USD });
const d2 = dinero({ amount: 500, currency: USD });

expect(compare(d1, d2)).toBe(0);
});
it('returns 1 when the first amount is greater than the other', () => {
const d1 = dinero({ amount: 800, currency: USD });
const d2 = dinero({ amount: 500, currency: USD });

expect(compare(d1, d2)).toBe(1);
});
it('normalizes the result to the highest scale', () => {
const d1 = dinero({ amount: 5000, currency: USD, scale: 3 });
const d2 = dinero({ amount: 800, currency: USD });

expect(compare(d1, d2)).toBe(-1);
});
it('throws when using different currencies', () => {
const d1 = dinero({ amount: 800, currency: USD });
const d2 = dinero({ amount: 500, currency: EUR });

expect(() => {
compare(d1, d2);
}).toThrowErrorMatchingInlineSnapshot(
`"[Dinero.js] Objects must have the same currency."`
);
});
});
20 changes: 20 additions & 0 deletions packages/dinero.js/src/api/compare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { safeCompare } from '@dinero.js/core';

import type { CompareParams } from '@dinero.js/core';

/**
* Compare the value of a Dinero object relative to another.
*
* @param dineroObject - The Dinero object to compare.
* @param comparator - The Dinero object to compare to.
*
* @returns One of -1, 0, or 1 depending on whether the first Dinero object is less than, equal to, or greater than the other.
*/
export function compare<TAmount>(
...[dineroObject, comparator]: CompareParams<TAmount>
) {
const { calculator } = dineroObject;
const compareFn = safeCompare({ calculator });

return compareFn(dineroObject, comparator);
}
1 change: 1 addition & 0 deletions packages/dinero.js/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './add';
export * from './allocate';
export * from './compare';
export * from './convert';
export * from './equal';
export * from './greaterThan';
Expand Down
79 changes: 79 additions & 0 deletions website/data/docs/api/comparisons/compare.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
title: compare
description: Compare the value of a Dinero object relative to another.
returns: number
---

Compare the value of a Dinero object relative to another. This is useful for sorting Dinero objects.

Possible return values are:
- `-1` if the first Dinero object is less than the other
- `1` if the first Dinero object is greater than the other
- `0` if both objects are equal

**You can only compare objects that share the same currency.** The function also normalizes objects to the same scale (the highest) before comparing them.

## Parameters

<Parameters>

<Parameter name="dineroObject" type="Dinero<TAmount>" required={true}>

The first Dinero object to compare.

</Parameter>

<Parameter name="comparator" type="Dinero<TAmount>" required={true}>

The second Dinero object to compare.

</Parameter>

</Parameters>

## Code examples

### Compare two objects

```js
import { dinero, compare } from 'dinero.js';
import { USD } from '@dinero.js/currencies';

const d1 = dinero({ amount: 800, currency: USD });
const d2 = dinero({ amount: 500, currency: USD });

compare(d1, d2); // 1
```

### Compare two objects after normalization

```js
import { dinero, compare } from 'dinero.js';
import { USD } from '@dinero.js/currencies';

const d1 = dinero({ amount: 5000, currency: USD, scale: 3 });
const d2 = dinero({ amount: 800, currency: USD });

compare(d1, d2); // -1

const d3 = dinero({ amount: 5000, currency: USD, scale: 3 });
const d4 = dinero({ amount: 500, currency: USD });

compare(d3, d4); // 0
```

### Sort arrays of objects

One of the main use cases of the `compare` function is to sort Dinero objects. For example, you can use it with [`Array.prototype.sort`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort).

```js
import { dinero, compare } from 'dinero.js';
import { USD } from '@dinero.js/currencies';

const d1 = dinero({ amount: 900, currency: USD });
const d2 = dinero({ amount: 500, currency: USD });
const d3 = dinero({ amount: 800, currency: USD });

const lowToHigh = [d1, d2, d3].sort(compare);
const highToLow = [d1, d2, d3].sort((a, b) => compare(b, a));
```
1 change: 1 addition & 0 deletions website/data/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ tree.add(Resource.create({ label: 'Transform scale', path: '/docs/api/conversion
tree.add(Resource.create({ label: 'Trim scale', path: '/docs/api/conversions/trim-scale' }));
tree.add(Resource.create({ label: 'Comparisons', path: '/docs/api/comparisons' }));
tree.add(Resource.create({ label: 'Equal', path: '/docs/api/comparisons/equal' }));
tree.add(Resource.create({ label: 'Compare', path: '/docs/api/comparisons/compare' }));
tree.add(Resource.create({ label: 'Greater than', path: '/docs/api/comparisons/greater-than' }));
tree.add(Resource.create({ label: 'Greater than or equal', path: '/docs/api/comparisons/greater-than-or-equal' }));
tree.add(Resource.create({ label: 'Less than', path: '/docs/api/comparisons/less-than' }));
Expand Down

1 comment on commit 53f84c2

@vercel
Copy link

@vercel vercel bot commented on 53f84c2 Jul 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.