Skip to content

Commit

Permalink
New truncate function
Browse files Browse the repository at this point in the history
  • Loading branch information
tkrotoff committed Feb 9, 2024
1 parent 17a5f32 commit 12f1dcf
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 0 deletions.
21 changes: 21 additions & 0 deletions benchmark/string/truncate.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { truncate as lodashVersion } from "lodash-es";
import { truncate } from "moderndash";
import { bench, describe } from "vitest";

import { randomStringArray } from "../testData.js";

describe("truncate", () => {
const stringArray = randomStringArray(200);

bench("moderndash", () => {
for (const str of stringArray) {
truncate(str);
}
});

bench("lodash", () => {
for (const str of stringArray) {
lodashVersion(str);
}
});
});
1 change: 1 addition & 0 deletions package/src/string/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from "./trim";
export * from "./trimEnd";
export * from "./trimStart";
export * from "./unescapeHtml";
export * from "./truncate";
61 changes: 61 additions & 0 deletions package/src/string/truncate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
type Options = {
/**
* The maximum string length.
*
* Default: 30
*/
length?: number;

/**
* The string to indicate text is omitted.
*
* Also named [ellipsis](https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow)
*
* Default: "...", you might want to use "…" (… U+02026) instead
*/
omission?: string;

/**
* The separator pattern to truncate to.
*
* Default: none
*/
separator?: string;
};

/**
* Truncates a string if it's longer than the given maximum length.
* The last characters of the truncated string are replaced with the omission
* string which defaults to "...".
*
* @param str The string to truncate
* @param options The options object
* @returns The truncated string
*/
export function truncate(str: string, options?: Options) {
// https://stackoverflow.com/q/1199352
// https://github.com/Maggi64/moderndash/issues/155
// https://lodash.com/docs/4.17.15#truncate

const { length = 30, omission = "...", separator } = options ?? {};

if (str.length <= length) {
return str;
}

let maxLength = length - omission.length;
if (maxLength < 0) {
maxLength = 0;
}
const subString = str.slice(
0,
// FYI .slice() is OK if maxLength > text.length
maxLength
);

return (
(separator
? subString.slice(0, subString.lastIndexOf(separator))
: subString) + omission
);
}
54 changes: 54 additions & 0 deletions package/test/string/truncate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { truncate } from "@string/truncate";

// Copy-pasted and adapted from https://github.com/lodash/lodash/blob/c7c70a7da5172111b99bb45e45532ed034d7b5b9/test/truncate.spec.js
// See also https://github.com/lodash/lodash/pull/5815

const string = "hi-diddly-ho there, neighborino";

it("should use a default `length` of `30`", () => {
expect(truncate(string)).toBe("hi-diddly-ho there, neighbo...");
});

it("should not truncate if `string` is <= `length`", () => {
expect(truncate(string, { length: string.length })).toBe(string);
expect(truncate(string, { length: string.length + 2 })).toBe(string);
});

it("should truncate string the given length", () => {
expect(truncate(string, { length: 24 })).toBe("hi-diddly-ho there, n...");
});

it("should support a `omission` option", () => {
expect(truncate(string, { omission: " [...]" })).toBe(
"hi-diddly-ho there, neig [...]"
);
});

it("should support empty `omission` option", () => {
expect(truncate(string, { omission: "" })).toBe(
"hi-diddly-ho there, neighborin"
);
});

it("should support a `length` option", () => {
expect(truncate(string, { length: 4 })).toBe("h...");
});

it("should support a `separator` option", () => {
expect(truncate(string, { length: 24, separator: " " })).toBe(
"hi-diddly-ho there,..."
);
});

it("should treat negative `length` as `0`", () => {
[0, -2].forEach((length) => {
expect(truncate(string, { length })).toBe("...");
});
});

it("should work as an iteratee for methods like `_.map`", () => {
const actual = [string, string, string].map((str) => truncate(str));
const truncated = "hi-diddly-ho there, neighbo...";

expect(actual).toEqual([truncated, truncated, truncated]);
});

0 comments on commit 12f1dcf

Please sign in to comment.