@pranaysahith/decap-cms-core 3.9.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 (299) hide show
  1. package/README.md +9 -0
  2. package/dist/@pranaysahith/decap-cms-core.js +52 -0
  3. package/dist/@pranaysahith/decap-cms-core.js.LICENSE.txt +141 -0
  4. package/dist/@pranaysahith/decap-cms-core.js.map +1 -0
  5. package/dist/decap-cms-core.js +47 -0
  6. package/dist/decap-cms-core.js.LICENSE.txt +116 -0
  7. package/dist/decap-cms-core.js.map +1 -0
  8. package/dist/esm/actions/auth.js +97 -0
  9. package/dist/esm/actions/collections.js +15 -0
  10. package/dist/esm/actions/config.js +493 -0
  11. package/dist/esm/actions/deploys.js +79 -0
  12. package/dist/esm/actions/editorialWorkflow.js +480 -0
  13. package/dist/esm/actions/entries.js +865 -0
  14. package/dist/esm/actions/media.js +147 -0
  15. package/dist/esm/actions/mediaLibrary.js +552 -0
  16. package/dist/esm/actions/notifications.js +21 -0
  17. package/dist/esm/actions/search.js +149 -0
  18. package/dist/esm/actions/status.js +74 -0
  19. package/dist/esm/actions/waitUntil.js +32 -0
  20. package/dist/esm/backend.js +1082 -0
  21. package/dist/esm/bootstrap.js +101 -0
  22. package/dist/esm/components/App/App.js +289 -0
  23. package/dist/esm/components/App/Header.js +172 -0
  24. package/dist/esm/components/App/NotFoundPage.js +19 -0
  25. package/dist/esm/components/Collection/Collection.js +198 -0
  26. package/dist/esm/components/Collection/CollectionControls.js +46 -0
  27. package/dist/esm/components/Collection/CollectionSearch.js +222 -0
  28. package/dist/esm/components/Collection/CollectionTop.js +68 -0
  29. package/dist/esm/components/Collection/ControlButton.js +17 -0
  30. package/dist/esm/components/Collection/Entries/Entries.js +73 -0
  31. package/dist/esm/components/Collection/Entries/EntriesCollection.js +241 -0
  32. package/dist/esm/components/Collection/Entries/EntriesSearch.js +113 -0
  33. package/dist/esm/components/Collection/Entries/EntryCard.js +177 -0
  34. package/dist/esm/components/Collection/Entries/EntryListing.js +143 -0
  35. package/dist/esm/components/Collection/FilterControl.js +33 -0
  36. package/dist/esm/components/Collection/FolderRenameControl.js +403 -0
  37. package/dist/esm/components/Collection/GroupControl.js +33 -0
  38. package/dist/esm/components/Collection/NestedCollection.js +308 -0
  39. package/dist/esm/components/Collection/Sidebar.js +91 -0
  40. package/dist/esm/components/Collection/SortControl.js +59 -0
  41. package/dist/esm/components/Collection/ViewStyleControl.js +38 -0
  42. package/dist/esm/components/Editor/Editor.js +466 -0
  43. package/dist/esm/components/Editor/EditorControlPane/EditorControl.js +395 -0
  44. package/dist/esm/components/Editor/EditorControlPane/EditorControlPane.js +254 -0
  45. package/dist/esm/components/Editor/EditorControlPane/Widget.js +374 -0
  46. package/dist/esm/components/Editor/EditorInterface.js +386 -0
  47. package/dist/esm/components/Editor/EditorPreviewPane/EditorPreview.js +47 -0
  48. package/dist/esm/components/Editor/EditorPreviewPane/EditorPreviewContent.js +66 -0
  49. package/dist/esm/components/Editor/EditorPreviewPane/EditorPreviewPane.js +288 -0
  50. package/dist/esm/components/Editor/EditorPreviewPane/PreviewHOC.js +27 -0
  51. package/dist/esm/components/Editor/EditorToolbar.js +536 -0
  52. package/dist/esm/components/Editor/EntryPathEditor.js +272 -0
  53. package/dist/esm/components/Editor/withWorkflow.js +56 -0
  54. package/dist/esm/components/EditorWidgets/Unknown/UnknownControl.js +18 -0
  55. package/dist/esm/components/EditorWidgets/Unknown/UnknownPreview.js +20 -0
  56. package/dist/esm/components/EditorWidgets/index.js +4 -0
  57. package/dist/esm/components/MediaLibrary/EmptyMessage.js +22 -0
  58. package/dist/esm/components/MediaLibrary/MediaLibrary.js +446 -0
  59. package/dist/esm/components/MediaLibrary/MediaLibraryButtons.js +93 -0
  60. package/dist/esm/components/MediaLibrary/MediaLibraryCard.js +99 -0
  61. package/dist/esm/components/MediaLibrary/MediaLibraryCardGrid.js +198 -0
  62. package/dist/esm/components/MediaLibrary/MediaLibraryHeader.js +32 -0
  63. package/dist/esm/components/MediaLibrary/MediaLibraryModal.js +156 -0
  64. package/dist/esm/components/MediaLibrary/MediaLibrarySearch.js +51 -0
  65. package/dist/esm/components/MediaLibrary/MediaLibraryTop.js +123 -0
  66. package/dist/esm/components/UI/DragDrop.js +67 -0
  67. package/dist/esm/components/UI/ErrorBoundary.js +173 -0
  68. package/dist/esm/components/UI/FileUploadButton.js +27 -0
  69. package/dist/esm/components/UI/Modal.js +104 -0
  70. package/dist/esm/components/UI/Notifications.js +62 -0
  71. package/dist/esm/components/UI/SettingsDropdown.js +107 -0
  72. package/dist/esm/components/UI/index.js +6 -0
  73. package/dist/esm/components/Workflow/Workflow.js +133 -0
  74. package/dist/esm/components/Workflow/WorkflowCard.js +128 -0
  75. package/dist/esm/components/Workflow/WorkflowList.js +204 -0
  76. package/dist/esm/constants/collectionTypes.js +2 -0
  77. package/dist/esm/constants/collectionViews.js +2 -0
  78. package/dist/esm/constants/commitProps.js +2 -0
  79. package/dist/esm/constants/configSchema.js +644 -0
  80. package/dist/esm/constants/fieldInference.js +57 -0
  81. package/dist/esm/constants/publishModes.js +18 -0
  82. package/dist/esm/constants/validationErrorTypes.js +6 -0
  83. package/dist/esm/formats/formats.js +83 -0
  84. package/dist/esm/formats/frontmatter.js +146 -0
  85. package/dist/esm/formats/helpers.js +12 -0
  86. package/dist/esm/formats/json.js +8 -0
  87. package/dist/esm/formats/toml.js +32 -0
  88. package/dist/esm/formats/yaml.js +51 -0
  89. package/dist/esm/index.js +7 -0
  90. package/dist/esm/integrations/index.js +28 -0
  91. package/dist/esm/integrations/providers/algolia/implementation.js +174 -0
  92. package/dist/esm/integrations/providers/assetStore/implementation.js +165 -0
  93. package/dist/esm/lib/consoleError.js +3 -0
  94. package/dist/esm/lib/formatters.js +191 -0
  95. package/dist/esm/lib/i18n.js +367 -0
  96. package/dist/esm/lib/phrases.js +6 -0
  97. package/dist/esm/lib/polyfill.js +8 -0
  98. package/dist/esm/lib/registry.js +329 -0
  99. package/dist/esm/lib/serializeEntryValues.js +67 -0
  100. package/dist/esm/lib/stega.js +142 -0
  101. package/dist/esm/lib/textHelper.js +9 -0
  102. package/dist/esm/lib/urlHelper.js +111 -0
  103. package/dist/esm/mediaLibrary.js +37 -0
  104. package/dist/esm/reducers/auth.js +27 -0
  105. package/dist/esm/reducers/collections.js +428 -0
  106. package/dist/esm/reducers/combinedReducer.js +8 -0
  107. package/dist/esm/reducers/config.js +29 -0
  108. package/dist/esm/reducers/cursors.js +31 -0
  109. package/dist/esm/reducers/deploys.js +45 -0
  110. package/dist/esm/reducers/editorialWorkflow.js +83 -0
  111. package/dist/esm/reducers/entries.js +568 -0
  112. package/dist/esm/reducers/entryDraft.js +212 -0
  113. package/dist/esm/reducers/globalUI.js +25 -0
  114. package/dist/esm/reducers/index.js +66 -0
  115. package/dist/esm/reducers/integrations.js +53 -0
  116. package/dist/esm/reducers/mediaLibrary.js +252 -0
  117. package/dist/esm/reducers/medias.js +68 -0
  118. package/dist/esm/reducers/notifications.js +23 -0
  119. package/dist/esm/reducers/search.js +92 -0
  120. package/dist/esm/reducers/status.js +30 -0
  121. package/dist/esm/redux/index.js +7 -0
  122. package/dist/esm/redux/middleware/waitUntilAction.js +48 -0
  123. package/dist/esm/routing/history.js +12 -0
  124. package/dist/esm/types/diacritics.d.js +0 -0
  125. package/dist/esm/types/global.d.js +1 -0
  126. package/dist/esm/types/immutable.js +7 -0
  127. package/dist/esm/types/redux.js +14 -0
  128. package/dist/esm/types/tomlify-j0.4.d.js +0 -0
  129. package/dist/esm/valueObjects/AssetProxy.js +44 -0
  130. package/dist/esm/valueObjects/EditorComponent.js +34 -0
  131. package/dist/esm/valueObjects/Entry.js +20 -0
  132. package/index.d.ts +618 -0
  133. package/package.json +106 -0
  134. package/src/__tests__/backend.spec.js +1161 -0
  135. package/src/actions/__tests__/config.spec.js +1009 -0
  136. package/src/actions/__tests__/editorialWorkflow.spec.js +216 -0
  137. package/src/actions/__tests__/entries.spec.js +596 -0
  138. package/src/actions/__tests__/media.spec.ts +171 -0
  139. package/src/actions/__tests__/mediaLibrary.spec.js +327 -0
  140. package/src/actions/__tests__/search.spec.js +209 -0
  141. package/src/actions/auth.ts +127 -0
  142. package/src/actions/collections.ts +18 -0
  143. package/src/actions/config.ts +565 -0
  144. package/src/actions/deploys.ts +104 -0
  145. package/src/actions/editorialWorkflow.ts +567 -0
  146. package/src/actions/entries.ts +1055 -0
  147. package/src/actions/media.ts +139 -0
  148. package/src/actions/mediaLibrary.ts +574 -0
  149. package/src/actions/notifications.ts +36 -0
  150. package/src/actions/search.ts +221 -0
  151. package/src/actions/status.ts +99 -0
  152. package/src/actions/waitUntil.ts +49 -0
  153. package/src/backend.ts +1400 -0
  154. package/src/bootstrap.js +104 -0
  155. package/src/components/App/App.js +286 -0
  156. package/src/components/App/Header.js +266 -0
  157. package/src/components/App/NotFoundPage.js +23 -0
  158. package/src/components/Collection/Collection.js +210 -0
  159. package/src/components/Collection/CollectionControls.js +58 -0
  160. package/src/components/Collection/CollectionSearch.js +243 -0
  161. package/src/components/Collection/CollectionTop.js +81 -0
  162. package/src/components/Collection/ControlButton.js +27 -0
  163. package/src/components/Collection/Entries/Entries.js +82 -0
  164. package/src/components/Collection/Entries/EntriesCollection.js +277 -0
  165. package/src/components/Collection/Entries/EntriesSearch.js +102 -0
  166. package/src/components/Collection/Entries/EntryCard.js +246 -0
  167. package/src/components/Collection/Entries/EntryListing.js +151 -0
  168. package/src/components/Collection/Entries/__tests__/EntriesCollection.spec.js +163 -0
  169. package/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap +46 -0
  170. package/src/components/Collection/FilterControl.js +39 -0
  171. package/src/components/Collection/GroupControl.js +39 -0
  172. package/src/components/Collection/NestedCollection.js +330 -0
  173. package/src/components/Collection/Sidebar.js +136 -0
  174. package/src/components/Collection/SortControl.js +68 -0
  175. package/src/components/Collection/ViewStyleControl.js +50 -0
  176. package/src/components/Collection/__tests__/Collection.spec.js +75 -0
  177. package/src/components/Collection/__tests__/NestedCollection.spec.js +445 -0
  178. package/src/components/Collection/__tests__/Sidebar.spec.js +87 -0
  179. package/src/components/Collection/__tests__/__snapshots__/Collection.spec.js.snap +144 -0
  180. package/src/components/Collection/__tests__/__snapshots__/NestedCollection.spec.js.snap +550 -0
  181. package/src/components/Collection/__tests__/__snapshots__/Sidebar.spec.js.snap +312 -0
  182. package/src/components/Editor/Editor.js +497 -0
  183. package/src/components/Editor/EditorControlPane/EditorControl.js +452 -0
  184. package/src/components/Editor/EditorControlPane/EditorControlPane.js +269 -0
  185. package/src/components/Editor/EditorControlPane/Widget.js +384 -0
  186. package/src/components/Editor/EditorInterface.js +444 -0
  187. package/src/components/Editor/EditorPreviewPane/EditorPreview.js +40 -0
  188. package/src/components/Editor/EditorPreviewPane/EditorPreviewContent.js +74 -0
  189. package/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js +333 -0
  190. package/src/components/Editor/EditorPreviewPane/PreviewHOC.js +33 -0
  191. package/src/components/Editor/EditorToolbar.js +691 -0
  192. package/src/components/Editor/__tests__/Editor.spec.js +221 -0
  193. package/src/components/Editor/__tests__/EditorToolbar.spec.js +120 -0
  194. package/src/components/Editor/__tests__/__snapshots__/Editor.spec.js.snap +45 -0
  195. package/src/components/Editor/__tests__/__snapshots__/EditorToolbar.spec.js.snap +4233 -0
  196. package/src/components/Editor/withWorkflow.js +61 -0
  197. package/src/components/EditorWidgets/Unknown/UnknownControl.js +17 -0
  198. package/src/components/EditorWidgets/Unknown/UnknownPreview.js +19 -0
  199. package/src/components/EditorWidgets/index.js +5 -0
  200. package/src/components/MediaLibrary/EmptyMessage.js +28 -0
  201. package/src/components/MediaLibrary/MediaLibrary.js +411 -0
  202. package/src/components/MediaLibrary/MediaLibraryButtons.js +136 -0
  203. package/src/components/MediaLibrary/MediaLibraryCard.js +128 -0
  204. package/src/components/MediaLibrary/MediaLibraryCardGrid.js +199 -0
  205. package/src/components/MediaLibrary/MediaLibraryHeader.js +48 -0
  206. package/src/components/MediaLibrary/MediaLibraryModal.js +200 -0
  207. package/src/components/MediaLibrary/MediaLibrarySearch.js +61 -0
  208. package/src/components/MediaLibrary/MediaLibraryTop.js +143 -0
  209. package/src/components/MediaLibrary/__tests__/MediaLibraryButtons.spec.js +45 -0
  210. package/src/components/MediaLibrary/__tests__/MediaLibraryCard.spec.js +49 -0
  211. package/src/components/MediaLibrary/__tests__/__snapshots__/MediaLibraryCard.spec.js.snap +264 -0
  212. package/src/components/UI/DragDrop.js +66 -0
  213. package/src/components/UI/ErrorBoundary.js +214 -0
  214. package/src/components/UI/FileUploadButton.js +24 -0
  215. package/src/components/UI/Modal.js +112 -0
  216. package/src/components/UI/Notifications.tsx +83 -0
  217. package/src/components/UI/SettingsDropdown.js +103 -0
  218. package/src/components/UI/__tests__/ErrorBoundary.spec.js +57 -0
  219. package/src/components/UI/index.js +6 -0
  220. package/src/components/Workflow/Workflow.js +169 -0
  221. package/src/components/Workflow/WorkflowCard.js +177 -0
  222. package/src/components/Workflow/WorkflowList.js +272 -0
  223. package/src/constants/__tests__/configSchema.spec.js +611 -0
  224. package/src/constants/collectionTypes.ts +2 -0
  225. package/src/constants/collectionViews.js +2 -0
  226. package/src/constants/commitProps.ts +2 -0
  227. package/src/constants/configSchema.js +441 -0
  228. package/src/constants/fieldInference.tsx +78 -0
  229. package/src/constants/publishModes.ts +22 -0
  230. package/src/constants/validationErrorTypes.js +6 -0
  231. package/src/formats/__tests__/formats.spec.js +87 -0
  232. package/src/formats/__tests__/frontmatter.spec.js +429 -0
  233. package/src/formats/__tests__/toml.spec.js +9 -0
  234. package/src/formats/__tests__/yaml.spec.js +162 -0
  235. package/src/formats/formats.ts +97 -0
  236. package/src/formats/frontmatter.ts +150 -0
  237. package/src/formats/helpers.ts +14 -0
  238. package/src/formats/json.ts +9 -0
  239. package/src/formats/toml.ts +33 -0
  240. package/src/formats/yaml.ts +58 -0
  241. package/src/index.js +8 -0
  242. package/src/integrations/index.js +35 -0
  243. package/src/integrations/providers/algolia/implementation.js +176 -0
  244. package/src/integrations/providers/assetStore/implementation.js +148 -0
  245. package/src/lib/__tests__/formatters.spec.js +751 -0
  246. package/src/lib/__tests__/i18n.spec.js +792 -0
  247. package/src/lib/__tests__/phrases.spec.js +119 -0
  248. package/src/lib/__tests__/registry.spec.js +261 -0
  249. package/src/lib/__tests__/serializeEntryValues.spec.js +22 -0
  250. package/src/lib/__tests__/urlHelper.spec.js +138 -0
  251. package/src/lib/consoleError.js +7 -0
  252. package/src/lib/formatters.ts +286 -0
  253. package/src/lib/i18n.ts +454 -0
  254. package/src/lib/phrases.js +8 -0
  255. package/src/lib/polyfill.js +9 -0
  256. package/src/lib/registry.js +312 -0
  257. package/src/lib/serializeEntryValues.js +75 -0
  258. package/src/lib/stega.ts +145 -0
  259. package/src/lib/textHelper.js +11 -0
  260. package/src/lib/urlHelper.ts +128 -0
  261. package/src/mediaLibrary.ts +51 -0
  262. package/src/reducers/__tests__/auth.spec.ts +38 -0
  263. package/src/reducers/__tests__/collections.spec.js +610 -0
  264. package/src/reducers/__tests__/config.spec.js +38 -0
  265. package/src/reducers/__tests__/entries.spec.js +694 -0
  266. package/src/reducers/__tests__/entryDraft.spec.js +297 -0
  267. package/src/reducers/__tests__/globalUI.js +43 -0
  268. package/src/reducers/__tests__/integrations.spec.ts +76 -0
  269. package/src/reducers/__tests__/mediaLibrary.spec.js +154 -0
  270. package/src/reducers/__tests__/medias.spec.ts +49 -0
  271. package/src/reducers/auth.ts +46 -0
  272. package/src/reducers/collections.ts +535 -0
  273. package/src/reducers/combinedReducer.ts +11 -0
  274. package/src/reducers/config.ts +38 -0
  275. package/src/reducers/cursors.js +36 -0
  276. package/src/reducers/deploys.ts +52 -0
  277. package/src/reducers/editorialWorkflow.ts +163 -0
  278. package/src/reducers/entries.ts +819 -0
  279. package/src/reducers/entryDraft.js +260 -0
  280. package/src/reducers/globalUI.ts +45 -0
  281. package/src/reducers/index.ts +82 -0
  282. package/src/reducers/integrations.ts +59 -0
  283. package/src/reducers/mediaLibrary.ts +296 -0
  284. package/src/reducers/medias.ts +66 -0
  285. package/src/reducers/notifications.ts +52 -0
  286. package/src/reducers/search.ts +111 -0
  287. package/src/reducers/status.ts +40 -0
  288. package/src/redux/index.ts +18 -0
  289. package/src/redux/middleware/waitUntilAction.ts +64 -0
  290. package/src/routing/__tests__/history.spec.ts +49 -0
  291. package/src/routing/history.ts +17 -0
  292. package/src/types/diacritics.d.ts +1 -0
  293. package/src/types/global.d.ts +8 -0
  294. package/src/types/immutable.ts +49 -0
  295. package/src/types/redux.ts +827 -0
  296. package/src/types/tomlify-j0.4.d.ts +13 -0
  297. package/src/valueObjects/AssetProxy.ts +48 -0
  298. package/src/valueObjects/EditorComponent.js +38 -0
  299. package/src/valueObjects/Entry.ts +63 -0
