@multiplatform.one/frappe-ui 7.28.1 → 7.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/dist/cjs/FrappeCalendar.cjs +3 -2
  2. package/dist/cjs/FrappeCalendar.native.js +3 -2
  3. package/dist/cjs/FrappeCalendar.native.js.map +1 -1
  4. package/dist/cjs/FrappeForm.cjs +148 -464
  5. package/dist/cjs/FrappeForm.native.js +152 -633
  6. package/dist/cjs/FrappeForm.native.js.map +1 -1
  7. package/dist/cjs/FrappeListView.cjs +5 -27
  8. package/dist/cjs/FrappeListView.native.js +11 -32
  9. package/dist/cjs/FrappeListView.native.js.map +1 -1
  10. package/dist/cjs/FrappeReportBuilder.cjs +523 -309
  11. package/dist/cjs/FrappeReportBuilder.native.js +658 -353
  12. package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
  13. package/dist/cjs/FrappeReportView.cjs +35 -234
  14. package/dist/cjs/FrappeReportView.native.js +39 -283
  15. package/dist/cjs/FrappeReportView.native.js.map +1 -1
  16. package/dist/cjs/dependsOn.cjs +2 -2
  17. package/dist/cjs/dependsOn.native.js +1 -2
  18. package/dist/cjs/dependsOn.native.js.map +1 -1
  19. package/dist/cjs/fields/shared/EditorPreviewPanes.cjs +2 -2
  20. package/dist/cjs/fields/shared/EditorPreviewPanes.native.js +2 -2
  21. package/dist/cjs/fields/shared/EditorPreviewPanes.native.js.map +1 -1
  22. package/dist/cjs/formEngine/docPermissions.cjs +20 -0
  23. package/dist/cjs/formEngine/docPermissions.native.js +69 -0
  24. package/dist/cjs/formEngine/docPermissions.native.js.map +1 -1
  25. package/dist/cjs/formEngine/index.cjs +2 -0
  26. package/dist/cjs/formEngine/index.native.js +2 -0
  27. package/dist/cjs/formEngine/index.native.js.map +1 -1
  28. package/dist/cjs/index.cjs +67 -7
  29. package/dist/cjs/index.native.js +67 -7
  30. package/dist/cjs/index.native.js.map +1 -1
  31. package/dist/cjs/liveErrors.cjs +8 -2
  32. package/dist/cjs/liveErrors.native.js +8 -2
  33. package/dist/cjs/liveErrors.native.js.map +1 -1
  34. package/dist/cjs/measures.cjs +31 -0
  35. package/dist/cjs/measures.native.js +34 -0
  36. package/dist/cjs/measures.native.js.map +1 -0
  37. package/dist/cjs/recordShape.cjs +103 -0
  38. package/dist/cjs/recordShape.native.js +115 -0
  39. package/dist/cjs/recordShape.native.js.map +1 -0
  40. package/dist/cjs/report/ReportGrid.cjs +453 -0
  41. package/dist/cjs/report/ReportGrid.native.js +510 -0
  42. package/dist/cjs/report/ReportGrid.native.js.map +1 -0
  43. package/dist/cjs/report/ReportViewToolbar.cjs +253 -0
  44. package/dist/cjs/report/ReportViewToolbar.native.js +303 -0
  45. package/dist/cjs/report/ReportViewToolbar.native.js.map +1 -0
  46. package/dist/cjs/report/queryReport.cjs +444 -0
  47. package/dist/cjs/report/queryReport.native.js +588 -0
  48. package/dist/cjs/report/queryReport.native.js.map +1 -0
  49. package/dist/cjs/report/queryReportColumns.cjs +131 -0
  50. package/dist/cjs/report/queryReportColumns.native.js +159 -0
  51. package/dist/cjs/report/queryReportColumns.native.js.map +1 -0
  52. package/dist/cjs/report/reportViewModel.cjs +386 -0
  53. package/dist/cjs/report/reportViewModel.native.js +657 -0
  54. package/dist/cjs/report/reportViewModel.native.js.map +1 -0
  55. package/dist/cjs/report/savedReports.cjs +137 -0
  56. package/dist/cjs/report/savedReports.native.js +169 -0
  57. package/dist/cjs/report/savedReports.native.js.map +1 -0
  58. package/dist/cjs/report/useFrappeReport.cjs +184 -0
  59. package/dist/cjs/report/useFrappeReport.native.js +214 -0
  60. package/dist/cjs/report/useFrappeReport.native.js.map +1 -0
  61. package/dist/cjs/report/useQueryReport.cjs +201 -0
  62. package/dist/cjs/report/useQueryReport.native.js +233 -0
  63. package/dist/cjs/report/useQueryReport.native.js.map +1 -0
  64. package/dist/cjs/useFrappeForm.cjs +558 -0
  65. package/dist/cjs/useFrappeForm.native.js +714 -0
  66. package/dist/cjs/useFrappeForm.native.js.map +1 -0
  67. package/dist/esm/FrappeCalendar.mjs +4 -3
  68. package/dist/esm/FrappeCalendar.mjs.map +1 -1
  69. package/dist/esm/FrappeCalendar.native.js +4 -3
  70. package/dist/esm/FrappeCalendar.native.js.map +1 -1
  71. package/dist/esm/FrappeForm.mjs +151 -467
  72. package/dist/esm/FrappeForm.mjs.map +1 -1
  73. package/dist/esm/FrappeForm.native.js +155 -636
  74. package/dist/esm/FrappeForm.native.js.map +1 -1
  75. package/dist/esm/FrappeListView.mjs +6 -28
  76. package/dist/esm/FrappeListView.mjs.map +1 -1
  77. package/dist/esm/FrappeListView.native.js +12 -33
  78. package/dist/esm/FrappeListView.native.js.map +1 -1
  79. package/dist/esm/FrappeReportBuilder.mjs +526 -312
  80. package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
  81. package/dist/esm/FrappeReportBuilder.native.js +661 -356
  82. package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
  83. package/dist/esm/FrappeReportView.mjs +30 -229
  84. package/dist/esm/FrappeReportView.mjs.map +1 -1
  85. package/dist/esm/FrappeReportView.native.js +34 -278
  86. package/dist/esm/FrappeReportView.native.js.map +1 -1
  87. package/dist/esm/dependsOn.mjs +2 -2
  88. package/dist/esm/dependsOn.mjs.map +1 -1
  89. package/dist/esm/dependsOn.native.js +1 -2
  90. package/dist/esm/dependsOn.native.js.map +1 -1
  91. package/dist/esm/fields/shared/EditorPreviewPanes.mjs +2 -2
  92. package/dist/esm/fields/shared/EditorPreviewPanes.mjs.map +1 -1
  93. package/dist/esm/fields/shared/EditorPreviewPanes.native.js +2 -2
  94. package/dist/esm/fields/shared/EditorPreviewPanes.native.js.map +1 -1
  95. package/dist/esm/formEngine/docPermissions.mjs +19 -1
  96. package/dist/esm/formEngine/docPermissions.mjs.map +1 -1
  97. package/dist/esm/formEngine/docPermissions.native.js +68 -1
  98. package/dist/esm/formEngine/docPermissions.native.js.map +1 -1
  99. package/dist/esm/formEngine/index.mjs +2 -2
  100. package/dist/esm/formEngine/index.mjs.map +1 -1
  101. package/dist/esm/formEngine/index.native.js +2 -2
  102. package/dist/esm/formEngine/index.native.js.map +1 -1
  103. package/dist/esm/index.mjs +16 -4
  104. package/dist/esm/index.mjs.map +1 -1
  105. package/dist/esm/index.native.js +16 -4
  106. package/dist/esm/index.native.js.map +1 -1
  107. package/dist/esm/liveErrors.mjs +8 -3
  108. package/dist/esm/liveErrors.mjs.map +1 -1
  109. package/dist/esm/liveErrors.native.js +8 -3
  110. package/dist/esm/liveErrors.native.js.map +1 -1
  111. package/dist/esm/measures.mjs +5 -0
  112. package/dist/esm/measures.mjs.map +1 -0
  113. package/dist/esm/measures.native.js +5 -0
  114. package/dist/esm/measures.native.js.map +1 -0
  115. package/dist/esm/recordShape.mjs +74 -0
  116. package/dist/esm/recordShape.mjs.map +1 -0
  117. package/dist/esm/recordShape.native.js +83 -0
  118. package/dist/esm/recordShape.native.js.map +1 -0
  119. package/dist/esm/report/ReportGrid.mjs +428 -0
  120. package/dist/esm/report/ReportGrid.mjs.map +1 -0
  121. package/dist/esm/report/ReportGrid.native.js +471 -0
  122. package/dist/esm/report/ReportGrid.native.js.map +1 -0
  123. package/dist/esm/report/ReportViewToolbar.mjs +228 -0
  124. package/dist/esm/report/ReportViewToolbar.mjs.map +1 -0
  125. package/dist/esm/report/ReportViewToolbar.native.js +264 -0
  126. package/dist/esm/report/ReportViewToolbar.native.js.map +1 -0
  127. package/dist/esm/report/queryReport.mjs +404 -0
  128. package/dist/esm/report/queryReport.mjs.map +1 -0
  129. package/dist/esm/report/queryReport.native.js +545 -0
  130. package/dist/esm/report/queryReport.native.js.map +1 -0
  131. package/dist/esm/report/queryReportColumns.mjs +105 -0
  132. package/dist/esm/report/queryReportColumns.mjs.map +1 -0
  133. package/dist/esm/report/queryReportColumns.native.js +119 -0
  134. package/dist/esm/report/queryReportColumns.native.js.map +1 -0
  135. package/dist/esm/report/reportViewModel.mjs +343 -0
  136. package/dist/esm/report/reportViewModel.mjs.map +1 -0
  137. package/dist/esm/report/reportViewModel.native.js +611 -0
  138. package/dist/esm/report/reportViewModel.native.js.map +1 -0
  139. package/dist/esm/report/savedReports.mjs +112 -0
  140. package/dist/esm/report/savedReports.mjs.map +1 -0
  141. package/dist/esm/report/savedReports.native.js +141 -0
  142. package/dist/esm/report/savedReports.native.js.map +1 -0
  143. package/dist/esm/report/useFrappeReport.mjs +158 -0
  144. package/dist/esm/report/useFrappeReport.mjs.map +1 -0
  145. package/dist/esm/report/useFrappeReport.native.js +185 -0
  146. package/dist/esm/report/useFrappeReport.native.js.map +1 -0
  147. package/dist/esm/report/useQueryReport.mjs +176 -0
  148. package/dist/esm/report/useQueryReport.mjs.map +1 -0
  149. package/dist/esm/report/useQueryReport.native.js +205 -0
  150. package/dist/esm/report/useQueryReport.native.js.map +1 -0
  151. package/dist/esm/useFrappeForm.mjs +533 -0
  152. package/dist/esm/useFrappeForm.mjs.map +1 -0
  153. package/dist/esm/useFrappeForm.native.js +686 -0
  154. package/dist/esm/useFrappeForm.native.js.map +1 -0
  155. package/dist/jsx/FrappeCalendar.mjs +4 -3
  156. package/dist/jsx/FrappeCalendar.mjs.map +1 -1
  157. package/dist/jsx/FrappeCalendar.native.js +3 -2
  158. package/dist/jsx/FrappeCalendar.native.js.map +1 -1
  159. package/dist/jsx/FrappeForm.mjs +151 -467
  160. package/dist/jsx/FrappeForm.mjs.map +1 -1
  161. package/dist/jsx/FrappeForm.native.js +152 -633
  162. package/dist/jsx/FrappeForm.native.js.map +1 -1
  163. package/dist/jsx/FrappeListView.mjs +6 -28
  164. package/dist/jsx/FrappeListView.mjs.map +1 -1
  165. package/dist/jsx/FrappeListView.native.js +11 -32
  166. package/dist/jsx/FrappeListView.native.js.map +1 -1
  167. package/dist/jsx/FrappeReportBuilder.mjs +526 -312
  168. package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
  169. package/dist/jsx/FrappeReportBuilder.native.js +658 -353
  170. package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
  171. package/dist/jsx/FrappeReportView.mjs +30 -229
  172. package/dist/jsx/FrappeReportView.mjs.map +1 -1
  173. package/dist/jsx/FrappeReportView.native.js +39 -283
  174. package/dist/jsx/FrappeReportView.native.js.map +1 -1
  175. package/dist/jsx/dependsOn.mjs +2 -2
  176. package/dist/jsx/dependsOn.mjs.map +1 -1
  177. package/dist/jsx/dependsOn.native.js +1 -2
  178. package/dist/jsx/dependsOn.native.js.map +1 -1
  179. package/dist/jsx/fields/shared/EditorPreviewPanes.mjs +2 -2
  180. package/dist/jsx/fields/shared/EditorPreviewPanes.mjs.map +1 -1
  181. package/dist/jsx/fields/shared/EditorPreviewPanes.native.js +2 -2
  182. package/dist/jsx/fields/shared/EditorPreviewPanes.native.js.map +1 -1
  183. package/dist/jsx/formEngine/docPermissions.mjs +19 -1
  184. package/dist/jsx/formEngine/docPermissions.mjs.map +1 -1
  185. package/dist/jsx/formEngine/docPermissions.native.js +69 -0
  186. package/dist/jsx/formEngine/docPermissions.native.js.map +1 -1
  187. package/dist/jsx/formEngine/index.mjs +2 -2
  188. package/dist/jsx/formEngine/index.mjs.map +1 -1
  189. package/dist/jsx/formEngine/index.native.js +2 -0
  190. package/dist/jsx/formEngine/index.native.js.map +1 -1
  191. package/dist/jsx/index.js +16 -4
  192. package/dist/jsx/index.js.map +1 -1
  193. package/dist/jsx/index.mjs +16 -4
  194. package/dist/jsx/index.mjs.map +1 -1
  195. package/dist/jsx/index.native.js +67 -7
  196. package/dist/jsx/index.native.js.map +1 -1
  197. package/dist/jsx/liveErrors.mjs +8 -3
  198. package/dist/jsx/liveErrors.mjs.map +1 -1
  199. package/dist/jsx/liveErrors.native.js +8 -2
  200. package/dist/jsx/liveErrors.native.js.map +1 -1
  201. package/dist/jsx/measures.mjs +5 -0
  202. package/dist/jsx/measures.mjs.map +1 -0
  203. package/dist/jsx/measures.native.js +34 -0
  204. package/dist/jsx/measures.native.js.map +1 -0
  205. package/dist/jsx/recordShape.mjs +74 -0
  206. package/dist/jsx/recordShape.mjs.map +1 -0
  207. package/dist/jsx/recordShape.native.js +115 -0
  208. package/dist/jsx/recordShape.native.js.map +1 -0
  209. package/dist/jsx/report/ReportGrid.mjs +428 -0
  210. package/dist/jsx/report/ReportGrid.mjs.map +1 -0
  211. package/dist/jsx/report/ReportGrid.native.js +510 -0
  212. package/dist/jsx/report/ReportGrid.native.js.map +1 -0
  213. package/dist/jsx/report/ReportViewToolbar.mjs +228 -0
  214. package/dist/jsx/report/ReportViewToolbar.mjs.map +1 -0
  215. package/dist/jsx/report/ReportViewToolbar.native.js +303 -0
  216. package/dist/jsx/report/ReportViewToolbar.native.js.map +1 -0
  217. package/dist/jsx/report/queryReport.mjs +404 -0
  218. package/dist/jsx/report/queryReport.mjs.map +1 -0
  219. package/dist/jsx/report/queryReport.native.js +588 -0
  220. package/dist/jsx/report/queryReport.native.js.map +1 -0
  221. package/dist/jsx/report/queryReportColumns.mjs +105 -0
  222. package/dist/jsx/report/queryReportColumns.mjs.map +1 -0
  223. package/dist/jsx/report/queryReportColumns.native.js +159 -0
  224. package/dist/jsx/report/queryReportColumns.native.js.map +1 -0
  225. package/dist/jsx/report/reportViewModel.mjs +343 -0
  226. package/dist/jsx/report/reportViewModel.mjs.map +1 -0
  227. package/dist/jsx/report/reportViewModel.native.js +657 -0
  228. package/dist/jsx/report/reportViewModel.native.js.map +1 -0
  229. package/dist/jsx/report/savedReports.mjs +112 -0
  230. package/dist/jsx/report/savedReports.mjs.map +1 -0
  231. package/dist/jsx/report/savedReports.native.js +169 -0
  232. package/dist/jsx/report/savedReports.native.js.map +1 -0
  233. package/dist/jsx/report/useFrappeReport.mjs +158 -0
  234. package/dist/jsx/report/useFrappeReport.mjs.map +1 -0
  235. package/dist/jsx/report/useFrappeReport.native.js +214 -0
  236. package/dist/jsx/report/useFrappeReport.native.js.map +1 -0
  237. package/dist/jsx/report/useQueryReport.mjs +176 -0
  238. package/dist/jsx/report/useQueryReport.mjs.map +1 -0
  239. package/dist/jsx/report/useQueryReport.native.js +233 -0
  240. package/dist/jsx/report/useQueryReport.native.js.map +1 -0
  241. package/dist/jsx/useFrappeForm.mjs +533 -0
  242. package/dist/jsx/useFrappeForm.mjs.map +1 -0
  243. package/dist/jsx/useFrappeForm.native.js +714 -0
  244. package/dist/jsx/useFrappeForm.native.js.map +1 -0
  245. package/package.json +16 -16
  246. package/src/FrappeCalendar.tsx +8 -2
  247. package/src/FrappeForm.stories.tsx +52 -0
  248. package/src/FrappeForm.tsx +176 -565
  249. package/src/FrappeListView.cards.stories.tsx +3 -6
  250. package/src/FrappeListView.tsx +15 -55
  251. package/src/FrappeReportBuilder.stories.tsx +34 -7
  252. package/src/FrappeReportBuilder.tsx +790 -380
  253. package/src/FrappeReportView.spec.tsx +10 -6
  254. package/src/FrappeReportView.stories.tsx +20 -1
  255. package/src/FrappeReportView.tsx +89 -415
  256. package/src/dependsOn.ts +4 -1
  257. package/src/fields/shared/EditorPreviewPanes.tsx +2 -2
  258. package/src/formEngine/docPermissions.spec.ts +36 -1
  259. package/src/formEngine/docPermissions.ts +34 -0
  260. package/src/formEngine/index.ts +8 -1
  261. package/src/frappeRemainder.designLaw.spec.ts +9 -1
  262. package/src/frappeRoutes.spec.tsx +9 -0
  263. package/src/frappeViews.designLaw.spec.ts +15 -2
  264. package/src/guestFallback.spec.tsx +236 -0
  265. package/src/index.ts +117 -16
  266. package/src/liveErrors.spec.ts +10 -0
  267. package/src/liveErrors.ts +16 -3
  268. package/src/measures.ts +11 -0
  269. package/src/recordShape.spec.ts +100 -0
  270. package/src/recordShape.ts +133 -0
  271. package/src/report/ReportGrid.spec.tsx +175 -0
  272. package/src/report/ReportGrid.tsx +530 -0
  273. package/src/report/ReportViewToolbar.spec.tsx +90 -0
  274. package/src/report/ReportViewToolbar.tsx +314 -0
  275. package/src/report/queryReport.spec.ts +483 -0
  276. package/src/report/queryReport.ts +722 -0
  277. package/src/report/queryReportColumns.tsx +123 -0
  278. package/src/report/reportViewModel.spec.ts +393 -0
  279. package/src/report/reportViewModel.ts +617 -0
  280. package/src/report/savedReports.ts +172 -0
  281. package/src/report/seam.spec.ts +216 -0
  282. package/src/report/useFrappeReport.ts +241 -0
  283. package/src/report/useQueryReport.spec.tsx +131 -0
  284. package/src/report/useQueryReport.ts +309 -0
  285. package/src/useFrappeForm.spec.tsx +209 -0
  286. package/src/useFrappeForm.ts +778 -0
  287. package/types/FrappeCalendar.d.ts.map +1 -1
  288. package/types/FrappeForm.d.ts +21 -1
  289. package/types/FrappeForm.d.ts.map +1 -1
  290. package/types/FrappeListView.d.ts.map +1 -1
  291. package/types/FrappeReportBuilder.d.ts +66 -16
  292. package/types/FrappeReportBuilder.d.ts.map +1 -1
  293. package/types/FrappeReportView.d.ts +9 -127
  294. package/types/FrappeReportView.d.ts.map +1 -1
  295. package/types/dependsOn.d.ts.map +1 -1
  296. package/types/formEngine/docPermissions.d.ts +14 -0
  297. package/types/formEngine/docPermissions.d.ts.map +1 -1
  298. package/types/formEngine/index.d.ts +1 -1
  299. package/types/formEngine/index.d.ts.map +1 -1
  300. package/types/index.d.ts +15 -3
  301. package/types/index.d.ts.map +1 -1
  302. package/types/liveErrors.d.ts +9 -1
  303. package/types/liveErrors.d.ts.map +1 -1
  304. package/types/measures.d.ts +3 -0
  305. package/types/measures.d.ts.map +1 -0
  306. package/types/recordShape.d.ts +45 -0
  307. package/types/recordShape.d.ts.map +1 -0
  308. package/types/report/ReportGrid.d.ts +20 -0
  309. package/types/report/ReportGrid.d.ts.map +1 -0
  310. package/types/report/ReportViewToolbar.d.ts +31 -0
  311. package/types/report/ReportViewToolbar.d.ts.map +1 -0
  312. package/types/report/queryReport.d.ts +181 -0
  313. package/types/report/queryReport.d.ts.map +1 -0
  314. package/types/report/queryReportColumns.d.ts +12 -0
  315. package/types/report/queryReportColumns.d.ts.map +1 -0
  316. package/types/report/reportViewModel.d.ts +146 -0
  317. package/types/report/reportViewModel.d.ts.map +1 -0
  318. package/types/report/savedReports.d.ts +27 -0
  319. package/types/report/savedReports.d.ts.map +1 -0
  320. package/types/report/useFrappeReport.d.ts +60 -0
  321. package/types/report/useFrappeReport.d.ts.map +1 -0
  322. package/types/report/useQueryReport.d.ts +37 -0
  323. package/types/report/useQueryReport.d.ts.map +1 -0
  324. package/types/useFrappeForm.d.ts +142 -0
  325. package/types/useFrappeForm.d.ts.map +1 -0
