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

feat(stringProp): add helper for when you always need a string #150

Merged
merged 1 commit into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
97 changes: 97 additions & 0 deletions src/string-prop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
always,
compose,
identity,
isEmpty,
isNil,
path,
prop,
unless,
useWith,
when,
is
} from 'ramda';
import castArray from './cast-array';
import curryN from './curry-n';
import { isNilOrEmpty } from './is-nil-empty';

/**
* @function
* @private
*/
const stringUnlessNilOrEmpty = compose(
unless(isNil, String),
when(isEmpty, always(null)),
when(is(Object), JSON.stringify)
);

/**
* Shorthand function to extract a property from an object and convert it to a string.
* If the property is `null` or `undefined` it will be returned as is; if the property is
* an empty string (`''`), `null` will be returned.
*
* @function
* @see https://ramdajs.com/docs/#prop
* @param {string} propName Name of the property to extract.
* @param {object} obj Source of the extracted property.
* @returns {string} The value of `obj` at `propName` as a string or null string.
*/
export const stringProp = curryN(2, compose(stringUnlessNilOrEmpty, prop));

/**
* Shorthand function to extract a nested property from an object and convert it to a string.
* If the property is `null` or `undefined` it will be returned as is; if the property is
* an empty string (`''`), `null` will be returned.
*
* @example
* stringPath(['life', 'count'], { life: { count: 1 }}); // '1'
*
* @function
* @see https://ramdajs.com/docs/#path
* @param {string|{String[]}} propPath Path to the property to extract. Also accepts a
* property name as a single string.
* @param {object} obj Source of the extracted property.
* @returns {string} The value of `obj` at `propPath` as a string or `null`.
*/
export const stringPath = curryN(
2,
compose(stringUnlessNilOrEmpty, useWith(path, [castArray, identity]))
);

/**
* Extract a property from an object and convert it to a string. If property
* is absent, `null`, `undefined` or empty, the default value will be returned instead.
*
* @function
* @see https://ramdajs.com/docs/#propOr
* @param {*} defaultValue The value to return if `propName` does not exist in `obj`
* or is nil.
* @param {string} propName Name of the property to extract.
* @param {object} obj Source of the extracted property.
* @returns {*} The value of `obj` at `propName` as a string or `defaultValue`.
*/
export const stringPropOr = curryN(3, function stringPropOr(defaultValue, propName, obj) {
const value = stringProp(propName, obj);
return isNilOrEmpty(value) ? defaultValue : value;
});

/**
* Extract a nested property from an object and convert it to a string. If property
* is absent, `null`, `undefined` or empty, the default value will be returned instead.
*
* @example
* stringPathOr('default', ['life', 'reason'], { foo: 'bar' }); // 'default'
*
* @function
* @see https://ramdajs.com/docs/#pathOr
* @param {*} defaultValue The value to return if `propPath` does not exist in `obj`
* or its value is nil or empty.
* @param {string|{String[]}} propPath Path to the property to extract. Also accepts a
* property name as a single string.
* @param {object} obj Source of the extracted property.
* @returns {*} The value of `obj` at `propName` as a string or `defaultValue`.
*/
export const stringPathOr = curryN(3, function stringPathOr(defaultValue, propPath, obj) {
const value = stringPath(propPath, obj);
return isNilOrEmpty(value) ? defaultValue : value;
});
165 changes: 165 additions & 0 deletions src/string-prop.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { stringProp, stringPath, stringPathOr, stringPropOr } from './string-prop';

describe('the `stringProp` function', () => {
test('should return a string from a number property', () => {
expect(stringProp('provincesId', { provincesId: 23 })).toEqual('23');
});

test('should return a stringified version of an array property', () => {
expect(stringProp('provincesId', { provincesId: [23, 22] })).toEqual('[23,22]');
});

test('should return a stringified version of an object property', () => {
expect(stringProp('provincesId', { provincesId: { id: 1 } })).toEqual('{"id":1}');
});

test('should return a string from a string property', () => {
expect(stringProp('provincesId', { provincesId: '23' })).toEqual('23');
});

test('should return `undefined` when extracted property does not exist', () => {
expect(stringProp('provincesId', {})).toBeUndefined();
});

test('should return `null` when extracted property is an empty string', () => {
expect(stringProp('provincesId', { provincesId: '' })).toBeNull();
});

test('should return `undefined` when extracted property is `undefined`', () => {
expect(stringProp('provincesId', { provincesId: undefined })).toBeUndefined();
});

test('should return `null` when extracted property is `null`', () => {
expect(stringProp('provincesId', { provincesId: null })).toBeNull();
});
});

