@wix/bex-core 2.328.0 → 2.330.0

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 (254) hide show
  1. package/dist/cjs/assets/locale/messages_ar.json +12 -0
  2. package/dist/cjs/assets/locale/messages_bg.json +12 -0
  3. package/dist/cjs/assets/locale/messages_ca.json +12 -0
  4. package/dist/cjs/assets/locale/messages_cs.json +12 -0
  5. package/dist/cjs/assets/locale/messages_da.json +17 -5
  6. package/dist/cjs/assets/locale/messages_de.json +12 -0
  7. package/dist/cjs/assets/locale/messages_el.json +12 -0
  8. package/dist/cjs/assets/locale/messages_en.json +12 -0
  9. package/dist/cjs/assets/locale/messages_es.json +16 -4
  10. package/dist/cjs/assets/locale/messages_fi.json +12 -0
  11. package/dist/cjs/assets/locale/messages_fr.json +12 -0
  12. package/dist/cjs/assets/locale/messages_he.json +12 -0
  13. package/dist/cjs/assets/locale/messages_hi.json +17 -5
  14. package/dist/cjs/assets/locale/messages_hu.json +12 -0
  15. package/dist/cjs/assets/locale/messages_id.json +17 -5
  16. package/dist/cjs/assets/locale/messages_it.json +17 -5
  17. package/dist/cjs/assets/locale/messages_ja.json +12 -0
  18. package/dist/cjs/assets/locale/messages_ko.json +17 -5
  19. package/dist/cjs/assets/locale/messages_lt.json +12 -0
  20. package/dist/cjs/assets/locale/messages_ms.json +12 -0
  21. package/dist/cjs/assets/locale/messages_nl.json +16 -4
  22. package/dist/cjs/assets/locale/messages_no.json +12 -0
  23. package/dist/cjs/assets/locale/messages_pl.json +12 -0
  24. package/dist/cjs/assets/locale/messages_pt.json +16 -4
  25. package/dist/cjs/assets/locale/messages_ro.json +12 -0
  26. package/dist/cjs/assets/locale/messages_ru.json +12 -0
  27. package/dist/cjs/assets/locale/messages_sk.json +12 -0
  28. package/dist/cjs/assets/locale/messages_sl.json +12 -0
  29. package/dist/cjs/assets/locale/messages_sv.json +17 -5
  30. package/dist/cjs/assets/locale/messages_th.json +17 -5
  31. package/dist/cjs/assets/locale/messages_tl.json +12 -0
  32. package/dist/cjs/assets/locale/messages_tr.json +17 -5
  33. package/dist/cjs/assets/locale/messages_uk.json +12 -0
  34. package/dist/cjs/assets/locale/messages_vi.json +17 -5
  35. package/dist/cjs/assets/locale/messages_zh.json +12 -0
  36. package/dist/cjs/assets/locale/version_file +1 -1
  37. package/dist/cjs/assets/visual-e2e-locale/messages_visuale2e.json +5 -1
  38. package/dist/cjs/hooks/useImport.js +7 -5
  39. package/dist/cjs/hooks/useImport.js.map +1 -1
  40. package/dist/cjs/hooks/useImportConfig.js.map +1 -1
  41. package/dist/cjs/index.js +5 -2
  42. package/dist/cjs/index.js.map +1 -1
  43. package/dist/cjs/services/DataExtensionFieldOperationsService.js +3 -3
  44. package/dist/cjs/services/DataExtensionFieldOperationsService.js.map +1 -1
  45. package/dist/cjs/services/translations.js.map +1 -1
  46. package/dist/cjs/state/ArchiveFieldModalState.js +5 -8
  47. package/dist/cjs/state/ArchiveFieldModalState.js.map +1 -1
  48. package/dist/cjs/state/CustomFieldModalBiReporter.js +5 -9
  49. package/dist/cjs/state/CustomFieldModalBiReporter.js.map +1 -1
  50. package/dist/cjs/state/CustomFieldModalState.js +32 -71
  51. package/dist/cjs/state/CustomFieldModalState.js.map +1 -1
  52. package/dist/cjs/state/DataExtensionState.js +0 -12
  53. package/dist/cjs/state/DataExtensionState.js.map +1 -1
  54. package/dist/cjs/state/DeleteFieldModalState.js +9 -11
  55. package/dist/cjs/state/DeleteFieldModalState.js.map +1 -1
  56. package/dist/cjs/state/{DataExtensionStateBIReporter.js → FieldOperationsBIReporter.js} +7 -32
  57. package/dist/cjs/state/FieldOperationsBIReporter.js.map +1 -0
  58. package/dist/cjs/state/ImportState/ImportState.js +383 -109
  59. package/dist/cjs/state/ImportState/ImportState.js.map +1 -1
  60. package/dist/cjs/state/ImportState/__tests__/ImportState.engine.spec.js +610 -0
  61. package/dist/cjs/state/ImportState/__tests__/ImportState.engine.spec.js.map +1 -0
  62. package/dist/cjs/state/ImportState/__tests__/hubJobClient.spec.js +209 -0
  63. package/dist/cjs/state/ImportState/__tests__/hubJobClient.spec.js.map +1 -0
  64. package/dist/cjs/state/ImportState/__tests__/importJobPoller.spec.js +86 -0
  65. package/dist/cjs/state/ImportState/__tests__/importJobPoller.spec.js.map +1 -0
  66. package/dist/cjs/state/ImportState/__tests__/parseCsv.spec.js +52 -0
  67. package/dist/cjs/state/ImportState/__tests__/parseCsv.spec.js.map +1 -0
  68. package/dist/cjs/state/ImportState/__tests__/rewriteCsv.spec.js +81 -0
  69. package/dist/cjs/state/ImportState/__tests__/rewriteCsv.spec.js.map +1 -0
  70. package/dist/cjs/state/ImportState/fieldEdit.js +46 -0
  71. package/dist/cjs/state/ImportState/fieldEdit.js.map +1 -0
  72. package/dist/cjs/state/ImportState/hubJobClient.js +101 -0
  73. package/dist/cjs/state/ImportState/hubJobClient.js.map +1 -0
  74. package/dist/cjs/state/ImportState/importJobPoller.js +44 -0
  75. package/dist/cjs/state/ImportState/importJobPoller.js.map +1 -0
  76. package/dist/cjs/state/ImportState/index.js +12 -0
  77. package/dist/cjs/state/ImportState/index.js.map +1 -1
  78. package/dist/cjs/state/ImportState/parseCsv.js +39 -10
  79. package/dist/cjs/state/ImportState/parseCsv.js.map +1 -1
  80. package/dist/cjs/state/ImportState/rewriteCsv.js +46 -0
  81. package/dist/cjs/state/ImportState/rewriteCsv.js.map +1 -0
  82. package/dist/cjs/state/formatPermissionsForBi.js +36 -0
  83. package/dist/cjs/state/formatPermissionsForBi.js.map +1 -0
  84. package/dist/cjs/testkit/WixPatternsContainer.driver.js +4 -1
  85. package/dist/cjs/testkit/WixPatternsContainer.driver.js.map +1 -1
  86. package/dist/cjs/testkit/client.js +6 -0
  87. package/dist/cjs/testkit/client.js.map +1 -1
  88. package/dist/cjs/testkit/hubJobImportMocks.js +166 -0
  89. package/dist/cjs/testkit/hubJobImportMocks.js.map +1 -0
  90. package/dist/cjs/testkit/index.js +6 -0
  91. package/dist/cjs/testkit/index.js.map +1 -1
  92. package/dist/cjs/types/FieldsSource.js +4 -0
  93. package/dist/cjs/types/FieldsSource.js.map +1 -0
  94. package/dist/cjs/types/Schema.js.map +1 -1
  95. package/dist/cjs/types/SchemaSource.js +81 -0
  96. package/dist/cjs/types/SchemaSource.js.map +1 -0
  97. package/dist/esm/assets/locale/messages_ar.json +12 -0
  98. package/dist/esm/assets/locale/messages_bg.json +12 -0
  99. package/dist/esm/assets/locale/messages_ca.json +12 -0
  100. package/dist/esm/assets/locale/messages_cs.json +12 -0
  101. package/dist/esm/assets/locale/messages_da.json +17 -5
  102. package/dist/esm/assets/locale/messages_de.json +12 -0
  103. package/dist/esm/assets/locale/messages_el.json +12 -0
  104. package/dist/esm/assets/locale/messages_en.json +12 -0
  105. package/dist/esm/assets/locale/messages_es.json +16 -4
  106. package/dist/esm/assets/locale/messages_fi.json +12 -0
  107. package/dist/esm/assets/locale/messages_fr.json +12 -0
  108. package/dist/esm/assets/locale/messages_he.json +12 -0
  109. package/dist/esm/assets/locale/messages_hi.json +17 -5
  110. package/dist/esm/assets/locale/messages_hu.json +12 -0
  111. package/dist/esm/assets/locale/messages_id.json +17 -5
  112. package/dist/esm/assets/locale/messages_it.json +17 -5
  113. package/dist/esm/assets/locale/messages_ja.json +12 -0
  114. package/dist/esm/assets/locale/messages_ko.json +17 -5
  115. package/dist/esm/assets/locale/messages_lt.json +12 -0
  116. package/dist/esm/assets/locale/messages_ms.json +12 -0
  117. package/dist/esm/assets/locale/messages_nl.json +16 -4
  118. package/dist/esm/assets/locale/messages_no.json +12 -0
  119. package/dist/esm/assets/locale/messages_pl.json +12 -0
  120. package/dist/esm/assets/locale/messages_pt.json +16 -4
  121. package/dist/esm/assets/locale/messages_ro.json +12 -0
  122. package/dist/esm/assets/locale/messages_ru.json +12 -0
  123. package/dist/esm/assets/locale/messages_sk.json +12 -0
  124. package/dist/esm/assets/locale/messages_sl.json +12 -0
  125. package/dist/esm/assets/locale/messages_sv.json +17 -5
  126. package/dist/esm/assets/locale/messages_th.json +17 -5
  127. package/dist/esm/assets/locale/messages_tl.json +12 -0
  128. package/dist/esm/assets/locale/messages_tr.json +17 -5
  129. package/dist/esm/assets/locale/messages_uk.json +12 -0
  130. package/dist/esm/assets/locale/messages_vi.json +17 -5
  131. package/dist/esm/assets/locale/messages_zh.json +12 -0
  132. package/dist/esm/assets/locale/version_file +1 -1
  133. package/dist/esm/assets/visual-e2e-locale/messages_visuale2e.json +5 -1
  134. package/dist/esm/hooks/useImport.js +5 -4
  135. package/dist/esm/hooks/useImport.js.map +1 -1
  136. package/dist/esm/hooks/useImportConfig.js.map +1 -1
  137. package/dist/esm/index.js +1 -0
  138. package/dist/esm/index.js.map +1 -1
  139. package/dist/esm/services/DataExtensionFieldOperationsService.js +3 -3
  140. package/dist/esm/services/DataExtensionFieldOperationsService.js.map +1 -1
  141. package/dist/esm/state/ArchiveFieldModalState.js +4 -7
  142. package/dist/esm/state/ArchiveFieldModalState.js.map +1 -1
  143. package/dist/esm/state/CustomFieldModalBiReporter.js +5 -8
  144. package/dist/esm/state/CustomFieldModalBiReporter.js.map +1 -1
  145. package/dist/esm/state/CustomFieldModalState.js +28 -52
  146. package/dist/esm/state/CustomFieldModalState.js.map +1 -1
  147. package/dist/esm/state/DataExtensionState.js +0 -10
  148. package/dist/esm/state/DataExtensionState.js.map +1 -1
  149. package/dist/esm/state/DeleteFieldModalState.js +7 -10
  150. package/dist/esm/state/DeleteFieldModalState.js.map +1 -1
  151. package/dist/esm/state/{DataExtensionStateBIReporter.js → FieldOperationsBIReporter.js} +5 -25
  152. package/dist/esm/state/FieldOperationsBIReporter.js.map +1 -0
  153. package/dist/esm/state/ImportState/ImportState.js +314 -101
  154. package/dist/esm/state/ImportState/ImportState.js.map +1 -1
  155. package/dist/esm/state/ImportState/__tests__/ImportState.engine.spec.js +434 -0
  156. package/dist/esm/state/ImportState/__tests__/ImportState.engine.spec.js.map +1 -0
  157. package/dist/esm/state/ImportState/__tests__/hubJobClient.spec.js +139 -0
  158. package/dist/esm/state/ImportState/__tests__/hubJobClient.spec.js.map +1 -0
  159. package/dist/esm/state/ImportState/__tests__/importJobPoller.spec.js +62 -0
  160. package/dist/esm/state/ImportState/__tests__/importJobPoller.spec.js.map +1 -0
  161. package/dist/esm/state/ImportState/__tests__/parseCsv.spec.js +59 -0
  162. package/dist/esm/state/ImportState/__tests__/parseCsv.spec.js.map +1 -0
  163. package/dist/esm/state/ImportState/__tests__/rewriteCsv.spec.js +68 -0
  164. package/dist/esm/state/ImportState/__tests__/rewriteCsv.spec.js.map +1 -0
  165. package/dist/esm/state/ImportState/fieldEdit.js +33 -0
  166. package/dist/esm/state/ImportState/fieldEdit.js.map +1 -0
  167. package/dist/esm/state/ImportState/hubJobClient.js +43 -0
  168. package/dist/esm/state/ImportState/hubJobClient.js.map +1 -0
  169. package/dist/esm/state/ImportState/importJobPoller.js +34 -0
  170. package/dist/esm/state/ImportState/importJobPoller.js.map +1 -0
  171. package/dist/esm/state/ImportState/index.js +2 -0
  172. package/dist/esm/state/ImportState/index.js.map +1 -1
  173. package/dist/esm/state/ImportState/parseCsv.js +34 -10
  174. package/dist/esm/state/ImportState/parseCsv.js.map +1 -1
  175. package/dist/esm/state/ImportState/rewriteCsv.js +39 -0
  176. package/dist/esm/state/ImportState/rewriteCsv.js.map +1 -0
  177. package/dist/esm/state/formatPermissionsForBi.js +26 -0
  178. package/dist/esm/state/formatPermissionsForBi.js.map +1 -0
  179. package/dist/esm/testkit/WixPatternsContainer.driver.js +4 -1
  180. package/dist/esm/testkit/WixPatternsContainer.driver.js.map +1 -1
  181. package/dist/esm/testkit/client.js +1 -0
  182. package/dist/esm/testkit/client.js.map +1 -1
  183. package/dist/esm/testkit/hubJobImportMocks.js +173 -0
  184. package/dist/esm/testkit/hubJobImportMocks.js.map +1 -0
  185. package/dist/esm/testkit/index.js +1 -0
  186. package/dist/esm/testkit/index.js.map +1 -1
  187. package/dist/esm/types/FieldsSource.js +2 -0
  188. package/dist/esm/types/FieldsSource.js.map +1 -0
  189. package/dist/esm/types/SchemaSource.js +52 -0
  190. package/dist/esm/types/SchemaSource.js.map +1 -0
  191. package/dist/types/hooks/useImport.d.ts +5 -3
  192. package/dist/types/hooks/useImport.d.ts.map +1 -1
  193. package/dist/types/hooks/useImportConfig.d.ts +2 -3
  194. package/dist/types/hooks/useImportConfig.d.ts.map +1 -1
  195. package/dist/types/index.d.ts +4 -1
  196. package/dist/types/index.d.ts.map +1 -1
  197. package/dist/types/services/translations.d.ts +1 -1
  198. package/dist/types/services/translations.d.ts.map +1 -1
  199. package/dist/types/state/ArchiveFieldModalState.d.ts +7 -5
  200. package/dist/types/state/ArchiveFieldModalState.d.ts.map +1 -1
  201. package/dist/types/state/CustomFieldModalBiReporter.d.ts +4 -3
  202. package/dist/types/state/CustomFieldModalBiReporter.d.ts.map +1 -1
  203. package/dist/types/state/CustomFieldModalState.d.ts +12 -6
  204. package/dist/types/state/CustomFieldModalState.d.ts.map +1 -1
  205. package/dist/types/state/DataExtensionState.d.ts +0 -2
  206. package/dist/types/state/DataExtensionState.d.ts.map +1 -1
  207. package/dist/types/state/DeleteFieldModalState.d.ts +8 -5
  208. package/dist/types/state/DeleteFieldModalState.d.ts.map +1 -1
  209. package/dist/types/state/{DataExtensionStateBIReporter.d.ts → FieldOperationsBIReporter.d.ts} +4 -5
  210. package/dist/types/state/FieldOperationsBIReporter.d.ts.map +1 -0
  211. package/dist/types/state/ImportState/ImportState.d.ts +120 -20
  212. package/dist/types/state/ImportState/ImportState.d.ts.map +1 -1
  213. package/dist/types/state/ImportState/__tests__/ImportState.engine.spec.d.ts +2 -0
  214. package/dist/types/state/ImportState/__tests__/ImportState.engine.spec.d.ts.map +1 -0
  215. package/dist/types/state/ImportState/__tests__/hubJobClient.spec.d.ts +2 -0
  216. package/dist/types/state/ImportState/__tests__/hubJobClient.spec.d.ts.map +1 -0
  217. package/dist/types/state/ImportState/__tests__/importJobPoller.spec.d.ts +2 -0
  218. package/dist/types/state/ImportState/__tests__/importJobPoller.spec.d.ts.map +1 -0
  219. package/dist/types/state/ImportState/__tests__/parseCsv.spec.d.ts +2 -0
  220. package/dist/types/state/ImportState/__tests__/parseCsv.spec.d.ts.map +1 -0
  221. package/dist/types/state/ImportState/__tests__/rewriteCsv.spec.d.ts +2 -0
  222. package/dist/types/state/ImportState/__tests__/rewriteCsv.spec.d.ts.map +1 -0
  223. package/dist/types/state/ImportState/fieldEdit.d.ts +10 -0
  224. package/dist/types/state/ImportState/fieldEdit.d.ts.map +1 -0
  225. package/dist/types/state/ImportState/hubJobClient.d.ts +47 -0
  226. package/dist/types/state/ImportState/hubJobClient.d.ts.map +1 -0
  227. package/dist/types/state/ImportState/importJobPoller.d.ts +20 -0
  228. package/dist/types/state/ImportState/importJobPoller.d.ts.map +1 -0
  229. package/dist/types/state/ImportState/index.d.ts +2 -0
  230. package/dist/types/state/ImportState/index.d.ts.map +1 -1
  231. package/dist/types/state/ImportState/parseCsv.d.ts +7 -0
  232. package/dist/types/state/ImportState/parseCsv.d.ts.map +1 -1
  233. package/dist/types/state/ImportState/rewriteCsv.d.ts +25 -0
  234. package/dist/types/state/ImportState/rewriteCsv.d.ts.map +1 -0
  235. package/dist/types/state/formatPermissionsForBi.d.ts +7 -0
  236. package/dist/types/state/formatPermissionsForBi.d.ts.map +1 -0
  237. package/dist/types/testkit/WixPatternsContainer.driver.d.ts +22 -0
  238. package/dist/types/testkit/WixPatternsContainer.driver.d.ts.map +1 -1
  239. package/dist/types/testkit/client.d.ts +1 -0
  240. package/dist/types/testkit/client.d.ts.map +1 -1
  241. package/dist/types/testkit/hubJobImportMocks.d.ts +40 -0
  242. package/dist/types/testkit/hubJobImportMocks.d.ts.map +1 -0
  243. package/dist/types/testkit/index.d.ts +1 -0
  244. package/dist/types/testkit/index.d.ts.map +1 -1
  245. package/dist/types/types/FieldsSource.d.ts +116 -0
  246. package/dist/types/types/FieldsSource.d.ts.map +1 -0
  247. package/dist/types/types/Schema.d.ts +28 -11
  248. package/dist/types/types/Schema.d.ts.map +1 -1
  249. package/dist/types/types/SchemaSource.d.ts +60 -0
  250. package/dist/types/types/SchemaSource.d.ts.map +1 -0
  251. package/package.json +4 -4
  252. package/dist/cjs/state/DataExtensionStateBIReporter.js.map +0 -1
  253. package/dist/esm/state/DataExtensionStateBIReporter.js.map +0 -1
  254. package/dist/types/state/DataExtensionStateBIReporter.d.ts.map +0 -1