@@ -0,0 +1,483 @@
1
+ // Normalizer specs pinned to PROBED bench payloads (see queryReport.ts
2
+ // header): v15 dict columns/rows from frappe.desk.query_report.run plus the
3
+ // defensive old-style string-column / positional-array-row forms, and the
4
+ // script-module filter extraction (probed get_script payloads).
5
+ import { describe, expect, it } from "vitest";
6
+ import {
7
+ buildRunFilters,
8
+ compactFilterValues,
9
+ compactQueryReportFilters,
10
+ defaultFilterValues,
11
+ extractScriptFilters,
12
+ humanizeFrappeError,
13
+ normalizeFilterMeta,
14
+ normalizeQueryReportColumns,
15
+ normalizeQueryReportPayload,
16
+ normalizeQueryReportRows,
17
+ scrubFieldname,
18
+ stripHtml,
19
+ } from "./queryReport";
20
+
21
+ describe("normalizeQueryReportColumns", () => {
22
+ it("normalizes v15 dict columns (probed ToDo report shape)", () => {
23
+ const columns = normalizeQueryReportColumns([
24
+ { fieldtype: "Link", options: "ToDo", width: "90", label: "ID", fieldname: "id" },
25
+ { fieldtype: "", width: "60", label: "Priority", fieldname: "priority" },
26
+ { fieldtype: "Date", label: "Date", fieldname: "date" },
27
+ {
28
+ fieldtype: "Data",
29
+ width: "120",
30
+ label: "Assigned To/Owner",
31
+ fieldname: "assigned_to/owner",
32
+ },
33
+ ]);
34
+ expect(columns).toEqual([
35
+ { fieldname: "id", label: "ID", fieldtype: "Link", options: "ToDo", width: 90 },
36
+ { fieldname: "priority", label: "Priority", fieldtype: "", options: undefined, width: 60 },
37
+ { fieldname: "date", label: "Date", fieldtype: "Date", options: undefined, width: undefined },
38
+ {
39
+ fieldname: "assigned_to/owner",
40
+ label: "Assigned To/Owner",
41
+ fieldtype: "Data",
42
+ options: undefined,
43
+ width: 120,
44
+ },
45
+ ]);
46
+ });
47
+
48
+ it("normalizes old-style SQL alias strings", () => {
49
+ const columns = normalizeQueryReportColumns([
50
+ "ID:Link/ToDo:120",
51
+ "Status::100",
52
+ "Modified On:Date:110",
53
+ ]);
54
+ expect(columns).toEqual([
55
+ { fieldname: "id", label: "ID", fieldtype: "Link", options: "ToDo", width: 120 },
56
+ { fieldname: "status", label: "Status", fieldtype: "", options: undefined, width: 100 },
57
+ {
58
+ fieldname: "modified_on",
59
+ label: "Modified On",
60
+ fieldtype: "Date",
61
+ options: undefined,
62
+ width: 110,
63
+ },
64
+ ]);
65
+ });
66
+
67
+ it("dedupes colliding fieldnames and skips junk entries", () => {
68
+ const columns = normalizeQueryReportColumns(["Total::80", "Total::80", null, 42]);
69
+ expect(columns.map((column) => column.fieldname)).toEqual(["total", "total_1"]);
70
+ });
71
+ });
72
+
73
+ describe("normalizeQueryReportRows", () => {
74
+ const columns = normalizeQueryReportColumns(["ID:Link/ToDo:120", "Status::100"]);
75
+
76
+ it("passes dict rows through (probed v15 shape)", () => {
77
+ const rows = normalizeQueryReportRows([{ id: "TD-1", status: "Open" }], columns);
78
+ expect(rows).toEqual([{ id: "TD-1", status: "Open" }]);
79
+ });
80
+
81
+ it("zips positional array rows onto column fieldnames", () => {
82
+ const rows = normalizeQueryReportRows([["TD-1", "Open"]], columns);
83
+ expect(rows).toEqual([{ id: "TD-1", status: "Open" }]);
84
+ });
85
+
86
+ it("drops scalar/null separator entries", () => {
87
+ expect(normalizeQueryReportRows([null, "sep", 3, ["TD-1", "Open"]], columns)).toHaveLength(1);
88
+ });
89
+ });
90
+
91
+ describe("scrubFieldname", () => {
92
+ // python: cstr(txt).replace(" ", "_").replace("-", "_").lower(). The probed
93
+ // ToDo report above sends fieldname "assigned_to/owner" for the label
94
+ // "Assigned To/Owner", which is the same function at work server-side.
95
+ it("is frappe.scrub, character for character", () => {
96
+ expect(scrubFieldname("Modified On")).toBe("modified_on");
97
+ expect(scrubFieldname("Assigned To/Owner")).toBe("assigned_to/owner");
98
+ expect(scrubFieldname("Multi-Word Label")).toBe("multi_word_label");
99
+ expect(scrubFieldname("Size (MB)")).toBe("size_(mb)");
100
+ expect(scrubFieldname(" Total (USD) ")).toBe("__total_(usd)_");
101
+ });
102
+
103
+ it("keys a label-only column the way the server keys its rows", () => {
104
+ const [column] = normalizeQueryReportColumns([{ label: "Assigned To/Owner" }]);
105
+ expect(column.fieldname).toBe("assigned_to/owner");
106
+ expect(
107
+ normalizeQueryReportRows([{ "assigned_to/owner": "jane@example.com" }], [column]),
108
+ ).toEqual([{ "assigned_to/owner": "jane@example.com" }]);
109
+ });
110
+ });
111
+
112
+ describe("normalizeQueryReportPayload", () => {
113
+ it("normalizes a run and flags the server total row", () => {
114
+ const payload = normalizeQueryReportPayload({
115
+ columns: ["Amount:Currency:120", { label: "Rate", fieldname: "rate", precision: "3" }],
116
+ result: [{ amount: 5, rate: 0.125 }, [10]],
117
+ message: ["Part one.", "Part two."],
118
+ add_total_row: 1,
119
+ });
120
+ expect(payload.columns.map((column) => [column.fieldname, column.width])).toEqual([
121
+ ["amount", 120],
122
+ ["rate", undefined],
123
+ ]);
124
+ expect(payload.columns[1].precision).toBe(3);
125
+ expect(payload.rows).toEqual([
126
+ { amount: 5, rate: 0.125 },
127
+ { amount: 10, rate: undefined },
128
+ ]);
129
+ expect(payload.message).toBe("Part one. Part two.");
130
+ expect(payload.addTotalRow).toBe(true);
131
+ });
132
+
133
+ it("leaves the total row off when the server skips it", () => {
134
+ expect(normalizeQueryReportPayload({ add_total_row: 1, skip_total_row: 1 }).addTotalRow).toBe(
135
+ false,
136
+ );
137
+ });
138
+ });
139
+
140
+ describe("compactFilterValues", () => {
141
+ it("drops empty values and keeps falsy ones that mean something", () => {
142
+ expect(compactFilterValues({ a: "", b: null, c: undefined, d: 0, e: false, f: "x" })).toEqual({
143
+ d: 0,
144
+ e: false,
145
+ f: "x",
146
+ });
147
+ expect(compactFilterValues(undefined)).toEqual({});
148
+ });
149
+ });
150
+
151
+ describe("humanizeFrappeError", () => {
152
+ it("reads the server's sentence and keeps the status and exc_type", () => {
153
+ const failure = Object.assign(new Error("request failed"), {
154
+ status: 403,
155
+ data: {
156
+ exc_type: "PermissionError",
157
+ _server_messages: JSON.stringify([JSON.stringify({ message: "Not permitted" })]),
158
+ },
159
+ });
160
+ const humanized = humanizeFrappeError(failure) as Error & {
161
+ status?: number;
162
+ exc_type?: string;
163
+ };
164
+ expect(humanized.message).toBe("Not permitted");
165
+ expect(humanized.status).toBe(403);
166
+ expect(humanized.exc_type).toBe("PermissionError");
167
+ });
168
+
169
+ it("keeps a <details> summary and its body on separate lines (MPO-473)", () => {
170
+ const failure = Object.assign(new Error("request failed"), {
171
+ status: 403,
172
+ data: {
173
+ exc_type: "PermissionError",
174
+ _server_messages: JSON.stringify([
175
+ JSON.stringify({
176
+ message:
177
+ "<details><summary>You are not permitted to access this resource. Login to access</summary>Function <strong>frappe.desk.query_report.run</strong> is not whitelisted.</details>",
178
+ }),
179
+ ]),
180
+ },
181
+ });
182
+ expect(humanizeFrappeError(failure).message).toBe(
183
+ "You are not permitted to access this resource. Login to access\nFunction frappe.desk.query_report.run is not whitelisted.",
184
+ );
185
+ });
186
+ });
187
+
188
+ describe("stripHtml", () => {
189
+ it("flattens desk anchor markup (probed ToDo reference cell)", () => {
190
+ expect(stripHtml('<a href="/app/Form/ToDo/oftgr5usq3">ToDo: oftgr5usq3</a>')).toBe(
191
+ "ToDo: oftgr5usq3",
192
+ );
193
+ });
194
+
195
+ it("decodes basic entities and leaves plain text alone", () => {
196
+ expect(stripHtml("A &amp; B")).toBe("A & B");
197
+ expect(stripHtml("plain")).toBe("plain");
198
+ });
199
+ });
200
+
201
+ describe("normalizeFilterMeta", () => {
202
+ it("normalizes Report Filter doc rows (mandatory flag, wildcard, defaults)", () => {
203
+ expect(
204
+ normalizeFilterMeta([
205
+ {
206
+ fieldname: "status",
207
+ label: "Status",
208
+ fieldtype: "Select",
209
+ options: "Open\nClosed",
210
+ mandatory: 1,
211
+ wildcard_filter: 0,
212
+ default: "Open",
213
+ },
214
+ { label: "Search Term", fieldtype: "Data", wildcard_filter: "1" },
215
+ null,
216
+ "junk",
217
+ ]),
218
+ ).toEqual([
219
+ {
220
+ fieldname: "status",
221
+ label: "Status",
222
+ fieldtype: "Select",
223
+ options: "Open\nClosed",
224
+ default: "Open",
225
+ mandatory: 1,
226
+ wildcard_filter: 0,
227
+ hidden: 0,
228
+ read_only: 0,
229
+ },
230
+ {
231
+ fieldname: "search_term",
232
+ label: "Search Term",
233
+ fieldtype: "Data",
234
+ options: undefined,
235
+ default: undefined,
236
+ mandatory: 0,
237
+ wildcard_filter: 1,
238
+ hidden: 0,
239
+ read_only: 0,
240
+ },
241
+ ]);
242
+ });
243
+
244
+ it("accepts script vocabulary: reqd, boolean/number defaults, options arrays", () => {
245
+ const [filter] = normalizeFilterMeta([
246
+ {
247
+ fieldname: "show_permissions",
248
+ fieldtype: "Check",
249
+ reqd: 1,
250
+ default: true,
251
+ options: ["", "Open", "Closed"],
252
+ },
253
+ ]);
254
+ expect(filter).toMatchObject({
255
+ fieldname: "show_permissions",
256
+ mandatory: 1,
257
+ default: 1,
258
+ options: "\nOpen\nClosed",
259
+ });
260
+ });
261
+
262
+ it("honors read_only / readOnly so locked filters stay visible but inert", () => {
263
+ const [locked] = normalizeFilterMeta([
264
+ { fieldname: "company", fieldtype: "Link", options: "Company", read_only: 1 },
265
+ ]);
266
+ const [alsoLocked] = normalizeFilterMeta([
267
+ { fieldname: "company", fieldtype: "Link", options: "Company", readOnly: true },
268
+ ]);
269
+ expect(locked).toMatchObject({ read_only: 1 });
270
+ expect(alsoLocked).toMatchObject({ read_only: 1 });
271
+ });
272
+ });
273
+
274
+ describe("extractScriptFilters", () => {
275
+ it("extracts the declarative subset (probed Permitted Documents For User script)", () => {
276
+ // Verbatim get_script payload from the bench probe (2026-08-13) —
277
+ // includes a get_query function that must be skipped, not parsed.
278
+ const script = `// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
279
+ // MIT License. See license.txt
280
+
281
+ frappe.query_reports["Permitted Documents For User"] = {
282
+ \tfilters: [
283
+ \t\t{
284
+ \t\t\tfieldname: "user",
285
+ \t\t\tlabel: __("User"),
286
+ \t\t\tfieldtype: "Link",
287
+ \t\t\toptions: "User",
288
+ \t\t\treqd: 1,
289
+ \t\t},
290
+ \t\t{
291
+ \t\t\tfieldname: "doctype",
292
+ \t\t\tlabel: __("DocType"),
293
+ \t\t\tfieldtype: "Link",
294
+ \t\t\toptions: "DocType",
295
+ \t\t\treqd: 1,
296
+ \t\t\tget_query: function () {
297
+ \t\t\t\treturn {
298
+ \t\t\t\t\tquery: "frappe.core.report.permitted_documents_for_user.permitted_documents_for_user.query_doctypes",
299
+ \t\t\t\t\tfilters: {
300
+ \t\t\t\t\t\tuser: frappe.query_report.get_filter_value("user"),
301
+ \t\t\t\t\t},
302
+ \t\t\t\t};
303
+ \t\t\t},
304
+ \t\t},
305
+ \t\t{
306
+ \t\t\tfieldname: "show_permissions",
307
+ \t\t\tlabel: __("Show Permissions"),
308
+ \t\t\tfieldtype: "Check",
309
+ \t\t},
310
+ \t],
311
+ };
312
+
313
+ //# sourceURL=permitted_documents_for_user.js`;
314
+ const extraction = extractScriptFilters(script);
315
+ expect(extraction.declared).toBe(true);
316
+ expect(extraction.declaredCount).toBe(3);
317
+ expect(extraction.filters).toEqual([
318
+ {
319
+ fieldname: "user",
320
+ label: "User",
321
+ fieldtype: "Link",
322
+ options: "User",
323
+ default: undefined,
324
+ mandatory: 1,
325
+ wildcard_filter: 0,
326
+ hidden: 0,
327
+ read_only: 0,
328
+ },
329
+ {
330
+ fieldname: "doctype",
331
+ label: "DocType",
332
+ fieldtype: "Link",
333
+ options: "DocType",
334
+ default: undefined,
335
+ mandatory: 1,
336
+ wildcard_filter: 0,
337
+ hidden: 0,
338
+ read_only: 0,
339
+ },
340
+ {
341
+ fieldname: "show_permissions",
342
+ label: "Show Permissions",
343
+ fieldtype: "Check",
344
+ options: undefined,
345
+ default: undefined,
346
+ mandatory: 0,
347
+ wildcard_filter: 0,
348
+ hidden: 0,
349
+ read_only: 0,
350
+ },
351
+ ]);
352
+ });
353
+
354
+ it("treats an empty declared array as declared with no filters (probed ToDo script)", () => {
355
+ const extraction = extractScriptFilters(
356
+ 'frappe.query_reports["ToDo"] = {\n\tfilters: [],\n};\n',
357
+ );
358
+ expect(extraction).toEqual({ declared: true, declaredCount: 0, filters: [] });
359
+ });
360
+
361
+ it("reports no declaration for scripts without a filters key (and for no script)", () => {
362
+ expect(extractScriptFilters('frappe.query_reports["X"] = {};').declared).toBe(false);
363
+ expect(extractScriptFilters(undefined).declared).toBe(false);
364
+ });
365
+
366
+ it("counts non-declarative and desk-only entries as gaps", () => {
367
+ const script = `frappe.query_reports["X"] = {
368
+ filters: [
369
+ { fieldname: "user", fieldtype: "Link", options: "User" },
370
+ { fieldname: dynamic_name, fieldtype: "Data" },
371
+ { fieldname: "companies", label: __("Companies"), fieldtype: "MultiSelectList",
372
+ get_data: function (txt) { return frappe.db.get_link_options("Company", txt); } },
373
+ ],
374
+ };`;
375
+ const extraction = extractScriptFilters(script);
376
+ expect(extraction.declaredCount).toBe(3);
377
+ expect(extraction.filters.map((filter) => filter.fieldname)).toEqual(["user"]);
378
+ });
379
+
380
+ it("skips dynamic defaults but keeps scalar ones (numbers, quoted strings, arrays)", () => {
381
+ const script = `frappe.query_reports["X"] = {
382
+ filters: [
383
+ { fieldname: "from_date", fieldtype: "Date", default: frappe.datetime.get_today() },
384
+ { fieldname: "status", fieldtype: "Select", options: ["", "Open", "Closed"], default: "Open" },
385
+ { fieldname: "limit", fieldtype: "Int", default: 20 },
386
+ // trailing comment inside the array
387
+ { fieldname: "hidden_flag", fieldtype: "Check", default: 1, hidden: 1 },
388
+ ],
389
+ };`;
390
+ const extraction = extractScriptFilters(script);
391
+ expect(extraction.declaredCount).toBe(4);
392
+ expect(extraction.filters).toHaveLength(4);
393
+ const [fromDate, status, limit, hiddenFlag] = extraction.filters;
394
+ expect(fromDate).toMatchObject({ fieldname: "from_date", default: undefined });
395
+ expect(status).toMatchObject({ options: "\nOpen\nClosed", default: "Open" });
396
+ expect(limit).toMatchObject({ default: 20 });
397
+ expect(hiddenFlag).toMatchObject({ hidden: 1, default: 1 });
398
+ });
399
+
400
+ it("handles strings containing commas, braces, and escapes", () => {
401
+ const script = `frappe.query_reports["X"] = {
402
+ filters: [
403
+ { fieldname: "note", fieldtype: "Data", label: __("A, {B} \\"quoted\\"") },
404
+ ],
405
+ };`;
406
+ const [filter] = extractScriptFilters(script).filters;
407
+ expect(filter.label).toBe('A, {B} "quoted"');
408
+ });
409
+
410
+ it("drops Select/Link entries without options (unrenderable)", () => {
411
+ const script = `frappe.query_reports["X"] = {
412
+ filters: [{ fieldname: "pick", fieldtype: "Select" }],
413
+ };`;
414
+ const extraction = extractScriptFilters(script);
415
+ expect(extraction.declaredCount).toBe(1);
416
+ expect(extraction.filters).toHaveLength(0);
417
+ });
418
+ });
419
+
420
+ describe("defaultFilterValues", () => {
421
+ it("collects seedable defaults and skips empty ones", () => {
422
+ expect(
423
+ defaultFilterValues([
424
+ { fieldname: "status", default: "Open" },
425
+ { fieldname: "limit", default: 0 },
426
+ { fieldname: "user", default: "" },
427
+ { fieldname: "doctype" },
428
+ ]),
429
+ ).toEqual({ status: "Open", limit: 0 });
430
+ });
431
+ });
432
+
433
+ describe("compactQueryReportFilters", () => {
434
+ it("drops empty values and keeps hidden defaults so a visible clear-all is honest", () => {
435
+ const meta = normalizeFilterMeta([
436
+ { fieldname: "status", fieldtype: "Select", options: "Open\nClosed" },
437
+ { fieldname: "owner", fieldtype: "Data" },
438
+ { fieldname: "hidden_flag", fieldtype: "Check", hidden: 1, default: 1 },
439
+ ]);
440
+ expect(
441
+ compactQueryReportFilters(meta, {
442
+ status: "Open",
443
+ owner: "",
444
+ hidden_flag: 1,
445
+ }),
446
+ ).toEqual({ status: "Open", hidden_flag: 1 });
447
+ });
448
+ });
449
+
450
+ describe("buildRunFilters", () => {
451
+ const meta = normalizeFilterMeta([
452
+ { fieldname: "search", fieldtype: "Data", wildcard_filter: 1 },
453
+ { fieldname: "limit", fieldtype: "Int" },
454
+ { fieldname: "show_permissions", fieldtype: "Check" },
455
+ ]);
456
+
457
+ it("omits empty and falsy values (desk get_filter_values parity)", () => {
458
+ expect(
459
+ buildRunFilters(meta, {
460
+ search: "",
461
+ limit: 0,
462
+ show_permissions: false,
463
+ user: null,
464
+ doctype: undefined,
465
+ }),
466
+ ).toEqual({});
467
+ });
468
+
469
+ it("wraps wildcard filters and coerces numeric fieldtypes", () => {
470
+ expect(buildRunFilters(meta, { search: "todo", limit: "25", show_permissions: true })).toEqual({
471
+ search: "%todo%",
472
+ limit: 25,
473
+ show_permissions: 1,
474
+ });
475
+ });
476
+
477
+ it("respects user-typed wildcards and passes unknown fieldnames through", () => {
478
+ expect(buildRunFilters(meta, { search: "to%do", extra: "kept" })).toEqual({
479
+ search: "to%do",
480
+ extra: "kept",
481
+ });
482
+ });
483
+ });