@adobe/spacecat-shared-tokowaka-client 1.0.5 → 1.1.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.
@@ -1321,4 +1321,144 @@ Overall, Bulk positions itself as a better choice for sports nutrition through i
1321
1321
  expect(patches[1].suggestionId).to.equal('sugg-new-1'); // FAQ
1322
1322
  });
1323
1323
  });
1324
+
1325
+ describe('rollbackPatches', () => {
1326
+ it('should remove FAQ heading when last FAQ suggestion is rolled back', () => {
1327
+ const config = {
1328
+ siteId: 'site-123',
1329
+ baseURL: 'https://example.com',
1330
+ version: '1.0',
1331
+ tokowakaOptimizations: {
1332
+ '/page1': {
1333
+ prerender: true,
1334
+ patches: [
1335
+ {
1336
+ opportunityId: 'opp-faq',
1337
+ // FAQ heading patch (no suggestionId)
1338
+ op: 'appendChild',
1339
+ selector: 'body',
1340
+ value: { type: 'element', tagName: 'h2', children: [{ type: 'text', value: 'FAQs' }] },
1341
+ },
1342
+ {
1343
+ opportunityId: 'opp-faq',
1344
+ suggestionId: 'sugg-1',
1345
+ op: 'appendChild',
1346
+ value: { type: 'element', tagName: 'div' },
1347
+ },
1348
+ ],
1349
+ },
1350
+ },
1351
+ };
1352
+
1353
+ const result = mapper.rollbackPatches(config, ['sugg-1'], 'opp-faq');
1354
+
1355
+ // Both FAQ item and heading should be removed
1356
+ expect(result.tokowakaOptimizations).to.not.have.property('/page1');
1357
+ expect(result.removedCount).to.equal(2);
1358
+ });
1359
+
1360
+ it('should keep FAQ heading when other FAQ suggestions remain', () => {
1361
+ const config = {
1362
+ siteId: 'site-123',
1363
+ baseURL: 'https://example.com',
1364
+ version: '1.0',
1365
+ tokowakaOptimizations: {
1366
+ '/page1': {
1367
+ prerender: true,
1368
+ patches: [
1369
+ {
1370
+ opportunityId: 'opp-faq',
1371
+ // FAQ heading patch
1372
+ op: 'appendChild',
1373
+ selector: 'body',
1374
+ value: { type: 'element', tagName: 'h2', children: [{ type: 'text', value: 'FAQs' }] },
1375
+ },
1376
+ {
1377
+ opportunityId: 'opp-faq',
1378
+ suggestionId: 'sugg-1',
1379
+ op: 'appendChild',
1380
+ value: { type: 'element', tagName: 'div' },
1381
+ },
1382
+ {
1383
+ opportunityId: 'opp-faq',
1384
+ suggestionId: 'sugg-2',
1385
+ op: 'appendChild',
1386
+ value: { type: 'element', tagName: 'div' },
1387
+ },
1388
+ ],
1389
+ },
1390
+ },
1391
+ };
1392
+
1393
+ const result = mapper.rollbackPatches(config, ['sugg-1'], 'opp-faq');
1394
+
1395
+ // Only sugg-1 removed, heading and sugg-2 remain
1396
+ expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(2);
1397
+ expect(result.tokowakaOptimizations['/page1'].patches[0]).to.not.have.property('suggestionId'); // Heading
1398
+ expect(result.tokowakaOptimizations['/page1'].patches[1].suggestionId).to.equal('sugg-2');
1399
+ expect(result.removedCount).to.equal(1);
1400
+ });
1401
+
1402
+ it('should handle multiple URLs independently', () => {
1403
+ const config = {
1404
+ siteId: 'site-123',
1405
+ baseURL: 'https://example.com',
1406
+ version: '1.0',
1407
+ tokowakaOptimizations: {
1408
+ '/page1': {
1409
+ prerender: true,
1410
+ patches: [
1411
+ { opportunityId: 'opp-faq', op: 'appendChild', value: 'FAQs' },
1412
+ {
1413
+ opportunityId: 'opp-faq',
1414
+ suggestionId: 'sugg-1',
1415
+ op: 'appendChild',
1416
+ value: 'FAQ1',
1417
+ },
1418
+ ],
1419
+ },
1420
+ '/page2': {
1421
+ prerender: true,
1422
+ patches: [
1423
+ { opportunityId: 'opp-faq', op: 'appendChild', value: 'FAQs' },
1424
+ {
1425
+ opportunityId: 'opp-faq',
1426
+ suggestionId: 'sugg-2',
1427
+ op: 'appendChild',
1428
+ value: 'FAQ2',
1429
+ },
1430
+ {
1431
+ opportunityId: 'opp-faq',
1432
+ suggestionId: 'sugg-3',
1433
+ op: 'appendChild',
1434
+ value: 'FAQ3',
1435
+ },
1436
+ ],
1437
+ },
1438
+ },
1439
+ };
1440
+
1441
+ // Remove sugg-1 from page1 (should remove heading too)
1442
+ // Remove sugg-2 from page2 (should keep heading because sugg-3 remains)
1443
+ const result = mapper.rollbackPatches(config, ['sugg-1', 'sugg-2'], 'opp-faq');
1444
+
1445
+ // page1 completely removed
1446
+ expect(result.tokowakaOptimizations).to.not.have.property('/page1');
1447
+
1448
+ // page2 still has heading + sugg-3
1449
+ expect(result.tokowakaOptimizations['/page2'].patches).to.have.lengthOf(2);
1450
+ expect(result.tokowakaOptimizations['/page2'].patches[0]).to.not.have.property('suggestionId');
1451
+ expect(result.tokowakaOptimizations['/page2'].patches[1].suggestionId).to.equal('sugg-3');
1452
+
1453
+ expect(result.removedCount).to.equal(3); // page1: heading+sugg-1, page2: sugg-2
1454
+ });
1455
+
1456
+ it('should handle null/undefined config gracefully', () => {
1457
+ const result1 = mapper.rollbackPatches(null, ['sugg-1'], 'opp-faq');
1458
+ expect(result1).to.be.null;
1459
+
1460
+ const result2 = mapper.rollbackPatches(undefined, ['sugg-1'], 'opp-faq');
1461
+ expect(result2).to.be.undefined;
1462
+ });
1463
+ });
1324
1464
  });
