@becollective/utils 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bundle.js CHANGED
@@ -39,26 +39,96 @@ var password = {
39
39
  };
40
40
 
41
41
  var moment = require('moment');
42
+ /**
43
+ * Get current age based on the given birth date
44
+ * @param {string} dateOfBirth
45
+ * @param {string} unit the measurement of the the difference
46
+ * The supported measurements are:
47
+ * years, months, weeks, days, hours, minutes, and seconds.
48
+ * @returns {number} age
49
+ */
50
+
51
+
52
+ var getAge = function getAge(dateOfBirth) {
53
+ var unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'years';
54
+ var age = moment().diff(dateOfBirth, unit);
55
+
56
+ if (isNaN(age)) {
57
+ throw new Error('Invalid date');
58
+ }
59
+
60
+ return age;
61
+ };
42
62
  /**
43
63
  * Check if a user requires parent(guardian) consent
44
64
  * i.e. Is under the age of 16 years, based on GDPR
45
65
  * https://gdpr-info.eu/art-8-gdpr/
46
- * @param {*} dateOfBirth moment.js compatible date time
66
+ * @param {string} dateOfBirth moment.js compatible date time
67
+ * @returns {boolean}
68
+ * NOTE:
69
+ * We used to not allow child (under 16) to register,
70
+ * and birthdate is not a required field for ghost user
71
+ * So an existing user could have no birthdate, we assume these users are adults
72
+ * TODO:
73
+ * Remove above logic after we migrated all legacy
74
+ * data to have a consistent birthdate property.
75
+ * Then we should throw error if birthdate is not valid
47
76
  */
48
77
 
49
-
50
78
  var isUnderSixteen = function isUnderSixteen(dateOfBirth) {
51
- var age = moment().diff(dateOfBirth, 'years');
79
+ if (!dateOfBirth || !moment(dateOfBirth).isValid()) {
80
+ return false;
81
+ }
52
82
 
53
- if (isNaN(age)) {
54
- throw new Error('Invalid date');
83
+ var age = getAge(dateOfBirth);
84
+ return isNaN(age) ? false : age < 16;
85
+ };
86
+
87
+ var _require = require('@becollective/constants'),
88
+ currencies = _require.currencies; // We should probably throw and error on bad input instead of falling back to a default.
89
+ // We're making an assumption that whoever is calling this function can safely work
90
+ // with AUD. But this assumption removes a lot of error handling elsewhere.
91
+
92
+
93
+ var getCurrencyFromCurrencyCode = function getCurrencyFromCurrencyCode(currencyCode) {
94
+ if (typeof currencyCode !== 'string' || !currencies[currencyCode]) {
95
+ console.error(new Error("Invalid currencyCode: ".concat(currencyCode, ". Falling back to 'AUD'.")));
96
+ return currencies.AUD;
97
+ }
98
+
99
+ return currencies[currencyCode];
100
+ };
101
+ var makeMoneyString = function makeMoneyString(_ref) {
102
+ var inputAmount = _ref.amount,
103
+ currencyCode = _ref.currencyCode,
104
+ _ref$rounded = _ref.rounded,
105
+ rounded = _ref$rounded === void 0 ? false : _ref$rounded,
106
+ _ref$showCurrencyCode = _ref.showCurrencyCode,
107
+ showCurrencyCode = _ref$showCurrencyCode === void 0 ? false : _ref$showCurrencyCode;
108
+ var amount = Number(inputAmount);
109
+
110
+ if (isNaN(amount)) {
111
+ console.error(new Error("Invalid amount: ".concat(inputAmount, ". Falling back to 0.")));
112
+ amount = 0;
55
113
  }
56
114
 
57
- return age < 16;
115
+ var currency = getCurrencyFromCurrencyCode(currencyCode);
116
+ var minorUnit = currency.minorUnit,
117
+ symbol = currency.symbol;
118
+ var decimalPlaces = rounded ? 0 : minorUnit;
119
+ var localeAmount = amount.toLocaleString(undefined, {
120
+ minimumFractionDigits: decimalPlaces,
121
+ maximumFractionDigits: decimalPlaces
122
+ });
123
+ var currencyCodeString = showCurrencyCode ? " ".concat(currency.code) : '';
124
+ return "".concat(symbol).concat(localeAmount).concat(currencyCodeString);
58
125
  };
59
126
 
60
127
  var util = {
61
128
  password: password,
62
- isUnderSixteen: isUnderSixteen
129
+ isUnderSixteen: isUnderSixteen,
130
+ getAge: getAge,
131
+ getCurrencyFromCurrencyCode: getCurrencyFromCurrencyCode,
132
+ makeMoneyString: makeMoneyString
63
133
  };
64
134
  module.exports = util;
package/index.js CHANGED
@@ -1,9 +1,13 @@
1
- import { password } from './password';
2
- import { isUnderSixteen } from './date-time';
1
+ import { password } from './src/password';
2
+ import { isUnderSixteen, getAge } from './src/date-time';
3
+ import { getCurrencyFromCurrencyCode, makeMoneyString } from './src/money';
3
4
 
4
5
  const util = {
5
6
  password,
6
7
  isUnderSixteen,
8
+ getAge,
9
+ getCurrencyFromCurrencyCode,
10
+ makeMoneyString,
7
11
  };
8
12
 
9
13
  module.exports = util;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@becollective/utils",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Common utilities",
5
5
  "main": "bundle.js",
6
6
  "scripts": {
7
- "test": "NODE_ENV=localtest npm run build && ./node_modules/.bin/jest tests/*",
7
+ "test": "NODE_ENV=localtest npm run build && ./node_modules/.bin/jest",
8
8
  "build": "./node_modules/.bin/rollup -c",
9
- "prepublish": "npm run build; npm test"
9
+ "prepare": "npm run build; npm test"
10
10
  },
11
11
  "author": "",
12
12
  "license": "ISC",
@@ -19,6 +19,15 @@
19
19
  "rollup-plugin-node-resolve": "^3.4.0"
20
20
  },
