@webitel/ui-sdk 26.8.55 → 26.8.57

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 (41) hide show
  1. package/dist/clients-B8MJJx6E.js +39958 -0
  2. package/dist/{components-DNSY1_s4.js → components-BWyW5EEY.js} +3 -3
  3. package/dist/{engineCallFileType.zod-C-t93kQg.js → engineCallFileType-D7QVFOac.js} +1 -1
  4. package/dist/{generateInstance-C9d7J_TX.js → genClient-HHiOUj56.js} +969 -755
  5. package/dist/ui-sdk.js +2 -2
  6. package/dist/ui-sdk.umd.cjs +2088 -1976
  7. package/dist/{wt-call-media-action-Bn2bGCWs.js → wt-call-media-action-Bkyppnsm.js} +1 -1
  8. package/dist/{wt-chat-emoji-B13JscVZ.js → wt-chat-emoji-Cj3B8oR5.js} +1 -1
  9. package/dist/{wt-display-chip-items-Cf8L5Upd.js → wt-display-chip-items-Cvm5rOYr.js} +1 -1
  10. package/dist/{wt-screen-recordings-action-Dj6ITuhE.js → wt-screen-recordings-action-DI1qlUi1.js} +1 -1
  11. package/dist/{wt-send-message-popup-CO92UYST.js → wt-send-message-popup-OYvzhdyL.js} +2 -2
  12. package/dist/{wt-type-extension-value-input-FHQ4bN4j.js → wt-type-extension-value-input-BfUPxO-s.js} +2 -2
  13. package/dist/{wt-vidstack-player-DcItshB2.js → wt-vidstack-player-DjQEPOTu.js} +1 -1
  14. package/package.json +6 -2
  15. package/src/api/clients/caseSources/caseSources.ts +2 -151
  16. package/src/api/clients/quickReplies/quickReplies.ts +2 -157
  17. package/src/modules/QueryFilters/mixins/__tests__/enumFilterMixin.spec.js +60 -0
  18. package/src/modules/QueryFilters/mixins/enumFilterMixin.js +21 -6
  19. package/src/modules/UploadCsvPopup/components/__tests__/wt-upload-csv-popup.spec.js +32 -0
  20. package/src/modules/UploadCsvPopup/components/wt-upload-csv-popup.vue +15 -26
  21. package/src/modules/UploadCsvPopup/composable/__tests__/useUploadCsv.spec.js +393 -0
  22. package/src/modules/UploadCsvPopup/composable/useUploadCsv.ts +36 -19
  23. package/src/modules/UploadCsvPopup/index.ts +11 -0
  24. package/src/modules/UploadCsvPopup/scripts/__tests__/normalizeCSVData.spec.js +125 -0
  25. package/src/modules/UploadCsvPopup/scripts/__tests__/parseCSV.spec.js +63 -0
  26. package/src/modules/UploadCsvPopup/scripts/normalizeCSVData.ts +2 -0
  27. package/src/modules/UploadCsvPopup/scripts/parseCSV.ts +0 -23
  28. package/src/modules/UserNotifications/api/UserNotifications.ts +1 -1
  29. package/types/.tsbuildinfo +1 -1
  30. package/types/api/clients/caseSources/caseSources.d.ts +1 -16
  31. package/types/api/clients/quickReplies/quickReplies.d.ts +1 -16
  32. package/types/api/defaults/getDefaultInstance/getDefaultInstance.d.ts +1 -1
  33. package/types/modules/UploadCsvPopup/components/__tests__/wt-upload-csv-popup.spec.d.ts +1 -0
  34. package/types/modules/UploadCsvPopup/components/wt-upload-csv-popup.vue.d.ts +5 -5
  35. package/types/modules/UploadCsvPopup/composable/__tests__/useUploadCsv.spec.d.ts +1 -0
  36. package/types/modules/UploadCsvPopup/composable/useUploadCsv.d.ts +7 -3
  37. package/types/modules/UploadCsvPopup/index.d.ts +5 -0
  38. package/types/modules/UploadCsvPopup/scripts/__tests__/normalizeCSVData.spec.d.ts +1 -0
  39. package/types/modules/UploadCsvPopup/scripts/__tests__/parseCSV.spec.d.ts +1 -0
  40. package/types/modules/UploadCsvPopup/scripts/normalizeCSVData.d.ts +2 -0
  41. package/dist/clients-Ck0KwDIR.js +0 -39275
