-
Notifications
You must be signed in to change notification settings - Fork 0
/
test24.js
executable file
·133 lines (122 loc) · 5.18 KB
/
test24.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
function assert(condition) {
if (!condition) { throw { name: "AssertionError", message: "Assertion failed" } }
}
function testPasses(toTry) {
try {
toTry()
return true;
} catch (failure) {
return false;
}
}
function report(testName, passed) {
console.log(passed ? '\x1b[0;32m passed ' + testName + '\x1b[0m' : '\x1b[0;31m FAILED ' + testName + '\x1b[0m')
}
function test(testCases) {
for (let testName in testCases) {
if (testCases.hasOwnProperty(testName)) {
report(testName, testPasses(testCases[testName]))
}
}
}
/*******************************************************************************
* Example Usage
*******************************************************************************/
function isValid(stale, latest, otjson) {
// apply otjson to stale and check if it matches latest
// return true or false
// otjson is an array of operations
// each operation is an object with an op property and a count or chars property depending on the op
// op can be "skip", "delete", or "insert"
// count is a number
// chars is a string
// skip means skip that many characters in stale
// delete means delete that many characters in stale
// insert means insert those characters in stale
// if any operation deletes past the end of stale, return false
// if any operation skips past the end of stale, return false
// if the final string does not match latest, return false
// otherwise return true
let current = stale; // track state of string as operations are applied
let index = 0; // track location of cursor in current
for (let i = 0; i < otjson.length; i++) {
let op = otjson[i];
if (op.op === "skip") {
// if skipping past the end of the string, return false
if (index + op.count > current.length) {
return false;
}
// move index forward count many characters
index += op.count;
} else if (op.op === "delete") {
// if deleting past the end of the string, return false
if (index + op.count > current.length) {
return false;
}
// delete count many characters starting at index
current = current.slice(0, index) + current.slice(index + op.count)
} else if (op.op === "insert") {
// insert chars at index, update index to be at the end of the inserted chars
current = current.slice(0, index) + op.chars + current.slice(index);
index += op.chars.length;
}
}
return current == latest;
}
test({
"skip-delete should return true": () => assert(isValid(
'Repl.it uses operational transformations to keep everyone in a multiplayer repl in sync.',
'Repl.it uses operational transformations.',
[
{ "op": "skip", "count": 40 },
{ "op": "delete", "count": 47 }
]
) === true ),
"skip-delete-skip should return false": () => assert(isValid(
'Repl.it uses operational transformations to keep everyone in a multiplayer repl in sync.',
'Repl.it uses operational transformations.',
[
{ "op": "skip", "count": 40 },
{ "op": "delete", "count": 47 },
{ "op": "skip", "count": 2 }
]
) === false ),
"skip-delete should return false": () => assert(isValid(
'Repl.it uses operational transformations to keep everyone in a multiplayer repl in sync.',
'Repl.it uses operational transformations.',
[
{ "op": "skip", "count": 45 },
{ "op": "delete", "count": 47 }
]
) === false ),
"delete-insert-skip-delete should return true": () => assert(isValid(
'Repl.it uses operational transformations to keep everyone in a multiplayer repl in sync.',
'We use operational transformations to keep everyone in a multiplayer repl in sync.',
[
{ "op": "delete", "count": 7 },
{ "op": "insert", "chars": "We" },
{ "op": "skip", "count": 4 },
{ "op": "delete", "count": 1 }
]
) === true ),
"delete-insert-skip-delete should return false": () => assert(isValid(
'Repl.it uses operational transformations to keep everyone in a multiplayer repl in sync.',
'We can use operational transformations to keep everyone in a multiplayer repl in sync.',
[
{ "op": "delete", "count": 7 },
{ "op": "insert", "chars": "We" },
{ "op": "skip", "count": 4 },
{ "op": "delete", "count": 1 }
]
) === false ),
"empty should return true": () => assert(isValid(
'Repl.it uses operational transformations to keep everyone in a multiplayer repl in sync.',
'Repl.it uses operational transformations to keep everyone in a multiplayer repl in sync.',
[]
) === true ),
"should fail": () => assert(isValid(
'Repl.it uses operational transformations to keep everyone in a multiplayer repl in sync.',
'Repl.it uses operational transformations to keep everyone in a multiplayer repl in sync.',
[]
) === false ),
})