Skip to content

Commit

Permalink
feat: port more string functions from lodash
Browse files Browse the repository at this point in the history
Added `camelCase`, `kebabCase`, `snakeCase` and `upperFirst`.

`camelCase`, `kebabCase` and `snakeCase` work with Unicode string if engine has `Unicode property escapes` support.
  • Loading branch information
alexkvak committed May 19, 2021
1 parent 81763cd commit cbd8343
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
@@ -1,6 +1,9 @@
language: node_js
node_js:
- "6"
- "8"
- "10"
- "12"
- "14"
script:
- npm run test:ci
8 changes: 8 additions & 0 deletions src/internal/support.ts
@@ -0,0 +1,8 @@
export function isUnicodePropertyEscapesSupported(): boolean {
try {
new RegExp(/\P{L}/u);
return true;
} catch (e) {
return false;
}
}
42 changes: 42 additions & 0 deletions src/string/__tests__/camelCase.ts
@@ -0,0 +1,42 @@
import camelCase from '../camelCase';
import { isUnicodePropertyEscapesSupported } from '../../internal/support';

jest.mock('../../internal/support', () => ({ isUnicodePropertyEscapesSupported: jest.fn() }));

describe('utils/string/camelCase', () => {
describe('modern engines', () => {
beforeAll(() => {
(isUnicodePropertyEscapesSupported as jest.Mock).mockReturnValue(true);
});

it.each([
['', ''],
['abc', 'abc'],
['Foo Bar', 'fooBar'],
['foo-bar', 'fooBar'],
['foo_bar', 'fooBar'],
['FOO_BAR', 'fooBar'],
['Foo Bär', 'fooBär'],
])('should return camel cased string for %s: %s', (str, expected) => {
expect(camelCase(str)).toEqual(expected);
});
});

describe('legacy engines', () => {
beforeAll(() => {
(isUnicodePropertyEscapesSupported as jest.Mock).mockReturnValue(false);
});

it.each([
['', ''],
['abc', 'abc'],
['Foo Bar', 'fooBar'],
['foo-bar', 'fooBar'],
['foo_bar', 'fooBar'],
['FOO_BAR', 'fooBar'],
['Foo Bär', 'fooBR'],
])('should return camel cased string for %s: %s', (str, expected) => {
expect(camelCase(str)).toEqual(expected);
});
});
});
42 changes: 42 additions & 0 deletions src/string/__tests__/kebabCase.ts
@@ -0,0 +1,42 @@
import kebabCase from '../kebabCase';
import { isUnicodePropertyEscapesSupported } from '../../internal/support';

jest.mock('../../internal/support', () => ({ isUnicodePropertyEscapesSupported: jest.fn() }));

describe('utils/string/kebabCase', () => {
describe('modern engines', () => {
beforeAll(() => {
(isUnicodePropertyEscapesSupported as jest.Mock).mockReturnValue(true);
});

it.each([
['', ''],
['abc', 'abc'],
['Foo Bar', 'foo-bar'],
['fooBar', 'foo-bar'],
['foo_Bar', 'foo-bar'],
['FOO_BAR', 'foo-bar'],
['Foo Bär', 'foo-bär'],
])('should return kebab cased string for %s: %s', (str, expected) => {
expect(kebabCase(str)).toEqual(expected);
});
});

describe('legacy engines', () => {
beforeAll(() => {
(isUnicodePropertyEscapesSupported as jest.Mock).mockReturnValue(false);
});

it.each([
['', ''],
['abc', 'abc'],
['Foo Bar', 'foo-bar'],
['fooBar', 'foo-bar'],
['foo_Bar', 'foo-bar'],
['FOO_BAR', 'foo-bar'],
['Foo Bär', 'foo-b-r'],
])('should return kebab cased string for %s: %s', (str, expected) => {
expect(kebabCase(str)).toEqual(expected);
});
});
});
45 changes: 45 additions & 0 deletions src/string/__tests__/snakeCase.ts
@@ -0,0 +1,45 @@
import snakeCase from '../snakeCase';

import { isUnicodePropertyEscapesSupported } from '../../internal/support';

jest.mock('../../internal/support', () => ({ isUnicodePropertyEscapesSupported: jest.fn() }));

describe('utils/string/snakeCase', () => {
describe('modern engines', () => {
beforeAll(() => {
(isUnicodePropertyEscapesSupported as jest.Mock).mockReturnValue(true);
});

it.each([
['', ''],
['abc', 'abc'],
['Foo Bar', 'foo_bar'],
['fooBar', 'foo_bar'],
['foo_Bar', 'foo_bar'],
['foo-Bar', 'foo_bar'],
['FOO_BAR', 'foo_bar'],
['Foo Bär', 'foo_bär'],
])('should return snake cased string for %s: %s', (str, expected) => {
expect(snakeCase(str)).toEqual(expected);
});
});

describe('legacy engines', () => {
beforeAll(() => {
(isUnicodePropertyEscapesSupported as jest.Mock).mockReturnValue(false);
});

it.each([
['', ''],
['abc', 'abc'],
['Foo Bar', 'foo_bar'],
['fooBar', 'foo_bar'],
['foo_Bar', 'foo_bar'],
['foo-Bar', 'foo_bar'],
['FOO_BAR', 'foo_bar'],
['Foo Bär', 'foo_b_r'],
])('should return snake cased string for %s: %s', (str, expected) => {
expect(snakeCase(str)).toEqual(expected);
});
});
});
12 changes: 12 additions & 0 deletions src/string/__tests__/upperFirst.ts
@@ -0,0 +1,12 @@
import upperFirst from '../upperFirst';