@@ -0,0 +1,393 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import { ref } from 'vue';
3
+
4
+ import HandlingCSVMode from '../../types/WtUploadCSVHandlingMode.enum';
5
+ import useUploadCsv from '../useUploadCsv';
6
+
7
+ const Component = {
8
+ props: {
9
+ file: {
10
+ default: null,
11
+ },
12
+ mappingFields: {
13
+ type: Array,
14
+ default: () => [],
15
+ },
16
+ addBulkItems: {
17
+ type: Function,
18
+ default: undefined,
19
+ },
20
+ handlingMode: {
21
+ type: String,
22
+ default: HandlingCSVMode.PROCESS,
23
+ },
24
+ fileUploadHandler: {
25
+ type: Function,
26
+ default: undefined,
27
+ },
28
+ },
29
+ emits: [
30
+ 'changeMappingFields',
31
+ 'save',
32
+ 'close',
33
+ ],
34
+ setup(props, { emit }) {
35
+ const skipHeaders = ref(true);
36
+ const separator = ref(',');
37
+
38
+ return {
39
+ skipHeaders,
40
+ separator,
41
+ ...useUploadCsv({
42
+ props,
43
+ emit,
44
+ skipHeaders,
45
+ separator,
46
+ }),
47
+ };
48
+ },
49
+ render: () => null,
50
+ };
51
+
52
+ const makeFile = (content) => {
53
+ const blob = new Blob(
54
+ [
55
+ content,
56
+ ],
57
+ {
58
+ type: 'text/csv',
59
+ },
60
+ );
61
+
62
+ return new File(
63
+ [
64
+ blob,
65
+ ],
66
+ 'test.csv',
67
+ {
68
+ type: 'text/csv',
69
+ },
70
+ );
71
+ };
72
+
73
+ // the composable reads the file through a watcher, so give FileReader a tick
74
+ const mountWithFile = async (csv, props = {}) => {
75
+ const wrapper = shallowMount(Component, {
76
+ props,
77
+ });
78
+
79
+ await wrapper.setProps({
80
+ file: makeFile(csv),
81
+ });
82
+
83
+ await new Promise((resolve) => {
84
+ setTimeout(resolve, 100);
85
+ });
86
+
87
+ return wrapper;
88
+ };
89
+
90
+ describe('useUploadCsv', () => {
91
+ it('mounts a component with the composable', () => {
92
+ const wrapper = shallowMount(Component, {
93
+ props: {
94
+ file: {},
95
+ },
96
+ });
97
+
98
+ expect(wrapper.exists()).toBe(true);
99
+ });
100
+
101
+ it('parses and saves simple csv', async () => {
102
+ const csv = `
103
+ col1,col2
104
+ John,30
105
+ Jane,25
106
+ `.replaceAll(/ +?/g, ''); // replace all whitespaces, but not newlines
107
+
108
+ const saveCallback = vi.fn();
109
+
110
+ const wrapper = await mountWithFile(csv, {
111
+ addBulkItems: saveCallback,
112
+ mappingFields: [
113
+ {
114
+ name: 'name',
115
+ csv: 'col1',
116
+ },
117
+ {
118
+ name: 'age',
119
+ csv: 'col2',
120
+ },
121
+ ],
122
+ });
123
+
124
+ await wrapper.vm.processCSV();
125
+
126
+ expect(saveCallback).toHaveBeenCalledWith([
127
+ {
128
+ name: 'John',
129
+ age: '30',
130
+ },
131
+ {
132
+ name: 'Jane',
133
+ age: '25',
134
+ },
135
+ ]);
136
+ });
137
+
138
+ it('parsing of csv with empty required throws error', async () => {
139
+ const csv = `
140
+ col1,col2
141
+ John,
142
+ Jane,25
143
+ `.replaceAll(/ +?/g, ''); // replace all whitespaces, but not newlines
144
+
145
+ const saveCallback = vi.fn();
146
+
147
+ const wrapper = await mountWithFile(csv, {
148
+ addBulkItems: saveCallback,
149
+ mappingFields: [
150
+ {
151
+ name: 'name',
152
+ csv: 'col1',
153
+ required: true,
154
+ },
155
+ {
156
+ name: 'age',
157
+ csv: 'col2',
158
+ required: true,
159
+ },
160
+ ],
161
+ });
162
+
163
+ await expect(wrapper.vm.processCSV()).rejects.toBeTruthy();
164
+ expect(saveCallback).not.toHaveBeenCalled();
165
+ });
166
+
167
+ it('parsing of csv with multiple columns selected to required field', async () => {
168
+ const csv = `
169
+ col1,col2,col3
170
+ John,,30
171
+ Jane,25,
172
+ `.replaceAll(/ +?/g, ''); // replace all whitespaces, but not newlines
173
+
174
+ const saveCallback = vi.fn();
175
+
176
+ const wrapper = await mountWithFile(csv, {
177
+ addBulkItems: saveCallback,
178
+ mappingFields: [
179
+ {
180
+ name: 'name',
181
+ csv: 'col1',
182
+ required: true,
183
+ },
184
+ {
185
+ name: 'age',
186
+ csv: [
187
+ 'col2',
188
+ 'col3',
189
+ ],
190
+ required: true,
191
+ },
192
+ ],
193
+ });
194
+
195
+ await wrapper.vm.processCSV();
196
+
197
+ expect(saveCallback).toHaveBeenCalledWith([
198
+ {
199
+ name: 'John',
200
+ age: [
201
+ '',
202
+ '30',
203
+ ],
204
+ },
205
+ {
206
+ name: 'Jane',
207
+ age: [
208
+ '25',
209
+ '',
210
+ ],
211
+ },
212
+ ]);
213
+ });
214
+
215
+ /*
216
+ WTEL-10058: guards the admin data that the parser never touched — queue
217
+ member variables, blacklist descriptions, device passwords. Type casting
218
+ lives in CRM now, so everything reaching a consumer is a string.
219
+ */
220
+ it('keeps plain values as strings, so text columns are not coerced', async () => {
221
+ const csv = `
222
+ col1,col2
223
+ true,"[1,2]"
224
+ `.replaceAll(/ +?/g, ''); // replace all whitespaces, but not newlines
225
+
226
+ const saveCallback = vi.fn();
227
+
228
+ const wrapper = await mountWithFile(csv, {
229
+ addBulkItems: saveCallback,
230
+ mappingFields: [
231
+ {
232
+ name: 'flag',
233
+ csv: 'col1',
234
+ },
235
+ {
236
+ name: 'list',
237
+ csv: 'col2',
238
+ },
239
+ ],
240
+ });
241
+
242
+ await wrapper.vm.processCSV();
243
+
244
+ expect(saveCallback).toHaveBeenCalledWith([
245
+ {
246
+ flag: 'true',
247
+ list: '[1,2]',
248
+ },
249
+ ]);
250
+ });
251
+
252
+ it('uploads the whole file in the upload handling mode', async () => {
253
+ const csv = `
254
+ col1
255
+ John
256
+ `.replaceAll(/ +?/g, ''); // replace all whitespaces, but not newlines
257
+
258
+ const saveCallback = vi.fn();
259
+ const fileUploadHandler = vi.fn();
260
+
261
+ const wrapper = await mountWithFile(csv, {
262
+ handlingMode: HandlingCSVMode.UPLOAD,
263
+ addBulkItems: saveCallback,
264
+ fileUploadHandler,
265
+ mappingFields: [
266
+ {
267
+ name: 'name',
268
+ csv: 'col1',
269
+ },
270
+ ],
271
+ });
272
+
273
+ await wrapper.vm.processCSV();
274
+
275
+ expect(fileUploadHandler).toHaveBeenCalled();
276
+ expect(saveCallback).not.toHaveBeenCalled();
277
+ });
278
+
279
+ /*
280
+ WTEL-10058: the popup owns the separator, so a consumer that ships the raw
281
+ file to the backend has no other way to learn which one the user picked.
282
+ */
283
+ it('hands the chosen separator to the file upload handler', async () => {
284
+ const csv = 'col1;col2\nJohn;30';
285
+
286
+ const fileUploadHandler = vi.fn();
287
+
288
+ const wrapper = await mountWithFile(csv, {
289
+ handlingMode: HandlingCSVMode.UPLOAD,
290
+ fileUploadHandler,
291
+ mappingFields: [
292
+ {
293
+ name: 'name',
294
+ csv: 'col1',
295
+ },
296
+ ],
297
+ });
298
+
299
+ wrapper.vm.separator = ';';
300
+ await wrapper.vm.processCSV();
301
+
302
+ expect(fileUploadHandler).toHaveBeenCalledWith({
303
+ separator: ';',
304
+ });
305
+ });
306
+
307
+ it('emits save and closes on a successful handleSave', async () => {
308
+ const csv = `
309
+ col1
310
+ John
311
+ `.replaceAll(/ +?/g, ''); // replace all whitespaces, but not newlines
312
+
313
+ const wrapper = await mountWithFile(csv, {
314
+ addBulkItems: vi.fn(),
315
+ mappingFields: [
316
+ {
317
+ name: 'name',
318
+ csv: 'col1',
319
+ },
320
+ ],
321
+ });
322
+
323
+ await wrapper.vm.handleSave();
324
+
325
+ expect(wrapper.emitted('save')).toHaveLength(1);
326
+ expect(wrapper.emitted('close')).toHaveLength(1);
327
+ });
328
+
329
+ it('does not close the popup when saving fails', async () => {
330
+ const csv = `
331
+ col1
332
+ John
333
+ `.replaceAll(/ +?/g, ''); // replace all whitespaces, but not newlines
334
+
335
+ const wrapper = await mountWithFile(csv, {
336
+ addBulkItems: vi.fn(() => Promise.reject(new Error('nope'))),
337
+ mappingFields: [
338
+ {
339
+ name: 'name',
340
+ csv: 'col1',
341
+ },
342
+ ],
343
+ });
344
+
345
+ await expect(wrapper.vm.processCSV()).rejects.toThrow();
346
+ expect(wrapper.emitted('close')).toBeUndefined();
347
+ });
348
+
349
+ it('resets the mappings when the parse options change', async () => {
350
+ const csv = `
351
+ col1,col2
352
+ John,30
353
+ `.replaceAll(/ +?/g, ''); // replace all whitespaces, but not newlines
354
+
355
+ const wrapper = await mountWithFile(csv, {
356
+ mappingFields: [
357
+ {
358
+ name: 'name',
359
+ csv: 'col1',
360
+ },
361
+ {
362
+ name: 'tags',
363
+ csv: [
364
+ 'col2',
365
+ ],
366
+ multiple: true,
367
+ },
368
+ ],
369
+ });
370
+
371
+ wrapper.vm.separator = ';';
372
+
373
+ // the parse options watcher is debounced by 1s
374
+ await new Promise((resolve) => {
375
+ setTimeout(resolve, 1200);
376
+ });
377
+
378
+ const emitted = wrapper.emitted('changeMappingFields');
379
+
380
+ expect(emitted).toBeTruthy();
381
+ expect(emitted.at(-1)[0]).toEqual([
382
+ {
383
+ name: 'name',
384
+ csv: '',
385
+ },
386
+ {
387
+ name: 'tags',
388
+ csv: [],
389
+ multiple: true,
390
+ },
391
+ ]);
392
+ });
393
+ });
@@ -1,6 +1,11 @@
1
1
  import { computed, type Ref, ref, watch } from 'vue';
