store.helpers.unit.test.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /**
  2. * Store helper-level unit tests (pure logic, no model/runtime dependency).
  3. */
  4. import { describe, test, expect } from "vitest";
  5. import {
  6. homedir,
  7. resolve,
  8. getDefaultDbPath,
  9. getPwd,
  10. getRealPath,
  11. isVirtualPath,
  12. parseVirtualPath,
  13. normalizeVirtualPath,
  14. normalizeDocid,
  15. isDocid,
  16. handelize,
  17. } from "../src/store";
  18. // =============================================================================
  19. // Path Utilities
  20. // =============================================================================
  21. describe("Path Utilities", () => {
  22. test("homedir returns HOME environment variable", () => {
  23. expect(homedir()).toBe(process.env.HOME || "/tmp");
  24. });
  25. test("resolve handles absolute paths", () => {
  26. expect(resolve("/foo/bar")).toBe("/foo/bar");
  27. expect(resolve("/foo", "/bar")).toBe("/bar");
  28. });
  29. test("resolve handles relative paths", () => {
  30. const pwd = process.env.PWD || process.cwd();
  31. expect(resolve("foo")).toBe(`${pwd}/foo`);
  32. expect(resolve("foo", "bar")).toBe(`${pwd}/foo/bar`);
  33. });
  34. test("resolve normalizes . and ..", () => {
  35. expect(resolve("/foo/bar/./baz")).toBe("/foo/bar/baz");
  36. expect(resolve("/foo/bar/../baz")).toBe("/foo/baz");
  37. expect(resolve("/foo/bar/../../baz")).toBe("/baz");
  38. });
  39. test("getDefaultDbPath throws in test mode without INDEX_PATH", () => {
  40. const originalIndexPath = process.env.INDEX_PATH;
  41. delete process.env.INDEX_PATH;
  42. expect(() => getDefaultDbPath()).toThrow("Database path not set");
  43. if (originalIndexPath) {
  44. process.env.INDEX_PATH = originalIndexPath;
  45. }
  46. });
  47. test("getDefaultDbPath uses INDEX_PATH when set", () => {
  48. const originalIndexPath = process.env.INDEX_PATH;
  49. process.env.INDEX_PATH = "/tmp/test-index.sqlite";
  50. expect(getDefaultDbPath()).toBe("/tmp/test-index.sqlite");
  51. expect(getDefaultDbPath("custom")).toBe("/tmp/test-index.sqlite");
  52. if (originalIndexPath) {
  53. process.env.INDEX_PATH = originalIndexPath;
  54. } else {
  55. delete process.env.INDEX_PATH;
  56. }
  57. });
  58. test("getPwd returns current working directory", () => {
  59. const pwd = getPwd();
  60. expect(pwd).toBeTruthy();
  61. expect(typeof pwd).toBe("string");
  62. });
  63. test("getRealPath resolves symlinks", () => {
  64. const result = getRealPath("/tmp");
  65. expect(result).toBeTruthy();
  66. expect(result === "/tmp" || result === "/private/tmp").toBe(true);
  67. });
  68. });
  69. // =============================================================================
  70. // Handelize Tests
  71. // =============================================================================
  72. describe("handelize", () => {
  73. test("converts to lowercase", () => {
  74. expect(handelize("README.md")).toBe("readme.md");
  75. expect(handelize("MyFile.MD")).toBe("myfile.md");
  76. });
  77. test("preserves folder structure", () => {
  78. expect(handelize("a/b/c/d.md")).toBe("a/b/c/d.md");
  79. expect(handelize("docs/api/README.md")).toBe("docs/api/readme.md");
  80. });
  81. test("replaces non-word characters with dash", () => {
  82. expect(handelize("hello world.md")).toBe("hello-world.md");
  83. expect(handelize("file (1).md")).toBe("file-1.md");
  84. expect(handelize("foo@bar#baz.md")).toBe("foo-bar-baz.md");
  85. });
  86. test("collapses multiple special chars into single dash", () => {
  87. expect(handelize("hello world.md")).toBe("hello-world.md");
  88. expect(handelize("foo---bar.md")).toBe("foo-bar.md");
  89. expect(handelize("a - b.md")).toBe("a-b.md");
  90. });
  91. test("removes leading and trailing dashes from segments", () => {
  92. expect(handelize("-hello-.md")).toBe("hello.md");
  93. expect(handelize("--test--.md")).toBe("test.md");
  94. expect(handelize("a/-b-/c.md")).toBe("a/b/c.md");
  95. });
  96. test("converts triple underscore to folder separator", () => {
  97. expect(handelize("foo___bar.md")).toBe("foo/bar.md");
  98. expect(handelize("notes___2025___january.md")).toBe("notes/2025/january.md");
  99. expect(handelize("a/b___c/d.md")).toBe("a/b/c/d.md");
  100. });
  101. test("handles complex real-world meeting notes", () => {
  102. const complexName = "Money Movement Licensing Review - 2025/11/19 10:25 EST - Notes by Gemini.md";
  103. const result = handelize(complexName);
  104. expect(result).toBe("money-movement-licensing-review-2025-11-19-10-25-est-notes-by-gemini.md");
  105. expect(result).not.toContain(" ");
  106. expect(result).not.toContain("/");
  107. expect(result).not.toContain(":");
  108. });
  109. test("handles unicode characters", () => {
  110. expect(handelize("日本語.md")).toBe("日本語.md");
  111. expect(handelize("Зоны и проекты.md")).toBe("зоны-и-проекты.md");
  112. expect(handelize("café-notes.md")).toBe("café-notes.md");
  113. expect(handelize("naïve.md")).toBe("naïve.md");
  114. expect(handelize("日本語-notes.md")).toBe("日本語-notes.md");
  115. });
  116. test("handles emoji filenames (issue #302)", () => {
  117. // Emoji-only filenames should convert to hex codepoints
  118. expect(handelize("🐘.md")).toBe("1f418.md");
  119. expect(handelize("🎉.md")).toBe("1f389.md");
  120. // Emoji mixed with text
  121. expect(handelize("notes 🐘.md")).toBe("notes-1f418.md");
  122. expect(handelize("🐘 elephant.md")).toBe("1f418-elephant.md");
  123. // Multiple emojis
  124. expect(handelize("🐘🎉.md")).toBe("1f418-1f389.md");
  125. // Emoji in directory names
  126. expect(handelize("🐘/notes.md")).toBe("1f418/notes.md");
  127. });
  128. test("handles dates and times in filenames", () => {
  129. expect(handelize("meeting-2025-01-15.md")).toBe("meeting-2025-01-15.md");
  130. expect(handelize("notes 2025/01/15.md")).toBe("notes-2025/01/15.md");
  131. expect(handelize("call_10:30_AM.md")).toBe("call-10-30-am.md");
  132. });
  133. test("handles special project naming patterns", () => {
  134. expect(handelize("PROJECT_ABC_v2.0.md")).toBe("project-abc-v2-0.md");
  135. expect(handelize("[WIP] Feature Request.md")).toBe("wip-feature-request.md");
  136. expect(handelize("(DRAFT) Proposal v1.md")).toBe("draft-proposal-v1.md");
  137. });
  138. test("handles symbol-only route filenames", () => {
  139. expect(handelize("routes/api/auth/$.ts")).toBe("routes/api/auth/$.ts");
  140. expect(handelize("app/routes/$id.tsx")).toBe("app/routes/$id.tsx");
  141. });
  142. test("filters out empty segments", () => {
  143. expect(handelize("a//b/c.md")).toBe("a/b/c.md");
  144. expect(handelize("/a/b/")).toBe("a/b");
  145. expect(handelize("///test///")).toBe("test");
  146. });
  147. test("throws error for invalid inputs", () => {
  148. expect(() => handelize("" )).toThrow("path cannot be empty");
  149. expect(() => handelize(" ")).toThrow("path cannot be empty");
  150. expect(() => handelize(".md")).toThrow("no valid filename content");
  151. expect(() => handelize("...")).toThrow("no valid filename content");
  152. expect(() => handelize("___")).toThrow("no valid filename content");
  153. });
  154. test("handles minimal valid inputs", () => {
  155. expect(handelize("a")).toBe("a");
  156. expect(handelize("1")).toBe("1");
  157. expect(handelize("a.md")).toBe("a.md");
  158. });
  159. test("normalizes virtual paths", () => {
  160. expect(normalizeVirtualPath("qmd://docs/readme.md")).toBe("qmd://docs/readme.md");
  161. expect(normalizeVirtualPath("docs/readme.md")).toBe("docs/readme.md");
  162. });
  163. test("detects virtual paths", () => {
  164. expect(isVirtualPath("qmd://docs/readme.md")).toBe(true);
  165. expect(isVirtualPath("/tmp/file.md")).toBe(false);
  166. });
  167. test("parses virtual paths", () => {
  168. expect(parseVirtualPath("qmd://docs/readme.md")).toEqual({
  169. collectionName: "docs",
  170. path: "readme.md",
  171. });
  172. });
  173. test("normalizes docids", () => {
  174. expect(normalizeDocid("123456")).toBe("123456");
  175. expect(normalizeDocid("#123456")).toBe("123456");
  176. });
  177. test("checks docid validity", () => {
  178. expect(isDocid("123456")).toBe(true);
  179. expect(isDocid("#123456")).toBe(true);
  180. expect(isDocid("bad-id")).toBe(false);
  181. expect(isDocid("12345")).toBe(false);
  182. });
  183. });