describe('utils/string/upperFirst', () => {
it.each([
['', ''],
['foo', 'Foo'],
['FOO', 'FOO'],
['über', 'Über'],
])('should convert the first character of string to upper case for %s: %s', (str, expected) => {
expect(upperFirst(str)).toEqual(expected);
});
});
22 changes: 22 additions & 0 deletions src/string/camelCase.ts
@@ -0,0 +1,22 @@
import { isUnicodePropertyEscapesSupported } from '../internal/support';

/**
* Converts string to camel case.
*
* @param {String} str The string to convert.
* @return {String} The camel cased string.
* @example
*
* camelCase('Foo Bar'); //=> 'fooBar'
* camelCase('foo-bar'); //=> 'fooBar'
* camelCase('foo_bar'); //=> 'fooBar'
* camelCase('FOO_BAR'); //=> 'fooBar'
* camelCase('Foo Bär'); //=> 'fooBär' only with Unicode property escapes support
*/
function camelCase(str: string): string {
const regex = isUnicodePropertyEscapesSupported() ? /\P{L}+(.)/gu : /[\W_]+(.)/g;

return str.toLowerCase().replace(regex, (match, chr) => chr.toUpperCase());
}

export default camelCase;
31 changes: 31 additions & 0 deletions src/string/kebabCase.ts
@@ -0,0 +1,31 @@
import { isUnicodePropertyEscapesSupported } from '../internal/support';

/**
* Converts string to kebab case.
*
* @param {String} str The string to convert.
* @return {String} The kebab cased string.
* @example
*
* kebabCase('Foo Bar'); //=> 'foo-bar'
* kebabCase('fooBar'); //=> 'foo-bar'
* kebabCase('foo_bar'); //=> 'foo-bar'
* kebabCase('FOO_BAR'); //=> 'foo-bar'
* kebabCase('Foo Bär'); //=> 'foo-bär' only with Unicode property escapes support
*/
function kebabCase(str: string): string {
const unicodePropertyEscapesSupported = isUnicodePropertyEscapesSupported();
const camelCaseRegex = unicodePropertyEscapesSupported ? /(\p{Lower})(\p{Upper})/gu : /([a-z])([A-Z])/g;
const nonLettersRegex = unicodePropertyEscapesSupported ? /\P{L}+/gu : /[\W_]+/g;

return (
str
// handle camelCase
.replace(camelCaseRegex, (match, first, second) => `${first}-${second}`)
// replace non-letters with hyphens
.replace(nonLettersRegex, '-')
.toLowerCase()
);
}

export default kebabCase;
31 changes: 31 additions & 0 deletions src/string/snakeCase.ts
@@ -0,0 +1,31 @@
import { isUnicodePropertyEscapesSupported } from '../internal/support';

/**
* Returns the snake case version of a string.
*
* @param {String} str The string to switch to snake case.
* @return {String} The snake case version of `str`.
* @example
*
* snakeCase('Foo Bar'); //=> 'foo_bar'
* snakeCase('fooBar'); //=> 'foo_bar'
* snakeCase('foo-bar'); //=> 'foo_bar'
* snakeCase('FOO-BAR'); //=> 'foo_bar'
* snakeCase('Foo Bär'); //=> 'foo_bär' only with Unicode property escapes support
*/
function snakeCase(str: string): string {
const unicodePropertyEscapesSupported = isUnicodePropertyEscapesSupported();
const camelCaseRegex = unicodePropertyEscapesSupported ? /(\p{Lower})(\p{Upper})/gu : /([a-z])([A-Z])/g;
const nonLettersRegex = unicodePropertyEscapesSupported ? /\P{L}+/gu : /[\W_]+/g;

return (
str
// handle camelCase
.replace(camelCaseRegex, (match, first, second) => `${first}-${second}`)
// replace non-letters with underscore
.replace(nonLettersRegex, '_')
.toLowerCase()
);
}

export default snakeCase;
16 changes: 16 additions & 0 deletions src/string/upperFirst.ts
@@ -0,0 +1,16 @@
/**
* Converts the first character of string to upper case and returns the new string.
*
* @param {String} str The string to convert.
* @return {String} The converted string.
* @example
*
* upperFirst('foo'); //=> 'Foo'
* upperFirst('über'); //=> 'Über'
* upperFirst('FOO'); //=> 'FOO'
*/
function upperFirst(str: string): string {
return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
}

export default upperFirst;

0 comments on commit cbd8343

Please sign in to comment.