@@ -0,0 +1,610 @@
1
+ "use strict";
2
+
3
+ var _util = require("util");
4
+ var _mobx = require("mobx");
5
+ var _ImportState = require("../ImportState");
6
+ var _types = require("@wix/bex-utils/@wix/ambassador-hub-v1-job/types");
7
+ var _hubJobClient = require("../hubJobClient");
8
+ // The jsdom test env doesn't expose these globals, but `onFileSelected` decodes
9
+ // the uploaded bytes with `TextDecoder` (and the test encodes CSV with
10
+ // `TextEncoder`). Polyfill from Node's `util`.
11
+ const globalScope = global;
12
+ globalScope.TextEncoder = globalScope.TextEncoder ?? _util.TextEncoder;
13
+ globalScope.TextDecoder = globalScope.TextDecoder ?? _util.TextDecoder;
14
+ jest.mock('../hubJobClient');
15
+ const COMPLETED_JOB = {
16
+ id: 'job-1',
17
+ status: _types.JobStatus.COMPLETED,
18
+ progress: {
19
+ itemsSuccessful: 2,
20
+ itemsRejected: 0,
21
+ total: 2,
22
+ current: 2
23
+ }
24
+ };
25
+ function setup(clientOverrides = {}, extraProps = {}) {
26
+ const client = {
27
+ uploadFile: jest.fn(async _file => 'file-1'),
28
+ submitImportJob: jest.fn(async () => 'job-1'),
29
+ getJob: jest.fn(async () => COMPLETED_JOB),
30
+ terminateJob: jest.fn(async () => undefined),
31
+ listMovementLogs: jest.fn(),
32
+ ...clientOverrides
33
+ };
34
+ _hubJobClient.createHubJobClient.mockReturnValue(client);
35
+ const importModalState = {
36
+ close: jest.fn()
37
+ };
38
+ const collection = {
39
+ clearResultAndMoveToStart: jest.fn()
40
+ };
41
+ const showToast = jest.fn();
42
+ const downloadCsv = jest.fn();
43
+ const translate = (key, params) => params ? `${key}:${JSON.stringify(params)}` : key;
44
+ const props = {
45
+ fields: [{
46
+ id: 'name',
47
+ header: 'Name'
48
+ }],
49
+ collectionId: 'Movies',
50
+ httpClient: {
51
+ request: jest.fn()
52
+ },
53
+ collection,
54
+ importModalState: importModalState,
55
+ translate: translate,
56
+ errorHandler: {
57
+ withErrorHandler: fn => fn()
58
+ },
59
+ showToast: showToast,
60
+ downloadCsv,
61
+ ...extraProps
62
+ };
63
+ const state = new _ImportState.ImportState(props);
64
+
65
+ // Seed a parsed CSV mapped to an existing field.
66
+ (0, _mobx.runInAction)(() => {
67
+ state.fileName = 'data.csv';
68
+ state.csvHeaders = ['Name'];
69
+ state.csvRows = [['Apple'], ['Pear']];
70
+ state.mappingByHeader = {
71
+ Name: {
72
+ kind: 'existing',
73
+ fieldId: 'name'
74
+ }
75
+ };
76
+ });
77
+ return {
78
+ state,
79
+ client,
80
+ importModalState,
81
+ collection,
82
+ showToast,
83
+ downloadCsv
84
+ };
85
+ }
86
+ describe('ImportState engine (data-hub job)', () => {
87
+ beforeEach(() => {
88
+ // The success path awaits a real setTimeout (completion linger); make sure
89
+ // timers are real even if the preset defaults to fake ones.
90
+ jest.useRealTimers();
91
+ });
92
+ it('uploads the aligned file, submits a job, and reports success', async () => {
93
+ const {
94
+ state,
95
+ client,
96
+ importModalState,
97
+ collection
98
+ } = setup();
99
+ await state.startImport();
100
+ expect(client.uploadFile).toHaveBeenCalledTimes(1);
101
+ expect(client.uploadFile.mock.calls[0][0]).toBeInstanceOf(File);
102
+ expect(client.submitImportJob).toHaveBeenCalledWith({
103
+ fileId: 'file-1',
104
+ name: 'data.csv',
105
+ destination: {
106
+ wixDataCollection: {
107
+ collectionId: 'Movies'
108
+ }
109
+ }
110
+ });
111
+ expect(state.result).toEqual({
112
+ succeeded: 2,
113
+ failed: 0
114
+ });
115
+ expect(state.importStatus.status).toBe('success');
116
+ expect(importModalState.close).toHaveBeenCalled();
117
+ expect(collection.clearResultAndMoveToStart).toHaveBeenCalledWith({
118
+ force: true
119
+ });
120
+ });
121
+ it('shows a partial-success toast when some rows are rejected', async () => {
122
+ const {
123
+ state,
124
+ showToast,
125
+ importModalState
126
+ } = setup({
127
+ getJob: jest.fn(async () => ({
128
+ id: 'job-1',
129
+ status: _types.JobStatus.PARTIALLY_SUCCESSFUL,
130
+ progress: {
131
+ itemsSuccessful: 1,
132
+ itemsRejected: 1,
133
+ total: 2,
134
+ current: 2
135
+ }
136
+ }))
137
+ });
138
+ await state.startImport();
139
+ expect(state.result).toEqual({
140
+ succeeded: 1,
141
+ failed: 1
142
+ });
143
+ expect(showToast).toHaveBeenCalledWith(expect.objectContaining({
144
+ type: 'WARNING'
145
+ }));
146
+ expect(importModalState.close).toHaveBeenCalled();
147
+ });
148
+ it('maps the failed "row N" by record (unaffected by an embedded-newline cell)', async () => {
149
+ const {
150
+ state,
151
+ downloadCsv,
152
+ showToast
153
+ } = setup({
154
+ getJob: jest.fn(async () => ({
155
+ id: 'job-1',
156
+ status: _types.JobStatus.PARTIALLY_SUCCESSFUL,
157
+ progress: {
158
+ itemsSuccessful: 2,
159
+ itemsRejected: 1
160
+ }
161
+ })),
162
+ // data-hub numbers records (csv-parse, RFC-4180): the 3rd data record is
163
+ // "row 4" regardless of the multi-line cell in the 1st record.
164
+ listMovementLogs: jest.fn(async () => ({
165
+ logs: [{
166
+ sourceItemId: 'row 4',
167
+ failure: {
168
+ code: 'x',
169
+ description: 'y'
170
+ }
171
+ }],
172
+ pagingMetadata: {
173
+ hasNext: false
174
+ }
175
+ }))
176
+ });
177
+
178
+ // Row 0 has a quoted embedded newline; the failed record is row 2 (Mango).
179
+ (0, _mobx.runInAction)(() => {
180
+ state.fileName = 'data.csv';
181
+ state.csvHeaders = ['Name', 'Notes'];
182
+ state.csvRows = [['Apple', 'line1\nline2'], ['Pear', 'ok'], ['Mango', 'bad']];
183
+ state.mappingByHeader = {
184
+ Name: {
185
+ kind: 'existing',
186
+ fieldId: 'name'
187
+ },
188
+ Notes: {
189
+ kind: 'existing',
190
+ fieldId: 'notes'
191
+ }
192
+ };
193
+ });
194
+ await state.startImport();
195
+ const toast = showToast.mock.calls[0][0];
196
+ await toast.action.onClick();
197
+
198
+ // "row 4" → csvRows[2] (Mango), NOT shifted by the embedded newline in row 0.
199
+ const [content] = downloadCsv.mock.calls[0];
200
+ expect(content).toContain('Mango');
201
+ expect(content).not.toContain('Apple');
202
+ });
203
+ it('downloads a re-importable CSV of the failed rows from the partial toast action', async () => {
204
+ const {
205
+ state,
206
+ client,
207
+ showToast,
208
+ downloadCsv
209
+ } = setup({
210
+ getJob: jest.fn(async () => ({
211
+ id: 'job-1',
212
+ status: _types.JobStatus.PARTIALLY_SUCCESSFUL,
213
+ progress: {
214
+ itemsSuccessful: 1,
215
+ itemsRejected: 1
216
+ }
217
+ })),
218
+ // csvRows[1] (the "Pear" row) failed → file line 3 → "row 3".
219
+ listMovementLogs: jest.fn(async () => ({
220
+ logs: [{
221
+ sourceItemId: 'row 3',
222
+ failure: {
223
+ code: 'bad',
224
+ description: 'x'
225
+ }
226
+ }],
227
+ pagingMetadata: {
228
+ hasNext: false
229
+ }
230
+ }))
231
+ });
232
+ await state.startImport();
233
+
234
+ // Trigger the toast's download action.
235
+ const toast = showToast.mock.calls[0][0];
236
+ await toast.action.onClick();
237
+ expect(client.listMovementLogs).toHaveBeenCalledWith('job-1', undefined);
238
+ // Original headers + only the failed row, ready to fix and re-upload.
239
+ expect(downloadCsv).toHaveBeenCalledWith('Name\r\nPear', 'failed-data.csv');
240
+ });
241
+ it('surfaces an error state with an application-error-shaped error when the job fails', async () => {
242
+ var _error$response;
243
+ const {
244
+ state,
245
+ importModalState,
246
+ collection
247
+ } = setup({
248
+ getJob: jest.fn(async () => ({
249
+ id: 'job-1',
250
+ status: _types.JobStatus.FAILED,
251
+ error: {
252
+ code: 'import_failed',
253
+ description: 'Bad rows'
254
+ }
255
+ }))
256
+ });
257
+ await state.startImport();
258
+ expect(state.importStatus.status).toBe('error');
259
+ // HTTP-error-shaped so the UI resolves the message via
260
+ // errorHandler.getResolvedError rather than reading the raw server string.
261
+ // The handler reads the code from `response.data.details.applicationError`.
262
+ const error = state.importStatus.error;
263
+ expect((_error$response = error.response) == null || (_error$response = _error$response.data) == null || (_error$response = _error$response.details) == null || (_error$response = _error$response.applicationError) == null ? void 0 : _error$response.code).toBe('import_failed');
264
+ expect(importModalState.close).not.toHaveBeenCalled();
265
+ expect(collection.clearResultAndMoveToStart).not.toHaveBeenCalled();
266
+ });
267
+ it('hands a failed job to onJobError, which surfaces the wrapped error', async () => {
268
+ var _error$response2;
269
+ // Wiring only: the consumer forwards `run` to their withErrorHandler exactly
270
+ // as documented, and the error it re-rejects is the one surfaced to the UI
271
+ // (with the application-error-shaped code intact). End-to-end resolution
272
+ // against the real error handler is covered in the cairo
273
+ // Table.importButton spec, which has a real errorHandler instance.
274
+ // Mirror `(run) => errorHandler.withErrorHandler(run, errorCodesMap)`;
275
+ // a passthrough stub, so this just runs `run` and re-rejects.
276
+ const withErrorHandler = fn => fn();
277
+ const onJobError = jest.fn(run => withErrorHandler(run));
278
+ const {
279
+ state
280
+ } = setup({
281
+ getJob: jest.fn(async () => ({
282
+ id: 'job-1',
283
+ status: _types.JobStatus.FAILED,
284
+ error: {
285
+ code: 'duplicate_key',
286
+ description: 'Bad rows'
287
+ }
288
+ }))
289
+ }, {
290
+ onJobError
291
+ });
292
+ await state.startImport();
293
+ expect(onJobError).toHaveBeenCalledTimes(1);
294
+ expect(typeof onJobError.mock.calls[0][0]).toBe('function');
295
+ expect(state.importStatus.status).toBe('error');
296
+ const error = state.importStatus.error;
297
+ expect((_error$response2 = error.response) == null || (_error$response2 = _error$response2.data) == null || (_error$response2 = _error$response2.details) == null || (_error$response2 = _error$response2.applicationError) == null ? void 0 : _error$response2.code).toBe('duplicate_key');
298
+ });
299
+ it('terminates the job when closed mid-import', async () => {
300
+ const {
301
+ state,
302
+ client,
303
+ importModalState
304
+ } = setup();
305
+ // Cancel during the first poll, before the job is terminal.
306
+ client.getJob.mockImplementation(async () => {
307
+ await state.close();
308
+ return {
309
+ id: 'job-1',
310
+ status: _types.JobStatus.IN_PROGRESS
311
+ };
312
+ });
313
+ await state.startImport();
314
+ expect(client.terminateJob).toHaveBeenCalledWith('job-1');
315
+ expect(importModalState.close).toHaveBeenCalled();
316
+ });
317
+ it('terminates the job when cancelled while submitImportJob is in flight', async () => {
318
+ const {
319
+ state,
320
+ client
321
+ } = setup();
322
+ // The user cancels while submit is still in flight: close() runs before
323
+ // _jobId is set (so it can't terminate), then submit resolves.
324
+ client.submitImportJob.mockImplementation(async () => {
325
+ await state.close();
326
+ return 'job-1';
327
+ });
328
+ await state.startImport();
329
+
330
+ // The late-cancel path must terminate the orphaned job, and polling never starts.
331
+ expect(client.terminateJob).toHaveBeenCalledWith('job-1');
332
+ expect(client.getJob).not.toHaveBeenCalled();
333
+ });
334
+ it('aborts the poll loop when disposed (unmounted) mid-import', async () => {
335
+ const {
336
+ state,
337
+ client
338
+ } = setup();
339
+ const dispose = state.init();
340
+ // Simulate the component unmounting during the first poll.
341
+ client.getJob.mockImplementation(async () => {
342
+ dispose();
343
+ return {
344
+ id: 'job-1',
345
+ status: _types.JobStatus.IN_PROGRESS
346
+ };
347
+ });
348
+ await state.startImport();
349
+
350
+ // Without the disposer the loop would keep polling the IN_PROGRESS job
351
+ // forever; it must stop after the first fetch.
352
+ expect(client.getJob).toHaveBeenCalledTimes(1);
353
+ });
354
+ it('does not restore when closed during the success completion linger', async () => {
355
+ const restoreFn = jest.fn().mockResolvedValue(undefined);
356
+ const {
357
+ state,
358
+ importModalState,
359
+ collection
360
+ } = setup({}, {
361
+ backup: async () => ({
362
+ restore: restoreFn
363
+ })
364
+ });
365
+
366
+ // A 0ms macrotask drains the pending async chain without firing the 600ms
367
+ // linger timer.
368
+ const flush = () => new Promise(resolve => setTimeout(resolve, 0));
369
+ const promise = state.startImport();
370
+ // Run backup → upload → submit → poll (terminal) → success, parking on the
371
+ // 600ms completion linger (still on the 'progress' step) without firing it.
372
+ await flush();
373
+ await flush();
374
+ await flush();
375
+ expect(state.importStatus.isSuccess).toBe(true);
376
+ expect(state.currentStep).toBe('progress');
377
+
378
+ // User closes the modal mid-linger.
379
+ await state.close();
380
+ await promise;
381
+
382
+ // A successful import must not be reverted; the modal closes and the
383
+ // collection refreshes.
384
+ expect(restoreFn).not.toHaveBeenCalled();
385
+ expect(importModalState.close).toHaveBeenCalled();
386
+ expect(collection.clearResultAndMoveToStart).toHaveBeenCalledWith({
387
+ force: true
388
+ });
389
+ });
390
+ });
391
+ describe('ImportState writePolicy (id-collision handling)', () => {
392
+ beforeEach(() => {
393
+ jest.useRealTimers();
394
+ });
395
+
396
+ // Seed a CSV column mapped to the `_id` field so collisions are possible.
397
+ function seedIdMapping(state) {
398
+ (0, _mobx.runInAction)(() => {
399
+ state.fileName = 'data.csv';
400
+ state.csvHeaders = ['ID', 'Name'];
401
+ state.csvRows = [['1', 'Apple'], ['2', 'Pear']];
402
+ state.mappingByHeader = {
403
+ ID: {
404
+ kind: 'existing',
405
+ fieldId: '_id'
406
+ },
407
+ Name: {
408
+ kind: 'existing',
409
+ fieldId: 'name'
410
+ }
411
+ };
412
+ });
413
+ }
414
+ it('exposes the header mapped to `_id` via idMappedHeader', () => {
415
+ const {
416
+ state
417
+ } = setup();
418
+ expect(state.idMappedHeader).toBeNull();
419
+ seedIdMapping(state);
420
+ expect(state.idMappedHeader).toBe('ID');
421
+ });
422
+ it('omits writePolicy when no column is mapped to `_id`', async () => {
423
+ const {
424
+ state,
425
+ client
426
+ } = setup();
427
+ await state.startImport();
428
+ expect(client.submitImportJob).toHaveBeenCalledWith(expect.objectContaining({
429
+ destination: {
430
+ wixDataCollection: {
431
+ collectionId: 'Movies'
432
+ }
433
+ }
434
+ }));
435
+ });
436
+ it('sends the default OVERWRITE policy when a column maps to `_id`', async () => {
437
+ const {
438
+ state,
439
+ client
440
+ } = setup();
441
+ seedIdMapping(state);
442
+ await state.startImport();
443
+ expect(client.submitImportJob).toHaveBeenCalledWith(expect.objectContaining({
444
+ destination: {
445
+ wixDataCollection: {
446
+ collectionId: 'Movies',
447
+ writePolicy: _types.WritePolicy.OVERWRITE
448
+ }
449
+ }
450
+ }));
451
+ });
452
+ it('sends SKIP_EXISTING after the user switches the policy', async () => {
453
+ const {
454
+ state,
455
+ client
456
+ } = setup();
457
+ seedIdMapping(state);
458
+ state.setWritePolicy(_types.WritePolicy.SKIP_EXISTING);
459
+ await state.startImport();
460
+ expect(client.submitImportJob).toHaveBeenCalledWith(expect.objectContaining({
461
+ destination: {
462
+ wixDataCollection: {
463
+ collectionId: 'Movies',
464
+ writePolicy: _types.WritePolicy.SKIP_EXISTING
465
+ }
466
+ }
467
+ }));
468
+ });
469
+ it('resets the policy back to OVERWRITE', () => {
470
+ const {
471
+ state
472
+ } = setup();
473
+ state.setWritePolicy(_types.WritePolicy.SKIP_EXISTING);
474
+ expect(state.writePolicy).toBe(_types.WritePolicy.SKIP_EXISTING);
475
+ state.reset();
476
+ expect(state.writePolicy).toBe(_types.WritePolicy.OVERWRITE);
477
+ });
478
+ it('resets the policy to OVERWRITE when a new file is uploaded mid-session', async () => {
479
+ const {
480
+ state
481
+ } = setup();
482
+ seedIdMapping(state);
483
+ // The user switched to Skip for the first file.
484
+ state.setWritePolicy(_types.WritePolicy.SKIP_EXISTING);
485
+ expect(state.writePolicy).toBe(_types.WritePolicy.SKIP_EXISTING);
486
+
487
+ // Uploading a different CSV in the same modal session must start from the
488
+ // documented default — otherwise the prior Skip silently carries over and
489
+ // rows with a colliding `_id` are skipped instead of updated.
490
+ const csv = 'ID,Name\nid-9,Zed\n';
491
+ const file = {
492
+ name: 'file-b.csv',
493
+ arrayBuffer: async () => new _util.TextEncoder().encode(csv).buffer
494
+ };
495
+ await state.onFileSelected(file);
496
+ expect(state.writePolicy).toBe(_types.WritePolicy.OVERWRITE);
497
+ });
498
+ it('keeps the chosen policy when the `_id` mapping is removed and re-added', () => {
499
+ const {
500
+ state
501
+ } = setup();
502
+ seedIdMapping(state);
503
+ state.setWritePolicy(_types.WritePolicy.SKIP_EXISTING);
504
+
505
+ // Remap the ID column away: the control hides and the destination drops
506
+ // writePolicy, but the user's choice is intentionally retained (editing
507
+ // mappings within a file never resets it — only a new file does).
508
+ state.setMapping('ID', null);
509
+ expect(state.idMappedHeader).toBeNull();
510
+ expect(state.writePolicy).toBe(_types.WritePolicy.SKIP_EXISTING);
511
+
512
+ // Map `_id` again — the prior choice is still in effect.
513
+ state.setMapping('ID', '_id');
514
+ expect(state.idMappedHeader).toBe('ID');
515
+ expect(state.writePolicy).toBe(_types.WritePolicy.SKIP_EXISTING);
516
+ });
517
+ it('omits writePolicy when the `_id` column is remapped away mid-session', async () => {
518
+ const {
519
+ state,
520
+ client
521
+ } = setup();
522
+ seedIdMapping(state);
523
+ state.setWritePolicy(_types.WritePolicy.SKIP_EXISTING);
524
+ // No column maps to `_id` anymore → no collisions possible → omit it.
525
+ state.setMapping('ID', null);
526
+ await state.startImport();
527
+ expect(client.submitImportJob).toHaveBeenCalledWith(expect.objectContaining({
528
+ destination: {
529
+ wixDataCollection: {
530
+ collectionId: 'Movies'
531
+ }
532
+ }
533
+ }));
534
+ });
535
+ });
536
+ describe('ImportState orderedHeaders (row order stability)', () => {
537
+ beforeEach(() => {
538
+ jest.useRealTimers();
539
+ });
540
+
541
+ // Parse a CSV whose `ID` column auto-maps to `_id` (a field with that id
542
+ // exists), so the `_id` row floats to the top of the ordered list.
543
+ async function parseWithIdColumn(state) {
544
+ const csv = 'Name,ID,Handle\nApple,id-1,a\nPear,id-2,p\n';
545
+ const file = {
546
+ name: 'data.csv',
547
+ arrayBuffer: async () => new _util.TextEncoder().encode(csv).buffer
548
+ };
549
+ await state.onFileSelected(file);
550
+ }
551
+ it('floats the `_id`-mapped column to the top for a freshly parsed file', async () => {
552
+ const {
553
+ state
554
+ } = setup({}, {
555
+ fields: [{
556
+ id: '_id',
557
+ header: 'ID'
558
+ }]
559
+ });
560
+ await parseWithIdColumn(state);
561
+ expect(state.idMappedHeader).toBe('ID');
562
+ expect(state.orderedHeaders).toEqual(['ID', 'Name', 'Handle']);
563
+ });
564
+ it('keeps row order stable when the `_id` mapping is cleared and re-added', async () => {
565
+ const {
566
+ state
567
+ } = setup({}, {
568
+ fields: [{
569
+ id: '_id',
570
+ header: 'ID'
571
+ }]
572
+ });
573
+ await parseWithIdColumn(state);
574
+
575
+ // Clearing the `_id` dropdown must not reorder the row under the cursor:
576
+ // the snapshot stays fixed even though `idMappedHeader` becomes null.
577
+ state.setMapping('ID', null);
578
+ expect(state.idMappedHeader).toBeNull();
579
+ expect(state.orderedHeaders).toEqual(['ID', 'Name', 'Handle']);
580
+
581
+ // Re-mapping `_id` also leaves the order untouched.
582
+ state.setMapping('ID', '_id');
583
+ expect(state.idMappedHeader).toBe('ID');
584
+ expect(state.orderedHeaders).toEqual(['ID', 'Name', 'Handle']);
585
+ });
586
+ it('re-snapshots the order for a new file and clears it on reset', async () => {
587
+ const {
588
+ state
589
+ } = setup({}, {
590
+ fields: [{
591
+ id: '_id',
592
+ header: 'ID'
593
+ }]
594
+ });
595
+ await parseWithIdColumn(state);
596
+ expect(state.orderedHeaders).toEqual(['ID', 'Name', 'Handle']);
597
+
598
+ // A file with no `_id` column keeps plain CSV order.
599
+ const csv = 'First,Second\n1,2\n';
600
+ const file = {
601
+ name: 'other.csv',
602
+ arrayBuffer: async () => new _util.TextEncoder().encode(csv).buffer
603
+ };
604
+ await state.onFileSelected(file);
605
+ expect(state.orderedHeaders).toEqual(['First', 'Second']);
606
+ state.reset();
607
+ expect(state.orderedHeaders).toEqual([]);
608
+ });
609
+ });
610
+ //# sourceMappingURL=ImportState.engine.spec.js.map