Files
tech-log-frontend/tests/unit/slug-from-name.test.ts
T
DongHyeonka 5cffe30200 fix: derive a topic slug that survives a Korean name
Creating a topic failed intermittently — "a topic with that slug already
exists" — and worked when the author retried with a different name. The rule
was never intermittent, only invisible: the slug kept `[a-z0-9]` and dropped
everything else, so a Korean name contributed nothing. Whatever Latin word or
number happened to be in it became the entire slug.

Two ways that goes wrong, and the author hit both. `인증` reduced to an empty
string, which the form refused before a request was ever sent. `Redis 캐시` and
`Redis 클러스터` both reduced to `redis`, so the second one collided with the
first — a real conflict, reported honestly, about a slug the author never chose
and could not see.

Hangul is now romanized rather than discarded. Syllables decompose
arithmetically into initial, medial and final jamo, so this needs no table and
is deterministic: `백엔드 아키텍처` becomes `baekendeu-akitekcheo`. Only the
jamo mapping from Revised Romanization is applied — the sound-change rules are
deliberately left out, because a slug is read, not pronounced, and those rules
would make one name produce different slugs in different contexts.

The output keeps the shape document slugs already use
(`^[a-z0-9]+(?:-[a-z0-9]+)*$`), so the repository has one slug rule rather than
two, and the tests assert exactly that.
2026-08-21 14:56:03 +09:00

45 lines
2.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { slugFromName } from "../../src/features/tech-log/presentation/studio/components/slug-from-name.ts";
/**
* 이 파일은 작성자가 본 증상에서 나왔다 — "가끔 이미 있는 slug 라고 거절당하는데 이름을 바꾸면
* 된다". 간헐적으로 보였을 뿐 규칙은 결정적이었다: 이전 구현이 한글을 전부 버려서, 서로 다른 두
* 주제가 이름에 섞인 같은 영단어 하나로 같은 slug 가 됐다.
*/
describe("이름에서 slug 만들기", () => {
it("한글만으로 된 이름도 slug 를 만든다", () => {
// 이전 규칙에서는 빈 문자열이었고, 폼이 저장 자체를 막았다.
expect(slugFromName("인증")).toBe("injeung");
expect(slugFromName("백엔드 아키텍처")).toBe("baekendeu-akitekcheo");
});
it("영단어를 공유하는 다른 이름이 다른 slug 가 된다", () => {
// 작성자가 실제로 부딪힌 충돌. 둘 다 "redis" 였다.
expect(slugFromName("Redis 캐시")).not.toBe(slugFromName("Redis 클러스터"));
});
it("숫자를 공유하는 다른 이름도 부딪히지 않는다", () => {
expect(slugFromName("검증 주제 16089")).toBe("geomjeung-juje-16089");
expect(slugFromName("다른 주제 16089")).toBe("dareun-juje-16089");
});
it("문서 slug 와 같은 모양을 지킨다", () => {
// 백엔드의 WorkingCopyInputValidator 가 문서 slug 에 요구하는 것과 같은 정규식이다.
const shape = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
for (const name of ["인증", "Redis 캐시", "JPA 최적화", "읽기 전용 복제본", " 공백 둘러싼 이름 "]) {
expect(slugFromName(name)).toMatch(shape);
}
});
it("받침과 겹받침을 자모 그대로 옮긴다", () => {
expect(slugFromName("한글")).toBe("hangeul");
expect(slugFromName("읽기")).toBe("ilggi");
});
it("옮길 것이 없으면 빈 문자열이고, 폼이 그때 slug 를 요구한다", () => {
expect(slugFromName("!!!")).toBe("");
expect(slugFromName(" ")).toBe("");
});
});