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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
// String utilities
const escaped = stringUtil.escapeHtml("<div>Hello</div>");
const unescaped = stringUtil.unescapeHtml("&lt;div&gt;Hello&lt;/div&gt;");
const slug = stringUtil.slugify("Hello World! 안녕하세요"); // "hello-world-안녕하세요"

// Object utilities
const cleaned = objectUtil.clearNullProperties({ a: 1, b: null, c: 3 });
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -213,6 +215,7 @@ storage.set("data", { key: "value" });
### Retry

- `retry<T>(fn: () => Promise<T>, loop?: number): Promise<T>` - 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<string, string | string[]>` - 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.
Expand Down
5 changes: 3 additions & 2 deletions package/stringUtil/index.ts
Original file line number Diff line number Diff line change
@@ -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";
52 changes: 52 additions & 0 deletions package/stringUtil/slugify/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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("특수 문자가 포함된 문자열을 올바르게 변환한다.", () => {
Copy link
Member

@klmhyeonwoo klmhyeonwoo Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 테스트 케이스도 궁금할 것 같은데 추가가 가능하실까요?!

  test("slug 형태의 문자열은 그대로 slug 형태로 반환한다.", () => {
    const input = "Hello-World";
    const output = slugify(input);
    expect(output).toBe("hello-world");
  });

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오!! 한 번 테스트해볼게요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

잘 되네요 :)

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("프로젝트-개발");
});

test("slug 형태의 문자열은 그대로 slug 형태로 반환한다.", () => {
const input = "Hello-World";
const output = slugify(input);
expect(output).toBe("hello-world");
});
});
19 changes: 19 additions & 0 deletions package/stringUtil/slugify/index.ts
Original file line number Diff line number Diff line change
@@ -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(/-+$/, "");
}