Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,21 +228,23 @@ export const concatChanges = (baseChanges: Change[], changesToAdd: Change[]) =>

// Search for overlap between the two changes for simplification
if (isAdd(prevChange) && isRemove(change)) {
const minLength = Math.min(getChangeLength(prevChange), getChangeLength(change))
const beginningOverlap = getBeginningOverlap(getChangeText(prevChange), getChangeText(change))
const endingOverlap = getEndingOverlap(getChangeText(prevChange), getChangeText(change))
// Clamp so a repeated boundary char (' ' vs ' ') isn't counted as both beginning and ending overlap
const endingOverlap = Math.min(
getEndingOverlap(getChangeText(prevChange), getChangeText(change)),
minLength - beginningOverlap
)

if (beginningOverlap > 0 || endingOverlap > 0) {
const minLength = Math.min(getChangeLength(prevChange), getChangeLength(change))
const getNonOverlap = (text: string) =>
text.slice(beginningOverlap, endingOverlap > 0 ? -endingOverlap : Infinity)
const newChanges = [
getChangeText(change).slice(0, beginningOverlap),
replaceChangeText(prevChange, getNonOverlap(getChangeText(prevChange))),
replaceChangeText(change, getNonOverlap(getChangeText(change))),
]
if (endingOverlap > 0 && minLength !== beginningOverlap) {
newChanges.push(getChangeText(change).slice(-Math.min(endingOverlap, minLength - beginningOverlap)))
}
if (endingOverlap > 0) newChanges.push(getChangeText(change).slice(-endingOverlap))

changes.splice(index - 1, 2, ...newChanges.filter(getChangeLength))
index--
Expand Down
21 changes: 20 additions & 1 deletion tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createStringTracker, StringOp, StringTracker } from '../src'
import { Change, createStringTracker, StringOp, StringTracker } from '../src'
import { assertValidTracker } from './helpers'

const str1 = 'this is my word'

Expand Down Expand Up @@ -180,6 +181,24 @@ it('should remove subsequent adds/strings with a remove in between in changes ar
expect(tracker.getChanges()).toStrictEqual(['asd', [1, 'a '], 'pog'])
})

it.each<[string, () => StringTracker, Change[]]>([
[
'add shorter than remove',
() => createStringTracker(' 0').remove(2, 3).add(2, 'x ').remove(0, 4),
[' ', [StringOp.Remove, ' 0']],
],
['add longer than remove', () => createStringTracker('aa').add(0, 'aaa').remove(3, 5), ['aa', [StringOp.Add, 'a']]],
[
'overlaps cross mid-text',
() => createStringTracker('aab').add(0, 'ab').remove(2, 5),
['a', [StringOp.Remove, 'a'], 'b'],
],
])('should not drop repeated chars when folding an overlapping add and remove (%s)', (_, build, changes) => {
const tracker = build()
expect(tracker.getChanges()).toStrictEqual(changes)
assertValidTracker(tracker)
})

// getIndexOnOriginal
it('getIndexOnOriginal should throw a RangeError when called with a negative number', () => {
expect(() => createStringTracker('asd').getIndexOnOriginal(-1))
Expand Down
Loading