From 63a347662ad042803afb2f50bd528db0cd2d5153 Mon Sep 17 00:00:00 2001 From: Chris Arter Date: Fri, 8 May 2026 11:33:51 -0400 Subject: [PATCH] feat: port slugify utility with tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port slugify from arter-blog as a pure TypeScript utility with 5 integration tests covering lowercase/hyphen conversion, accent stripping (NFD + Unicode range ̀-ͯ), separator collapsing, special char dropping, and undefined input. Co-Authored-By: Claude Sonnet 4.6 --- src/lib/slugify.ts | 8 ++++++++ tests/int/slugify.int.spec.ts | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/lib/slugify.ts create mode 100644 tests/int/slugify.int.spec.ts diff --git a/src/lib/slugify.ts b/src/lib/slugify.ts new file mode 100644 index 0000000..c9fc59f --- /dev/null +++ b/src/lib/slugify.ts @@ -0,0 +1,8 @@ +export function slugify(input?: string): string { + if (!input) return "" + let slug = input.toLowerCase().trim() + slug = slug.normalize("NFD").replace(/[\u0300-\u036f]/g, "") + slug = slug.replace(/[^a-z0-9\s-]/g, " ").trim() + slug = slug.replace(/[\s-]+/g, "-") + return slug +} diff --git a/tests/int/slugify.int.spec.ts b/tests/int/slugify.int.spec.ts new file mode 100644 index 0000000..ca400b0 --- /dev/null +++ b/tests/int/slugify.int.spec.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { slugify } from '@/lib/slugify' + +describe('slugify', () => { + it('lowercases and replaces spaces with hyphens', () => { + expect(slugify('Hello World')).toBe('hello-world') + }) + it('strips accents', () => { + expect(slugify('Café Résumé')).toBe('cafe-resume') + }) + it('collapses multiple separators', () => { + expect(slugify('a b---c')).toBe('a-b-c') + }) + it('drops disallowed characters', () => { + expect(slugify("It's awesome!")).toBe('it-s-awesome') + }) + it('returns empty string for undefined input', () => { + expect(slugify(undefined)).toBe('') + }) +}) -- 2.51.2