@createiq/htmldiff 1.0.4 → 1.0.5-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,7 +51,7 @@ describe('HtmlDiff', () => {
51
51
  [
52
52
  '<table><tr><td>col1</td><td>col2</td></tr><tr><td>Data 1</td><td>Data 2</td></tr></table>',
53
53
  '<table><tr><td>col1</td><td>col2</td></tr></table>',
54
- "<table><tr><td>col1</td><td>col2</td></tr><tr><td><del class='diffdel'>Data 1</del></td><td><del class='diffdel'>Data 2</del></td></tr></table>",
54
+ "<table><tr><td>col1</td><td>col2</td></tr><tr class='diffdel'><td class='diffdel'><del class='diffdel'>Data 1</del></td><td class='diffdel'><del class='diffdel'>Data 2</del></td></tr></table>",
55
55
  ],
56
56
  [
57
57
  'text',
@@ -339,4 +339,122 @@ describe('HtmlDiff', () => {
339
339
  )
340
340
  ).toEqual(`<div data-html="bar">Some<ins class='diffins'>&nbsp;different</ins> text here</div>`)
341
341
  })
342
+
343
+ it('should keep added paragraphs as separate <p> blocks instead of concatenating their content', () => {
344
+ // When new content adds extra paragraphs alongside an existing one, structural
345
+ // normalization currently strips the wrappers and emits the added content as a
346
+ // single concatenated <ins> ("BoopShoop"). Each added paragraph should remain
347
+ // its own <p> so they render on separate lines.
348
+ expect(HtmlDiff.execute('<p>shared</p>', '<p>shared</p><p>Boop</p><p>Shoop</p>')).toEqual(
349
+ `<p>shared</p><p><ins class='diffins'>Boop</ins></p><p><ins class='diffins'>Shoop</ins></p>`
350
+ )
351
+ })
352
+
353
+ it('should keep removed paragraphs as separate <p> blocks instead of concatenating their content', () => {
354
+ expect(HtmlDiff.execute('<p>shared</p><p>Boop</p><p>Shoop</p>', '<p>shared</p>')).toEqual(
355
+ `<p>shared</p><p><del class='diffdel'>Boop</del></p><p><del class='diffdel'>Shoop</del></p>`
356
+ )
357
+ })
358
+
359
+ it('should keep an added paragraph distinct from the preceding paragraph', () => {
360
+ // The compare-bubble regression: a single added <p>Boop</p> after a modified
361
+ // paragraph used to collapse onto the same line as "discovered." because the
362
+ // <p> wrapper was stripped from the inserted content.
363
+ expect(
364
+ HtmlDiff.execute(
365
+ '<p>Shared content has extra removed has been discovered.</p>',
366
+ '<p>Shared content has been discovered.</p><p>Boop</p>'
367
+ )
368
+ ).toEqual(
369
+ `<p>Shared content<del class='diffdel'>&nbsp;has extra removed</del> has been discovered.</p><p><ins class='diffins'>Boop</ins></p>`
370
+ )
371
+ })
372
+
373
+ it('should preserve new wrappers when old is plain text and new is wrapped', () => {
374
+ // Asymmetric structure case: when one side has no structural wrappers at all,
375
+ // normalization would produce dangling tags. We disable it for this case so the
376
+ // word-level diff can correctly emit new's structural wrappers around the diff.
377
+ expect(HtmlDiff.execute('X with extra', '<p>X</p><p>Boop</p>')).toEqual(
378
+ `<p>X<del class='diffmod'>&nbsp;with extra</del></p><p><ins class='diffmod'>Boop</ins></p>`
379
+ )
380
+ })
381
+
382
+ it('should preserve new wrappers when old is plain text spanning multiple paragraphs in new', () => {
383
+ expect(
384
+ HtmlDiff.execute(
385
+ 'Shared content has extra removed has been discovered.',
386
+ '<div><p>Shared content has been discovered.</p><p>Boop</p></div>'
387
+ )
388
+ ).toEqual(
389
+ `<div><p>Shared content<del class='diffdel'>&nbsp;has extra removed</del> has been discovered.</p><p><ins class='diffins'>Boop</ins></p></div>`
390
+ )
391
+ })
392
+
393
+ it('should keep a paragraph added in the middle as its own block', () => {
394
+ expect(HtmlDiff.execute('<p>A</p><p>C</p>', '<p>A</p><p>B</p><p>C</p>')).toEqual(
395
+ `<p>A</p><p><ins class='diffins'>B</ins></p><p>C</p>`
396
+ )
397
+ })
398
+
399
+ it('should keep a paragraph added at the start as its own block', () => {
400
+ expect(HtmlDiff.execute('<p>B</p>', '<p>A</p><p>B</p>')).toEqual(`<p><ins class='diffins'>A</ins></p><p>B</p>`)
401
+ })
402
+
403
+ it('should keep multiple paragraphs added at the start as separate blocks', () => {
404
+ expect(HtmlDiff.execute('<p>C</p>', '<p>A</p><p>B</p><p>C</p>')).toEqual(
405
+ `<p><ins class='diffins'>A</ins></p><p><ins class='diffins'>B</ins></p><p>C</p>`
406
+ )
407
+ })
408
+
409
+ it('should keep a paragraph removed from the start as its own block', () => {
410
+ expect(HtmlDiff.execute('<p>A</p><p>B</p>', '<p>B</p>')).toEqual(`<p><del class='diffdel'>A</del></p><p>B</p>`)
411
+ })
412
+
413
+ it('should keep a paragraph removed from the middle as its own block', () => {
414
+ expect(HtmlDiff.execute('<p>A</p><p>B</p><p>C</p>', '<p>A</p><p>C</p>')).toEqual(
415
+ `<p>A</p><p><del class='diffdel'>B</del></p><p>C</p>`
416
+ )
417
+ })
418
+
419
+ it('should keep paragraph boundaries on a replace operation that spans multiple paragraphs', () => {
420
+ // The diff algorithm treats this as a single Replace block (delete A,B then insert X,Y,Z)
421
+ // rather than aligning per-paragraph; we accept that as the price of a single-pass algorithm
422
+ // but verify each paragraph still renders as its own <p> rather than concatenating.
423
+ expect(HtmlDiff.execute('<p>A</p><p>B</p>', '<p>X</p><p>Y</p><p>Z</p>')).toEqual(
424
+ `<p><del class='diffmod'>A</del></p><p><del class='diffmod'>B</del></p><p><ins class='diffmod'>X</ins></p><p><ins class='diffmod'>Y</ins></p><p><ins class='diffmod'>Z</ins></p>`
425
+ )
426
+ })
427
+
428
+ it('should keep added paragraphs containing inline formatting as their own blocks', () => {
429
+ expect(HtmlDiff.execute('<p>shared</p>', '<p>shared</p><p><strong>bold added</strong></p>')).toEqual(
430
+ `<p>shared</p><p><strong><ins class='diffins'>bold added</ins></strong></p>`
431
+ )
432
+ })
433
+
434
+ it('should preserve a paragraph added inside a list item', () => {
435
+ expect(HtmlDiff.execute('<ol><li><p>shared</p></li></ol>', '<ol><li><p>shared</p><p>added</p></li></ol>')).toEqual(
436
+ `<ol><li><p>shared</p><p><ins class='diffins'>added</ins></p></li></ol>`
437
+ )
438
+ })
439
+
440
+ it('should preserve old wrapper structure when old is wrapped and new is plain text', () => {
441
+ // Inverse of the plain-text-old / wrapped-new asymmetric case. With one side unwrapped,
442
+ // the asymmetric guardrail should disable structural normalization. The word-level diff
443
+ // emits old's paragraph structure with deletions in their original paragraphs and the
444
+ // new content appended inline.
445
+ expect(HtmlDiff.execute('<p>X</p><p>Boop</p>', 'X with extra')).toEqual(
446
+ `<p>X</p><p><del class='diffmod'>Boop</del></p><ins class='diffmod'>&nbsp;with extra</ins>`
447
+ )
448
+ })
449
+
450
+ it.each([
451
+ ['section', '<section>X</section>', '<section>X</section><section>Boop</section>'],
452
+ ['article', '<article>X</article>', '<article>X</article><article>Boop</article>'],
453
+ ['aside', '<aside>X</aside>', '<aside>X</aside><aside>Boop</aside>'],
454
+ ['nav', '<nav>X</nav>', '<nav>X</nav><nav>Boop</nav>'],
455
+ ])('should treat <%s> as a structural wrapper for paragraph-add diffs', (tag, oldText, newText) => {
456
+ expect(HtmlDiff.execute(oldText, newText)).toEqual(
457
+ `<${tag}>X</${tag}><${tag}><ins class='diffins'>Boop</ins></${tag}>`
458
+ )
459
+ })
342
460
  })