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

New truncate function #156

Merged
merged 2 commits into from
Feb 10, 2024
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
5 changes: 5 additions & 0 deletions .changeset/chilled-ducks-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"moderndash": minor
---

Add `truncate` function
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]);
});
Loading