start test suite for dubdiff engine

This commit is contained in:
Adam Brown 2016-12-09 18:01:59 -05:00
parent 55a30797ec
commit 755143c0c3
5 changed files with 156 additions and 17 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["node6", "react"],
}

View File

@ -9,7 +9,8 @@
"start": "npm run copy-css && webpack --progress --colors --watch", "start": "npm run copy-css && webpack --progress --colors --watch",
"serve": "node src/server/babel.index.js", "serve": "node src/server/babel.index.js",
"webpack-stats": "webpack --profile --json > stats.json", "webpack-stats": "webpack --profile --json > stats.json",
"test": "echo \"Error: no test specified\" && exit 1" "test": "mocha --watch --compilers js:babel-register"
}, },
"author": "", "author": "",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
@ -39,7 +40,10 @@
"babel-preset-es2015-native-modules": "^6.9.4", "babel-preset-es2015-native-modules": "^6.9.4",
"babel-preset-node6": "^11.0.0", "babel-preset-node6": "^11.0.0",
"babel-preset-react": "^6.3.13", "babel-preset-react": "^6.3.13",
"babel-register": "^6.18.0",
"chai": "^3.5.0",
"copyfiles": "^0.2.2", "copyfiles": "^0.2.2",
"mocha": "^3.2.0",
"piping": "^1.0.0-rc.4", "piping": "^1.0.0-rc.4",
"webpack": "^2.1.0-beta.27" "webpack": "^2.1.0-beta.27"
} }

View File

