diff --git a/packages/utils/src/lib/formatting.ts b/packages/utils/src/lib/formatting.ts index 1d88f77aa..d546bacc8 100644 --- a/packages/utils/src/lib/formatting.ts +++ b/packages/utils/src/lib/formatting.ts @@ -71,12 +71,39 @@ export function formatDate(date: Date): string { .replace(/\u202F/g, ' '); // see https://github.com/nodejs/node/issues/45171 } -export function truncateText(text: string, maxChars: number): string { +export function truncateText( + text: string, + options: + | number + | { + maxChars: number; + position?: 'start' | 'middle' | 'end'; + ellipsis?: string; + }, +): string { + const { + maxChars, + position = 'end', + ellipsis = '...', + } = typeof options === 'number' ? { maxChars: options } : options; if (text.length <= maxChars) { return text; } - const ellipsis = '...'; - return text.slice(0, maxChars - ellipsis.length) + ellipsis; + + const maxLength = maxChars - ellipsis.length; + switch (position) { + case 'start': + return ellipsis + text.slice(-maxLength).trim(); + case 'middle': + const halfMaxChars = Math.floor(maxLength / 2); + return ( + text.slice(0, halfMaxChars).trim() + + ellipsis + + text.slice(-halfMaxChars).trim() + ); + default: + return text.slice(0, maxLength).trim() + ellipsis; + } } export function truncateTitle(text: string): string { diff --git a/packages/utils/src/lib/formatting.unit.test.ts b/packages/utils/src/lib/formatting.unit.test.ts index eb58df929..997470047 100644 --- a/packages/utils/src/lib/formatting.unit.test.ts +++ b/packages/utils/src/lib/formatting.unit.test.ts @@ -97,19 +97,55 @@ describe('formatDate', () => { }); describe('truncateText', () => { - it('should replace overflowing text with ellipsis', () => { + it('should replace overflowing text with ellipsis at the end', () => { expect(truncateText('All work and no play makes Jack a dull boy', 32)).toBe( 'All work and no play makes Ja...', ); }); - it('should produce truncated text which fits within limit', () => { + it('should leave text unchanged when within character limit passed as number', () => { + expect(truncateText("Here's Johnny!", 32)).toBe("Here's Johnny!"); + }); + + it('should produce truncated text which fits within limit passed as number', () => { expect( truncateText('All work and no play makes Jack a dull boy', 32).length, ).toBeLessThanOrEqual(32); }); - it('should leave text unchanged when within character limit', () => { - expect(truncateText("Here's Johnny!", 32)).toBe("Here's Johnny!"); + it('should leave text unchanged when within character limit passed as options', () => { + expect(truncateText("Here's Johnny!", { maxChars: 32 })).toBe( + "Here's Johnny!", + ); + }); + + it('should produce truncated text with ellipsis at the start', () => { + expect( + truncateText('Yesterday cloudy day.', { + maxChars: 10, + position: 'start', + }), + ).toBe('...dy day.'); + }); + + it('should produce truncated text with ellipsis at the middle', () => { + expect( + truncateText('Horrendous amounts of lint issues are present Tony!', { + maxChars: 10, + position: 'middle', + }), + ).toBe('Hor...ny!'); + }); + + it('should produce truncated text with ellipsis at the end', () => { + expect(truncateText("I'm Johnny!", { maxChars: 10, position: 'end' })).toBe( + "I'm Joh...", + ); + }); + + it('should produce truncated text with custom ellipsis', () => { + expect(truncateText("I'm Johnny!", { maxChars: 10, ellipsis: '*' })).toBe( + "I'm Johnn*", + ); }); });