@@ -13,7 +13,7 @@
13
13
  /* eslint-env mocha */
14
14
 
15
15
  import { expect } from 'chai';
16
- import { mergePatches } from '../../src/utils/patch-utils.js';
16
+ import { mergePatches, removePatchesBySuggestionIds } from '../../src/utils/patch-utils.js';
17
17
 
18
18
  describe('Patch Utils', () => {
19
19
  describe('mergePatches', () => {
@@ -145,4 +145,296 @@ describe('Patch Utils', () => {
145
145
  expect(result.patches[0].value.children[0].value).to.equal('New');
146
146
  });
147
147
  });
148
+
149
+ describe('removePatchesBySuggestionIds', () => {
150
+ it('should remove patches with matching suggestion IDs', () => {
151
+ const config = {
152
+ siteId: 'site-123',
153
+ baseURL: 'https://example.com',
154
+ version: '1.0',
155
+ tokowakaOptimizations: {
156
+ '/page1': {
157
+ prerender: true,
158
+ patches: [
159
+ {
160
+ opportunityId: 'opp-1',
161
+ suggestionId: 'sugg-1',
162
+ op: 'replace',
163
+ value: 'value-1',
164
+ },
165
+ {
166
+ opportunityId: 'opp-1',
167
+ suggestionId: 'sugg-2',
168
+ op: 'replace',
169
+ value: 'value-2',
170
+ },
171
+ ],
172
+ },
173
+ '/page2': {
174
+ prerender: true,
175
+ patches: [
176
+ {
177
+ opportunityId: 'opp-1',
178
+ suggestionId: 'sugg-3',
179
+ op: 'replace',
180
+ value: 'value-3',
181
+ },
182
+ ],
183
+ },
184
+ },
185
+ };
186
+
187
+ const result = removePatchesBySuggestionIds(config, ['sugg-1', 'sugg-3']);
188
+
189
+ expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(1);
190
+ expect(result.tokowakaOptimizations['/page1'].patches[0].suggestionId).to.equal('sugg-2');
191
+ expect(result.tokowakaOptimizations['/page2']).to.be.undefined; // URL removed because no patches left
192
+ expect(result.removedCount).to.equal(2);
193
+ });
194
+
195
+ it('should remove URL paths with no remaining patches', () => {
196
+ const config = {
197
+ siteId: 'site-123',
198
+ baseURL: 'https://example.com',
199
+ version: '1.0',
200
+ tokowakaOptimizations: {
201
+ '/page1': {
202
+ prerender: true,
203
+ patches: [
204
+ {
205
+ opportunityId: 'opp-1',
206
+ suggestionId: 'sugg-1',
207
+ op: 'replace',
208
+ value: 'value-1',
209
+ },
210
+ ],
211
+ },
212
+ },
213
+ };
214
+
215
+ const result = removePatchesBySuggestionIds(config, ['sugg-1']);
216
+
217
+ expect(result.tokowakaOptimizations).to.deep.equal({});
218
+ expect(result.removedCount).to.equal(1);
219
+ });
220
+
221
+ it('should handle empty suggestion IDs array', () => {
222
+ const config = {
223
+ siteId: 'site-123',
224
+ baseURL: 'https://example.com',
225
+ version: '1.0',
226
+ tokowakaOptimizations: {
227
+ '/page1': {
228
+ prerender: true,
229
+ patches: [
230
+ {
231
+ opportunityId: 'opp-1',
232
+ suggestionId: 'sugg-1',
233
+ op: 'replace',
234
+ value: 'value-1',
235
+ },
236
+ ],
237
+ },
238
+ },
239
+ };
240
+
241
+ const result = removePatchesBySuggestionIds(config, []);
242
+
243
+ expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(1);
244
+ expect(result.removedCount).to.equal(0);
245
+ });
246
+
247
+ it('should handle non-matching suggestion IDs', () => {
248
+ const config = {
249
+ siteId: 'site-123',
250
+ baseURL: 'https://example.com',
251
+ version: '1.0',
252
+ tokowakaOptimizations: {
253
+ '/page1': {
254
+ prerender: true,
255
+ patches: [
256
+ {
257
+ opportunityId: 'opp-1',
258
+ suggestionId: 'sugg-1',
259
+ op: 'replace',
260
+ value: 'value-1',
261
+ },
262
+ ],
263
+ },
264
+ },
265
+ };
266
+
267
+ const result = removePatchesBySuggestionIds(config, ['sugg-999']);
268
+
269
+ expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(1);
270
+ expect(result.removedCount).to.equal(0);
271
+ });
272
+
273
+ it('should handle null/undefined config gracefully', () => {
274
+ const result1 = removePatchesBySuggestionIds(null, ['sugg-1']);
275
+ expect(result1).to.be.null;
276
+
277
+ const result2 = removePatchesBySuggestionIds(undefined, ['sugg-1']);
278
+ expect(result2).to.be.undefined;
279
+ });
280
+
281
+ it('should preserve patches without suggestionId (heading patches)', () => {
282
+ const config = {
283
+ siteId: 'site-123',
284
+ baseURL: 'https://example.com',
285
+ version: '1.0',
286
+ tokowakaOptimizations: {
287
+ '/page1': {
288
+ prerender: true,
289
+ patches: [
290
+ {
291
+ opportunityId: 'opp-1',
292
+ // No suggestionId - heading patch
293
+ op: 'replace',
294
+ value: 'heading-value',
295
+ },
296
+ {
297
+ opportunityId: 'opp-1',
298
+ suggestionId: 'sugg-1',
299
+ op: 'replace',
300
+ value: 'value-1',
301
+ },
302
+ ],
303
+ },
304
+ },
305
+ };
306
+
307
+ const result = removePatchesBySuggestionIds(config, ['sugg-1']);
308
+
309
+ expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(1);
310
+ expect(result.tokowakaOptimizations['/page1'].patches[0]).to.not.have.property('suggestionId');
311
+ expect(result.removedCount).to.equal(1);
312
+ });
313
+
314
+ it('should remove patches by additional patch keys', () => {
315
+ const config = {
316
+ siteId: 'site-123',
317
+ baseURL: 'https://example.com',
318
+ version: '1.0',
319
+ tokowakaOptimizations: {
320
+ '/page1': {
321
+ prerender: true,
322
+ patches: [
323
+ {
324
+ opportunityId: 'opp-faq',
325
+ // No suggestionId - FAQ heading patch
326
+ op: 'appendChild',
327
+ value: 'FAQs',
328
+ },
329
+ {
330
+ opportunityId: 'opp-faq',
331
+ suggestionId: 'sugg-1',
332
+ op: 'appendChild',
333
+ value: 'FAQ item 1',
334
+ },
335
+ {
336
+ opportunityId: 'opp-faq',
337
+ suggestionId: 'sugg-2',
338
+ op: 'appendChild',
339
+ value: 'FAQ item 2',
340
+ },
341
+ ],
342
+ },
343
+ },
344
+ };
345
+
346
+ // Remove all FAQ suggestions and the heading patch (identified by opportunityId only)
347
+ const result = removePatchesBySuggestionIds(
348
+ config,
349
+ ['sugg-1', 'sugg-2'],
350
+ ['/page1:opp-faq'], // Additional patch key for FAQ heading
351
+ );
352
+
353
+ expect(result.tokowakaOptimizations).to.deep.equal({});
354
+ expect(result.removedCount).to.equal(3);
355
+ });
356
+
357
+ it('should remove patches by additional patch keys while keeping other suggestions', () => {
358
+ const config = {
359
+ siteId: 'site-123',
360
+ baseURL: 'https://example.com',
361
+ version: '1.0',
362
+ tokowakaOptimizations: {
363
+ '/page1': {
364
+ prerender: true,
365
+ patches: [
366
+ {
367
+ opportunityId: 'opp-faq',
368
+ // No suggestionId - FAQ heading patch
369
+ op: 'appendChild',
370
+ value: 'FAQs',
371
+ },
372
+ {
373
+ opportunityId: 'opp-faq',
374
+ suggestionId: 'sugg-1',
375
+ op: 'appendChild',
376
+ value: 'FAQ item 1',
377
+ },
378
+ {
379
+ opportunityId: 'opp-faq',
380
+ suggestionId: 'sugg-2',
381
+ op: 'appendChild',
382
+ value: 'FAQ item 2',
383
+ },
384
+ ],
385
+ },
386
+ },
387
+ };
388
+
389
+ // Remove only one FAQ suggestion, keep the heading
390
+ const result = removePatchesBySuggestionIds(config, ['sugg-1'], []);
391
+
392
+ expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(2);
393
+ expect(result.removedCount).to.equal(1);
394
+ });
395
+
396
+ it('should handle both suggestionIds and additional patch keys together', () => {
397
+ const config = {
398
+ siteId: 'site-123',
399
+ baseURL: 'https://example.com',
400
+ version: '1.0',
401
+ tokowakaOptimizations: {
402
+ '/page1': {
403
+ prerender: true,
404
+ patches: [
405
+ {
406
+ opportunityId: 'opp-headings',
407
+ suggestionId: 'sugg-h1',
408
+ op: 'replace',
409
+ value: 'Heading 1',
410
+ },
411
+ {
412
+ opportunityId: 'opp-faq',
413
+ // FAQ heading patch
414
+ op: 'appendChild',
415
+ value: 'FAQs',
416
+ },
417
+ {
418
+ opportunityId: 'opp-faq',
419
+ suggestionId: 'sugg-f1',
420
+ op: 'appendChild',
421
+ value: 'FAQ 1',
422
+ },
423
+ ],
424
+ },
425
+ },
426
+ };
427
+
428
+ // Remove FAQ suggestion and heading, keep heading patch
429
+ const result = removePatchesBySuggestionIds(
430
+ config,
431
+ ['sugg-f1'],
432
+ ['/page1:opp-faq'], // Remove FAQ heading
433
+ );
434
+
435
+ expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(1);
436
+ expect(result.tokowakaOptimizations['/page1'].patches[0].suggestionId).to.equal('sugg-h1');
437
+ expect(result.removedCount).to.equal(2);
438
+ });
439
+ });
148
440
  });