@@ -0,0 +1,751 @@
1
+ import { List, Map, fromJS } from 'immutable';
2
+
3
+ import {
4
+ commitMessageFormatter,
5
+ prepareSlug,
6
+ slugFormatter,
7
+ previewUrlFormatter,
8
+ summaryFormatter,
9
+ folderFormatter,
10
+ } from '../formatters';
11
+
12
+ jest.spyOn(console, 'warn').mockImplementation(() => {});
13
+ jest.mock('../../reducers/collections');
14
+
15
+ describe('formatters', () => {
16
+ describe('commitMessageFormatter', () => {
17
+ const config = {
18
+ backend: {
19
+ name: 'git-gateway',
20
+ },
21
+ };
22
+
23
+ beforeEach(() => {
24
+ jest.clearAllMocks();
25
+ });
26
+
27
+ it('should return default commit message on create, label_singular', () => {
28
+ const collection = Map({ label_singular: 'Collection' });
29
+
30
+ expect(
31
+ commitMessageFormatter('create', config, {
32
+ slug: 'doc-slug',
33
+ path: 'file-path',
34
+ collection,
35
+ }),
36
+ ).toEqual('Create Collection “doc-slug”');
37
+ });
38
+
39
+ it('should return default commit message on create, label', () => {
40
+ const collection = Map({ label: 'Collections' });
41
+
42
+ expect(
43
+ commitMessageFormatter('update', config, {
44
+ slug: 'doc-slug',
45
+ path: 'file-path',
46
+ collection,
47
+ }),
48
+ ).toEqual('Update Collections “doc-slug”');
49
+ });
50
+
51
+ it('should return default commit message on delete', () => {
52
+ const collection = Map({ label_singular: 'Collection' });
53
+
54
+ expect(
55
+ commitMessageFormatter('delete', config, {
56
+ slug: 'doc-slug',
57
+ path: 'file-path',
58
+ collection,
59
+ }),
60
+ ).toEqual('Delete Collection “doc-slug”');
61
+ });
62
+
63
+ it('should return default commit message on uploadMedia', () => {
64
+ const collection = Map({});
65
+
66
+ expect(
67
+ commitMessageFormatter('uploadMedia', config, {
68
+ slug: 'doc-slug',
69
+ path: 'file-path',
70
+ collection,
71
+ }),
72
+ ).toEqual('Upload “file-path”');
73
+ });
74
+
75
+ it('should return default commit message on deleteMedia', () => {
76
+ const collection = Map({});
77
+
78
+ expect(
79
+ commitMessageFormatter('deleteMedia', config, {
80
+ slug: 'doc-slug',
81
+ path: 'file-path',
82
+ collection,
83
+ }),
84
+ ).toEqual('Delete “file-path”');
85
+ });
86
+
87
+ it('should log warning on unknown variable', () => {
88
+ const config = {
89
+ backend: {
90
+ commit_messages: {
91
+ create: 'Create {{collection}} “{{slug}}” with "{{unknown variable}}"',
92
+ },
93
+ },
94
+ };
95
+ const collection = Map({ label_singular: 'Collection' });
96
+ expect(
97
+ commitMessageFormatter('create', config, {
98
+ slug: 'doc-slug',
99
+ path: 'file-path',
100
+ collection,
101
+ }),
102
+ ).toEqual('Create Collection “doc-slug” with ""');
103
+ expect(console.warn).toHaveBeenCalledTimes(1);
104
+ expect(console.warn).toHaveBeenCalledWith(
105
+ 'Ignoring unknown variable “unknown variable” in commit message template.',
106
+ );
107
+ });
108
+
109
+ it('should return custom commit message on update', () => {
110
+ const config = {
111
+ backend: {
112
+ commit_messages: {
113
+ update: 'Custom commit message',
114
+ },
115
+ },
116
+ };
117
+ const collection = Map({});
118
+ expect(
119
+ commitMessageFormatter('update', config, {
120
+ slug: 'doc-slug',
121
+ path: 'file-path',
122
+ collection,
123
+ }),
124
+ ).toEqual('Custom commit message');
125
+ });
126
+
127
+ it('should use empty values if "authorLogin" and "authorName" are missing in commit message', () => {
128
+ const config = {
129
+ backend: {
130
+ commit_messages: {
131
+ update: '{{author-login}} - {{author-name}}: Create {{collection}} “{{slug}}”',
132
+ },
133
+ },
134
+ };
135
+ const collection = Map({ label_singular: 'Collection' });
136
+ expect(
137
+ commitMessageFormatter(
138
+ 'update',
139
+ config,
140
+ {
141
+ slug: 'doc-slug',
142
+ path: 'file-path',
143
+ collection,
144
+ },
145
+ true,
146
+ ),
147
+ ).toEqual(' - : Create Collection “doc-slug”');
148
+ });
149
+
150
+ it('should return custom create message with author information', () => {
151
+ const config = {
152
+ backend: {
153
+ commit_messages: {
154
+ create: '{{author-login}} - {{author-name}}: Create {{collection}} “{{slug}}”',
155
+ },
156
+ },
157
+ };
158
+ const collection = Map({ label_singular: 'Collection' });
159
+ expect(
160
+ commitMessageFormatter(
161
+ 'create',
162
+ config,
163
+ {
164
+ slug: 'doc-slug',
165
+ path: 'file-path',
166
+ collection,
167
+ authorLogin: 'user-login',
168
+ authorName: 'Test User',
169
+ },
170
+ true,
171
+ ),
172
+ ).toEqual('user-login - Test User: Create Collection “doc-slug”');
173
+ });
174
+
175
+ it('should return custom open authoring message', () => {
176
+ const config = {
177
+ backend: {
178
+ commit_messages: {
179
+ openAuthoring: '{{author-login}} - {{author-name}}: {{message}}',
180
+ },
181
+ },
182
+ };
183
+ const collection = Map({ label_singular: 'Collection' });
184
+ expect(
185
+ commitMessageFormatter(
186
+ 'create',
187
+ config,
188
+ {
189
+ slug: 'doc-slug',
190
+ path: 'file-path',
191
+ collection,
192
+ authorLogin: 'user-login',
193
+ authorName: 'Test User',
194
+ },
195
+ true,
196
+ ),
197
+ ).toEqual('user-login - Test User: Create Collection “doc-slug”');
198
+ });
199
+
200
+ it('should use empty values if "authorLogin" and "authorName" are missing in open authoring message', () => {
201
+ const config = {
202
+ backend: {
203
+ commit_messages: {
204
+ openAuthoring: '{{author-login}} - {{author-name}}: {{message}}',
205
+ },
206
+ },
207
+ };
208
+ const collection = Map({ label_singular: 'Collection' });
209
+ expect(
210
+ commitMessageFormatter(
211
+ 'create',
212
+ config,
213
+ {
214
+ slug: 'doc-slug',
215
+ path: 'file-path',
216
+ collection,
217
+ },
218
+ true,
219
+ ),
220
+ ).toEqual(' - : Create Collection “doc-slug”');
221
+ });
222
+
223
+ it('should log warning on unknown variable in open authoring template', () => {
224
+ const config = {
225
+ backend: {
226
+ commit_messages: {
227
+ openAuthoring: '{{author-email}}: {{message}}',
228
+ },
229
+ },
230
+ };
231
+ const collection = Map({ label_singular: 'Collection' });
232
+ commitMessageFormatter(
233
+ 'create',
234
+ config,
235
+ {
236
+ slug: 'doc-slug',
237
+ path: 'file-path',
238
+ collection,
239
+ authorLogin: 'user-login',
240
+ authorName: 'Test User',
241
+ },
242
+ true,
243
+ );
244
+
245
+ expect(console.warn).toHaveBeenCalledTimes(1);
246
+ expect(console.warn).toHaveBeenCalledWith(
247
+ 'Ignoring unknown variable “author-email” in open authoring message template.',
248
+ );
249
+ });
250
+ });
251
+
252
+ describe('prepareSlug', () => {
253
+ it('should trim slug', () => {
254
+ expect(prepareSlug(' slug ')).toBe('slug');
255
+ });
256
+
257
+ it('should lowercase slug', () => {
258
+ expect(prepareSlug('Slug')).toBe('slug');
259
+ });
260
+
261
+ it('should remove single quotes', () => {
262
+ expect(prepareSlug(`sl'ug`)).toBe('slug');
263
+ });
264
+
265
+ it('should replace periods with slashes', () => {
266
+ expect(prepareSlug(`sl.ug`)).toBe('sl-ug');
267
+ });
268
+ });
269
+
270
+ const slugConfig = {
271
+ encoding: 'unicode',
272
+ clean_accents: false,
273
+ sanitize_replacement: '-',
274
+ };
275
+
276
+ describe('slugFormatter', () => {
277
+ const date = new Date('2020-01-01').valueOf();
278
+ Date.now = jest.spyOn(Date, 'now').mockImplementation(() => date);
279
+
280
+ const { selectIdentifier } = require('../../reducers/collections');
281
+
282
+ beforeEach(() => {
283
+ jest.clearAllMocks();
284
+ });
285
+
286
+ it('should format with default pattern', () => {
287
+ selectIdentifier.mockReturnValueOnce('title');
288
+ expect(slugFormatter(Map(), Map({ title: 'Post Title' }), slugConfig)).toBe('post-title');
289
+ });
290
+
291
+ it('should format with date', () => {
292
+ selectIdentifier.mockReturnValueOnce('title');
293
+
294
+ expect(
295
+ slugFormatter(
296
+ Map({ slug: '{{year}}-{{month}}-{{day}}_{{slug}}' }),
297
+ Map({ title: 'Post Title' }),
298
+ slugConfig,
299
+ ),
300
+ ).toBe('2020-01-01_post-title');
301
+ });
302
+
303
+ it('should format with entry field', () => {
304
+ selectIdentifier.mockReturnValueOnce('slug');
305
+
306
+ expect(
307
+ slugFormatter(
308
+ Map({ slug: '{{fields.slug}}' }),
309
+ Map({ title: 'Post Title', slug: 'entry-slug' }),
310
+ slugConfig,
311
+ ),
312
+ ).toBe('entry-slug');
313
+ });
314
+
315
+ it('should see date filters applied to date from entry if it exists', () => {
316
+ const { selectInferredField } = require('../../reducers/collections');
317
+ selectInferredField.mockReturnValue('date');
318
+ const entryDate = new Date('2026-10-20');
319
+
320
+ expect(
321
+ slugFormatter(
322
+ Map({ slug: '{{year}}-{{month}}-{{day}}-{{title}}' }),
323
+ Map({ date: entryDate, title: 'post title' }),
324
+ slugConfig,
325
+ ),
326
+ ).toBe('2026-10-20-post-title');
327
+ });
328
+
329
+ it('should see date filters applied to publishDate from entry if it exists', () => {
330
+ const { selectInferredField } = require('../../reducers/collections');
331
+ selectInferredField.mockReturnValue('publishDate');
332
+ const entryDate = new Date('2026-10-20');
333
+
334
+ expect(
335
+ slugFormatter(
336
+ Map({ slug: '{{year}}-{{month}}-{{day}}-{{title}}' }),
337
+ Map({ publishDate: entryDate, title: 'post title' }),
338
+ slugConfig,
339
+ ),
340
+ ).toBe('2026-10-20-post-title');
341
+ });
342
+
343
+ it('should return slug', () => {
344
+ selectIdentifier.mockReturnValueOnce('title');
345
+
346
+ expect(
347
+ slugFormatter(Map({ slug: '{{slug}}' }), Map({ title: 'Post Title' }), slugConfig),
348
+ ).toBe('post-title');
349
+ });
350
+
351
+ it('should return slug with path', () => {
352
+ selectIdentifier.mockReturnValueOnce('title');
353
+
354
+ expect(
355
+ slugFormatter(
356
+ Map({ slug: '{{year}}-{{month}}-{{day}}-{{slug}}', path: 'sub_dir/{{year}}/{{slug}}' }),
357
+ Map({ title: 'Post Title' }),
358
+ slugConfig,
359
+ ),
360
+ ).toBe('sub_dir/2020/2020-01-01-post-title');
361
+ });
362
+
363
+ it('should only sanitize template variables', () => {
364
+ selectIdentifier.mockReturnValueOnce('title');
365
+
366
+ expect(
367
+ slugFormatter(
368
+ Map({
369
+ slug: '{{year}}-{{month}}-{{day}}-{{slug}}.en',
370
+ path: 'sub_dir/{{year}}/{{slug}}',
371
+ }),
372
+ Map({ title: 'Post Title' }),
373
+ slugConfig,
374
+ ),
375
+ ).toBe('sub_dir/2020/2020-01-01-post-title.en');
376
+ });
377
+
378
+ it(`should replace '.' in path with -`, () => {
379
+ selectIdentifier.mockReturnValueOnce('title');
380
+
381
+ expect(
382
+ slugFormatter(
383
+ Map({
384
+ slug: '{{slug}}.en',
385
+ path: '../dir/{{slug}}',
386
+ }),
387
+ Map({ title: 'Post Title' }),
388
+ slugConfig,
389
+ ),
390
+ ).toBe('--/dir/post-title.en');
391
+ });
392
+ });
393
+
394
+ describe('previewUrlFormatter', () => {
395
+ it('should return undefined when missing baseUrl', () => {
396
+ expect(previewUrlFormatter('')).toBeUndefined();
397
+ });
398
+
399
+ it('should return baseUrl for collection with no preview_path', () => {
400
+ expect(previewUrlFormatter('https://www.example.com', Map({}))).toBe(
401
+ 'https://www.example.com',
402
+ );
403
+ });
404
+
405
+ it('should return preview url based on preview_path and preview_path_date_field', () => {
406
+ const date = new Date('2020-01-02T13:28:27.679Z');
407
+ expect(
408
+ previewUrlFormatter(
409
+ 'https://www.example.com',
410
+ Map({
411
+ preview_path: '{{year}}/{{slug}}/{{title}}/{{fields.slug}}',
412
+ preview_path_date_field: 'customDateField',
413
+ }),
414
+ 'backendSlug',
415
+ Map({ data: Map({ customDateField: date, slug: 'entrySlug', title: 'title' }) }),
416
+ slugConfig,
417
+ ),
418
+ ).toBe('https://www.example.com/2020/backendslug/title/entryslug');
419
+ });
420
+
421
+ it('should return preview url for files in file collection', () => {
422
+ const file = Map({ name: 'about-file', preview_path: '{{slug}}/{{fields.slug}}/{{title}}' });
423
+
424
+ const { getFileFromSlug } = require('../../reducers/collections');
425
+ getFileFromSlug.mockReturnValue(file);
426
+
427
+ expect(
428
+ previewUrlFormatter(
429
+ 'https://www.example.com',
430
+ Map({
431
+ preview_path: '{{slug}}/{{title}}/{{fields.slug}}',
432
+ type: 'file_based_collection',
433
+ files: List([file]),
434
+ }),
435
+ 'backendSlug',
436
+ Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }),
437
+ slugConfig,
438
+ ),
439
+ ).toBe('https://www.example.com/backendslug/about-the-project/title');
440
+ });
441
+
442
+ it('should return preview url for files in file collection when defined on file-level only', () => {
443
+ const file = Map({ name: 'about-file', preview_path: '{{slug}}/{{fields.slug}}/{{title}}' });
444
+
445
+ const { getFileFromSlug } = require('../../reducers/collections');
446
+ getFileFromSlug.mockReturnValue(file);
447
+
448
+ expect(
449
+ previewUrlFormatter(
450
+ 'https://www.example.com',
451
+ Map({
452
+ type: 'file_based_collection',
453
+ files: List([file]),
454
+ }),
455
+ 'backendSlug',
456
+ Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }),
457
+ slugConfig,
458
+ ),
459
+ ).toBe('https://www.example.com/backendslug/about-the-project/title');
460
+ });
461
+
462
+ it('should fall back to collection preview url for files in file collection', () => {
463
+ const file = Map({ name: 'about-file' });
464
+
465
+ const { getFileFromSlug } = require('../../reducers/collections');
466
+ getFileFromSlug.mockReturnValue(file);
467
+
468
+ expect(
469
+ previewUrlFormatter(
470
+ 'https://www.example.com',
471
+ Map({
472
+ preview_path: '{{slug}}/{{title}}/{{fields.slug}}',
473
+ type: 'file_based_collection',
474
+ files: List([file]),
475
+ }),
476
+ 'backendSlug',
477
+ Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }),
478
+ slugConfig,
479
+ ),
480
+ ).toBe('https://www.example.com/backendslug/title/about-the-project');
481
+ });
482
+
483
+ it('should infer date field when preview_path_date_field is not configured', () => {
484
+ const { selectInferredField } = require('../../reducers/collections');
485
+ selectInferredField.mockReturnValue('date');
486
+
487
+ const date = new Date('2020-01-02T13:28:27.679Z');
488
+ expect(
489
+ previewUrlFormatter(
490
+ 'https://www.example.com',
491
+ fromJS({
492
+ name: 'posts',
493
+ preview_path: '{{year}}/{{month}}/{{slug}}/{{title}}/{{fields.slug}}',
494
+ }),
495
+ 'backendSlug',
496
+ Map({ data: Map({ date, slug: 'entrySlug', title: 'title' }) }),
497
+ slugConfig,
498
+ ),
499
+ ).toBe('https://www.example.com/2020/01/backendslug/title/entryslug');
500
+ });
501
+
502
+ it('should compile filename and extension template values', () => {
503
+ expect(
504
+ previewUrlFormatter(
505
+ 'https://www.example.com',
506
+ Map({
507
+ preview_path: 'posts/{{filename}}.{{extension}}',
508
+ }),
509
+ 'backendSlug',
510
+ Map({ data: Map({}), path: 'src/content/posts/title.md' }),
511
+ slugConfig,
512
+ ),
513
+ ).toBe('https://www.example.com/posts/title.md');
514
+ });
515
+
516
+ it('should compile the dirname template value to empty in a regular collection', () => {
517
+ expect(
518
+ previewUrlFormatter(
519
+ 'https://www.example.com',
520
+ Map({
521
+ folder: '_portfolio',
522
+ preview_path: 'portfolio/{{dirname}}',
523
+ }),
524
+ 'backendSlug',
525
+ Map({ data: Map({}), path: '_portfolio/i-am-the-slug.md' }),
526
+ slugConfig,
527
+ ),
528
+ ).toBe('https://www.example.com/portfolio/');
529
+ });
530
+
531
+ it('should compile dirname template value when in a nested collection', () => {
532
+ expect(
533
+ previewUrlFormatter(
534
+ 'https://www.example.com',
535
+ Map({
536
+ folder: '_portfolio',
537
+ preview_path: 'portfolio/{{dirname}}',
538
+ nested: { depth: 100 },
539
+ meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } },
540
+ }),
541
+ 'backendSlug',
542
+ Map({ data: Map({}), path: '_portfolio/drawing/i-am-the-slug/index.md' }),
543
+ slugConfig,
544
+ ),
545
+ ).toBe('https://www.example.com/portfolio/drawing/i-am-the-slug');
546
+ });
547
+
548
+ it('should log error and ignore preview_path when date is missing', () => {
549
+ jest.spyOn(console, 'error').mockImplementation(() => {});
550
+ expect(
551
+ previewUrlFormatter(
552
+ 'https://www.example.com',
553
+ Map({
554
+ name: 'posts',
555
+ preview_path: '{{year}}',
556
+ preview_path_date_field: 'date',
557
+ }),
558
+ 'backendSlug',
559
+ Map({ data: Map({}) }),
560
+ slugConfig,
561
+ ),
562
+ ).toBe('https://www.example.com');
563
+
564
+ expect(console.error).toHaveBeenCalledTimes(1);
565
+ expect(console.error).toHaveBeenCalledWith(
566
+ 'Collection "posts" configuration error:\n `preview_path_date_field` must be a field with a valid date. Ignoring `preview_path`.',
567
+ );
568
+ });
569
+ });
570
+
571
+ describe('summaryFormatter', () => {
572
+ it('should return summary from template', () => {
573
+ const { selectInferredField } = require('../../reducers/collections');
574
+ selectInferredField.mockReturnValue('date');
575
+
576
+ const date = new Date('2020-01-02T13:28:27.679Z');
577
+ const entry = fromJS({ data: { date, title: 'title' } });
578
+ const collection = fromJS({ fields: [{ name: 'date', widget: 'date' }] });
579
+
580
+ expect(summaryFormatter('{{title}}-{{year}}', entry, collection)).toBe('title-2020');
581
+ });
582
+
583
+ it('should handle filename and extension variables', () => {
584
+ const { selectInferredField } = require('../../reducers/collections');
585
+ selectInferredField.mockReturnValue('date');
586
+
587
+ const date = new Date('2020-01-02T13:28:27.679Z');
588
+ const entry = fromJS({ path: 'post.md', data: { date, title: 'title' } });
589
+ const collection = fromJS({ fields: [{ name: 'date', widget: 'date' }] });
590
+
591
+ expect(
592
+ summaryFormatter('{{title}}-{{year}}-{{filename}}.{{extension}}', entry, collection),
593
+ ).toBe('title-2020-post.md');
594
+ });
595
+
596
+ it('should handle the dirname variable in a regular collection', () => {
597
+ const { selectInferredField } = require('../../reducers/collections');
598
+ selectInferredField.mockReturnValue('date');
599
+
600
+ const date = new Date('2020-01-02T13:28:27.679Z');
601
+ const entry = fromJS({
602
+ path: '_portfolio/drawing.md',
603
+ data: { date, title: 'title' },
604
+ });
605
+ const collection = fromJS({
606
+ folder: '_portfolio',
607
+ fields: [{ name: 'date', widget: 'date' }],
608
+ });
609
+
610
+ expect(summaryFormatter('{{dirname}}/{{title}}-{{year}}', entry, collection)).toBe(
611
+ '/title-2020',
612
+ );
613
+ });
614
+
615
+ it('should handle the dirname variable in a nested collection', () => {
616
+ const { selectInferredField } = require('../../reducers/collections');
617
+ selectInferredField.mockReturnValue('date');
618
+
619
+ const date = new Date('2020-01-02T13:28:27.679Z');
620
+ const entry = fromJS({
621
+ path: '_portfolio/drawing/index.md',
622
+ data: { date, title: 'title' },
623
+ });
624
+ const collection = fromJS({
625
+ folder: '_portfolio',
626
+ nested: { depth: 100 },
627
+ meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } },
628
+ fields: [{ name: 'date', widget: 'date' }],
629
+ });
630
+
631
+ expect(summaryFormatter('{{dirname}}/{{title}}-{{year}}', entry, collection)).toBe(
632
+ 'drawing/title-2020',
633
+ );
634
+ });
635
+ });
636
+
637
+ describe('folderFormatter', () => {
638
+ it('should return folder is entry is undefined', () => {
639
+ expect(folderFormatter('static/images', undefined)).toBe('static/images');
640
+ });
641
+
642
+ it('should return folder is entry data is undefined', () => {
643
+ expect(folderFormatter('static/images', Map({}))).toBe('static/images');
644
+ });
645
+
646
+ it('should return formatted folder', () => {
647
+ const { selectIdentifier } = require('../../reducers/collections');
648
+ selectIdentifier.mockReturnValue('title');
649
+
650
+ const entry = fromJS({
651
+ path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
652
+ data: { title: 'Deployment With NanoBox', category: 'Hosting And Deployment' },
653
+ });
654
+ const collection = fromJS({});
655
+
656
+ expect(
657
+ folderFormatter(
658
+ '../../../{{media_folder}}/{{category}}/{{slug}}',
659
+ entry,
660
+ collection,
661
+ 'static/images',
662
+ 'media_folder',
663
+ slugConfig,
664
+ ),
665
+ ).toBe('../../../static/images/hosting-and-deployment/deployment-with-nanobox');
666
+ });
667
+
668
+ it('should compile filename template value', () => {
669
+ const entry = fromJS({
670
+ path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
671
+ data: { category: 'Hosting And Deployment' },
672
+ });
673
+ const collection = fromJS({});
674
+
675
+ expect(
676
+ folderFormatter(
677
+ '../../../{{media_folder}}/{{category}}/{{filename}}',
678
+ entry,
679
+ collection,
680
+ 'static/images',
681
+ 'media_folder',
682
+ slugConfig,
683
+ ),
684
+ ).toBe('../../../static/images/hosting-and-deployment/deployment-with-nanobox');
685
+ });
686
+
687
+ it('should compile extension template value', () => {
688
+ const entry = fromJS({
689
+ path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
690
+ data: { category: 'Hosting And Deployment' },
691
+ });
692
+ const collection = fromJS({});
693
+
694
+ expect(
695
+ folderFormatter(
696
+ '{{extension}}',
697
+ entry,
698
+ collection,
699
+ 'static/images',
700
+ 'media_folder',
701
+ slugConfig,
702
+ ),
703
+ ).toBe('md');
704
+ });
705
+
706
+ it('should compile dirname template value in a regular collection', () => {
707
+ const entry = fromJS({
708
+ path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
709
+ data: { category: 'Hosting And Deployment' },
710
+ });
711
+ const collection = fromJS({
712
+ folder: 'content/en/',
713
+ });
714
+
715
+ expect(
716
+ folderFormatter(
717
+ '{{dirname}}',
718
+ entry,
719
+ collection,
720
+ 'static/images',
721
+ 'media_folder',
722
+ slugConfig,
723
+ ),
724
+ ).toBe('hosting-and-deployment');
725
+ });
726
+
727
+ it('should compile dirname template value in a nested collection', () => {
728
+ const entry = fromJS({
729
+ path: '_portfolio/drawing/i-am-the-slug/index.md',
730
+ data: { category: 'Hosting And Deployment' },
731
+ });
732
+ const collection = fromJS({
733
+ folder: '_portfolio',
734
+ nested: { depth: 100 },
735
+ meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } },
736
+ fields: [{ name: 'date', widget: 'date' }],
737
+ });
738
+
739
+ expect(
740
+ folderFormatter(
741
+ '{{dirname}}',
742
+ entry,
743
+ collection,
744
+ 'static/images',
745
+ 'media_folder',
746
+ slugConfig,
747
+ ),
748
+ ).toBe('drawing/i-am-the-slug');
749
+ });
750
+ });
751
+ });