@capillarytech/creatives-library 8.0.329 → 8.0.330
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.
- package/constants/unified.js +0 -14
- package/package.json +1 -1
- package/services/api.js +0 -17
- package/services/tests/api.test.js +0 -85
- package/utils/commonUtils.js +0 -10
- package/utils/tests/commonUtil.test.js +0 -169
- package/v2Components/CapTagList/index.js +0 -10
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +53 -87
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +1 -20
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -145
- package/v2Components/CommonTestAndPreview/actions.js +0 -10
- package/v2Components/CommonTestAndPreview/constants.js +1 -53
- package/v2Components/CommonTestAndPreview/index.js +168 -1006
- package/v2Components/CommonTestAndPreview/messages.js +3 -147
- package/v2Components/CommonTestAndPreview/reducer.js +0 -10
- package/v2Components/CommonTestAndPreview/sagas.js +6 -15
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +286 -328
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +24 -65
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
- package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -168
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
- package/v2Components/FormBuilder/index.js +1 -7
- package/v2Components/TestAndPreviewSlidebox/index.js +1 -8
- package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
- package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
- package/v2Containers/CreativesContainer/constants.js +0 -9
- package/v2Containers/CreativesContainer/index.js +93 -286
- package/v2Containers/CreativesContainer/index.scss +1 -51
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -20
- package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
- package/v2Containers/Rcs/constants.js +1 -34
- package/v2Containers/Rcs/index.js +884 -999
- package/v2Containers/Rcs/index.scss +6 -85
- package/v2Containers/Rcs/messages.js +1 -10
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2453 -41456
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
- package/v2Containers/Rcs/tests/index.test.js +38 -41
- package/v2Containers/Rcs/tests/mockData.js +0 -38
- package/v2Containers/Rcs/tests/utils.test.js +1 -379
- package/v2Containers/Rcs/utils.js +10 -358
- package/v2Containers/Sms/Create/index.js +38 -100
- package/v2Containers/SmsTrai/Create/index.js +4 -9
- package/v2Containers/SmsTrai/Edit/constants.js +0 -2
- package/v2Containers/SmsTrai/Edit/index.js +128 -609
- package/v2Containers/SmsTrai/Edit/messages.js +4 -9
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2600 -4586
- package/v2Containers/SmsWrapper/index.js +8 -37
- package/v2Containers/TagList/index.js +0 -6
- package/v2Containers/Templates/_templates.scss +2 -63
- package/v2Containers/Templates/actions.js +0 -11
- package/v2Containers/Templates/constants.js +0 -2
- package/v2Containers/Templates/index.js +40 -90
- package/v2Containers/Templates/sagas.js +12 -57
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
- package/v2Containers/Templates/tests/sagas.test.js +123 -193
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
- package/v2Containers/TemplatesV2/index.js +23 -86
- package/v2Containers/Whatsapp/index.js +20 -3
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5790
- package/utils/templateVarUtils.js +0 -172
- package/utils/tests/templateVarUtils.test.js +0 -160
- package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
- package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -155
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -93
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
- package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -648
- package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -174
- package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
- package/v2Components/SmsFallback/constants.js +0 -73
- package/v2Components/SmsFallback/index.js +0 -955
- package/v2Components/SmsFallback/index.scss +0 -265
- package/v2Components/SmsFallback/messages.js +0 -78
- package/v2Components/SmsFallback/smsFallbackUtils.js +0 -107
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -261
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
- package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
- package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
- package/v2Components/VarSegmentMessageEditor/index.js +0 -125
- package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -205
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -251
- package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
- package/v2Containers/SmsTrai/Edit/index.scss +0 -121
- package/v2Containers/Templates/TemplatesActionBar.js +0 -101
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildFallbackDataFromTemplate,
|
|
3
|
-
mapFallbackValueToEditTemplateData,
|
|
4
|
-
getBaseFromSmsTraiFormData,
|
|
5
|
-
getSmsFallbackCardDisplayContent,
|
|
6
|
-
resolveContentFromTraiBase,
|
|
7
|
-
filterSmsTemplatesByCategory,
|
|
8
|
-
buildFallbackDataFromCreativesPayload,
|
|
9
|
-
} from '../smsFallbackUtils';
|
|
10
|
-
import { SMS_CATEGORY_FILTERS } from '../constants';
|
|
11
|
-
|
|
12
|
-
describe('smsFallbackUtils', () => {
|
|
13
|
-
describe('buildFallbackDataFromTemplate', () => {
|
|
14
|
-
it('maps template versions.base and header sender list', () => {
|
|
15
|
-
const template = {
|
|
16
|
-
_id: 'tid',
|
|
17
|
-
name: 'My SMS',
|
|
18
|
-
versions: {
|
|
19
|
-
base: {
|
|
20
|
-
'sms-editor': 'Hello {{1}}',
|
|
21
|
-
header: ['S1', 'S2'],
|
|
22
|
-
'unicode-validity': false,
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
};
|
|
26
|
-
expect(buildFallbackDataFromTemplate(template)).toEqual({
|
|
27
|
-
smsTemplateId: 'tid',
|
|
28
|
-
templateName: 'My SMS',
|
|
29
|
-
content: 'Hello {{1}}',
|
|
30
|
-
templateContent: 'Hello {{1}}',
|
|
31
|
-
senderId: 'S1',
|
|
32
|
-
registeredSenderIds: ['S1', 'S2'],
|
|
33
|
-
unicodeValidity: false,
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('falls back to senderId when header is missing', () => {
|
|
38
|
-
const template = {
|
|
39
|
-
_id: '',
|
|
40
|
-
name: '',
|
|
41
|
-
versions: { base: { 'sms-editor': 'x', senderId: 'SID' } },
|
|
42
|
-
};
|
|
43
|
-
expect(buildFallbackDataFromTemplate(template).senderId).toBe('SID');
|
|
44
|
-
expect(buildFallbackDataFromTemplate(template).registeredSenderIds).toEqual([]);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('defaults unicodeValidity to true when not a boolean', () => {
|
|
48
|
-
const template = {
|
|
49
|
-
versions: { base: { 'sms-editor': '' } },
|
|
50
|
-
};
|
|
51
|
-
expect(buildFallbackDataFromTemplate(template).unicodeValidity).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('getSmsFallbackCardDisplayContent', () => {
|
|
56
|
-
it('returns raw template when rcsSmsFallbackVarMapped is empty', () => {
|
|
57
|
-
expect(
|
|
58
|
-
getSmsFallbackCardDisplayContent({
|
|
59
|
-
templateContent: 'A {#var#} B',
|
|
60
|
-
rcsSmsFallbackVarMapped: {},
|
|
61
|
-
}),
|
|
62
|
-
).toBe('A {#var#} B');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('substitutes slot values like preview when var map is set', () => {
|
|
66
|
-
expect(
|
|
67
|
-
getSmsFallbackCardDisplayContent({
|
|
68
|
-
templateContent: 'Hi {{name}}',
|
|
69
|
-
rcsSmsFallbackVarMapped: { '{{name}}_1': 'Pat' },
|
|
70
|
-
}),
|
|
71
|
-
).toBe('Hi Pat');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('shows raw {#var#} in card when slots saved empty (DLT, no labels)', () => {
|
|
75
|
-
expect(
|
|
76
|
-
getSmsFallbackCardDisplayContent({
|
|
77
|
-
templateContent: 'Balance {#var#}',
|
|
78
|
-
rcsSmsFallbackVarMapped: { '{#var#}_1': '' },
|
|
79
|
-
}),
|
|
80
|
-
).toBe('Balance {#var#}');
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe('mapFallbackValueToEditTemplateData', () => {
|
|
85
|
-
it('returns null when source is missing', () => {
|
|
86
|
-
expect(mapFallbackValueToEditTemplateData(null)).toBeNull();
|
|
87
|
-
expect(mapFallbackValueToEditTemplateData(undefined)).toBeNull();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('maps content, optional header and unicode flag', () => {
|
|
91
|
-
const source = {
|
|
92
|
-
smsTemplateId: 'id1',
|
|
93
|
-
templateName: 'N',
|
|
94
|
-
templateContent: 'body',
|
|
95
|
-
registeredSenderIds: ['h1'],
|
|
96
|
-
unicodeValidity: false,
|
|
97
|
-
};
|
|
98
|
-
expect(mapFallbackValueToEditTemplateData(source)).toEqual({
|
|
99
|
-
_id: 'id1',
|
|
100
|
-
name: 'N',
|
|
101
|
-
versions: {
|
|
102
|
-
base: {
|
|
103
|
-
'sms-editor': 'body',
|
|
104
|
-
header: ['h1'],
|
|
105
|
-
'unicode-validity': false,
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('uses content when templateContent is empty', () => {
|
|
112
|
-
const source = {
|
|
113
|
-
smsTemplateId: 'x',
|
|
114
|
-
templateName: 'y',
|
|
115
|
-
content: 'from-content',
|
|
116
|
-
};
|
|
117
|
-
expect(mapFallbackValueToEditTemplateData(source).versions.base['sms-editor']).toBe('from-content');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('omits header when registeredSenderIds is empty', () => {
|
|
121
|
-
const source = {
|
|
122
|
-
smsTemplateId: 'x',
|
|
123
|
-
templateName: 'y',
|
|
124
|
-
templateContent: 't',
|
|
125
|
-
registeredSenderIds: [],
|
|
126
|
-
};
|
|
127
|
-
expect(mapFallbackValueToEditTemplateData(source).versions.base.header).toBeUndefined();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('omits unicode-validity when value is not a boolean', () => {
|
|
131
|
-
const source = {
|
|
132
|
-
smsTemplateId: 'x',
|
|
133
|
-
templateName: 'y',
|
|
134
|
-
templateContent: 't',
|
|
135
|
-
unicodeValidity: 'yes',
|
|
136
|
-
};
|
|
137
|
-
expect(mapFallbackValueToEditTemplateData(source).versions.base['unicode-validity']).toBeUndefined();
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe('getBaseFromSmsTraiFormData', () => {
|
|
142
|
-
it('reads versions.base', () => {
|
|
143
|
-
const fd = { versions: { base: { 'sms-editor': 'a' } } };
|
|
144
|
-
expect(getBaseFromSmsTraiFormData(fd)).toEqual({ base: { 'sms-editor': 'a' } });
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('falls back to value.base', () => {
|
|
148
|
-
const fd = { value: { base: { 'sms-editor': 'b' } } };
|
|
149
|
-
expect(getBaseFromSmsTraiFormData(fd)).toEqual({ base: { 'sms-editor': 'b' } });
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('uses empty base when neither path exists', () => {
|
|
153
|
-
expect(getBaseFromSmsTraiFormData({})).toEqual({ base: {} });
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('normalizes null versions.base to empty object', () => {
|
|
157
|
-
expect(getBaseFromSmsTraiFormData({ versions: { base: null } })).toEqual({ base: {} });
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('resolveContentFromTraiBase', () => {
|
|
162
|
-
it('returns sms-editor string', () => {
|
|
163
|
-
expect(resolveContentFromTraiBase({ 'sms-editor': 'z' })).toBe('z');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('returns empty string when missing', () => {
|
|
167
|
-
expect(resolveContentFromTraiBase({})).toBe('');
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
describe('filterSmsTemplatesByCategory', () => {
|
|
172
|
-
const templates = [
|
|
173
|
-
{ versions: { base: { type: 'Promo' } } },
|
|
174
|
-
{ versions: { base: { type: 'Explicit' } } },
|
|
175
|
-
];
|
|
176
|
-
|
|
177
|
-
it('returns empty array for non-array input', () => {
|
|
178
|
-
expect(filterSmsTemplatesByCategory(null, SMS_CATEGORY_FILTERS.PROMOTIONAL)).toEqual([]);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('returns all templates for ALL or missing filter', () => {
|
|
182
|
-
expect(filterSmsTemplatesByCategory(templates, SMS_CATEGORY_FILTERS.ALL)).toEqual(templates);
|
|
183
|
-
expect(filterSmsTemplatesByCategory(templates, null)).toEqual(templates);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('filters by lowercase type substring', () => {
|
|
187
|
-
const filtered = filterSmsTemplatesByCategory(templates, SMS_CATEGORY_FILTERS.PROMOTIONAL);
|
|
188
|
-
expect(filtered).toHaveLength(1);
|
|
189
|
-
expect(filtered[0].versions.base.type).toBe('Promo');
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('treats missing type as empty string for filtering', () => {
|
|
193
|
-
const mixed = [{}, { versions: { base: {} } }];
|
|
194
|
-
expect(filterSmsTemplatesByCategory(mixed, SMS_CATEGORY_FILTERS.PROMOTIONAL)).toEqual([]);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
describe('buildFallbackDataFromCreativesPayload', () => {
|
|
199
|
-
it('returns null for missing or non-SMS channel', () => {
|
|
200
|
-
expect(buildFallbackDataFromCreativesPayload(null)).toBeNull();
|
|
201
|
-
expect(buildFallbackDataFromCreativesPayload({ channel: 'RCS' })).toBeNull();
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('maps SMS payload with messageBody and templateConfigs', () => {
|
|
205
|
-
const creativesData = {
|
|
206
|
-
channel: 'SMS',
|
|
207
|
-
messageBody: 'Hello',
|
|
208
|
-
templateConfigs: {
|
|
209
|
-
templateId: 99,
|
|
210
|
-
templateName: 'T',
|
|
211
|
-
template: 'Template str',
|
|
212
|
-
registeredSenderIds: ['R1'],
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
expect(buildFallbackDataFromCreativesPayload(creativesData)).toEqual({
|
|
216
|
-
smsTemplateId: '99',
|
|
217
|
-
templateName: 'T',
|
|
218
|
-
content: 'Hello',
|
|
219
|
-
templateContent: 'Template str',
|
|
220
|
-
senderId: 'R1',
|
|
221
|
-
registeredSenderIds: ['R1'],
|
|
222
|
-
unicodeValidity: true,
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('uses template string as body when messageBody is not a string', () => {
|
|
227
|
-
const creativesData = {
|
|
228
|
-
channel: 'SMS',
|
|
229
|
-
messageBody: null,
|
|
230
|
-
templateConfigs: {
|
|
231
|
-
template: 'only-template',
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
const out = buildFallbackDataFromCreativesPayload(creativesData);
|
|
235
|
-
expect(out.content).toBe('only-template');
|
|
236
|
-
expect(out.templateContent).toBe('only-template');
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('stringifies templateId and templateName when present', () => {
|
|
240
|
-
const creativesData = {
|
|
241
|
-
channel: 'SMS',
|
|
242
|
-
messageBody: 'm',
|
|
243
|
-
templateConfigs: { templateId: 0, templateName: null },
|
|
244
|
-
};
|
|
245
|
-
const out = buildFallbackDataFromCreativesPayload(creativesData);
|
|
246
|
-
expect(out.smsTemplateId).toBe('0');
|
|
247
|
-
expect(out.templateName).toBe('');
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('uses empty templateConfigs when null', () => {
|
|
251
|
-
const creativesData = {
|
|
252
|
-
channel: 'SMS',
|
|
253
|
-
messageBody: 'only-body',
|
|
254
|
-
templateConfigs: null,
|
|
255
|
-
};
|
|
256
|
-
const out = buildFallbackDataFromCreativesPayload(creativesData);
|
|
257
|
-
expect(out.templateContent).toBe('only-body');
|
|
258
|
-
expect(out.content).toBe('only-body');
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
});
|
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
import { renderHook, act } from '@testing-library/react-hooks';
|
|
2
|
-
import { waitFor } from '@testing-library/react';
|
|
3
|
-
import { useLocalTemplateList } from '../useLocalTemplateList';
|
|
4
|
-
|
|
5
|
-
const makeDeferred = () => {
|
|
6
|
-
let resolve;
|
|
7
|
-
let reject;
|
|
8
|
-
const promise = new Promise((res, rej) => {
|
|
9
|
-
resolve = res;
|
|
10
|
-
reject = rej;
|
|
11
|
-
});
|
|
12
|
-
return { promise, resolve, reject };
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
describe('useLocalTemplateList', () => {
|
|
16
|
-
it('drops stale response when a newer reset starts', async () => {
|
|
17
|
-
const first = makeDeferred();
|
|
18
|
-
const second = makeDeferred();
|
|
19
|
-
const fetchTemplates = jest
|
|
20
|
-
.fn()
|
|
21
|
-
.mockImplementationOnce(() => first.promise)
|
|
22
|
-
.mockImplementationOnce(() => second.promise);
|
|
23
|
-
|
|
24
|
-
const { result } = renderHook(() =>
|
|
25
|
-
useLocalTemplateList({ fetchTemplates, perPage: 2 })
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
act(() => {
|
|
29
|
-
result.current.reset('old-search');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
act(() => {
|
|
33
|
-
result.current.reset('new-search');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
await act(async () => {
|
|
37
|
-
second.resolve({
|
|
38
|
-
templates: [{ _id: 'new-1' }],
|
|
39
|
-
totalCount: 1,
|
|
40
|
-
});
|
|
41
|
-
await Promise.resolve();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
expect(result.current.templates).toEqual([{ _id: 'new-1' }]);
|
|
45
|
-
expect(result.current.totalCount).toBe(1);
|
|
46
|
-
|
|
47
|
-
await act(async () => {
|
|
48
|
-
first.resolve({
|
|
49
|
-
templates: [{ _id: 'old-1' }],
|
|
50
|
-
totalCount: 1,
|
|
51
|
-
});
|
|
52
|
-
await Promise.resolve();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Older response should not overwrite latest state.
|
|
56
|
-
expect(result.current.templates).toEqual([{ _id: 'new-1' }]);
|
|
57
|
-
expect(result.current.search).toBe('new-search');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('keeps existing templates when loadMore fails', async () => {
|
|
61
|
-
const fetchTemplates = jest.fn(({ page }) => {
|
|
62
|
-
if (page === 1) {
|
|
63
|
-
return Promise.resolve({
|
|
64
|
-
templates: [{ _id: 't1' }, { _id: 't2' }],
|
|
65
|
-
totalCount: 4,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
return Promise.reject(new Error('load-more-failed'));
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const { result } = renderHook(() =>
|
|
72
|
-
useLocalTemplateList({ fetchTemplates, perPage: 2 })
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
act(() => {
|
|
76
|
-
result.current.reset('');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
await waitFor(() => {
|
|
80
|
-
expect(result.current.loading).toBe(false);
|
|
81
|
-
expect(result.current.templates).toHaveLength(2);
|
|
82
|
-
expect(result.current.page).toBe(1);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
act(() => {
|
|
86
|
-
result.current.loadMore();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
await waitFor(() => {
|
|
90
|
-
expect(result.current.loading).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Non-reset failure should not wipe already loaded data.
|
|
94
|
-
expect(result.current.templates).toEqual([{ _id: 't1' }, { _id: 't2' }]);
|
|
95
|
-
expect(result.current.totalCount).toBe(4);
|
|
96
|
-
expect(result.current.page).toBe(1);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('clears list on reset failure', async () => {
|
|
100
|
-
const fetchTemplates = jest
|
|
101
|
-
.fn()
|
|
102
|
-
.mockResolvedValueOnce({
|
|
103
|
-
templates: [{ _id: 'seed-1' }, { _id: 'seed-2' }],
|
|
104
|
-
totalCount: 2,
|
|
105
|
-
})
|
|
106
|
-
.mockRejectedValueOnce(new Error('reset-failed'));
|
|
107
|
-
|
|
108
|
-
const { result } = renderHook(() =>
|
|
109
|
-
useLocalTemplateList({ fetchTemplates, perPage: 2 })
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
act(() => {
|
|
113
|
-
result.current.reset('');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
await waitFor(() => {
|
|
117
|
-
expect(result.current.loading).toBe(false);
|
|
118
|
-
expect(result.current.templates).toHaveLength(2);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
act(() => {
|
|
122
|
-
result.current.reset('fresh-search');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
await waitFor(() => {
|
|
126
|
-
expect(result.current.loading).toBe(false);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
expect(result.current.templates).toEqual([]);
|
|
130
|
-
expect(result.current.totalCount).toBe(0);
|
|
131
|
-
expect(result.current.page).toBe(1);
|
|
132
|
-
expect(result.current.search).toBe('fresh-search');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('keeps previous totalCount when loadMore returns zero totalCount', async () => {
|
|
136
|
-
const fetchTemplates = jest.fn(({ page }) => {
|
|
137
|
-
if (page === 1) {
|
|
138
|
-
return Promise.resolve({
|
|
139
|
-
templates: [{ _id: 't1' }],
|
|
140
|
-
totalCount: 10,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
return Promise.resolve({
|
|
144
|
-
templates: [{ _id: 't2' }],
|
|
145
|
-
totalCount: 0,
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const { result } = renderHook(() =>
|
|
150
|
-
useLocalTemplateList({ fetchTemplates, perPage: 1 })
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
act(() => {
|
|
154
|
-
result.current.reset('');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
await waitFor(() => {
|
|
158
|
-
expect(result.current.loading).toBe(false);
|
|
159
|
-
expect(result.current.templates).toHaveLength(1);
|
|
160
|
-
expect(result.current.totalCount).toBe(10);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
act(() => {
|
|
164
|
-
result.current.loadMore();
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
await waitFor(() => {
|
|
168
|
-
expect(result.current.loading).toBe(false);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
expect(result.current.templates.map((t) => t._id)).toEqual(['t1', 't2']);
|
|
172
|
-
expect(result.current.totalCount).toBe(10);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('ignores rejection from a superseded fetch (stale generation)', async () => {
|
|
176
|
-
const slowFirst = makeDeferred();
|
|
177
|
-
const fetchTemplates = jest
|
|
178
|
-
.fn()
|
|
179
|
-
.mockImplementationOnce(() => slowFirst.promise)
|
|
180
|
-
.mockImplementationOnce(() =>
|
|
181
|
-
Promise.resolve({ templates: [{ _id: 'from-second' }], totalCount: 1 }),
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
const { result } = renderHook(() =>
|
|
185
|
-
useLocalTemplateList({ fetchTemplates, perPage: 25 })
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
act(() => {
|
|
189
|
-
result.current.reset('first-search');
|
|
190
|
-
});
|
|
191
|
-
act(() => {
|
|
192
|
-
result.current.reset('second-search');
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
await act(async () => {
|
|
196
|
-
await Promise.resolve();
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
await waitFor(() => {
|
|
200
|
-
expect(result.current.loading).toBe(false);
|
|
201
|
-
expect(result.current.templates).toEqual([{ _id: 'from-second' }]);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
await act(async () => {
|
|
205
|
-
slowFirst.reject(new Error('stale'));
|
|
206
|
-
await Promise.resolve();
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
expect(result.current.templates).toEqual([{ _id: 'from-second' }]);
|
|
210
|
-
expect(result.current.search).toBe('second-search');
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('does not leave loading stuck when stale resolve completes after newer fetch (finally gen check)', async () => {
|
|
214
|
-
const d1 = makeDeferred();
|
|
215
|
-
const fetchTemplates = jest
|
|
216
|
-
.fn()
|
|
217
|
-
.mockImplementationOnce(() => d1.promise)
|
|
218
|
-
.mockResolvedValueOnce({ templates: [{ _id: 'new' }], totalCount: 1 });
|
|
219
|
-
|
|
220
|
-
const { result } = renderHook(() =>
|
|
221
|
-
useLocalTemplateList({ fetchTemplates, perPage: 2 }),
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
act(() => {
|
|
225
|
-
result.current.reset('a');
|
|
226
|
-
});
|
|
227
|
-
act(() => {
|
|
228
|
-
result.current.reset('b');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
await act(async () => {
|
|
232
|
-
d1.resolve({ templates: [{ _id: 'old' }], totalCount: 1 });
|
|
233
|
-
await Promise.resolve();
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
await waitFor(() => {
|
|
237
|
-
expect(result.current.loading).toBe(false);
|
|
238
|
-
expect(result.current.templates).toEqual([{ _id: 'new' }]);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('coerces setSearch non-string values to empty search term', () => {
|
|
243
|
-
const fetchTemplates = jest.fn().mockResolvedValue({ templates: [], totalCount: 0 });
|
|
244
|
-
const { result } = renderHook(() =>
|
|
245
|
-
useLocalTemplateList({ fetchTemplates, perPage: 25 }),
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
act(() => {
|
|
249
|
-
result.current.setSearch(null);
|
|
250
|
-
});
|
|
251
|
-
expect(result.current.search).toBe('');
|
|
252
|
-
|
|
253
|
-
act(() => {
|
|
254
|
-
result.current.setSearch(123);
|
|
255
|
-
});
|
|
256
|
-
expect(result.current.search).toBe('');
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it('does not call fetch when loadMore runs but canLoadMore is false', async () => {
|
|
260
|
-
const fetchTemplates = jest.fn().mockResolvedValue({
|
|
261
|
-
templates: [{ _id: 'only' }],
|
|
262
|
-
totalCount: 1,
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
const { result } = renderHook(() =>
|
|
266
|
-
useLocalTemplateList({ fetchTemplates, perPage: 1 }),
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
act(() => {
|
|
270
|
-
result.current.reset('');
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
await waitFor(() => {
|
|
274
|
-
expect(result.current.loading).toBe(false);
|
|
275
|
-
expect(result.current.canLoadMore).toBe(false);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const callsAfterFirst = fetchTemplates.mock.calls.length;
|
|
279
|
-
|
|
280
|
-
act(() => {
|
|
281
|
-
result.current.loadMore();
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
expect(fetchTemplates.mock.calls.length).toBe(callsAfterFirst);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it('passes explicit search term from reset() through to fetchTemplates', async () => {
|
|
288
|
-
const fetchTemplates = jest.fn().mockResolvedValue({
|
|
289
|
-
templates: [{ _id: 'r1' }],
|
|
290
|
-
totalCount: 5,
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
const { result } = renderHook(() =>
|
|
294
|
-
useLocalTemplateList({ fetchTemplates, perPage: 25 }),
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
act(() => {
|
|
298
|
-
result.current.reset('explicit-search');
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
await waitFor(() => {
|
|
302
|
-
expect(result.current.loading).toBe(false);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
expect(fetchTemplates).toHaveBeenCalledWith(
|
|
306
|
-
expect.objectContaining({ search: 'explicit-search', page: 1, reset: true }),
|
|
307
|
-
);
|
|
308
|
-
expect(result.current.search).toBe('explicit-search');
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it('stores a positive totalCount returned by the API', async () => {
|
|
312
|
-
const fetchTemplates = jest.fn().mockResolvedValue({
|
|
313
|
-
templates: [{ _id: 'a' }, { _id: 'b' }],
|
|
314
|
-
totalCount: 42,
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
const { result } = renderHook(() =>
|
|
318
|
-
useLocalTemplateList({ fetchTemplates, perPage: 25 }),
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
act(() => {
|
|
322
|
-
result.current.reset('');
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
await waitFor(() => {
|
|
326
|
-
expect(result.current.loading).toBe(false);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
expect(result.current.totalCount).toBe(42);
|
|
330
|
-
expect(result.current.templates).toHaveLength(2);
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it('canLoadMore is false while a fetch is in progress even when more items exist', async () => {
|
|
334
|
-
let resolveFirst;
|
|
335
|
-
const fetchTemplates = jest.fn(
|
|
336
|
-
() => new Promise((res) => { resolveFirst = res; }),
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
const { result } = renderHook(() =>
|
|
340
|
-
useLocalTemplateList({ fetchTemplates, perPage: 2 }),
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
act(() => {
|
|
344
|
-
result.current.reset('');
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// Loading is true → canLoadMore must be false regardless of hasMoreByTotal
|
|
348
|
-
expect(result.current.loading).toBe(true);
|
|
349
|
-
expect(result.current.canLoadMore).toBe(false);
|
|
350
|
-
|
|
351
|
-
await act(async () => {
|
|
352
|
-
resolveFirst({ templates: [{ _id: 'x' }, { _id: 'y' }], totalCount: 10 });
|
|
353
|
-
await Promise.resolve();
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
expect(result.current.loading).toBe(false);
|
|
357
|
-
expect(result.current.canLoadMore).toBe(true);
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it('canLoadMore is false when templates is empty (hasMoreByFullPage requires length > 0)', async () => {
|
|
361
|
-
const fetchTemplates = jest.fn().mockResolvedValue({
|
|
362
|
-
templates: [],
|
|
363
|
-
totalCount: 0,
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
const { result } = renderHook(() =>
|
|
367
|
-
useLocalTemplateList({ fetchTemplates, perPage: 0 }),
|
|
368
|
-
);
|
|
369
|
-
|
|
370
|
-
act(() => {
|
|
371
|
-
result.current.reset('');
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
await waitFor(() => {
|
|
375
|
-
expect(result.current.loading).toBe(false);
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
// templates.length === 0 means hasMoreByFullPage is false even if lastFetchFullPage would be true
|
|
379
|
-
expect(result.current.canLoadMore).toBe(false);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
it('allows loadMore when totalCount is unknown but first page is full (hasMoreByFullPage)', async () => {
|
|
383
|
-
const fetchTemplates = jest.fn(({ page }) => {
|
|
384
|
-
if (page === 1) {
|
|
385
|
-
return Promise.resolve({
|
|
386
|
-
templates: [{ _id: 'a' }, { _id: 'b' }],
|
|
387
|
-
totalCount: 0,
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
return Promise.resolve({
|
|
391
|
-
templates: [{ _id: 'c' }],
|
|
392
|
-
totalCount: 0,
|
|
393
|
-
});
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
const { result } = renderHook(() =>
|
|
397
|
-
useLocalTemplateList({ fetchTemplates, perPage: 2 }),
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
act(() => {
|
|
401
|
-
result.current.reset('');
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
await waitFor(() => {
|
|
405
|
-
expect(result.current.loading).toBe(false);
|
|
406
|
-
expect(result.current.canLoadMore).toBe(true);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
act(() => {
|
|
410
|
-
result.current.loadMore();
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
await waitFor(() => {
|
|
414
|
-
expect(result.current.loading).toBe(false);
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
expect(result.current.templates.map((t) => t._id)).toEqual(['a', 'b', 'c']);
|
|
418
|
-
expect(fetchTemplates).toHaveBeenCalledWith(
|
|
419
|
-
expect.objectContaining({ page: 2, reset: false }),
|
|
420
|
-
);
|
|
421
|
-
});
|
|
422
|
-
});
|