@ -6,6 +6,8 @@ import * as JsDiff from 'diff'
// the diff would use a custom compare function that would disregard the spaces // the diff would use a custom compare function that would disregard the spaces
// alternately, the text could be split with the spaces included in the array and then compared with a // alternately, the text could be split with the spaces included in the array and then compared with a
// custom diff function that would treat the space elements as null/ignored // custom diff function that would treat the space elements as null/ignored
//the current mechanism for adding and removing spaces is fragile and broken
export function plaintextDiff(original, final) { export function plaintextDiff(original, final) {
let arrOriginal = plaintextSplit(original) let arrOriginal = plaintextSplit(original)
let arrFinal = plaintextSplit(final) let arrFinal = plaintextSplit(final)
@ -27,22 +29,29 @@ export function markdownDiff(original, final) {
return diff return diff
} }
// returns a string version of the diff, with "{+ ... +}" and "[- ... -]"
export function diffToLogString(diff) { // representing ins and del blocks
export function diffToString(diff) {
return diff.map(({added, removed, value}) => { return diff.map(({added, removed, value}) => {
let sym = added ? "+" : removed ? '-' : '/' let start = added ? '{+' : removed ? '[-' : ''
return sym+value+sym let end = added ? '+}' : removed ? '-]' : ''
}) let string = value
if (Array.isArray(value))
string = value.join('')
return start+string+end
}).join(' ')
} }
let plaintextSplit = text =>text.split(/[ ]|(\n)/) let plaintextSplit = text =>text.split(/[ ]|(\n)/)
function plaintextRestoreSpaces (diff) { function plaintextRestoreSpaces (diff) {
return diff.map(({added, removed, value}) => ({ return diff.map(({added, removed, value}) => ({
added, added,
removed, removed,
value:value.map((str, idx, arr) => ( value:value.map((str, idx, arr) => (
(str!='\n' && (idx<arr.length-1)) ? str+" " : str) (str!='\n' && (idx<arr.length-1)) ? str+" " : str)
).join('') )
})) }))
} }
@ -79,7 +88,7 @@ function rewriteMarkdownDiff(diff) {
function applyTransformationRule1(diff) { function applyTransformationRule1(diff) {
let transformedDiff = [] let transformedDiff = []
const B_ADD='added', B_REM='removed', B_SAME='same' const B_ADDED='added', B_REMOVED='removed', B_SAME='same'
let previousBlockType = null let previousBlockType = null
let currentBlockType = null let currentBlockType = null
let previousBlockWasMultiline = false let previousBlockWasMultiline = false
@ -87,16 +96,18 @@ function applyTransformationRule1(diff) {
//iterate the input tokens to create the intermediate representation //iterate the input tokens to create the intermediate representation
diff.forEach((currentBlock) => { diff.forEach((currentBlock) => {
previousBlockType = currentBlockType previousBlockType = currentBlockType
previousBlockWasMultiline = currentBlockIsMultiline previousBlockWasMultiline = currentBlockIsMultiline
currentBlockType = (currentBlock.added ? B_ADD : (currentBlock.removed ? B_REMOVED : B_SAME)) currentBlockType = (currentBlock.added ? B_ADDED : (currentBlock.removed ? B_REMOVED : B_SAME))
currentBlockIsMultiline = isMultilineDiffBlock(currentBlock) currentBlockIsMultiline = isMultilineDiffBlock(currentBlock)
//transform rule 1 applys when: //transform rule 1 applys when:
// the previous block was a del and had multiple lines // the previous block was a del and had multiple lines
// the current block is an ins // the current block is an ins
if (previousBlockType == B_REM && currentBlockType == B_INS && previousBlockWasMultiline) { if (previousBlockType == B_REMOVED && currentBlockType == B_ADDED && previousBlockWasMultiline) {
console.log('trigger rule 1')
//split the first line from the current block //split the first line from the current block
let currentBlockSplit = splitMultilineDiffBlock(currentBlock) let currentBlockSplit = splitMultilineDiffBlock(currentBlock)
@ -104,13 +115,14 @@ function applyTransformationRule1(diff) {
let previousBlock = transformedDiff.pop() let previousBlock = transformedDiff.pop()
//split the first line from the previous block //split the first line from the previous block
let previousBlockSplit = splitMultilineDiffBlock(currentBlock) let previousBlockSplit = splitMultilineDiffBlock(previousBlock)
console.log({currentBlock, currentBlockSplit, previousBlock, previousBlockSplit})
//now add the blocks back, interleaving del and ins blocks //now add the blocks back, interleaving del and ins blocks
for (let i=0; i<Math.max(previousBlockSplit.length, currentBlockSplit.length); i++) { for (let i=0; i<Math.max(previousBlockSplit.length, currentBlockSplit.length); i++) {
if (i<previousBlockSplit.length) if (i<previousBlockSplit.length)
transformedDiff.push(previousBlockSplit[i]) transformedDiff.push(previousBlockSplit[i])
if (i<currentBlockSplit.length) if (i<currentBlockSplit.length)
transformedDiff.push(currentBlockSplit[i]) transformedDiff.push(currentBlockSplit[i])
} }
@ -142,6 +154,7 @@ function applyTransformationRule2(diff) {
/// ... /// ...
/*
transform.forEach(function(item) { transform.forEach(function(item) {
//newlines are undecorated //newlines are undecorated
if (item.string == '\n') { if (item.string == '\n') {
@ -193,7 +206,7 @@ function applyTransformationRule2(diff) {
} }
}); });
*/
} }
@ -213,23 +226,31 @@ function isMultilineDiffBlock({value}) {
//if the diff block begins with a newline, the returned array will begin with an empty diff //if the diff block begins with a newline, the returned array will begin with an empty diff
function splitMultilineDiffBlock({added, removed, value}) { function splitMultilineDiffBlock({added, removed, value}) {
//find the indices of the diff block that coorespond to newlines //find the indices of the diff block that coorespond to newlines
const splits = findIndicesOf(value, '\n') const splits = findIndicesOf(value, str => str=='\n')
splits.push(value.length)
//create a range from each index //create a range from each index
const ranges = splits.reduce( const ranges = splits.reduce(
//the accumulator is a structure with the last index and the list of ranges //the accumulator is a structure with the last index and the list of ranges
//the ranges are a {start, end} structure //the ranges are a {start, end} structure
({last, ranges}, i) => {i, ranges.concat([{start:last, end:i}])}, ({last, ranges}, i) => {
ranges = ranges.concat([{start:last, end:i}])
return {last:i, ranges}
},
//start with the zero index and an empty array //start with the zero index and an empty array
{last: 0, ranges:[]} {last: 0, ranges:[]}
).ranges ).ranges
//map the ranges into blocks //map the ranges into blocks
const blocks = ranges.map( const blocks = ranges.map(
//each block is the same as the given original block, but with the values split at newlines //each block is the same as the given original block, but with the values split at newlines
({start, end}) => {added, removed, value.slice(start, end)} ({start, end}) => ({added, removed, value:value.slice(start, end)})
) )
console.log({value, splits, ranges, blocks})
return blocks return blocks
} }

48
test/dubdiffMarkdown.js Normal file
View File

@ -0,0 +1,48 @@
/*eslint-env node, mocha */
/*global expect */
/*eslint no-console: 0*/
'use strict';
import chai from 'chai'
import {markdownDiff, diffToString} from '../src/common/util/dubdiff'
let diff = (a,b) => diffToString(markdownDiff(a,b))
const expect = chai.expect
describe('dubdiff', () => {
let db;
beforeEach(() => {
});
it('plaintext diffs consecutive words', ()=>{
expect(diff(
'This is a smlb sentnce with no errors.',
'This is a simple sentence with no errors.'
)).to.equal('This is a [-smlb sentnce-] {+simple sentence+} with no errors.')
})
it('plaintext diffs with word deletion', ()=>{
expect(diff(
'Gonna delete a word.',
'Gonna delete word.'
)).to.equal('Gonna delete [-a-] word.')
})
it('plaintext diffs with word insertion', ()=>{
expect(diff(
'Gonna delete word.',
'Gonna delete a word.'
)).to.equal('Gonna delete {+a+} word.')
})
it('reorganizes insertions after multiline deletions', ()=>{
expect(diff(
`# Title
other`,
`# Subtitle`
)).to.equal('# [-Title-] {+Subtitle+}[-\nother-]')
})
})

63
test/dubdiffPlaintext.js Normal file
View File

@ -0,0 +1,63 @@
/*eslint-env node, mocha */
/*global expect */
/*eslint no-console: 0*/
'use strict';
import chai from 'chai'
import {plaintextDiff, diffToString} from '../src/common/util/dubdiff'
let diff = (a,b) => diffToString(plaintextDiff(a,b))
const expect = chai.expect
describe('dubdiff', () => {
beforeEach(() => {
});
it('diffs single words', ()=>{
expect(diff(
'This is a smlb sentence.',
'This is a simple sentence.'
)).to.equal('This is a [-smlb-] {+simple+} sentence.')
})
it('diffs consecutive words', ()=>{
expect(diff(
'This is a smlb sentnce with no errors.',
'This is a simple sentence with no errors.'
)).to.equal('This is a [-smlb sentnce-] {+simple sentence+} with no errors.')
})
it('diffs with word deletion', ()=>{
expect(diff(
'Gonna delete a word.',
'Gonna delete word.'
)).to.equal('Gonna delete [-a-] word.')
})
it('diffs with word insertion', ()=>{
expect(diff(
'Gonna delete word.',
'Gonna delete a word.'
)).to.equal('Gonna delete {+a+} word.')
})
it('diffs accross newline without weird spaces', () => {
expect(diff(
'This is a flawed\ncomment',
'This is a corrected\nitem'
)).to.equal('This is a [-flawed-] {+corrected+}\n[-comment-] {+item+}')
})
it('doesn\'t add spaces after newline', () => {
expect(diff(
'\nhere',
'\nhere'
)).to.equal('\nhere')
})
it('doesn\'t add spaces before newline', () => {
expect(diff(
'there\n',
'there\n'
)).to.equal('there\n')
})
})