2
2
 
3
- import { debounce, isEmpty } from '../../../scripts';
3
+ import { useEventBus } from '../../../composables';
4
+ import {
5
+ debounce,
6
+ eventBus as defaultEventBus,
7
+ isEmpty,
8
+ } from '../../../scripts';
4
9
  import normalizeCSVData, {
5
10
  type CsvDataRow,
6
11
  type CsvMappingField,
@@ -17,12 +22,20 @@ import HandlingCSVMode from '../types/WtUploadCSVHandlingMode.enum';
17
22
  * @param {import('vue').Ref<boolean>} params.skipHeaders
18
23
  * @param {import('vue').Ref<string>} params.separator
19
24
  */
25
+
26
+ export interface UploadCsvParseSettings {
27
+ separator: string;
28
+ }
29
+
20
30
  export interface UseUploadCsvProps {
21
31
  file?: File | null;
22
32
  mappingFields?: CsvMappingField[];
23
33
  addBulkItems?: (items: unknown[]) => unknown | Promise<unknown>;
24
- handlingMode?: string;
25
- fileUploadHandler?: () => unknown | Promise<unknown>;
34
+ handlingMode?: HandlingCSVMode;
35
+ fileUploadHandler?: (
36
+ settings: UploadCsvParseSettings,
37
+ ) => unknown | Promise<unknown>;
38
+ charset?: string;
26
39
  }
27
40
 
28
41
  interface UseUploadCsvParams {
@@ -39,11 +52,12 @@ const useUploadCsv = ({
39
52
  skipHeaders,
40
53
  separator,
41
54
  }: UseUploadCsvParams) => {
55
+ const eventBus = useEventBus() ?? defaultEventBus;
56
+
42
57
  const isReadingFile = ref(false);
43
58
  const isParsingCSV = ref(false);
44
59
  const parsedFile = ref<unknown>(null);
45
60
  const isParsingPreview = ref(false);
46
- const parseErrorStackTrace = ref<unknown>('');
47
61
  const csvPreview = ref<unknown[]>([
48
62
  [],
49
63
  ]);
@@ -101,15 +115,21 @@ const useUploadCsv = ({
101
115
  ),
102
116
  );
103
117
 
118
+ function notifyError(err: unknown) {
119
+ eventBus.$emit('notification', {
120
+ type: 'error',
121
+ text: err instanceof Error ? err.message : String(err),
122
+ });
123
+ }
124
+
104
125
  async function createCSVPreview(file = parsedFile.value) {
105
126
  try {
106
- parseErrorStackTrace.value = '';
107
127
  csvPreview.value = await parseCSV(file as string, {
108
128
  ...parseCSVOptions.value,
109
129
  toLine: 4,
110
130
  });
111
131
  } catch (err) {
112
- parseErrorStackTrace.value = err;
132
+ notifyError(err);
113
133
  csvPreview.value = [
114
134
  [],
115
135
  ];
@@ -138,7 +158,9 @@ const useUploadCsv = ({
138
158
 
139
159
  isReadingFile.value = true;
140
160
 
141
- parsedFile.value = await processFile(props.file as File, {});
161
+ parsedFile.value = await processFile(props.file as File, {
162
+ charset: props.charset,
163
+ });
142
164
  await createCSVPreview(parsedFile.value);
143
165
 
144
166
  isReadingFile.value = false;
@@ -165,20 +187,17 @@ const useUploadCsv = ({
165
187
  isParsingCSV.value = true;
166
188
 
167
189
  try {
168
- parseErrorStackTrace.value = '';
169
-
170
- const handler =
171
- props.handlingMode === HandlingCSVMode.PROCESS
172
- ? handleCSVProcessing
173
- : props.fileUploadHandler;
174
-
175
- if (handler) {
176
- await handler();
190
+ if (props.handlingMode === HandlingCSVMode.PROCESS) {
191
+ await handleCSVProcessing();
192
+ } else {
193
+ await props.fileUploadHandler?.({
194
+ separator: separator.value,
195
+ });
177
196
  }
178
197
 
179
198
  close();
180
199
  } catch (err) {
181
- parseErrorStackTrace.value = err;
200
+ notifyError(err);
182
201
  throw err;
183
202
  } finally {
184
203
  isParsingCSV.value = false;
@@ -194,7 +213,6 @@ const useUploadCsv = ({
194
213
  emit('close');
195
214
  }
196
215
 
197
- // watchers (те, що були в mixin.watch)
198
216
  watch(skipHeaders, async () => {
199
217
  await handleParseOptionsChange();
200
218
  });
@@ -214,7 +232,6 @@ const useUploadCsv = ({
214
232
  isReadingFile,
215
233
  isParsingCSV,
216
234
  isParsingPreview,
217
- parseErrorStackTrace,
218
235
  csvPreviewTableData,
219
236
  csvPreviewTableHeaders,
220
237
  filteredCsvPreviewTableHeaders,
@@ -0,0 +1,11 @@
1
+ export { default as WtUploadCsvPopup } from './components/wt-upload-csv-popup.vue';
2
+ export type {
3
+ UploadCsvParseSettings,
4
+ UseUploadCsvProps,
5
+ } from './composable/useUploadCsv';
6
+ export { default as useUploadCsv } from './composable/useUploadCsv';
7
+ export type {
8
+ CsvDataRow,
9
+ CsvMappingField,
10
+ } from './scripts/normalizeCSVData';
11
+ export { HandlingCSVMode } from './types/WtUploadCSVHandlingMode.enum';
@@ -0,0 +1,125 @@
1
+ import normalizeCSVData from '../normalizeCSVData';
2
+
3
+ describe('normalizeCSVData', () => {
4
+ const mappings = [
5
+ {
6
+ // webitel field name
7
+ name: 'name',
8
+ // csv column name
9
+ csv: 'col1',
10
+ // is this webitel field required?
11
+ required: true,
12
+ // ui
13
+ locale: '',
14
+ },
15
+ {
16
+ name: 'phone',
17
+ csv: [
18
+ 'col2',
19
+ 'col3',
20
+ ],
21
+ required: true,
22
+ },
23
+ ];
24
+
25
+ it('normalizes simple data', () => {
26
+ const input = [
27
+ {
28
+ col1: 'John',
29
+ col2: '123',
30
+ col3: '456',
31
+ },
32
+ {
33
+ col1: 'Jane',
34
+ col2: '789',
35
+ col3: '012',
36
+ },
37
+ ];
38
+ const result = normalizeCSVData({
39
+ data: input,
40
+ mappings,
41
+ });
42
+ const output = [
43
+ {
44
+ name: 'John',
45
+ phone: [
46
+ '123',
47
+ '456',
48
+ ],
49
+ },
50
+ {
51
+ name: 'Jane',
52
+ phone: [
53
+ '789',
54
+ '012',
55
+ ],
56
+ },
57
+ ];
58
+ expect(result).toEqual(output);
59
+ });
60
+
61
+ it('throws an error at normalizing data with required (!) empty value', () => {
62
+ const input = [
63
+ {
64
+ col1: '',
65
+ col2: '123',
66
+ col3: '3424',
67
+ },
68
+ {
69
+ col1: 'name',
70
+ col2: '131312',
71
+ col3: '012',
72
+ },
73
+ ];
74
+ try {
75
+ normalizeCSVData({
76
+ data: input,
77
+ mappings,
78
+ });
79
+ } catch (err) {
80
+ expect(err).toBeTruthy();
81
+ return;
82
+ }
83
+ expect(false).toBe(true); // reject test if no error
84
+ });
85
+
86
+ it(`returns correct fields for mapping, including empty fields,
87
+ if one of them is empty`, () => {
88
+ const input = [
89
+ {
90
+ col1: 'name',
91
+ col2: '123',
92
+ col3: '',
93
+ },
94
+ {
95
+ col1: 'name',
96
+ col2: '123',
97
+ col3: '012',
98
+ },
99
+ ];
100
+
101
+ const result = normalizeCSVData({
102
+ data: input,
103
+ mappings,
104
+ });
105
+
106
+ const output = [
107
+ {
108
+ name: 'name',
109
+ phone: [
110
+ '123',
111
+ '',
112
+ ],
113
+ },
114
+ {
115
+ name: 'name',
116
+ phone: [
117
+ '123',
118
+ '012',
119
+ ],
120
+ },
121
+ ];
122
+
123
+ expect(result).toEqual(output);
124
+ });
125
+ });
@@ -0,0 +1,63 @@
1
+ import parseCSV from '../parseCSV';
2
+
3
+ describe('parseCSV', () => {
4
+ it('parses a csv into rows keyed by the header', async () => {
5
+ const csv = 'col1,col2\nJohn,30';
6
+
7
+ const result = await parseCSV(csv, {
8
+ columns: true,
9
+ });
10
+
11
+ expect(result).toEqual([
12
+ {
13
+ col1: 'John',
14
+ col2: '30',
15
+ },
16
+ ]);
17
+ });
18
+
19
+ /*
20
+ WTEL-10058: the parser used to coerce 'true'/'false' and JSON-looking cells
21
+ for every consumer. Type casting belongs to whoever knows the field types —
22
+ CRM does it in prepareCsvLookupRows. Everything here stays a string.
23
+ */
24
+ it.each([
25
+ 'true',
26
+ 'false',
27
+ '[1,2]',
28
+ '{"a":1}',
29
+ '42',
30
+ ])('keeps %s as a string', async (value) => {
31
+ const csv = `col1\n"${value.replaceAll('"', '""')}"`;
32
+
33
+ const result = await parseCSV(csv, {
34
+ columns: true,
35
+ });
36
+
37
+ expect(result[0].col1).toBe(value);
38
+ });
39
+
40
+ it('respects a custom delimiter', async () => {
41
+ const csv = 'col1;col2\nJohn;30';
42
+
43
+ const result = await parseCSV(csv, {
44
+ columns: true,
45
+ delimiter: ';',
46
+ });
47
+
48
+ expect(result[0]).toEqual({
49
+ col1: 'John',
50
+ col2: '30',
51
+ });
52
+ });
53
+
54
+ it('rejects on a malformed csv', async () => {
55
+ const csv = 'col1,col2\n"unterminated,30\nJane,25';
56
+
57
+ await expect(
58
+ parseCSV(csv, {
59
+ columns: true,
60
+ }),
61
+ ).rejects.toBeTruthy();
62
+ });
63
+ });