describe('the `stringPath` function', () => {
test('should return a string from a number property', () => {
expect(stringPath(['data', 'count'], { data: { count: 23 } })).toEqual('23');
});

test('should return a stringified version of an array property', () => {
expect(stringPath(['data', 'count'], { data: { count: [23, 22] } })).toEqual('[23,22]');
});

test('should return a stringified version of an object property', () => {
expect(stringPath(['data', 'count'], { data: { count: { number: 1 } } })).toEqual(
'{"number":1}'
);
});

test('should return a string from a nested string property', () => {
expect(stringPath(['data', 'count'], { data: { count: '23' } })).toEqual('23');
});

test('should return `undefined` when extracted property does not exist', () => {
expect(stringPath(['data', 'count'], { data: {} })).toBeUndefined();
});

test('should return `null` when extracted property is an empty string', () => {
expect(stringPath(['data', 'count'], { data: { count: '' } })).toBeNull();
});

test('should return `undefined` when extracted property is `undefined`', () => {
expect(stringPath(['data', 'count'], { data: { count: undefined } })).toBeUndefined();
});

test('should return `null` when extracted property is `null`', () => {
expect(stringPath(['data', 'count'], { data: { count: null } })).toBeNull();
});

test('should support a single string as property path', () => {
expect(stringPath('count', { count: '23' })).toEqual('23');
});
});

describe('the `stringPropOr` function', () => {
test('should return a string from a number property', () => {
expect(stringPropOr('number-string', 'provincesId', { provincesId: 23 })).toEqual('23');
});

test('should return a stringified version of an array property', () => {
expect(stringPropOr('array', 'provincesId', { provincesId: [23, 22] })).toEqual('[23,22]');
});

test('should return a stringified version of an object property', () => {
expect(stringPropOr('object', 'provincesId', { provincesId: { id: 1 } })).toEqual('{"id":1}');
});

test('should return a string from a string property', () => {
expect(stringPropOr('string', 'provincesId', { provincesId: '23' })).toEqual('23');
});

test('should return the given default value when extracted property does not exist', () => {
expect(stringPropOr('default-string', 'provincesId', {})).toEqual('default-string');
});

test('should return the given default value when extracted property is `null`', () => {
expect(stringPropOr('default-for-null', 'provincesId', { provincesId: null })).toEqual(
'default-for-null'
);
});

test('should return the given default value when extracted property is `undefined`', () => {
expect(
stringPropOr('default-for-undefined', 'provincesId', { provincesId: undefined })
).toEqual('default-for-undefined');
});

test('should return the given default value when extracted property is an empty string', () => {
expect(stringPropOr('default-for-empty', 'provincesId', { provincesId: '' })).toEqual(
'default-for-empty'
);
});
});

describe('the `stringPathOr` function', () => {
test('should return a string from a number property', () => {
expect(stringPathOr('string-number', ['data', 'count'], { data: { count: 23 } })).toEqual('23');
});

test('should return a stringified version of an array property', () => {
expect(stringPathOr('string-array', ['data', 'count'], { data: { count: [23, 22] } })).toEqual(
'[23,22]'
);
});

test('should return a stringified version of an object property', () => {
expect(
stringPathOr('string-object', ['data', 'count'], { data: { count: { number: 1 } } })
).toEqual('{"number":1}');
});

test('should return a string from a nested string property', () => {
expect(stringPathOr('default-string', ['data', 'count'], { data: { count: '23' } })).toEqual(
'23'
);
});

test('should return the given default value when extracted property does not exist', () => {
expect(stringPathOr('default-string', ['data', 'count'], {})).toEqual('default-string');
});

test('should return the given default value when extracted property is `null`', () => {
expect(stringPathOr('default-for-null', ['data', 'count'], { data: { count: null } })).toEqual(
'default-for-null'
);
});

test('should return the given default value when extracted property is `undefined`', () => {
expect(
stringPathOr('default-for-undefined', ['data', 'count'], { data: { count: undefined } })
).toEqual('default-for-undefined');
});

test('should return the given default value when extracted property is an empty string', () => {
expect(stringPathOr('default-for-empty', ['data', 'count'], { data: { count: '' } })).toEqual(
'default-for-empty'
);
});

test('should support a single string as property path', () => {
expect(stringPathOr('default-string', 'count', { count: '23' })).toEqual('23');
});
});