urlState.test.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /* @vitest-environment node */
  2. import { describe, it, expect } from "vitest";
  3. import {
  4. SEARCH_SCOPE,
  5. SEARCH_LIMITS,
  6. DEFAULT_SEARCH_LIMIT,
  7. parseBranchesCsv,
  8. serializeBranchesCsv,
  9. parseSearchUrlState,
  10. serializeSearchUrlState,
  11. } from "./urlState.js";
  12. describe("lib/frontend/search/urlState", () => {
  13. describe("parseBranchesCsv / serializeBranchesCsv", () => {
  14. it("parses CSV into a unique, trimmed list", () => {
  15. expect(parseBranchesCsv(" NL06, NL20 ,NL06,, ")).toEqual([
  16. "NL06",
  17. "NL20",
  18. ]);
  19. });
  20. it("serializes branches into CSV with stable order and dedupe", () => {
  21. expect(serializeBranchesCsv(["NL20", " NL06 ", "NL20"])).toBe(
  22. "NL20,NL06"
  23. );
  24. });
  25. it("returns null when serializing empty branches", () => {
  26. expect(serializeBranchesCsv([])).toBe(null);
  27. expect(serializeBranchesCsv(null)).toBe(null);
  28. });
  29. });
  30. describe("parseSearchUrlState", () => {
  31. it("defaults to SINGLE with routeBranch when no params are present", () => {
  32. const sp = new URLSearchParams();
  33. const state = parseSearchUrlState(sp, { routeBranch: "NL01" });
  34. expect(state).toEqual({
  35. q: null,
  36. scope: SEARCH_SCOPE.SINGLE,
  37. branch: "NL01",
  38. branches: [],
  39. limit: DEFAULT_SEARCH_LIMIT,
  40. from: null,
  41. to: null,
  42. });
  43. });
  44. it("parses SINGLE with explicit branch param", () => {
  45. const sp = new URLSearchParams({ q: " test ", branch: "NL02" });
  46. const state = parseSearchUrlState(sp, { routeBranch: "NL01" });
  47. expect(state.scope).toBe(SEARCH_SCOPE.SINGLE);
  48. expect(state.q).toBe("test");
  49. expect(state.branch).toBe("NL02");
  50. expect(state.branches).toEqual([]);
  51. expect(SEARCH_LIMITS.includes(state.limit)).toBe(true);
  52. });
  53. it("parses ALL when scope=all is set (highest precedence)", () => {
  54. const sp = new URLSearchParams({
  55. q: "x",
  56. scope: "all",
  57. branch: "NL01",
  58. branches: "NL06,NL20",
  59. limit: "200",
  60. });
  61. const state = parseSearchUrlState(sp, { routeBranch: "NL99" });
  62. expect(state).toEqual({
  63. q: "x",
  64. scope: SEARCH_SCOPE.ALL,
  65. branch: null,
  66. branches: [],
  67. limit: 200,
  68. from: null,
  69. to: null,
  70. });
  71. });
  72. it("parses MULTI when scope=multi is set", () => {
  73. const sp = new URLSearchParams({
  74. q: " reifen ",
  75. scope: "multi",
  76. branches: "NL06, NL20, NL06",
  77. limit: "50",
  78. });
  79. const state = parseSearchUrlState(sp, { routeBranch: "NL01" });
  80. expect(state.scope).toBe(SEARCH_SCOPE.MULTI);
  81. expect(state.q).toBe("reifen");
  82. expect(state.branch).toBe(null);
  83. expect(state.branches).toEqual(["NL06", "NL20"]);
  84. expect(state.limit).toBe(50);
  85. });
  86. it("parses MULTI when branches=... is present even without scope", () => {
  87. const sp = new URLSearchParams({
  88. q: "x",
  89. branches: "NL06,NL20",
  90. });
  91. const state = parseSearchUrlState(sp, { routeBranch: "NL01" });
  92. expect(state.scope).toBe(SEARCH_SCOPE.MULTI);
  93. expect(state.branches).toEqual(["NL06", "NL20"]);
  94. expect(state.limit).toBe(DEFAULT_SEARCH_LIMIT);
  95. });
  96. it("keeps from/to when provided", () => {
  97. const sp = new URLSearchParams({
  98. q: "x",
  99. branch: "NL01",
  100. from: "2025-12-01",
  101. to: "2025-12-31",
  102. limit: "200",
  103. });
  104. const state = parseSearchUrlState(sp, { routeBranch: "NL01" });
  105. expect(state.from).toBe("2025-12-01");
  106. expect(state.to).toBe("2025-12-31");
  107. expect(state.limit).toBe(200);
  108. });
  109. it("falls back to default limit for invalid values", () => {
  110. const sp = new URLSearchParams({ q: "x", branch: "NL01", limit: "999" });
  111. const state = parseSearchUrlState(sp, { routeBranch: "NL01" });
  112. expect(state.limit).toBe(DEFAULT_SEARCH_LIMIT);
  113. });
  114. });
  115. describe("serializeSearchUrlState", () => {
  116. it("serializes SINGLE as q + branch (no scope param)", () => {
  117. const qs = serializeSearchUrlState({
  118. q: "bridgestone",
  119. scope: SEARCH_SCOPE.SINGLE,
  120. branch: "NL01",
  121. limit: DEFAULT_SEARCH_LIMIT,
  122. });
  123. expect(qs).toBe("q=bridgestone&branch=NL01");
  124. });
  125. it("serializes MULTI as q + scope=multi + branches", () => {
  126. const qs = serializeSearchUrlState({
  127. q: "reifen",
  128. scope: SEARCH_SCOPE.MULTI,
  129. branches: ["NL06", "NL20"],
  130. limit: DEFAULT_SEARCH_LIMIT,
  131. });
  132. expect(qs).toBe("q=reifen&scope=multi&branches=NL06%2CNL20");
  133. });
  134. it("serializes ALL as q + scope=all", () => {
  135. const qs = serializeSearchUrlState({
  136. q: "x",
  137. scope: SEARCH_SCOPE.ALL,
  138. limit: DEFAULT_SEARCH_LIMIT,
  139. });
  140. expect(qs).toBe("q=x&scope=all");
  141. });
  142. it("includes limit when non-default", () => {
  143. const qs = serializeSearchUrlState({
  144. q: "x",
  145. scope: SEARCH_SCOPE.SINGLE,
  146. branch: "NL01",
  147. limit: 200,
  148. });
  149. expect(qs).toBe("q=x&branch=NL01&limit=200");
  150. });
  151. it("includes from/to when present (future-proof for RHL-025)", () => {
  152. const qs = serializeSearchUrlState({
  153. q: "x",
  154. scope: SEARCH_SCOPE.SINGLE,
  155. branch: "NL01",
  156. limit: 200,
  157. from: "2025-12-01",
  158. to: "2025-12-31",
  159. });
  160. expect(qs).toBe(
  161. "q=x&branch=NL01&limit=200&from=2025-12-01&to=2025-12-31"
  162. );
  163. });
  164. });
  165. });