Skip to content

Commit

Permalink
Merge pull request #935 from CommitChange/widget-custom-amounts-with-…
Browse files Browse the repository at this point in the history
…highlights

allow custom amounts with highlight icons in widget custom_amounts param
  • Loading branch information
wwahammy authored Oct 30, 2024
2 parents 4c6e9be + 26f6a9c commit 5a71162
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 12 deletions.
10 changes: 3 additions & 7 deletions client/js/nonprofits/donate/get-params.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
// License: LGPL-3.0-or-later
const R = require('ramda')
const {getDefaultAmounts} = require('./custom_amounts');
const { parseCustomFields, splitParam } = require('./parseFields');
const { parseCustomFields, parseCustomAmounts, splitParam } = require('./parseFields');

module.exports = params => {
const defaultAmts = getDefaultAmounts().join()
// Set defaults
const merge = R.merge({
custom_amounts: ''
})
const merge = R.merge({ custom_amounts: '' })
// Preprocess data
const evolve = R.evolve({
multiple_designations: splitParam
, custom_amounts: amts => R.compose(R.map(Number), splitParam)(amts || defaultAmts)
, custom_amounts: parseCustomAmounts
, custom_fields: parseCustomFields
, tags: tags => R.map(tag => {
return tag.trim()
Expand Down
28 changes: 23 additions & 5 deletions client/js/nonprofits/donate/get-params.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,37 @@ const {getDefaultAmounts} = require('./custom_amounts');
describe('.getParams', () => {
describe('custom_amounts:', () => {
it('gives custom_amounts defaults if not passed in', () => {
expect(getParams({})).toHaveProperty('custom_amounts', getDefaultAmounts());
expect(getParams({})).toHaveProperty(
'custom_amounts',
getDefaultAmounts().map((a) => ({ amount: a, highlight: false }))
);
});

it('accepts integers', () => {
expect(getParams({custom_amounts: '3'})).toHaveProperty('custom_amounts', [3]);
expect(getParams({custom_amounts: '3'})).toHaveProperty('custom_amounts', [{ amount: 3, highlight: false }]);
});

it('accepts floats', () => {
expect(getParams({custom_amounts: '3.5'})).toHaveProperty('custom_amounts', [3.5]);
expect(getParams({ custom_amounts: '3.5' })).toHaveProperty('custom_amounts', [
{ amount: 3.5, highlight: false },
]);
});

it('splits properly', () => {
expect(getParams({custom_amounts: '3.5, 600\n;400;3'})).toHaveProperty('custom_amounts', [3.5, 600, 400, 3]);
expect(getParams({ custom_amounts: '3.5, 600\n;400;3' })).toHaveProperty('custom_amounts', [
{ amount: 3.5, highlight: false },
{ amount: 600, highlight: false },
{ amount: 400, highlight: false },
{ amount: 3, highlight: false },
]);
});

it('accepts custom amounts with highlight icons properly', () => {
expect(getParams({ custom_amounts: "5,{amount:30,highlight:'car'},50" })).toHaveProperty('custom_amounts', [
{ amount: 5, highlight: false },
{ amount: 30, highlight: 'car' },
{ amount: 50, highlight: false },
]);
});

});
Expand Down Expand Up @@ -57,4 +75,4 @@ describe('.getParams', () => {
expect(getParams({tags: ' \tA tag name\n'})).toHaveProperty('tags', ['A tag name']);
});
});
});
});
11 changes: 11 additions & 0 deletions client/js/nonprofits/donate/parseFields/customAmount/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// License: LGPL-3.0-or-later
import has from 'lodash/has';

export interface CustomAmount {
amount: NonNullable<number>;
highlight: NonNullable<string | false>;
}

export function isCustomAmountObject(item: unknown): item is CustomAmount {
return typeof item == 'object' && has(item, 'amount');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// License: LGPL-3.0-or-later
import JsonStringParser from './JsonStringParser';

describe('JsonStringParser', () => {
describe.each([
['with bracket', '['],
['with brace', '[{]'],
['without brackets', '2,3,4'],
['with letters', '[letters]'],
['with no amount given', "[{highlight: 'car'}]"],
])('when invalid %s', (_n, input) => {
const parser = new JsonStringParser(input);
it('has correct result', () => {
expect(parser.results).toStrictEqual([]);
});

it('has error', () => {
expect(parser.errors).not.toBeEmpty();
});

it('is marked not valid', () => {
expect(parser.isValid).toBe(false);
});
});

describe.each([
['when an empty array', '[]', []],
[
'with all numbers',
'[1,2.5,3]',
[
{ amount: 1, highlight: false },
{ amount: 2.5, highlight: false },
{ amount: 3, highlight: false },
],
],
[
'with some numbers and some objects',
"[1,{amount:2.5,highlight:'icon'},3]",
[
{ amount: 1, highlight: false },
{ amount: 2.5, highlight: 'icon' },
{ amount: 3, highlight: false },
],
],
[
'with objects',
"[{amount:2.5,highlight:'icon'},{amount:5}]",
[
{ amount: 2.5, highlight: 'icon' },
{ amount: 5, highlight: false },
],
],
])('when valid %s', (_name, input, result) => {
const parser = new JsonStringParser(input);

it('has no errors', () => {
expect(parser.errors).toBeEmpty();
});

it('has is marked valid', () => {
expect(parser.isValid).toStrictEqual(true);
});

it('matches expected result', () => {
expect(parser.results).toStrictEqual(result);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// License: LGPL-3.0-or-later
import { parse } from 'json5';
import { CustomAmount, isCustomAmountObject } from '../customAmount';

export default class JsonStringParser {
public errors: SyntaxError[] = [];
public readonly results: CustomAmount[] = [];
constructor(public readonly fieldsString: string) {
this._parse();
}

get isValid(): boolean {
return this.errors.length == 0;
}

private _parse = (): void => {
try {
const result = parse(this.fieldsString);
const emptyCustomAmount = { highlight: false };
if (result instanceof Array) {
result.forEach((i) => {
if (isCustomAmountObject(i)) {
this.results.push({ ...emptyCustomAmount, ...i });
} else if (typeof i == 'number') {
this.results.push({ amount: i, highlight: false });
} else {
this.errors.push(new SyntaxError(JSON.stringify(i) + ' is not a valid custom amount'));
}
});
} else {
this.errors.push(new SyntaxError('Input did not parse to an array'));
}
} catch (e: any) {
this.errors.push(e);
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// License: LGPL-3.0-or-later
import parseCustomAmounts from '.';
import { getDefaultAmounts } from '../../custom_amounts';

describe.each([
['maps default amounts', '', getDefaultAmounts().map((a: number) => ({ amount: a, highlight: false }))],
[
'maps integers correctly',
'1,2,3',
[
{ amount: 1, highlight: false },
{ amount: 2, highlight: false },
{ amount: 3, highlight: false },
],
],
[
'accepts integers, floats, and extraneous spaces',
'1, 2.5,3 ,456',
[
{ amount: 1, highlight: false },
{ amount: 2.5, highlight: false },
{ amount: 3, highlight: false },
{ amount: 456, highlight: false },
],
],
[
'accepts a mix of numbers and objects with amounts and highlights',
'1, {amount: 2.5, highlight:"icon"},3',
[
{ amount: 1, highlight: false },
{ amount: 2.5, highlight: 'icon' },
{ amount: 3, highlight: false },
],
],
[
'omits invalid objects',
'1, {highlight: "icon"},3',
[
{ amount: 1, highlight: false },
{ amount: 3, highlight: false },
],
],
[
'accepts objects without highlights and maps them to false',
'1, {amount:2 },3',
[
{ amount: 1, highlight: false },
{ amount: 2, highlight: false },
{ amount: 3, highlight: false },
],
],
[
'accepts mixed inputs with array brackets',
'[1,{amount:52},3]',
[
{ amount: 1, highlight: false },
{ amount: 52, highlight: false },
{ amount: 3, highlight: false },
],
],
])('parseCustomField', (name, input, result) => {
describe('parseCustomAmounts', () => {
it(name, () => {
expect(parseCustomAmounts(input)).toStrictEqual(result);
});
});
});
17 changes: 17 additions & 0 deletions client/js/nonprofits/donate/parseFields/customAmounts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// License: LGPL-3.0-or-later
import JsonStringParser from './JsonStringParser';
import { getDefaultAmounts } from '../../custom_amounts';
import { CustomAmount } from '../customAmount';
import parseNumberAmounts from './parseNumberAmounts';

export default function parseCustomAmounts(amountsString: string): CustomAmount[] {
const defaultAmts = getDefaultAmounts().join();

if (amountsString.includes('{')) {
if (!amountsString.startsWith('[')) amountsString = `[${amountsString}`;
if (!amountsString.endsWith(']')) amountsString = `${amountsString}]`;
return new JsonStringParser(amountsString).results;
} else {
return parseNumberAmounts(amountsString || defaultAmts);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// License: LGPL-3.0-or-later
import parseNumberAmounts from './parseNumberAmounts';

describe.each([
['when empty', '', []],
[
'when integers',
'1,2,3',
[
{ amount: 1, highlight: false },
{ amount: 2, highlight: false },
{ amount: 3, highlight: false },
],
],
[
'when integers, floats, and spaces',
'1,2.5,3 ,456',
[
{ amount: 1, highlight: false },
{ amount: 2.5, highlight: false },
{ amount: 3, highlight: false },
{ amount: 456, highlight: false },
],
],
])('parseCustomField', (name, input, result) => {
describe(name, () => {
it('maps the numbers as expected', () => {
expect(parseNumberAmounts(input)).toStrictEqual(result);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// License: LGPL-3.0-or-later
import { CustomAmount } from '../customAmount';
import { splitParam } from '..';

export default function parseNumberAmounts(amountsString: string): CustomAmount[] {
if (amountsString.length === 0) return [];
return splitParam(amountsString).map((n) => ({ amount: Number(n), highlight: false }));
}
2 changes: 2 additions & 0 deletions client/js/nonprofits/donate/parseFields/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// License: LGPL-3.0-or-later
export {default as parseCustomField, CustomFieldDescription} from "./customField";
export {default as parseCustomFields} from "./customFields";
export {default as parseCustomAmounts} from "./customAmounts";
export { CustomAmount } from "./customAmount";

export function splitParam(param:string) : string[] {
return param.split(/[_;,]/);
Expand Down

0 comments on commit 5a71162

Please sign in to comment.