From 1e53ac7dfa3f60abcb926907cfe4a8898fc75133 Mon Sep 17 00:00:00 2001 From: prgmr99 Date: Fri, 31 Oct 2025 13:40:23 +0900 Subject: [PATCH 1/3] feat: add slugify utility and tests --- package/stringUtil/index.ts | 5 +-- package/stringUtil/slugify/index.test.ts | 46 ++++++++++++++++++++++++ package/stringUtil/slugify/index.ts | 19 ++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 package/stringUtil/slugify/index.test.ts create mode 100644 package/stringUtil/slugify/index.ts diff --git a/package/stringUtil/index.ts b/package/stringUtil/index.ts index 14d19e3..2dabd38 100644 --- a/package/stringUtil/index.ts +++ b/package/stringUtil/index.ts @@ -1,2 +1,3 @@ -export { default as escapeHtml } from './escapeHtml'; -export { default as unescapeHtml } from './unescapeHtml'; +export { default as escapeHtml } from "./escapeHtml"; +export { default as unescapeHtml } from "./unescapeHtml"; +export { default as slugify } from "./slugify"; diff --git a/package/stringUtil/slugify/index.test.ts b/package/stringUtil/slugify/index.test.ts new file mode 100644 index 0000000..1ed8d08 --- /dev/null +++ b/package/stringUtil/slugify/index.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, test } from "vitest"; +import slugify from "."; + +describe("slugify", () => { + test("문자열을 slug 형태로 변환한다.", () => { + const input = " Hello World! This is a Test. "; + const output = slugify(input); + expect(output).toBe("hello-world-this-is-a-test"); + }); + + test("특수 문자가 포함된 문자열을 올바르게 변환한다.", () => { + const input = "Café & Restaurant @ Downtown #1"; + const output = slugify(input); + expect(output).toBe("caf-restaurant-downtown-1"); + }); + + test("여러 공백과 대시가 포함된 문자열을 올바르게 변환한다.", () => { + const input = "This is---a test---string"; + const output = slugify(input); + expect(output).toBe("this-is-a-test-string"); + }); + + test("빈 문자열을 처리한다.", () => { + expect(slugify("")).toBe(""); + expect(slugify(" ")).toBe(""); + }); + + test("숫자가 포함된 문자열을 올바르게 변환한다.", () => { + const input = "Product 123 - Version 2.0"; + const output = slugify(input); + expect(output).toBe("product-123-version-20"); + }); + + test("특수 문자만 있는 경우를 처리한다.", () => { + const input = "!@#$%^&*()"; + const output = slugify(input); + expect(output).toBe(""); + }); + + test("한글이 포함된 문자열을 처리한다.", () => { + expect(slugify("안녕하세요")).toBe("안녕하세요"); + expect(slugify("한글 테스트")).toBe("한글-테스트"); + expect(slugify("안녕하세요 Hello World")).toBe("안녕하세요-hello-world"); + expect(slugify("프로젝트 개발")).toBe("프로젝트-개발"); + }); +}); diff --git a/package/stringUtil/slugify/index.ts b/package/stringUtil/slugify/index.ts new file mode 100644 index 0000000..d8d2c1d --- /dev/null +++ b/package/stringUtil/slugify/index.ts @@ -0,0 +1,19 @@ +/** + * 문자열을 URL 친화적인 slug 형태로 변환합니다. + * 공백을 대시(-)로 변환하고, 특수문자를 제거하며, 소문자로 변환합니다. + * 한글도 지원합니다. + * + * @param text - slug로 변환할 문자열 + * @returns URL 친화적인 slug 문자열 + */ +export default function slugify(text: string): string { + return text + .toString() + .trim() + .toLowerCase() + .replace(/\s+/g, "-") + .replace(/[^a-zA-Z0-9가-힣\-]+/g, "") + .replace(/\-\-+/g, "-") + .replace(/^-+/, "") + .replace(/-+$/, ""); +} From e17cd68951e1d456bbaccbf7f52c519019b70335 Mon Sep 17 00:00:00 2001 From: prgmr99 Date: Fri, 31 Oct 2025 13:42:25 +0900 Subject: [PATCH 2/3] feat: add slugify example and documentation to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e147912..5422901 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ import { // String utilities const escaped = stringUtil.escapeHtml("
Hello
"); const unescaped = stringUtil.unescapeHtml("<div>Hello</div>"); +const slug = stringUtil.slugify("Hello World! 안녕하세요"); // "hello-world-안녕하세요" // Object utilities const cleaned = objectUtil.clearNullProperties({ a: 1, b: null, c: 3 }); @@ -150,6 +151,7 @@ storage.set("data", { key: "value" }); - `escapeHtml(str: string): string` - Escapes HTML special characters - `unescapeHtml(str: string): string` - Unescapes HTML entities +- `slugify(text: string): string` - Converts a string to URL-friendly slug format. Replaces spaces with hyphens, removes special characters, converts to lowercase, and supports Korean characters (e.g., "Hello World! 안녕" → "hello-world-안녕") ### ObjectUtil @@ -213,6 +215,7 @@ storage.set("data", { key: "value" }); ### Retry - `retry(fn: () => Promise, loop?: number): Promise` - Retries an asynchronous function up to the specified number of times (default 3) if it fails. Automatically re-attempts on error and returns the result of the first successful execution, or throws the last error if all retries fail. + ### SearchQueryUtil - `getAllQuery(): Record` - Parses the current URL's query string and returns an object with key-value pairs. Values appear as arrays when the same key is used multiple times. From 88bbd3eec10a38eac75c94d391d82e727bba96b7 Mon Sep 17 00:00:00 2001 From: prgmr99 Date: Fri, 31 Oct 2025 15:06:11 +0900 Subject: [PATCH 3/3] test: add test case for returning slug format strings unchanged --- package/stringUtil/slugify/index.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package/stringUtil/slugify/index.test.ts b/package/stringUtil/slugify/index.test.ts index 1ed8d08..a8e7b92 100644 --- a/package/stringUtil/slugify/index.test.ts +++ b/package/stringUtil/slugify/index.test.ts @@ -43,4 +43,10 @@ describe("slugify", () => { expect(slugify("안녕하세요 Hello World")).toBe("안녕하세요-hello-world"); expect(slugify("프로젝트 개발")).toBe("프로젝트-개발"); }); + + test("slug 형태의 문자열은 그대로 slug 형태로 반환한다.", () => { + const input = "Hello-World"; + const output = slugify(input); + expect(output).toBe("hello-world"); + }); });