21
21
  "dependencies": {
22
+ "@becollective/constants": "^3.3.2",
22
23
  "moment": "^2.24.0"
24
+ },
25
+ "jest": {
26
+ "testMatch": [
27
+ "**/*.test.js"
28
+ ],
29
+ "setupFiles": [
30
+ "./testUtils/setup.js"
31
+ ]
23
32
  }
24
33
  }
@@ -0,0 +1,39 @@
1
+ const moment = require('moment');
2
+
3
+ /**
4
+ * Get current age based on the given birth date
5
+ * @param {string} dateOfBirth
6
+ * @param {string} unit the measurement of the the difference
7
+ * The supported measurements are:
8
+ * years, months, weeks, days, hours, minutes, and seconds.
9
+ * @returns {number} age
10
+ */
11
+ export const getAge = (dateOfBirth, unit='years') => {
12
+ const age = moment().diff(dateOfBirth, unit);
13
+ if (isNaN(age)) {
14
+ throw new Error('Invalid date');
15
+ }
16
+ return age;
17
+ };
18
+ /**
19
+ * Check if a user requires parent(guardian) consent
20
+ * i.e. Is under the age of 16 years, based on GDPR
21
+ * https://gdpr-info.eu/art-8-gdpr/
22
+ * @param {string} dateOfBirth moment.js compatible date time
23
+ * @returns {boolean}
24
+ * NOTE:
25
+ * We used to not allow child (under 16) to register,
26
+ * and birthdate is not a required field for ghost user
27
+ * So an existing user could have no birthdate, we assume these users are adults
28
+ * TODO:
29
+ * Remove above logic after we migrated all legacy
30
+ * data to have a consistent birthdate property.
31
+ * Then we should throw error if birthdate is not valid
32
+ */
33
+ export const isUnderSixteen = dateOfBirth => {
34
+ if (!dateOfBirth || !moment(dateOfBirth).isValid()) {
35
+ return false;
36
+ }
37
+ const age = getAge(dateOfBirth);
38
+ return isNaN(age) ? false : age < 16;
39
+ };
@@ -0,0 +1,56 @@
1
+ const { isUnderSixteen, getAge } = require('../bundle.js');
2
+ const moment = require('moment');
3
+
4
+ describe('getAge', () => {
5
+ const refPoint = moment().subtract(16, 'years');
6
+
7
+ test('Returns the correct age', () => {
8
+ const dob = moment(refPoint);
9
+
10
+ expect(getAge(dob)).toBe(16);
11
+ expect(getAge(dob.subtract(1, 'year').utc().format())).toBe(17);
12
+ expect(getAge(dob.add(13, 'months').utc().format())).toBe(15);
13
+ });
14
+
15
+ test('Returns the age in specified measurement', () => {
16
+ const dob = moment().subtract(15, 'months');
17
+
18
+ expect(getAge(dob)).toBe(1);
19
+ expect(getAge(dob, 'years')).toBe(1);
20
+ expect(getAge(dob, 'months')).toBe(15);
21
+ });
22
+
23
+ test('Throws Error if dob is not a valid date-string', () => {
24
+ expect(() => getAge(notDefined)).toThrow(/not defined/);
25
+ expect(() => getAge(null)).toThrow(/Invalid date/);
26
+ expect(() => getAge('not-valid')).toThrow(/Invalid date/);
27
+ });
28
+ });
29
+
30
+ describe('isUnderSixteen', () => {
31
+ const refPoint = moment().subtract(16, 'years');
32
+ const obj = {};
33
+ test('Is not U16 if dob is 16 years ago', () => {
34
+ const dob = moment(refPoint);
35
+
36
+ expect(isUnderSixteen(dob.subtract(1, 'minute').utc().format())).toBe(false);
37
+ expect(isUnderSixteen(dob.subtract(100, 'year').utc().format())).toBe(false);
38
+ });
39
+
40
+ test('Is U16 if dob is less than 16 years ago', () => {
41
+ const dob = moment(refPoint);
42
+
43
+ expect(isUnderSixteen(dob.add(1, 'minute').utc().format())).toBe(true);
44
+ expect(isUnderSixteen(dob.add(100, 'year').utc().format())).toBe(true);
45
+ });
46
+
47
+ test('Undefined dob', () => {
48
+ expect(isUnderSixteen(obj.notDefined)).toBe(false);
49
+ });
50
+ test('Null dob', () => {
51
+ expect(isUnderSixteen(null)).toBe(false);
52
+ });
53
+ test('Invalid date string', () => {
54
+ expect(isUnderSixteen('not-valid')).toBe(false);
55
+ });
56
+ });
package/src/money.js ADDED
@@ -0,0 +1,37 @@
1
+ const { currencies } = require('@becollective/constants');
2
+
3
+ // We should probably throw and error on bad input instead of falling back to a default.
4
+ // We're making an assumption that whoever is calling this function can safely work
5
+ // with AUD. But this assumption removes a lot of error handling elsewhere.
6
+ export const getCurrencyFromCurrencyCode = currencyCode => {
7
+ if (typeof currencyCode !== 'string' || !currencies[currencyCode]) {
8
+ console.error(new Error(`Invalid currencyCode: ${currencyCode}. Falling back to 'AUD'.`));
9
+ return currencies.AUD;
10
+ }
11
+
12
+ return currencies[currencyCode];
13
+ };
14
+
15
+ export const makeMoneyString = ({
16
+ amount: inputAmount,
17
+ currencyCode,
18
+ rounded = false,
19
+ showCurrencyCode = false,
20
+ }) => {
21
+ let amount = Number(inputAmount);
22
+ if (isNaN(amount)) {
23
+ console.error(new Error(`Invalid amount: ${inputAmount}. Falling back to 0.`));
24
+ amount = 0;
25
+ }
26
+
27
+ const currency = getCurrencyFromCurrencyCode(currencyCode);
28
+ const { minorUnit, symbol } = currency;
29
+
30
+ const decimalPlaces = rounded ? 0 : minorUnit;
31
+ const localeAmount = amount.toLocaleString(undefined, {
32
+ minimumFractionDigits: decimalPlaces,
33
+ maximumFractionDigits: decimalPlaces,
34
+ });
35
+ const currencyCodeString = showCurrencyCode ? ` ${currency.code}` : '';
36
+ return `${symbol}${localeAmount}${currencyCodeString}`;
37
+ };
@@ -0,0 +1,99 @@
1
+ const { currencies } = require('@becollective/constants');
2
+
3
+ const { getCurrencyFromCurrencyCode, makeMoneyString } = require('../bundle.js');
4
+
5
+ describe('getCurrencyFromCurrencyCode', () => {
6
+ test('Returns the correct currency', () => {
7
+ expect(getCurrencyFromCurrencyCode('AUD')).toMatchObject(currencies.AUD);
8
+ expect(getCurrencyFromCurrencyCode('NZD')).toMatchObject(currencies.NZD);
9
+ expect(getCurrencyFromCurrencyCode('GBP')).toMatchObject(currencies.GBP);
10
+ });
11
+
12
+ test('Returns AUD currency if currencyCode not found or invalid', () => {
13
+ const bogusCurrencyCodes = [
14
+ undefined,
15
+ 1234,
16
+ 'some junk that is not a currency code',
17
+ { foo: 'bar' },
18
+ ];
19
+ bogusCurrencyCodes.forEach(bogusCurrencyCode => {
20
+ expect(getCurrencyFromCurrencyCode(bogusCurrencyCode)).toMatchObject(currencies.AUD);
21
+ });
22
+ });
23
+ });
24
+
25
+ describe('makeMoneyString', () => {
26
+ test('Outputs a rounded money string', () => {
27
+ expect(
28
+ makeMoneyString({
29
+ amount: 123.05,
30
+ rounded: true,
31
+ currencyCode: 'AUD',
32
+ })
33
+ ).toEqual('$123');
34
+
35
+ expect(
36
+ makeMoneyString({
37
+ amount: 123.5,
38
+ rounded: true,
39
+ currencyCode: 'AUD',
40
+ })
41
+ ).toEqual('$124');
42
+
43
+ expect(
44
+ makeMoneyString({
45
+ amount: 123.95,
46
+ rounded: true,
47
+ currencyCode: 'AUD',
48
+ })
49
+ ).toEqual('$124');
50
+ });
51
+
52
+ test('Outputs a money string with currency code', () => {
53
+ expect(
54
+ makeMoneyString({
55
+ amount: 123.05,
56
+ currencyCode: 'NZD',
57
+ showCurrencyCode: true,
58
+ })
59
+ ).toEqual('$123.05 NZD');
60
+ });
61
+
62
+ test('Outputs a money string for AUD if bad input is provided', () => {
63
+ const bogusCurrencyCodes = [
64
+ undefined,
65
+ 1234,
66
+ 'some junk that is not a currency code',
67
+ { foo: 'bar' },
68
+ ];
69
+ bogusCurrencyCodes.forEach(bogusCurrencyCode => {
70
+ expect(
71
+ makeMoneyString({
72
+ amount: 123.05,
73
+ currencyCode: bogusCurrencyCode,
74
+ showCurrencyCode: true,
75
+ })
76
+ ).toEqual('$123.05 AUD');
77
+ });
78
+ });
79
+
80
+ test('Gracefully handles non-Number `amount` input', () => {
81
+ expect(
82
+ makeMoneyString({
83
+ amount: '100.25',
84
+ rounded: true,
85
+ currencyCode: 'GBP',
86
+ })
87
+ ).toEqual('£100');
88
+
89
+ const bogusAmountValues = [undefined, null, 'some junk that is not a number', { foo: 'bar' }];
90
+ bogusAmountValues.forEach(bogusAmountValue => {
91
+ expect(
92
+ makeMoneyString({
93
+ amount: bogusAmountValue,
94
+ currencyCode: 'GBP',
95
+ })
96
+ ).toEqual('£0.00');
97
+ });
98
+ });
99
+ });
@@ -0,0 +1,21 @@
1
+ process.env.NODE_ENV = 'localtest';
2
+
3
+ let hasListened = false;
4
+ if(!hasListened) {
5
+ hasListened = true;
6
+ process.on('unhandledRejection', (err) => {
7
+ console.debug(err.stack);
8
+ });
9
+ }
10
+
11
+ global.console = {
12
+ // These could be called in parts of the code for good reason but pollute the test output.
13
+ // So suppress the while testing.
14
+ // Ref: https://stackoverflow.com/questions/44467657/jest-better-way-to-disable-console-inside-unit-tests
15
+ log: jest.fn(),
16
+ error: jest.fn(),
17
+ warn: jest.fn(),
18
+ info: jest.fn(),
19
+ // Use console.debug if you need to debug while testing
20
+ debug: console.debug,
21
+ };
package/date-time.js DELETED
@@ -1,15 +0,0 @@
1
- const moment = require('moment');
2
-
3
- /**
4
- * Check if a user requires parent(guardian) consent
5
- * i.e. Is under the age of 16 years, based on GDPR
6
- * https://gdpr-info.eu/art-8-gdpr/
7
- * @param {*} dateOfBirth moment.js compatible date time
8
- */
9
- export const isUnderSixteen = dateOfBirth => {
10
- const age = moment().diff(dateOfBirth, 'years');
11
- if (isNaN(age)) {
12
- throw new Error('Invalid date');
13
- }
14
- return age < 16;
15
- };
@@ -1,26 +0,0 @@
1
- const { isUnderSixteen } = require('../bundle.js');
2
- const moment = require('moment');
3
-
4
- describe('isUnderSixteen', () => {
5
- const refPoint = moment().subtract(16, 'years');
6
-
7
- test('Is not U16 if dob is 16 years ago', () => {
8
- const dob = moment(refPoint);
9
-
10
- expect(isUnderSixteen(dob.subtract(1, 'second').utc().format())).toBe(false);
11
- expect(isUnderSixteen(dob.subtract(100, 'year').utc().format())).toBe(false);
12
- });
13
-
14
- test('Is U16 if dob is less than 16 years ago', () => {
15
- const dob = moment(refPoint);
16
-
17
- expect(isUnderSixteen(dob.add(1, 'second').utc().format())).toBe(true);
18
- expect(isUnderSixteen(dob.add(100, 'year').utc().format())).toBe(true);
19
- });
20
-
21
- test('Throws Error if dob is not a valid date-string', () => {
22
- expect(() => isUnderSixteen(notDefined)).toThrow(/not defined/);
23
- expect(() => isUnderSixteen(null)).toThrow(/Invalid date/);
24
- expect(() => isUnderSixteen('not-valid')).toThrow(/Invalid date/);
25
- });
26
- });
package/tests/setup.js DELETED
@@ -1,9 +0,0 @@
1
- process.env.NODE_ENV = 'localtest';
2
-
3
- let hasListened = false;
4
- if(!hasListened) {
5
- hasListened = true;
6
- process.on('unhandledRejection', (err) => {
7
- console.error(err.stack);
8
- });
9
- }
File without changes
File without changes