Skip to content
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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const count = stringzy.analyze.wordCount('Hello world'); // 2
- [formatPercentage](#formatpercentage) - Converts a number into a percentage string with configurable decimal precision.
- [formatFileSize](#formatfilesize) - Converts a number of bytes into a human-readable file size string (B, KB, MB, GB, TB).
- [formatOrdinal](#formatordinal) - Converts a number into its ordinal string representation (e.g., 1 → "1st", 2 → "2nd").
- [formatList](#formatlist) - Formats an array of strings into a human-readable list with proper commas and "and".

## 📋 API Reference

Expand Down Expand Up @@ -1409,6 +1410,29 @@ formatOrdinal(null); // TypeError
| --------- | ------ | -------- | ----------------------------------- |
| num | number | required | The number to format as an ordinal. |

#### <a id="formatlist"></a>formatList(arr)

Formats an array of strings into a human-readable list using commas and "and".
Automatically applies the Oxford comma for lists of three or more items.
Returns an empty string for empty arrays and throws a TypeError for invalid inputs.

```javascript
import { formatList } from 'stringzy';

formatList(['apples', 'bananas', 'cherries']); // "apples, bananas, and cherries"
formatList(['apples', 'bananas']); // "apples and bananas"
formatList(['apple']); // "apple"
formatList([]); // ""

// Invalid cases
formatList('apple'); // TypeError
formatList(['apple', 123]); // TypeError
```

| Parameter | Type | Default | Description |
| --------- | -------- | -------- | ----------------------------------------- |
| arr | string[] | required | The array of strings to format as a list. |

## 🔧 Usage Patterns

### Individual Function Imports
Expand Down
5 changes: 4 additions & 1 deletion src/formatting/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { formatRomanNumeral } from './romanNumerals';
export { formatPercentage } from './percentage';
export { formatFileSize } from './fileSize';
export { formatOrdinal } from './ordinal';
export { formatList } from './listToString';

import { capitalize } from './capitalize';
import { formatNumber } from './number';
Expand All @@ -17,6 +18,7 @@ import { formatRomanNumeral } from './romanNumerals';
import { formatPercentage } from './percentage';
import { formatFileSize } from './fileSize';
import { formatOrdinal } from './ordinal';
import { formatList } from './listToString';

export const formatting = {
capitalize,
Expand All @@ -27,5 +29,6 @@ export const formatting = {
formatRomanNumeral,
formatPercentage,
formatFileSize,
formatOrdinal
formatOrdinal,
formatList
};
32 changes: 32 additions & 0 deletions src/formatting/listToString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Formats an array of strings into a human-readable list with proper commas and "and".
*
* Supports the Oxford comma for lists of three or more items.
* Returns an empty string for empty arrays.
*
* Examples:
* ["apples", "bananas", "cherries"] → "apples, bananas, and cherries"
* ["apples", "bananas"] → "apples and bananas"
* ["apple"] → "apple"
* [] → ""
*
* @param {string[]} arr - The array of strings to format into a readable list.
* @returns {string} The formatted human-readable list.
* @throws {TypeError} If the input is not an array of strings.
*/
export function formatList(arr: string[]): string {
if (!Array.isArray(arr)) {
throw new TypeError('Input must be an array');
}

if (!arr.every(item => typeof item === 'string')) {
throw new TypeError('All elements in the array must be strings');
}

const len = arr.length;
if (len === 0) return '';
if (len === 1) return arr[0];
if (len === 2) return `${arr[0]} and ${arr[1]}`;

return `${arr.slice(0, -1).join(', ')}, and ${arr[len - 1]}`;
}
39 changes: 39 additions & 0 deletions src/tests/formatting/listToString.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { formatList } from '../../formatting/listToString';

describe('formatList', () => {
it('formats empty arrays correctly', () => {
assert.strictEqual(formatList([]), '');
});

it('formats single-item arrays correctly', () => {
assert.strictEqual(formatList(['apple']), 'apple');
});

it('formats two-item arrays correctly', () => {
assert.strictEqual(formatList(['apples', 'bananas']), 'apples and bananas');
});

it('formats three or more items with Oxford comma', () => {
assert.strictEqual(formatList(['apples', 'bananas', 'cherries']), 'apples, bananas, and cherries');
assert.strictEqual(formatList(['red', 'blue', 'green', 'yellow']), 'red, blue, green, and yellow');
});

it('throws TypeError for non-array inputs', () => {
assert.throws(() => formatList('apple' as any), /Input must be an array/);
assert.throws(() => formatList(123 as any), /Input must be an array/);
});

it('throws TypeError for non-string elements in array', () => {
assert.throws(() => formatList(['apple', 123 as any]), /All elements in the array must be strings/);
assert.throws(() => formatList(['apple', null as any]), /All elements in the array must be strings/);
});

it('throws TypeError for mixed arrays (strings + other types)', () => {
assert.throws(() => formatList(['apple', 42, 'banana'] as any), /All elements in the array must be strings/);
assert.throws(() => formatList(['apple', true, 'banana'] as any), /All elements in the array must be strings/);
assert.throws(() => formatList(['apple', undefined, 'banana'] as any), /All elements in the array must be strings/);
assert.throws(() => formatList(['apple', {}, 'banana'] as any), /All elements in the array must be strings/);
});
});