diff --git a/package-lock.json b/package-lock.json
index b194d5c..8917150 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,382 +4,51 @@
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
-    "@goto-bus-stop/common-shake": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/@goto-bus-stop/common-shake/-/common-shake-2.2.0.tgz",
-      "integrity": "sha512-AlNzclZ0UebzHyxYfHSKqmlwCtwcECirZDLhN96gGuj5oHAkba/27+4AlCWyXaycM9cUh12L8/2vjmvjn60pkQ==",
-      "requires": {
-        "acorn": "^5.1.1",
-        "debug": "^2.6.8",
-        "escope": "^3.6.0"
-      },
-      "dependencies": {
-        "acorn": {
-          "version": "5.7.3",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
-          "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
-        },
-        "debug": {
-          "version": "2.6.9",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "requires": {
-            "ms": "2.0.0"
-          }
-        }
-      }
-    },
-    "JSONStream": {
-      "version": "1.3.5",
-      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
-      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
-      "requires": {
-        "jsonparse": "^1.2.0",
-        "through": ">=2.2.7 <3"
-      }
-    },
-    "acorn": {
-      "version": "6.0.4",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz",
-      "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg=="
-    },
-    "acorn-dynamic-import": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz",
-      "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw=="
-    },
-    "acorn-node": {
-      "version": "1.6.2",
-      "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.6.2.tgz",
-      "integrity": "sha512-rIhNEZuNI8ibQcL7ANm/mGyPukIaZsRNX9psFNQURyJW0nu6k8wjSDld20z6v2mDBWqX13pIEnk9gGZJHIlEXg==",
-      "requires": {
-        "acorn": "^6.0.2",
-        "acorn-dynamic-import": "^4.0.0",
-        "acorn-walk": "^6.1.0",
-        "xtend": "^4.0.1"
-      }
-    },
-    "acorn-walk": {
-      "version": "6.1.1",
-      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz",
-      "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw=="
-    },
-    "amdefine": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
-      "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
-    },
-    "ansi-regex": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
-    },
-    "ansi-styles": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-      "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
-    },
-    "array-from": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz",
-      "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU="
-    },
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
     },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
       "requires": {
         "balanced-match": "^1.0.0",
         "concat-map": "0.0.1"
       }
     },
-    "browser-pack": {
-      "version": "5.0.1",
-      "resolved": "http://registry.npmjs.org/browser-pack/-/browser-pack-5.0.1.tgz",
-      "integrity": "sha1-QZdxmyDG4KqglFHFER5T77b7wY0=",
-      "requires": {
-        "JSONStream": "^1.0.3",
-        "combine-source-map": "~0.6.1",
-        "defined": "^1.0.0",
-        "through2": "^1.0.0",
-        "umd": "^3.0.0"
-      },
-      "dependencies": {
-        "combine-source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.6.1.tgz",
-          "integrity": "sha1-m0oJwxYDPXaODxHgKfonMOB5rZY=",
-          "requires": {
-            "convert-source-map": "~1.1.0",
-            "inline-source-map": "~0.5.0",
-            "lodash.memoize": "~3.0.3",
-            "source-map": "~0.4.2"
-          }
-        },
-        "convert-source-map": {
-          "version": "1.1.3",
-          "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
-          "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA="
-        },
-        "inline-source-map": {
-          "version": "0.5.0",
-          "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.5.0.tgz",
-          "integrity": "sha1-Skxd2OT7Xps82mDIIt+tyu5m4K8=",
-          "requires": {
-            "source-map": "~0.4.0"
-          }
-        },
-        "isarray": {
-          "version": "0.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
-        },
-        "readable-stream": {
-          "version": "1.1.14",
-          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
-          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.1",
-            "isarray": "0.0.1",
-            "string_decoder": "~0.10.x"
-          }
-        },
-        "source-map": {
-          "version": "0.4.4",
-          "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
-          "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
-          "requires": {
-            "amdefine": ">=0.0.4"
-          }
-        },
-        "string_decoder": {
-          "version": "0.10.31",
-          "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
-          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
-        },
-        "through2": {
-          "version": "1.1.1",
-          "resolved": "http://registry.npmjs.org/through2/-/through2-1.1.1.tgz",
-          "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=",
-          "requires": {
-            "readable-stream": ">=1.1.13-1 <1.2.0-0",
-            "xtend": ">=4.0.0 <4.1.0-0"
-          }
-        }
-      }
-    },
-    "browser-pack-flat": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/browser-pack-flat/-/browser-pack-flat-3.2.0.tgz",
-      "integrity": "sha512-tk/LexpgMImZyDfpWSPyIlQ3frZYTyGLpW+Ytd0Fj9VW03Fil9IrKzcVKN87wZHWhP6LbdKh3STRnIkHIR+UTQ==",
-      "requires": {
-        "JSONStream": "^1.3.2",
-        "combine-source-map": "^0.8.0",
-        "convert-source-map": "^1.5.1",
-        "count-lines": "^0.1.2",
-        "dedent": "^0.7.0",
-        "estree-is-member-expression": "^1.0.0",
-        "estree-is-require": "^1.0.0",
-        "esutils": "^2.0.2",
-        "path-parse": "^1.0.5",
-        "scope-analyzer": "^2.0.0",
-        "stream-combiner": "^0.2.2",
-        "through2": "^2.0.3",
-        "transform-ast": "^2.4.2",
-        "umd": "^3.0.3",
-        "wrap-comment": "^1.0.0"
-      }
-    },
-    "browser-process-hrtime": {
-      "version": "0.1.3",
-      "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz",
-      "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw=="
-    },
     "browser-stdout": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
       "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
       "dev": true
     },
-    "browser-unpack": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/browser-unpack/-/browser-unpack-1.3.0.tgz",
-      "integrity": "sha512-hK81IeAN/PcqzSKTqKWe3jzmB41PV/23N1w1w2N/KBPv+sjp31sMlen9arVIENzrH8IzaCWA2Fx6SV0kJyhAFQ==",
-      "requires": {
-        "acorn": "^5.6.2",
-        "concat-stream": "^1.5.0",
-        "minimist": "^1.1.1"
-      },
-      "dependencies": {
-        "acorn": {
-          "version": "5.7.3",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
-          "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
-        },
-        "minimist": {
-          "version": "1.2.0",
-          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
-        }
-      }
-    },
     "buffer-from": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
-      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
-    },
-    "bundle-collapser": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/bundle-collapser/-/bundle-collapser-1.3.0.tgz",
-      "integrity": "sha1-9LT/WLLyLudwGyD6djBuI/U6P7Y=",
-      "requires": {
-        "browser-pack": "^5.0.1",
-        "browser-unpack": "^1.1.0",
-        "concat-stream": "^1.5.0",
-        "falafel": "^2.1.0",
-        "minimist": "^1.1.1",
-        "through2": "^2.0.0"
-      },
-      "dependencies": {
-        "minimist": {
-          "version": "1.2.0",
-          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
-        }
-      }
-    },
-    "call-matcher": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.1.0.tgz",
-      "integrity": "sha512-IoQLeNwwf9KTNbtSA7aEBb1yfDbdnzwjCetjkC8io5oGeOmK2CBNdg0xr+tadRYKO0p7uQyZzvon0kXlZbvGrw==",
-      "requires": {
-        "core-js": "^2.0.0",
-        "deep-equal": "^1.0.0",
-        "espurify": "^1.6.0",
-        "estraverse": "^4.0.0"
-      }
-    },
-    "chalk": {
-      "version": "1.1.3",
-      "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
-      "requires": {
-        "ansi-styles": "^2.2.1",
-        "escape-string-regexp": "^1.0.2",
-        "has-ansi": "^2.0.0",
-        "strip-ansi": "^3.0.0",
-        "supports-color": "^2.0.0"
-      },
-      "dependencies": {
-        "supports-color": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
-        }
-      }
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+      "dev": true
     },
     "coffeescript": {
-      "version": "2.3.2",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.4.1.tgz",
+      "integrity": "sha512-34GV1aHrsMpTaO3KfMJL40ZNuvKDR/g98THHnE9bQj8HjMaZvSrLik99WWqyMhRtbe8V5hpx5iLgdcSvM/S2wg==",
       "dev": true
     },
-    "combine-source-map": {
-      "version": "0.8.0",
-      "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
-      "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=",
-      "requires": {
-        "convert-source-map": "~1.1.0",
-        "inline-source-map": "~0.6.0",
-        "lodash.memoize": "~3.0.3",
-        "source-map": "~0.5.3"
-      },
-      "dependencies": {
-        "convert-source-map": {
-          "version": "1.1.3",
-          "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
-          "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA="
-        },
-        "source-map": {
-          "version": "0.5.7",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
-        }
-      }
-    },
     "commander": {
       "version": "2.15.1",
-      "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
       "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
       "dev": true
     },
-    "common-shakeify": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/common-shakeify/-/common-shakeify-0.5.2.tgz",
-      "integrity": "sha512-znhuzdj4zgvMz5u6cM9FI1WFcxTJ8lvGGW1FaaU0eMZ9o9LqIy3j43SrUYsQJKwdI8+1p/55YpeHTz68G+0Zsw==",
-      "requires": {
-        "@goto-bus-stop/common-shake": "^2.2.0",
-        "convert-source-map": "^1.5.1",
-        "through2": "^2.0.3",
-        "transform-ast": "^2.4.3",
-        "wrap-comment": "^1.0.1"
-      }
-    },
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
-    },
-    "concat-stream": {
-      "version": "1.6.2",
-      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
-      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
-      "requires": {
-        "buffer-from": "^1.0.0",
-        "inherits": "^2.0.3",
-        "readable-stream": "^2.2.2",
-        "typedarray": "^0.0.6"
-      }
-    },
-    "convert-source-map": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
-      "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
-      "requires": {
-        "safe-buffer": "~5.1.1"
-      }
-    },
-    "core-js": {
-      "version": "2.6.0",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz",
-      "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw=="
-    },
-    "core-util-is": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
-    },
-    "count-lines": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/count-lines/-/count-lines-0.1.2.tgz",
-      "integrity": "sha1-4zST+2hgqC9xWdgjeEP7+u/uWWI="
-    },
-    "d": {
-      "version": "1.0.0",
-      "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz",
-      "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
-      "requires": {
-        "es5-ext": "^0.10.9"
-      }
-    },
-    "dash-ast": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz",
-      "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA=="
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
     },
     "debug": {
       "version": "3.1.0",
@@ -390,282 +59,17 @@
         "ms": "2.0.0"
       }
     },
-    "dedent": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
-      "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw="
-    },
-    "deep-equal": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
-      "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU="
-    },
-    "deep-is": {
-      "version": "0.1.3",
-      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
-      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
-    },
-    "defined": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
-      "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
-    },
     "diff": {
       "version": "3.5.0",
       "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
       "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
       "dev": true
     },
-    "duplexer": {
-      "version": "0.1.1",
-      "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
-      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E="
-    },
-    "duplexify": {
-      "version": "3.6.1",
-      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz",
-      "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==",
-      "requires": {
-        "end-of-stream": "^1.0.0",
-        "inherits": "^2.0.1",
-        "readable-stream": "^2.0.0",
-        "stream-shift": "^1.0.0"
-      }
-    },
-    "end-of-stream": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
-      "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
-      "requires": {
-        "once": "^1.4.0"
-      }
-    },
-    "envify": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/envify/-/envify-4.1.0.tgz",
-      "integrity": "sha512-IKRVVoAYr4pIx4yIWNsz9mOsboxlNXiu7TNBnem/K/uTHdkyzXWDzHCK7UTolqBbgaBz0tQHsD3YNls0uIIjiw==",
-      "requires": {
-        "esprima": "^4.0.0",
-        "through": "~2.3.4"
-      }
-    },
-    "es5-ext": {
-      "version": "0.10.46",
-      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz",
-      "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==",
-      "requires": {
-        "es6-iterator": "~2.0.3",
-        "es6-symbol": "~3.1.1",
-        "next-tick": "1"
-      }
-    },
-    "es6-iterator": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
-      "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
-      "requires": {
-        "d": "1",
-        "es5-ext": "^0.10.35",
-        "es6-symbol": "^3.1.1"
-      }
-    },
-    "es6-map": {
-      "version": "0.1.5",
-      "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz",
-      "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
-      "requires": {
-        "d": "1",
-        "es5-ext": "~0.10.14",
-        "es6-iterator": "~2.0.1",
-        "es6-set": "~0.1.5",
-        "es6-symbol": "~3.1.1",
-        "event-emitter": "~0.3.5"
-      }
-    },
-    "es6-set": {
-      "version": "0.1.5",
-      "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
-      "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
-      "requires": {
-        "d": "1",
-        "es5-ext": "~0.10.14",
-        "es6-iterator": "~2.0.1",
-        "es6-symbol": "3.1.1",
-        "event-emitter": "~0.3.5"
-      }
-    },
-    "es6-symbol": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
-      "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
-      "requires": {
-        "d": "1",
-        "es5-ext": "~0.10.14"
-      }
-    },
-    "es6-weak-map": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
-      "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
-      "requires": {
-        "d": "1",
-        "es5-ext": "^0.10.14",
-        "es6-iterator": "^2.0.1",
-        "es6-symbol": "^3.1.1"
-      }
-    },
     "escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
-    },
-    "escodegen": {
-      "version": "1.11.0",
-      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz",
-      "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==",
-      "requires": {
-        "esprima": "^3.1.3",
-        "estraverse": "^4.2.0",
-        "esutils": "^2.0.2",
-        "optionator": "^0.8.1",
-        "source-map": "~0.6.1"
-      },
-      "dependencies": {
-        "esprima": {
-          "version": "3.1.3",
-          "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
-          "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
-        }
-      }
-    },
-    "escope": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz",
-      "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=",
-      "requires": {
-        "es6-map": "^0.1.3",
-        "es6-weak-map": "^2.0.1",
-        "esrecurse": "^4.1.0",
-        "estraverse": "^4.1.1"
-      }
-    },
-    "esprima": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
-      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
-    },
-    "espurify": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/espurify/-/espurify-1.8.1.tgz",
-      "integrity": "sha512-ZDko6eY/o+D/gHCWyHTU85mKDgYcS4FJj7S+YD6WIInm7GQ6AnOjmcL4+buFV/JOztVLELi/7MmuGU5NHta0Mg==",
-      "requires": {
-        "core-js": "^2.0.0"
-      }
-    },
-    "esrecurse": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
-      "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
-      "requires": {
-        "estraverse": "^4.1.0"
-      }
-    },
-    "estraverse": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
-      "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
-    },
-    "estree-is-function": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/estree-is-function/-/estree-is-function-1.0.0.tgz",
-      "integrity": "sha512-nSCWn1jkSq2QAtkaVLJZY2ezwcFO161HVc174zL1KPW3RJ+O6C3eJb8Nx7OXzvhoEv+nLgSR1g71oWUHUDTrJA=="
-    },
-    "estree-is-identifier": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/estree-is-identifier/-/estree-is-identifier-1.0.0.tgz",
-      "integrity": "sha512-2BDRGrkQJV/NhCAmmE33A35WAaxq3WQaGHgQuD//7orGWfpFqj8Srkwvx0TH+20yIdOF1yMQwi8anv5ISec2AQ=="
-    },
-    "estree-is-member-expression": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/estree-is-member-expression/-/estree-is-member-expression-1.0.0.tgz",
-      "integrity": "sha512-Ec+X44CapIGExvSZN+pGkmr5p7HwUVQoPQSd458Lqwvaf4/61k/invHSh4BYK8OXnCkfEhWuIoG5hayKLQStIg=="
-    },
-    "estree-is-require": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/estree-is-require/-/estree-is-require-1.0.0.tgz",
-      "integrity": "sha512-oWxQdSEmnUwNZsDQYiBNpVxKEhMmsJQSSxnDrwsr1MWtooCLfhgzsNGzmokdmfK0EzEIS5V4LPvqxv1Kmb1vvA==",
-      "requires": {
-        "estree-is-identifier": "^1.0.0"
-      }
-    },
-    "esutils": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
-      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
-    },
-    "event-emitter": {
-      "version": "0.3.5",
-      "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
-      "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
-      "requires": {
-        "d": "1",
-        "es5-ext": "~0.10.14"
-      }
-    },
-    "extend": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/extend/-/extend-1.3.0.tgz",
-      "integrity": "sha1-0VFvsP9WJNLr+RI+odrFoZlABPg="
-    },
-    "falafel": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.1.0.tgz",
-      "integrity": "sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw=",
-      "requires": {
-        "acorn": "^5.0.0",
-        "foreach": "^2.0.5",
-        "isarray": "0.0.1",
-        "object-keys": "^1.0.6"
-      },
-      "dependencies": {
-        "acorn": {
-          "version": "5.7.3",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
-          "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
-        },
-        "isarray": {
-          "version": "0.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
-        }
-      }
-    },
-    "fast-levenshtein": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
-    },
-    "foreach": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
-      "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
-    },
-    "from2": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
-      "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
-      "requires": {
-        "inherits": "^2.0.1",
-        "readable-stream": "^2.0.0"
-      }
-    },
-    "from2-string": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/from2-string/-/from2-string-1.1.0.tgz",
-      "integrity": "sha1-GCgrJ9CKJnyzAwzSuLSw8hKvdSo=",
-      "requires": {
-        "from2": "^2.0.3"
-      }
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true
     },
     "fs.realpath": {
       "version": "1.0.0",
@@ -673,11 +77,6 @@
       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
       "dev": true
     },
-    "get-assigned-identifiers": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz",
-      "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ=="
-    },
     "glob": {
       "version": "7.1.2",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
@@ -698,14 +97,6 @@
       "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
       "dev": true
     },
-    "has-ansi": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
-      "requires": {
-        "ansi-regex": "^2.0.0"
-      }
-    },
     "has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -731,105 +122,27 @@
     "inherits": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
-    },
-    "inline-source-map": {
-      "version": "0.6.2",
-      "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz",
-      "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=",
-      "requires": {
-        "source-map": "~0.5.3"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.5.7",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
-        }
-      }
-    },
-    "is-buffer": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
-      "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
-    },
-    "isarray": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
-    },
-    "jsonparse": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
-      "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
-    },
-    "levn": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
-      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
-      "requires": {
-        "prelude-ls": "~1.1.2",
-        "type-check": "~0.3.2"
-      }
-    },
-    "lodash.memoize": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
-      "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8="
-    },
-    "magic-string": {
-      "version": "0.23.2",
-      "resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.23.2.tgz",
-      "integrity": "sha512-oIUZaAxbcxYIp4AyLafV6OVKoB3YouZs0UTCJ8mOKBHNyJgGDaMJ4TgA+VylJh6fx7EQCC52XkbURxxG9IoJXA==",
-      "requires": {
-        "sourcemap-codec": "^1.4.1"
-      }
-    },
-    "merge-source-map": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz",
-      "integrity": "sha1-pd5GU42uhNQRTMXqArR3KmNGcB8=",
-      "requires": {
-        "source-map": "^0.5.6"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.5.7",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
-        }
-      }
-    },
-    "minify-stream": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/minify-stream/-/minify-stream-1.2.0.tgz",
-      "integrity": "sha512-bIjBH7uGROwzWwgtbLO7U/yi+NBTLGs5YYidUiGD9nJZ5wuxX0485c48vtJ7WlNZNnKvHXA1D1ZXpfWJqf4fyg==",
-      "requires": {
-        "concat-stream": "^1.6.0",
-        "convert-source-map": "^1.5.0",
-        "duplexify": "^3.5.1",
-        "from2-string": "^1.1.0",
-        "terser": "^3.7.5",
-        "xtend": "^4.0.1"
-      }
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+      "dev": true
     },
     "minimatch": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
       "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
       "requires": {
         "brace-expansion": "^1.1.7"
       }
     },
     "minimist": {
       "version": "0.0.8",
-      "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
       "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
       "dev": true
     },
     "mkdirp": {
       "version": "0.5.1",
-      "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
       "dev": true,
       "requires": {
@@ -838,6 +151,8 @@
     },
     "mocha": {
       "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
+      "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
       "dev": true,
       "requires": {
         "browser-stdout": "1.3.1",
@@ -856,509 +171,66 @@
     "ms": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
-    },
-    "multi-stage-sourcemap": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/multi-stage-sourcemap/-/multi-stage-sourcemap-0.2.1.tgz",
-      "integrity": "sha1-sJ/IWG6qF/gdV1xK0C4Pej9rEQU=",
-      "requires": {
-        "source-map": "^0.1.34"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.1.43",
-          "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
-          "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
-          "requires": {
-            "amdefine": ">=0.0.4"
-          }
-        }
-      }
-    },
-    "mutexify": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.2.0.tgz",
-      "integrity": "sha512-oprzxd2zhfrJqEuB98qc1dRMMonClBQ57UPDjnbcrah4orEMTq1jq3+AcdFe5ePzdbJXI7zmdhfftIdMnhYFoQ=="
-    },
-    "nanobench": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/nanobench/-/nanobench-2.1.1.tgz",
-      "integrity": "sha512-z+Vv7zElcjN+OpzAxAquUayFLGK3JI/ubCl0Oh64YQqsTGG09CGqieJVQw4ui8huDnnAgrvTv93qi5UaOoNj8A==",
-      "requires": {
-        "browser-process-hrtime": "^0.1.2",
-        "chalk": "^1.1.3",
-        "mutexify": "^1.1.0",
-        "pretty-hrtime": "^1.0.2"
-      }
-    },
-    "next-tick": {
-      "version": "1.0.0",
-      "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
-      "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
-    },
-    "object-assign": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
-    },
-    "object-keys": {
-      "version": "1.0.12",
-      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
-      "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag=="
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+      "dev": true
     },
     "once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
       "requires": {
         "wrappy": "1"
       }
     },
-    "optionator": {
-      "version": "0.8.2",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
-      "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
-      "requires": {
-        "deep-is": "~0.1.3",
-        "fast-levenshtein": "~2.0.4",
-        "levn": "~0.3.0",
-        "prelude-ls": "~1.1.2",
-        "type-check": "~0.3.2",
-        "wordwrap": "~1.0.0"
-      }
-    },
     "ot-fuzzer": {
-      "version": "1.2.0",
-      "dev": true,
-      "requires": {
-        "cli-progress": "^2.1.1",
-        "seedrandom": "^2.4.4"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "3.0.0",
-          "bundled": true,
-          "dev": true
-        },
-        "cli-progress": {
-          "version": "2.1.1",
-          "bundled": true,
-          "dev": true,
-          "requires": {
-            "colors": "^1.1.2",
-            "string-width": "^2.1.1"
-          }
-        },
-        "coffee-script": {
-          "version": "1.12.7",
-          "bundled": true
-        },
-        "colors": {
-          "version": "1.3.3",
-          "bundled": true,
-          "dev": true
-        },
-        "commander": {
-          "version": "2.3.0",
-          "bundled": true
-        },
-        "debug": {
-          "version": "2.0.0",
-          "bundled": true,
-          "requires": {
-            "ms": "0.6.2"
-          }
-        },
-        "diff": {
-          "version": "1.0.8",
-          "bundled": true
-        },
-        "escape-string-regexp": {
-          "version": "1.0.2",
-          "bundled": true
-        },
-        "glob": {
-          "version": "3.2.3",
-          "bundled": true,
-          "requires": {
-            "graceful-fs": "~2.0.0",
-            "inherits": "2",
-            "minimatch": "~0.2.11"
-          }
-        },
-        "graceful-fs": {
-          "version": "2.0.3",
-          "bundled": true
-        },
-        "growl": {
-          "version": "1.8.1",
-          "bundled": true
-        },
-        "inherits": {
-          "version": "2.0.3",
-          "bundled": true
-        },
-        "is-fullwidth-code-point": {
-          "version": "2.0.0",
-          "bundled": true,
-          "dev": true
-        },
-        "jade": {
-          "version": "0.26.3",
-          "bundled": true,
-          "requires": {
-            "commander": "0.6.1",
-            "mkdirp": "0.3.0"
-          },
-          "dependencies": {
-            "commander": {
-              "version": "0.6.1",
-              "bundled": true
-            },
-            "mkdirp": {
-              "version": "0.3.0",
-              "bundled": true
-            }
-          }
-        },
-        "lru-cache": {
-          "version": "2.7.3",
-          "bundled": true
-        },
-        "minimatch": {
-          "version": "0.2.14",
-          "bundled": true,
-          "requires": {
-            "lru-cache": "2",
-            "sigmund": "~1.0.0"
-          }
-        },
-        "minimist": {
-          "version": "0.0.8",
-          "bundled": true
-        },
-        "mkdirp": {
-          "version": "0.5.0",
-          "bundled": true,
-          "requires": {
-            "minimist": "0.0.8"
-          }
-        },
-        "mocha": {
-          "version": "1.21.5",
-          "bundled": true,
-          "requires": {
-            "commander": "2.3.0",
-            "debug": "2.0.0",
-            "diff": "1.0.8",
-            "escape-string-regexp": "1.0.2",
-            "glob": "3.2.3",
-            "growl": "1.8.1",
-            "jade": "0.26.3",
-            "mkdirp": "0.5.0"
-          }
-        },
-        "ms": {
-          "version": "0.6.2",
-          "bundled": true
-        },
-        "seedrandom": {
-          "version": "2.4.4",
-          "bundled": true,
-          "dev": true
-        },
-        "sigmund": {
-          "version": "1.0.1",
-          "bundled": true
-        },
-        "string-width": {
-          "version": "2.1.1",
-          "bundled": true,
-          "dev": true,
-          "requires": {
-            "is-fullwidth-code-point": "^2.0.0",
-            "strip-ansi": "^4.0.0"
-          }
-        },
-        "strip-ansi": {
-          "version": "4.0.0",
-          "bundled": true,
-          "dev": true,
-          "requires": {
-            "ansi-regex": "^3.0.0"
-          }
-        }
-      }
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/ot-fuzzer/-/ot-fuzzer-1.1.0.tgz",
+      "integrity": "sha512-48w+h509Sq48JMsit5/qPCxhWIzztS52DkhIztqiFV0UT2MA6BWu2JiLHSw6cLren1O5IWhNjU7LObOmVOAFGg==",
+      "dev": true
     },
     "ot-simple": {
       "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/ot-simple/-/ot-simple-1.0.0.tgz",
+      "integrity": "sha1-B42ED4HqOq04y+aUdfgbLGdU+1A=",
       "dev": true
     },
     "ot-text": {
       "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/ot-text/-/ot-text-1.0.2.tgz",
+      "integrity": "sha512-1xjcAjB57tYtv722j8J+IaYe4fKZOeaQ3zDa0clUlkhboawrq+dk1cpDfe4+TBSb9NNe7Yra9+tJKsw+8q4H9A==",
       "dev": true
     },
     "ot-text-unicode": {
-      "version": "2.0.0",
-      "dependencies": {
-        "balanced-match": {
-          "version": "1.0.0",
-          "bundled": true
-        },
-        "brace-expansion": {
-          "version": "1.1.11",
-          "bundled": true,
-          "requires": {
-            "balanced-match": "^1.0.0",
-            "concat-map": "0.0.1"
-          }
-        },
-        "browser-stdout": {
-          "version": "1.3.1",
-          "bundled": true
-        },
-        "coffeescript": {
-          "bundled": true
-        },
-        "commander": {
-          "version": "2.15.1",
-          "bundled": true
-        },
-        "concat-map": {
-          "version": "0.0.1",
-          "bundled": true
-        },
-        "debug": {
-          "version": "3.1.0",
-          "bundled": true,
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "diff": {
-          "version": "3.5.0",
-          "bundled": true
-        },
-        "escape-string-regexp": {
-          "version": "1.0.5",
-          "bundled": true
-        },
-        "fs.realpath": {
-          "version": "1.0.0",
-          "bundled": true
-        },
-        "glob": {
-          "version": "7.1.2",
-          "bundled": true,
-          "requires": {
-            "fs.realpath": "^1.0.0",
-            "inflight": "^1.0.4",
-            "inherits": "2",
-            "minimatch": "^3.0.4",
-            "once": "^1.3.0",
-            "path-is-absolute": "^1.0.0"
-          }
-        },
-        "growl": {
-          "version": "1.10.5",
-          "bundled": true
-        },
-        "has-flag": {
-          "version": "3.0.0",
-          "bundled": true
-        },
-        "he": {
-          "version": "1.1.1",
-          "bundled": true
-        },
-        "inflight": {
-          "version": "1.0.6",
-          "bundled": true,
-          "requires": {
-            "once": "^1.3.0",
-            "wrappy": "1"
-          }
-        },
-        "inherits": {
-          "version": "2.0.3",
-          "bundled": true
-        },
-        "minimatch": {
-          "version": "3.0.4",
-          "bundled": true,
-          "requires": {
-            "brace-expansion": "^1.1.7"
-          }
-        },
-        "minimist": {
-          "version": "0.0.8",
-          "bundled": true
-        },
-        "mkdirp": {
-          "version": "0.5.1",
-          "bundled": true,
-          "requires": {
-            "minimist": "0.0.8"
-          }
-        },
-        "mocha": {
-          "bundled": true,
-          "requires": {
-            "browser-stdout": "1.3.1",
-            "commander": "2.15.1",
-            "debug": "3.1.0",
-            "diff": "3.5.0",
-            "escape-string-regexp": "1.0.5",
-            "glob": "7.1.2",
-            "growl": "1.10.5",
-            "he": "1.1.1",
-            "minimatch": "3.0.4",
-            "mkdirp": "0.5.1",
-            "supports-color": "5.4.0"
-          }
-        },
-        "ms": {
-          "version": "2.0.0",
-          "bundled": true
-        },
-        "once": {
-          "version": "1.4.0",
-          "bundled": true,
-          "requires": {
-            "wrappy": "1"
-          }
-        },
-        "ot-fuzzer": {
-          "bundled": true
-        },
-        "path-is-absolute": {
-          "version": "1.0.1",
-          "bundled": true
-        },
-        "supports-color": {
-          "version": "5.4.0",
-          "bundled": true,
-          "requires": {
-            "has-flag": "^3.0.0"
-          }
-        },
-        "typescript": {
-          "bundled": true
-        },
-        "wrappy": {
-          "version": "1.0.2",
-          "bundled": true
-        }
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/ot-text-unicode/-/ot-text-unicode-3.0.0.tgz",
+      "integrity": "sha512-YwVxEFVLV9WE40KavenjlHy3Y0C2OZoJeNCHKEpJlmu/scJaY9tlSrlYNBU7WJgboIz/IoKsd0Poxf+SYLRmrw==",
+      "requires": {
+        "unicount": "^1.0.0"
       }
     },
     "path-is-absolute": {
       "version": "1.0.1",
-      "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
       "dev": true
     },
-    "path-parse": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
-      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
-    },
-    "prelude-ls": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
-      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
-    },
-    "pretty-hrtime": {
-      "version": "1.0.3",
-      "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
-      "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE="
-    },
-    "process-nextick-args": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
-      "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
-    },
-    "readable-stream": {
-      "version": "2.3.6",
-      "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
-      "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
-      "requires": {
-        "core-util-is": "~1.0.0",
-        "inherits": "~2.0.3",
-        "isarray": "~1.0.0",
-        "process-nextick-args": "~2.0.0",
-        "safe-buffer": "~5.1.1",
-        "string_decoder": "~1.1.1",
-        "util-deprecate": "~1.0.1"
-      }
-    },
-    "safe-buffer": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
-    },
-    "scope-analyzer": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/scope-analyzer/-/scope-analyzer-2.0.5.tgz",
-      "integrity": "sha512-+U5H0417mnTEstCD5VwOYO7V4vYuSqwqjFap40ythe67bhMFL5C3UgPwyBv7KDJsqUBIKafOD57xMlh1rN7eaw==",
-      "requires": {
-        "array-from": "^2.1.1",
-        "es6-map": "^0.1.5",
-        "es6-set": "^0.1.5",
-        "es6-symbol": "^3.1.1",
-        "estree-is-function": "^1.0.0",
-        "get-assigned-identifiers": "^1.1.0"
-      }
-    },
     "source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "dev": true
     },
     "source-map-support": {
-      "version": "0.5.9",
-      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz",
-      "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==",
+      "version": "0.5.12",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz",
+      "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==",
+      "dev": true,
       "requires": {
         "buffer-from": "^1.0.0",
         "source-map": "^0.6.0"
       }
     },
-    "sourcemap-codec": {
-      "version": "1.4.4",
-      "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz",
-      "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg=="
-    },
-    "stream-combiner": {
-      "version": "0.2.2",
-      "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
-      "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=",
-      "requires": {
-        "duplexer": "~0.1.1",
-        "through": "~2.3.4"
-      }
-    },
-    "stream-shift": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
-      "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
-    },
-    "string_decoder": {
-      "version": "1.1.1",
-      "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-      "requires": {
-        "safe-buffer": "~5.1.0"
-      }
-    },
-    "strip-ansi": {
-      "version": "3.0.1",
-      "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
-      "requires": {
-        "ansi-regex": "^2.0.0"
-      }
-    },
     "supports-color": {
       "version": "5.4.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
@@ -1372,6 +244,7 @@
       "version": "3.11.0",
       "resolved": "https://registry.npmjs.org/terser/-/terser-3.11.0.tgz",
       "integrity": "sha512-5iLMdhEPIq3zFWskpmbzmKwMQixKmTYwY3Ox9pjtSklBLnHiuQ0GKJLhL1HSYtyffHM3/lDIFBnb82m9D7ewwQ==",
+      "dev": true,
       "requires": {
         "commander": "~2.17.1",
         "source-map": "~0.6.1",
@@ -1381,154 +254,21 @@
         "commander": {
           "version": "2.17.1",
           "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
-          "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
-        }
-      }
-    },
-    "through": {
-      "version": "2.3.8",
-      "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
-      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
-    },
-    "through2": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
-      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
-      "requires": {
-        "readable-stream": "~2.3.6",
-        "xtend": "~4.0.1"
-      }
-    },
-    "tinyify": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/tinyify/-/tinyify-2.5.0.tgz",
-      "integrity": "sha512-SdKsUZM0k57hwdhjqZedWI4YSUcuVMmJabP6kOAvwHSlJJFR9PkTJ5meIiBRwjOnVwc0Q1xiTdj/FjzyGi099A==",
-      "requires": {
-        "browser-pack-flat": "^3.0.9",
-        "bundle-collapser": "^1.3.0",
-        "common-shakeify": "^0.5.2",
-        "envify": "^4.1.0",
-        "minify-stream": "^1.1.0",
-        "uglifyify": "^5.0.0",
-        "unassertify": "^2.1.1"
-      }
-    },
-    "transform-ast": {
-      "version": "2.4.4",
-      "resolved": "https://registry.npmjs.org/transform-ast/-/transform-ast-2.4.4.tgz",
-      "integrity": "sha512-AxjeZAcIOUO2lev2GDe3/xZ1Q0cVGjIMk5IsriTy8zbWlsEnjeB025AhkhBJHoy997mXpLd4R+kRbvnnQVuQHQ==",
-      "requires": {
-        "acorn-node": "^1.3.0",
-        "convert-source-map": "^1.5.1",
-        "dash-ast": "^1.0.0",
-        "is-buffer": "^2.0.0",
-        "magic-string": "^0.23.2",
-        "merge-source-map": "1.0.4",
-        "nanobench": "^2.1.1"
-      }
-    },
-    "type-check": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
-      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
-      "requires": {
-        "prelude-ls": "~1.1.2"
-      }
-    },
-    "typedarray": {
-      "version": "0.0.6",
-      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
-      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
-    },
-    "uglifyify": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/uglifyify/-/uglifyify-5.0.1.tgz",
-      "integrity": "sha512-PO44rgExvwj3rkK0UzenHVnPU18drBy9x9HOUmgkuRh6K2KIsDqrB5LqxGtjybgGTOS1JeP8SBc+TN5rhiva6w==",
-      "requires": {
-        "convert-source-map": "~1.1.0",
-        "extend": "^1.2.1",
-        "minimatch": "^3.0.2",
-        "terser": "^3.7.5",
-        "through": "~2.3.4"
-      },
-      "dependencies": {
-        "convert-source-map": {
-          "version": "1.1.3",
-          "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
-          "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA="
-        }
-      }
-    },
-    "umd": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz",
-      "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow=="
-    },
-    "unassert": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/unassert/-/unassert-1.5.1.tgz",
-      "integrity": "sha1-y8iOw4dBfFpeTALTzQe+mL11/3Y=",
-      "requires": {
-        "acorn": "^4.0.0",
-        "call-matcher": "^1.0.1",
-        "deep-equal": "^1.0.0",
-        "espurify": "^1.3.0",
-        "estraverse": "^4.1.0",
-        "esutils": "^2.0.2",
-        "object-assign": "^4.1.0"
-      },
-      "dependencies": {
-        "acorn": {
-          "version": "4.0.13",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
-          "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
-        }
-      }
-    },
-    "unassertify": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/unassertify/-/unassertify-2.1.1.tgz",
-      "integrity": "sha512-YIAaIlc6/KC9Oib8cVZLlpDDhK1UTEuaDyx9BwD97xqxDZC0cJOqwFcs/Y6K3m73B5VzHsRTBLXNO0dxS/GkTw==",
-      "requires": {
-        "acorn": "^5.1.0",
-        "convert-source-map": "^1.1.1",
-        "escodegen": "^1.6.1",
-        "multi-stage-sourcemap": "^0.2.1",
-        "through": "^2.3.7",
-        "unassert": "^1.3.1"
-      },
-      "dependencies": {
-        "acorn": {
-          "version": "5.7.3",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
-          "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
+          "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
+          "dev": true
         }
       }
     },
-    "util-deprecate": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
-    },
-    "wordwrap": {
+    "unicount": {
       "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
-      "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
-    },
-    "wrap-comment": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/wrap-comment/-/wrap-comment-1.0.1.tgz",
-      "integrity": "sha512-APccrMwl/ont0RHFTXNAQfM647duYYEfs6cngrIyTByTI0xbWnDnPSptFZhS68L4WCjt2ZxuhCFwuY6Pe88KZQ=="
+      "resolved": "https://registry.npmjs.org/unicount/-/unicount-1.0.0.tgz",
+      "integrity": "sha512-laO0gw4tnW759KI25w8z+58D4K+pnXehZvfKdFj3meYn/NSH85/0I8Gb4jj32tuZaX7zv1rSeP2K87rdChIZUw=="
     },
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
-    },
-    "xtend": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
-      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
     }
   }
 }
diff --git a/package.json b/package.json
index af4f737..df7bcff 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
     "ot-text-unicode": "^3.0.0"
   },
   "devDependencies": {
-    "coffeescript": "^2.3.2",
+    "coffeescript": "^2.4.1",
     "mocha": "^5.2.0",
     "ot-fuzzer": "^1.1.0",
     "ot-simple": "^1.0.0",
@@ -16,7 +16,7 @@
     "terser": "^3.11.0"
   },
   "scripts": {
-    "test": "mocha test/cursor.coffee test/test.coffee test/immutable.coffee",
+    "test": "mocha test/cursor.js test/test.js test/immutable.js",
     "fuzzer": "node test/fuzzer.js",
     "prepare": "terser -d process.env.JSON1_RELEASE_MODE=true -c pure_funcs=log,keep_fargs=false,passes=2 -b < lib/json1.js > lib/json1.release.js"
   },
diff --git a/test/cursor.coffee b/test/cursor.coffee
deleted file mode 100644
index 113803c..0000000
--- a/test/cursor.coffee
+++ /dev/null
@@ -1,76 +0,0 @@
-{writeCursor, readCursor} = require '../lib/cursor'
-assert = require 'assert'
-
-data = require('fs').readFileSync(__dirname + '/ops.json', 'utf8').split('\n').filter((x) -> x != '').map(JSON.parse)
-
-describe 'cursors', ->
-  describe 'writeCursor duplicates', ->
-    test = (op) ->
-      w = writeCursor()
-
-      f = (l) ->
-        assert Array.isArray l
-        depth = 0
-        for c in l
-          if typeof c in ['string', 'number']
-            depth++
-            w.descend c
-          else if Array.isArray c
-            f c
-          else if typeof c is 'object'
-            for k, v of c
-              w.write k, v
-
-        w.ascend() for [0...depth]
-
-      f op if op != null
-      assert.deepEqual op, w.get()
-
-    for d in data
-      do (d) -> it "#{JSON.stringify d}", -> test d
-
-  describe 'copy using read cursors', ->
-    test = (op) ->
-      r = readCursor op
-      w = writeCursor()
-      path = []
-      do f = ->
-        if component = r.getComponent()
-          # console.log 'component', component
-          w.write k, v for k, v of component
-
-        assert.deepStrictEqual r.getPath(), path
-        assert.deepStrictEqual w.getPath(), path
-
-        for k from r
-          path.push k
-          w.descend k
-          f()
-          w.ascend()
-          path.pop()
-
-      assert.deepEqual op, w.get()
-        # console.log op
-        # console.log w.get()
-    for d in data
-      do (d) -> it "#{JSON.stringify d}", -> test d
-
-  describe 'fuzzer', ->
-
-    it 'cleans up position after mergeTree', ->
-      a = [ 1, 'c', { d: 1 } ]
-      w = writeCursor(a)
-
-      w.descend(0)
-        
-      w.descend('a')
-      w.write('p', 1)
-      w.ascend()
-      w.ascend()
-
-      w.descend(1)
-      w.mergeTree([{r:true}])
-      w.descend('b')
-      w.write('p', 0) # Crash!
-      w.ascend()
-      w.ascend()
diff --git a/test/cursor.js b/test/cursor.js
new file mode 100644
index 0000000..1efa76b
--- /dev/null
+++ b/test/cursor.js
@@ -0,0 +1,111 @@
+const { writeCursor, readCursor } = require('../lib/cursor')
+const assert = require('assert')
+
+const data = require('fs')
+  .readFileSync(__dirname + '/ops.json', 'utf8')
+  .split('\n')
+  .filter(x => x !== '')
+  .map(JSON.parse)
+
+describe('cursors', function() {
+  describe('writeCursor duplicates', function() {
+    const test = op => {
+      const w = writeCursor()
+
+      const f = l => {
+        assert(Array.isArray(l))
+        let depth = 0
+        for (let c of l) {
+          if (['string', 'number'].includes(typeof c)) {
+            depth++
+            w.descend(c)
+          } else if (Array.isArray(c)) {
+            f(c)
+          } else if (typeof c === 'object') {
+            for (let k in c) {
+              const v = c[k]
+              w.write(k, v)
+            }
+          }
+        }
+
+        return __range__(0, depth, false).map(i => w.ascend())
+      }
+
+      if (op !== null) {
+        f(op)
+      }
+      return assert.deepEqual(op, w.get())
+    }
+
+    return data.map(d => (d => it(`${JSON.stringify(d)}`, () => test(d)))(d))
+  })
+
+  describe('copy using read cursors', function() {
+    const test = op => {
+      const r = readCursor(op)
+      const w = writeCursor()
+      const path = []
+      const f = () => {
+        const component = r.getComponent()
+        if (component) {
+          // console.log 'component', component
+          for (let k in component) {
+            const v = component[k]
+            w.write(k, v)
+          }
+        }
+
+        assert.deepStrictEqual(r.getPath(), path)
+        assert.deepStrictEqual(w.getPath(), path)
+
+        const result = []
+        for (let k of r) {
+          path.push(k)
+          w.descend(k)
+          f()
+          w.ascend()
+          result.push(path.pop())
+        }
+        return result
+      }
+
+      f()
+
+      return assert.deepEqual(op, w.get())
+    }
+    // console.log op
+    // console.log w.get()
+    return data.map(d => it(`${JSON.stringify(d)}`, () => test(d)))
+  })
+
+  return describe('fuzzer', () =>
+    it('cleans up position after mergeTree', () => {
+      const a = [1, 'c', { d: 1 }]
+      const w = writeCursor(a)
+
+      w.descend(0)
+
+      w.descend('a')
+      w.write('p', 1)
+      w.ascend()
+      w.ascend()
+
+      w.descend(1)
+      w.mergeTree([{ r: true }])
+      w.descend('b')
+      w.write('p', 0) // Crash!
+      w.ascend()
+      return w.ascend()
+    }))
+})
+
+function __range__(left, right, inclusive) {
+  const range = []
+  const ascending = left < right
+  const end = !inclusive ? right : ascending ? right + 1 : right - 1
+  for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
+    range.push(i)
+  }
+  return range
+}
diff --git a/test/fuzzer.js b/test/fuzzer.js
index b705856..9bfa43a 100644
--- a/test/fuzzer.js
+++ b/test/fuzzer.js
@@ -1,10 +1,8 @@
-require('coffeescript/register')
-
 const assert = require('assert')
 // const {type} = require('../index')
-const {type} = require('../lib/json1')
+const { type } = require('../lib/json1')
 
-const run = module.exports = () => {
+const run = (module.exports = () => {
   // require('./lib/log').quiet = true
   // type.debug = true
   const fuzzer = require('ot-fuzzer')
@@ -15,6 +13,6 @@ const run = module.exports = () => {
   // const tracer = require('./tracer')(_t, genOp)
   //fuzzer(tracer, tracer.genOp, 100000)
   fuzzer(_t, genOp, 10000000)
-}
+})
 
 if (require.main === module) run()
diff --git a/test/genOp.coffee b/test/genOp.coffee
deleted file mode 100644
index 63215c6..0000000
--- a/test/genOp.coffee
+++ /dev/null
@@ -1,215 +0,0 @@
-{randomInt, randomReal, randomWord} = require 'ot-fuzzer'
-# require 'ot-fuzzer'
-
-assert = require 'assert'
-{writeCursor} = require '../lib/cursor'
-log = require '../lib/log'
-{type} = require '../lib/json1'
-
-
-# This is an awful function to clone a document snapshot for use by the random
-# op generator. .. Since we don't want to corrupt the original object with
-# the changes the op generator will make.
-clone = (o) -> if typeof o is 'object' then JSON.parse(JSON.stringify(o)) else o
-
-randomKey = (obj) -> # this works on arrays too!
-  if Array.isArray obj
-    if obj.length is 0
-      return undefined
-    else
-      return randomInt obj.length
-  else
-    keys = Object.keys obj
-    if keys.length is 0
-      return undefined
-    else
-      return keys[randomInt keys.length]
-
-letters = 'abxyz'
-
-# Generate a random new key for a value in obj.
-randomNewKey = (obj) ->
-  if Array.isArray obj
-    return randomInt obj.length + 1
-  else
-    loop
-      # Mostly try just use a small letter
-      key = if randomInt(20) == 0 then randomWord() else letters[randomInt(letters.length)]
-      break if obj[key] == undefined
-    return key
-
-# Generate a random object
-randomThing = ->
-  switch randomInt 7
-    when 0 then null
-    when 1 then ''
-    when 2 then randomWord()
-    when 3
-      obj = {}
-      obj[randomNewKey(obj)] = randomThing() for [1..randomInt(2)]
-      obj
-    when 4 then (randomThing() for [1..randomInt(2)])
-    when 5 then 0 # bias toward zeros to find accidental truthy checks
-    when 6 then randomInt(50)
-
-# Pick a random path to something in the object.
-randomPath = (data) ->
-  return [] if !data? or typeof data != 'object'
-
-  path = []
-  while randomReal() < 0.9 and data? and typeof data == 'object'
-    key = randomKey data
-    break unless key?
-
-    path.push key
-    data = data[key]
-
-  path
-
-randomWalkPick = (w, container) ->
-  path = randomPath container.data
-  parent = container
-  key = 'data'
-  #log 'rwp', container, path, parent
-
-  for p in path
-    parent = parent[key]
-    key = p
-    w.descend key
-  operand = parent[key]
-  return [path, parent, key, operand]
-
-# Returns [path, parent, key] if we can drop here, or null if no drop is
-# possible
-randomWalkDrop = (w, container) ->
-  if container.data == undefined
-    return [[], container, 'data']
-  else if typeof container.data != 'object' or container.data == null
-    return null # Can't insert into a document that is a string or number
-
-  [path, parent, key, operand] = randomWalkPick w, container
-  if typeof operand == 'object' and operand != null
-    parent = operand
-  else
-    w.ascend()
-    w.reset()
-    path.pop()
-  key = randomNewKey parent
-  # log 'key', key
-  return [path, parent, key]
-
-set = (container, key, value) ->
-  if value == undefined
-    if Array.isArray container
-      container.splice key, 1
-    else
-      delete container[key]
-  else
-    if Array.isArray container
-      container.splice key, 0, value
-    else
-      container[key] = value
-
-genRandomOpPart = (data) ->
-  # log 'genRandomOp', data
-
-  container = {data}
-  w = writeCursor()
-
-  # Remove something
-
-  # Things we can do:
-  # 0. remove something
-  # 1. move something
-  # 2. insert something
-  # 3. edit a string
-  mode = if data == undefined and randomReal() < 0.9 then 2 else randomInt 4
-  #mode = 1
-  #log 'mode', mode
-  switch mode
-    when 0, 1
-      [path, parent, key, operand] = randomWalkPick w, container
-      #log 'ppko', path, parent, key, operand
-      if mode is 1 and typeof operand is 'string'
-        # Edit it!
-        genString = require 'ot-text/test/genOp'
-        [stringOp, result] = genString operand
-        w.write 'es', stringOp
-        parent[key] = result
-      else if mode is 1 and typeof operand is 'number'
-        increment = randomInt 10
-        w.write 'ena', increment
-        parent[key] += increment
-      else
-        # Remove it
-        if operand != undefined
-          w.write 'r', true #operand
-          set parent, key, undefined
-
-    when 2
-      # insert something
-      walk = randomWalkDrop w, container
-      if walk != null
-        [path, parent, key] = walk
-        #log 'walk', walk
-        val = randomThing()
-        w.descend key if parent != container
-        w.write 'i', val
-        set parent, key, clone val
-
-    when 3
-      # Move something. We'll pick up the current operand...
-      [path1, parent1, key1, operand] = randomWalkPick w, container
-      if operand != undefined
-        set parent1, key1, undefined # remove it from the result...
-
-        if parent1 == container # We're removing the whole thing.
-          w.write 'r', true
-        else
-          w.write 'p', 0
-
-          # ... and find another place to insert it!
-          w.ascend() for [0...path1.length]
-          w.reset()
-          [path2, parent2, key2] = randomWalkDrop w, container
-          w.descend key2
-          set parent2, key2, operand
-          w.write 'd', 0
-
-  doc = container.data
-  op = w.get()
-
-  type.checkValidOp op
-
-  # assert.deepEqual doc, type.apply clone(data), op
-
-  return [op, doc]
-
-module.exports = genRandomOp = (doc) ->
-  doc = clone doc
-
-  # 90% chance of adding an op the first time through, then 50% each successive time.
-  chance = 0.99
-
-  op = null # Aggregate op
-
-  while randomReal() < chance
-    [opc, doc] = genRandomOpPart(doc)
-    log opc
-    # type.setDebug false
-    op = type.compose op, opc
-
-    chance = 0.5
-
-  # log.quiet = false
-  log '-> generated op', op, 'doc', doc
-  return [op, doc]
-
-if require.main == module
-  # log genRandomOp {}
-  # log genRandomOp({x:5, y:7, z:[1,2,3]}) for [1..10]
-  for [1..10]
-    type.debug = true
-    log.quiet = false
-    log genRandomOp({x:"hi", y:'omg', z:[1,'whoa',3]})
-    # log genRandomOp(undefined)
diff --git a/test/genOp.js b/test/genOp.js
new file mode 100644
index 0000000..0e35598
--- /dev/null
+++ b/test/genOp.js
@@ -0,0 +1,278 @@
+let genRandomOp
+const { randomInt, randomReal, randomWord } = require('ot-fuzzer')
+
+const assert = require('assert')
+const { writeCursor } = require('../lib/cursor')
+const log = require('../lib/log')
+const { type } = require('../lib/json1')
+
+// This is an awful function to clone a document snapshot for use by the random
+// op generator. .. Since we don't want to corrupt the original object with
+// the changes the op generator will make.
+const clone = o => (typeof o === 'object' ? JSON.parse(JSON.stringify(o)) : o)
+
+const randomKey = obj => {
+  // this works on arrays too!
+  if (Array.isArray(obj)) {
+    return obj.length === 0 ? undefined : randomInt(obj.length)
+  } else {
+    const keys = Object.keys(obj)
+    return keys.length === 0 ? undefined : keys[randomInt(keys.length)]
+  }
+}
+
+const letters = 'abxyz'
+
+// Generate a random new key for a value in obj.
+const randomNewKey = obj => {
+  if (Array.isArray(obj)) {
+    return randomInt(obj.length + 1)
+  } else {
+    let key
+    while (true) {
+      // Mostly try just use a small letter
+      key =
+        randomInt(20) === 0 ? randomWord() : letters[randomInt(letters.length)]
+      if (obj[key] === undefined) {
+        break
+      }
+    }
+    return key
+  }
+}
+
+// Generate a random object
+const randomThing = () => {
+  switch (randomInt(7)) {
+    case 0:
+      return null
+    case 1:
+      return ''
+    case 2:
+      return randomWord()
+    case 3:
+      const obj = {}
+      for (
+        let i = 1, end = randomInt(2), asc = 1 <= end;
+        asc ? i <= end : i >= end;
+        asc ? i++ : i--
+      ) {
+        obj[randomNewKey(obj)] = randomThing()
+      }
+      return obj
+    case 4:
+      return __range__(1, randomInt(2), true).map(j => randomThing())
+    case 5:
+      return 0 // bias toward zeros to find accidental truthy checks
+    case 6:
+      return randomInt(50)
+  }
+}
+
+// Pick a random path to something in the object.
+const randomPath = data => {
+  if (data == null || typeof data !== 'object') {
+    return []
+  }
+
+  const path = []
+  while (randomReal() < 0.9 && data != null && typeof data === 'object') {
+    const key = randomKey(data)
+    if (key == null) {
+      break
+    }
+
+    path.push(key)
+    data = data[key]
+  }
+
+  return path
+}
+
+const randomWalkPick = (w, container) => {
+  const path = randomPath(container.data)
+  let parent = container
+  let key = 'data'
+
+  for (let p of Array.from(path)) {
+    parent = parent[key]
+    key = p
+    w.descend(key)
+  }
+  const operand = parent[key]
+  return [path, parent, key, operand]
+}
+
+// Returns [path, parent, key] if we can drop here,
+// or null if no drop is possible
+const randomWalkDrop = (w, container) => {
+  if (container.data === undefined) {
+    return [[], container, 'data']
+  } else if (typeof container.data !== 'object' || container.data === null) {
+    return null // Can't insert into a document that is a string or number
+  }
+
+  let [path, parent, key, operand] = randomWalkPick(w, container)
+  if (typeof operand === 'object' && operand !== null) {
+    parent = operand
+  } else {
+    w.ascend()
+    w.reset()
+    path.pop()
+  }
+  key = randomNewKey(parent)
+  return [path, parent, key]
+}
+
+const set = (container, key, value) =>
+  value === undefined
+    ? Array.isArray(container)
+      ? container.splice(key, 1)
+      : delete container[key]
+    : Array.isArray(container)
+    ? container.splice(key, 0, value)
+    : (container[key] = value)
+
+const genRandomOpPart = data => {
+  // log 'genRandomOp', data
+
+  let key1, parent1, path1
+  const container = { data }
+  const w = writeCursor()
+
+  // Remove something
+
+  // Things we can do:
+  // 0. remove something
+  // 1. move something
+  // 2. insert something
+  // 3. edit a string
+  const mode = data === undefined && randomReal() < 0.9 ? 2 : randomInt(4)
+  //mode = 1
+  //log 'mode', mode
+  switch (mode) {
+    case 0:
+    case 1:
+      const [path, parent, key, operand] = randomWalkPick(w, container)
+      //log 'ppko', path, parent, key, operand
+      if (mode === 1 && typeof operand === 'string') {
+        // Edit it!
+        const genString = require('ot-text/test/genOp')
+        const [stringOp, result] = genString(operand)
+        w.write('es', stringOp)
+        parent[key] = result
+      } else if (mode === 1 && typeof operand === 'number') {
+        const increment = randomInt(10)
+        w.write('ena', increment)
+        parent[key] += increment
+      } else {
+        // Remove it
+        if (operand !== undefined) {
+          w.write('r', true) //operand
+          set(parent, key, undefined)
+        }
+      }
+      break
+
+    case 2:
+      // insert something
+      const walk = randomWalkDrop(w, container)
+      if (walk !== null) {
+        const [path, parent, key] = walk
+        //log 'walk', walk
+        const val = randomThing()
+        if (parent !== container) {
+          w.descend(key)
+        }
+        w.write('i', val)
+        set(parent, key, clone(val))
+      }
+      break
+
+    case 3:
+      // Move something. We'll pick up the current operand...
+      const [path1, parent1, key1, operand1] = randomWalkPick(w, container)
+      if (operand1 !== undefined) {
+        set(parent1, key1, undefined) // remove it from the result...
+
+        if (parent1 === container) {
+          // We're removing the whole thing.
+          w.write('r', true)
+        } else {
+          w.write('p', 0)
+
+          // ... and find another place to insert it!
+          for (
+            let i = 0, end = path1.length, asc = 0 <= end;
+            asc ? i < end : i > end;
+            asc ? i++ : i--
+          ) {
+            w.ascend()
+          }
+          w.reset()
+          const [path2, parent2, key2] = Array.from(
+            randomWalkDrop(w, container)
+          )
+          w.descend(key2)
+          set(parent2, key2, operand1)
+          w.write('d', 0)
+        }
+      }
+      break
+  }
+
+  const doc = container.data
+  const op = w.get()
+
+  type.checkValidOp(op)
+
+  // assert.deepEqual doc, type.apply clone(data), op
+
+  return [op, doc]
+}
+
+module.exports = genRandomOp = doc => {
+  doc = clone(doc)
+
+  // 90% chance of adding an op the first time through, then 50% each successive time.
+  let chance = 0.99
+
+  let op = null // Aggregate op
+
+  while (randomReal() < chance) {
+    let opc
+    ;[opc, doc] = genRandomOpPart(doc)
+    log(opc)
+    // type.setDebug false
+    op = type.compose(
+      op,
+      opc
+    )
+    chance = 0.5
+  }
+
+  // log.quiet = false
+  log('-> generated op', op, 'doc', doc)
+  return [op, doc]
+}
+
+if (require.main === module) {
+  // log genRandomOp {}
+  // log genRandomOp({x:5, y:7, z:[1,2,3]}) for [1..10]
+  for (let i = 1; i <= 10; i++) {
+    type.debug = true
+    log.quiet = false
+    log(genRandomOp({ x: 'hi', y: 'omg', z: [1, 'whoa', 3] }))
+  }
+}
+// log genRandomOp(undefined)
+
+function __range__(left, right, inclusive) {
+  const range = []
+  const ascending = left < right
+  const end = !inclusive ? right : ascending ? right + 1 : right - 1
+  for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
+    range.push(i)
+  }
+  return range
+}
diff --git a/test/immutable.coffee b/test/immutable.coffee
deleted file mode 100644
index 0f9522d..0000000
--- a/test/immutable.coffee
+++ /dev/null
@@ -1,50 +0,0 @@
-assert = require 'assert'
-{type} = require '../lib/json1'
-log = require '../lib/log'
-genOp = require './genOp'
-deepClone = require '../lib/deepClone'
-
-# This tests that none of apply / compose / transform / genOp mutate their
-# input
-
-describe 'immutable guarantees', ->
-  origDoc = {x:"hi", y:'omg', z:[1,'whoa',3]}
-  expectDoc = deepClone origDoc
-  n = 1000
-  this.slow n*10
-
-  it 'apply does not mutate', ->
-    for [1..n]
-      [op, doc] = genOp origDoc
-      assert.deepStrictEqual origDoc, expectDoc
-
-      expectOp = deepClone op
-      type.apply origDoc, op
-
-      assert.deepStrictEqual origDoc, expectDoc
-      assert.deepStrictEqual op, expectOp
-
-  it 'compose does not mutate', ->
-    for [1..n]
-      [op1, doc] = genOp origDoc
-      [op2, doc] = genOp doc
-
-      expectOp1 = deepClone op1
-      expectOp2 = deepClone op2
-      type.compose op1, op2
-
-      assert.deepStrictEqual op1, expectOp1
-      assert.deepStrictEqual op2, expectOp2
-
-  it 'transform does not mutate', ->
-    for [1..n]
-      [op1, doc1] = genOp origDoc
-      [op2, doc2] = genOp origDoc
-
-      expectOp1 = deepClone op1
-      expectOp2 = deepClone op2
-
-      type.transformNoConflict op1, op2, 'left'
-      type.transformNoConflict op2, op1, 'right'
-      assert.deepStrictEqual op1, expectOp1
-      assert.deepStrictEqual op2, expectOp2
diff --git a/test/immutable.js b/test/immutable.js
new file mode 100644
index 0000000..a88ab11
--- /dev/null
+++ b/test/immutable.js
@@ -0,0 +1,73 @@
+const assert = require('assert')
+const { type } = require('../lib/json1')
+const log = require('../lib/log')
+const genOp = require('./genOp')
+const deepClone = require('../lib/deepClone')
+
+// This tests that none of apply / compose / transform / genOp mutate their input
+describe('immutable guarantees', function() {
+  const origDoc = { x: 'hi', y: 'omg', z: [1, 'whoa', 3] }
+  const expectDoc = deepClone(origDoc)
+  const n = 1000
+  this.slow(n * 10)
+
+  it('apply does not mutate', () => {
+    const result = []
+    for (
+      let i = 1, end = n, asc = 1 <= end;
+      asc ? i <= end : i >= end;
+      asc ? i++ : i--
+    ) {
+      const [op, doc] = genOp(origDoc)
+      assert.deepStrictEqual(origDoc, expectDoc)
+
+      const expectOp = deepClone(op)
+      type.apply(origDoc, op)
+
+      assert.deepStrictEqual(origDoc, expectDoc)
+      result.push(assert.deepStrictEqual(op, expectOp))
+    }
+    return result
+  })
+
+  it('compose does not mutate', () => {
+    for (
+      let i = 1, end = n, asc = 1 <= end;
+      asc ? i <= end : i >= end;
+      asc ? i++ : i--
+    ) {
+      let op2
+      let [op1, doc] = genOp(origDoc)
+      ;[op2, doc] = genOp(doc)
+
+      const expectOp1 = deepClone(op1)
+      const expectOp2 = deepClone(op2)
+      type.compose(
+        op1,
+        op2
+      )
+
+      assert.deepStrictEqual(op1, expectOp1)
+      assert.deepStrictEqual(op2, expectOp2)
+    }
+  })
+
+  it('transform does not mutate', () => {
+    for (
+      let i = 1, end = n, asc = 1 <= end;
+      asc ? i <= end : i >= end;
+      asc ? i++ : i--
+    ) {
+      const [op1, doc1] = genOp(origDoc)
+      const [op2, doc2] = genOp(origDoc)
+
+      const expectOp1 = deepClone(op1)
+      const expectOp2 = deepClone(op2)
+
+      type.transformNoConflict(op1, op2, 'left')
+      type.transformNoConflict(op2, op1, 'right')
+      assert.deepStrictEqual(op1, expectOp1)
+      assert.deepStrictEqual(op2, expectOp2)
+    }
+  })
+})
diff --git a/test/test.coffee b/test/test.coffee
deleted file mode 100644
index 8d550ec..0000000
--- a/test/test.coffee
+++ /dev/null
@@ -1,2268 +0,0 @@
-# Unit tests for the JSON1 OT type.
-#
-# These tests are quite unstructured. You can see the skeletons of a few
-# organizing systems, but ultimately there's just lots of test cases to run.
-#
-# Cleanups welcome, so long as you don't remove any tests.
-
-assert = require 'assert'
-# {type} = require '../index'
-{type} = require '../lib/json1'
-log = require '../lib/log'
-deepClone = require '../lib/deepClone'
-
-{transform} = type
-{DROP_COLLISION, RM_UNEXPECTED_CONTENT, BLACKHOLE} = type
-
-apply = ({doc:snapshot, op, expect}) ->
-  type.setDebug(false)
-
-  orig = deepClone snapshot
-  try
-    result = type.apply snapshot, op
-    assert.deepStrictEqual snapshot, orig, 'Original snapshot was mutated'
-    assert.deepStrictEqual result, expect
-  catch e
-    console.log "Apply failed! Repro apply( #{JSON.stringify(snapshot)}, #{JSON.stringify(op)} )"
-    console.log "expected output: #{JSON.stringify(expect)}"
-    throw e
-
-d = (fn) ->
-  type.setDebug(true)
-  fn()
-  type.setDebug(false)
-
-compose = ({op1, op2, expect}) ->
-  try
-    result = type.compose op1, op2
-    assert.deepStrictEqual result, expect
-  catch e
-    d ->
-      console.error 'FAIL! Repro with:'
-      console.log "compose( #{JSON.stringify(op1)}, #{JSON.stringify(op2)} )"
-      console.log "expected output: #{JSON.stringify(expect)}"
-      type.compose op1, op2
-    throw e
-
-invConflict = ({type, op1, op2}) -> {type, op1:op2, op2:op1}
-
-
-
-otherSide = (side) -> if side == 'left' then 'right' else 'left'
-checkConflict = ({op1, op2, side, conflict: expectConflict, expect}) ->
-  # We should get the same conflict with xf(op1, op2, left) and xf(op2, op1, right).
-  if expectConflict?
-    expectConflict.op1 = type.normalize(op1) if !expectConflict.op1
-    expectConflict.op2 = type.normalize(op2) if !expectConflict.op2
-
-  for [side_, op1_, op2_, ec] in [
-        [side, op1, op2, expectConflict],
-        [otherSide(side), op2, op1, if expectConflict then invConflict(expectConflict) else null]
-      ]
-    try
-
-      # d -> log('tryTransform', side_, op1_, op2_)
-      {ok, conflict} = type.tryTransform op1_, op2_, side_
-      if !ec?
-        # We don't care what the result is here; just that it doesn't conflict.
-        assert ok
-      else
-        assert !ok, "Conflict erroneously succeeded (#{side_})"
-        # d -> log('conflict', conflict)
-        conflict.op1 = type.normalize conflict.op1
-        conflict.op2 = type.normalize conflict.op2
-        assert.deepStrictEqual conflict, ec
-    catch e
-      d ->
-        console.error 'FAIL! Repro with:'
-        console.log "tryTransform(#{JSON.stringify(op1_)}, #{JSON.stringify(op2_)}, '#{side_}')"
-        type.tryTransform op1_, op2_, side_
-      throw e
-
-xf = ({op1, op2, conflict, conflictLeft, conflictRight, expect, expectLeft, expectRight}) ->
-  if expect != undefined then expectLeft = expectRight = expect
-  if conflict != undefined then conflictLeft = conflictRight = conflict
-
-  for [side, e, c] in [['left', expectLeft, conflictLeft], ['right', expectRight, conflictRight]]
-    checkConflict {op1, op2, side, conflict: c, expect: e}
-
-    try
-      result = if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side
-      assert.deepStrictEqual result, e
-    catch e
-      d ->
-        console.error 'FAIL! Repro with:'
-        console.log "transform(#{JSON.stringify(op1)}, #{JSON.stringify(op2)}, '#{side}')"
-        # if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side
-      throw e
-
-
-
-diamond = ({doc, op1, op2}) ->
-  type.setDebug(false)
-
-  try
-    # Test that the diamond property holds
-    op1_ = transform op1, op2, 'left'
-    op2_ = transform op2, op1, 'right'
-
-    doc1 = type.apply doc, op1
-    doc2 = type.apply doc, op2
-
-    doc12 = type.apply doc1, op2_
-    doc21 = type.apply doc2, op1_
-
-    assert.deepStrictEqual doc12, doc21
-  catch e
-    log.quiet = false
-    log '\nOops! Diamond property does not hold. Given document', doc
-    log 'op1 ', op1, '   /    op2', op2
-    log 'op1_', op1_, '   /    op2_', op2_
-    log '---- 1'
-    log 'op1', op1, '->', doc1
-    log 'op2', op2_, '->', doc12
-    log '---- 2'
-    log 'op2', op2, '->', doc2
-    log 'op1', op1_, '->', doc21
-    log '----------'
-    log doc12, '!=', doc21
-    throw e
-
-
-path = (path, {op, expect}) ->
-  expect = path.slice() if expect == undefined
-
-  result = type.transformPosition path, op
-  assert.deepStrictEqual result, expect
-
-  # Also check that path+X = expect+X
-  path2 = path.concat 'x'
-  expect2 = if expect? then expect.concat('x') else null
-  
-  result2 = type.transformPosition path2, op
-  assert.deepStrictEqual result2, expect2
-
-
-describe 'json1', ->
-  before ->
-    type.registerSubtype require 'ot-simple'
-    type.setDebug(true)
-  after -> type.setDebug(false)
-
-  describe 'checkOp', ->
-    pass = (op) ->
-      try
-        type.checkValidOp op
-      catch e
-        console.log("FAIL! Repro with:\ncheckOp( #{JSON.stringify(op)} )")
-        throw e
-
-    fail = (op) ->
-      try
-        assert.throws -> type.checkValidOp op
-      catch e
-        console.log("FAIL! Repro with:\ncheckOp( #{JSON.stringify(op)} )")
-        console.log('Should throw!')
-        throw e
-
-    it 'allows some simple valid ops', ->
-      pass null
-      pass [{i:[1,2,3]}]
-      pass [{r:{}}]
-      pass [['x',{p:0}], ['y',{d:0}]]
-      pass [[0,{p:0}], [10,{d:0}]]
-      pass [['a',{p:0}],['b',{d:0}],['x',{p:1}],['y',{d:1}]]
-      pass [{e:"hi", et:'simple'}]
-      pass [{es:["hi"]}]
-      pass [{ena:5}]
-
-    it 'disallows invalid syntax', ->
-      fail undefined
-      fail {}
-      fail "hi"
-      fail true
-      fail false
-      fail 0
-      fail 10
-      fail [{}]
-      fail [{invalid:true}]
-      fail [10, {}]
-      fail [10, {invalid:true}]
-      fail [10, 'hi']
-
-    it 'throws if there is any empty leaves', ->
-      fail []
-      fail ['x']
-      fail ['x', {}]
-      fail ['x', []]
-      fail [10]
-      fail [10, {}]
-      fail [10, []]
-
-    it 'ensures path components are non-zero integers or strings', ->
-      fail [-1, {r:{}}]
-      fail [0.5, {r:{}}]
-      fail [true, {r:{}}]
-      fail [false, {r:{}}]
-      fail [null, {r:{}}]
-      fail [undefined, {r:{}}]
-
-    it 'does not allow two pickups or two drops in a component', ->
-      fail [{p:0, r:{}}]
-      fail [{p:1, r:{}}]
-      fail ['x', {p:0, r:{}}]
-      fail ['x', {p:1, r:{}}]
-
-      fail [{d:0, i:'hi'}]
-      fail [{d:1, i:'hi'}]
-      fail [10, {d:0, i:'hi'}]
-      fail [10, {d:1, i:'hi'}]
-
-    it 'throws if there are mismatched pickups / drops', ->
-      fail [{p:0}]
-      fail [{d:0}]
-      fail ['x', {p:0}]
-      fail [10, {p:0}]
-      fail ['x', {d:0}]
-      fail [10, {d:0}]
-
-    it 'throws if pick/drop indexes dont start at 0', ->
-      fail [['x', {p:1}], ['y', {d:1}]]
-      fail [[10, {p:1}], [20, {d:1}]]
-
-    it 'throws if a descent starts with an edit', ->
-      fail [10, [{i:"hi"}]]
-
-    it 'throws if descents are out of order', ->
-      fail ['x', ['b', {r:{}}], ['a', {r:{}}]]
-      fail ['x', [10, {r:{}}], [5, {r:{}}]]
-      fail ['x', ['a', {r:{}}], [5, {r:{}}]]
-      fail ['x', ['a', {r:{}}], ['a', {r:{}}]]
-      fail ['x', [10, {r:{}}], [10, {r:{}}]]
-
-    it 'throws if descents start with the same scalar', ->
-      fail ['x', ['a', {r:{}}], ['a', {e:{}}]]
-
-    it 'throws if descents have two adjacent edits', ->
-      fail [{r:{}}, {p:0}]
-      fail ['x', {r:{}}, {p:0}]
-      fail ['x', {r:{}}, {p:0}, 'y', {r:{}}]
-
-    it.skip 'does not allow ops to overwrite their own inserted data', ->
-      fail [{i:{x:5}}, 'x', {i:6}]
-      fail [{i:['hi']}, 0, {i:'omg'}]
-
-    it.skip 'does not allow immediate data directly parented in other immediate data', ->
-      fail [{i:{}}, 'x', {i:5}]
-      fail [{i:{x:5}}, 'x', 'y', {i:6}]
-      fail [{i:[]}, 0, {i:5}]
-
-    it 'does not allow the final item to be a single descent', ->
-      fail ['a', ['b', r:{}]] # It should be ['a', 'b', r:{}]
-
-    it 'does not allow anything after the descents at the end', ->
-      fail [[1, r:{}], [2, r:{}], 5]
-      fail [[1, r:{}], [2, r:{}], 5, r:{}]
-      fail [[1, r:{}], [2, r:{}], r:{}]
-
-    it 'allows removes inside removes', ->
-      pass ['x', r:true, 'y', r:true]
-      pass ['x', r:{}, 'y', r:true]
-      pass [['x', r:true, 'y', p:0, 'z', r:true], ['y', d:0]]
-      pass [['x', r:{}, 'y', p:0, 'z', r:true], ['y', d:0]]
-
-    it 'allows inserts inside inserts', ->
-      pass [1, i:{}, 'x', i:10]
-      pass [[0, 'x', p:0], [1, i:{}, 'x', d:0, 'y', i:10]]
-
-    it.skip 'fails if the operation drops items inside something it picked up', ->
-      fail ['x', r:true, 1, i:'hi']
-      fail ['x', d:0, 1, p:0]
-      fail [r:true, 1, p:0, d:0]
-
-    describe 'edit', ->
-      it 'requires all edits to specify their type', ->
-        fail [{e:{}}]
-        fail [5, {e:{}}]
-        pass [{e:{}, et:'simple'}]
-
-      it 'allows edits to have null or false for the operation', ->
-        # These aren't valid operations according to the simple type, but the
-        # type doesn't define a checkValidOp so we wouldn't be able to tell
-        # anyway.
-        pass [{e:null, et:'simple'}]
-        pass [5, {e:null, et:'simple'}]
-        pass [{e:false, et:'simple'}]
-        pass [5, {e:false, et:'simple'}]
-
-      it 'does not allow an edit to use an unregistered type', ->
-        fail [{e:{}, et:'an undefined type'}]
-        fail [{e:null, et:'an undefined type'}]
-
-      it 'does not allow two edits in the same operation', ->
-        fail [{e:{}, et:'simple', es:[1,2,3]}]
-        fail [{es:[], ena:5}]
-        fail [{e:{}, et:'simple', ena:5}]
-
-      it 'fails if the type is missing', ->
-        fail [et:'missing', e:{}]
-
-      it 'does not allow anything inside an edited subtree'
-
-      it.skip 'does not allow an edit inside removed or picked up content', ->
-        fail [r:true, 1, es:['hi']]
-        pass [1, r:true, 1, es:['hi']]
-        fail ['x', r:true, 1, es:['hi']]
-        pass [[1, p:0, 1, es:['hi']], [2, d:0]]
-        fail [['x', p:0, 1, es:['hi']], ['y', d:0]]
-
-        # This is actually ok.
-        pass [ 0, { p: 0 }, [ 'a', { es: [], r: true } ], [ 'x', { d: 0 } ] ]
-
-      it.skip 'does not allow you to drop inside something that was removed', ->
-        # These insert into the next list item
-        pass [[1, r:true, 1, d:0], [2, p:0]]
-        pass [1, {p: 0}, 'x', {d: 0}]
-
-        # But this is not ok.
-        fail ['x', {p:0}, 'a', {d:0}]
-
-  describe 'normalize', ->
-    n = (opIn, expect) ->
-      expect = opIn if expect == undefined
-      op = type.normalize opIn
-      assert.deepStrictEqual op, expect
-
-    it 'does the right thing for noops', ->
-      n null
-      n [], null
-
-    it 'normalizes some regular ops', ->
-      n [{i:'hi'}]
-      n [{i:'hi'}, 1,2,3], [{i:'hi'}]
-      n [[1,2,3, {p:0}], [1,2,3, {d:0}]], [1,2,3, {p:0, d:0}]
-      n [[1,2,3, {p:0}], [1,2,30, {d:0}]], [1,2, [3, {p:0}], [30, {d:0}]]
-      n [[1,2,30, {p:0}], [1,2,3, {d:0}]], [1,2, [3, {d:0}], [30, {p:0}]]
-
-    it 'will let you insert null', ->
-      n [{i:null}]
-
-    it 'normalizes embedded ops when available', ->
-      n [{es:[0, 'hi']}], [{es:['hi']}]
-      n [{et:'text-unicode', e:['hi']}], [{es:['hi']}]
-      n [{et:'text-unicode', e:[0, 'hi']}], [{es:['hi']}]
-      n [{et:'simple', e:{}}]
-      n [{et:'number', e:5}], [{ena:5}]
-      n [{ena:5}]
-
-    it.skip 'normalizes embedded removes', ->
-      n [1, {r:true}, 2, {r:true}], [1, {r:true}]
-      n [{r:true}, 2, {r:true}], [{r:true}]
-
-    it 'throws if the type is missing', ->
-      # Not sure if this is the best behaviour but ... eh.
-      assert.throws -> n [et:'missing', e:{}]
-
-    it 'corrects weird pick and drop ids', ->
-      n [['x', p:1], ['y', d:1]], [['x', p:0], ['y', d:0]]
-
-# ****** Apply ******
-
-  describe 'apply', ->
-    it 'Can set properties', ->
-      apply
-        doc: []
-        op: [0, {i:17}]
-        expect: [17]
-
-      apply
-        doc: {}
-        op: ['x', {i:5}]
-        expect: {x:5}
-
-    it 'can edit the root', ->
-      apply
-        doc: {x:5}
-        op: [r:true]
-        expect: undefined
-
-      apply
-        doc: ''
-        op: [r:true]
-        expect: undefined
-
-      apply
-        doc: 'hi'
-        op: [r:true, i:null]
-        expect: null
-
-      apply
-        doc: 'hi'
-        op: [es:[2, ' there']]
-        expect: 'hi there'
-
-      assert.throws -> type.apply null, [{i:5}]
-
-      apply
-        doc: undefined
-        op: [i:5]
-        expect: 5
-
-      apply
-        doc: {x:5}
-        op: [r:{}, i:[1,2,3]]
-        expect: [1,2,3]
-
-      # TODO: And an edit of the root.
-
-    it 'can move 1', -> apply
-      doc: {x:5}
-      op: [['x', p:0], ['y', d:0]]
-      expect: {y:5}
-
-    it 'can move 2', -> apply
-      doc: [0,1,2]
-      op: [[1, p:0], [2, d:0]]
-      expect: [0,2,1]
-
-    it 'can handle complex list index stuff', -> apply
-      doc: [0,1,2,3,4,5]
-      op: [[1, r:{}, i:11], [2, r:{}, i:12]]
-      expect: [0,11,12,3,4,5]
-
-    it 'correctly handles interspersed descent and edits', -> apply
-      doc: {x: {y: {was:'y'}, was:'x'}}
-      op: [['X',{d:0},'Y',{d:1}], ['x',{p:0},'y',{p:1}]]
-      expect: {X: {Y: {was:'y'}, was:'x'}}
-
-    it 'can edit strings', -> apply
-      doc: "errd"
-      op: [{es:[2,"maghe"]}]
-      expect: "ermagherd"
-
-    it 'can edit numbers', -> apply
-      doc: 5
-      op: [ena:10]
-      expect: 15
-
-    it 'can edit child numbers', -> apply
-      doc: [20]
-      op: [0, ena:-100]
-      expect: [-80]
-
-    it 'can edit subdocuments using an embedded type', -> apply
-      doc: {str:'hai'}
-      op: [{e:{position:2, text:'wai'}, et:'simple'}]
-      expect: {str:'hawaii'}
-
-    it 'applies edits after drops', -> apply
-      doc: {x: "yooo"}
-      op: [['x', p:0], ['y', d:0, es:['sup']]]
-      expect: {y: "supyooo"}
-
-    it 'throws when the op traverses missing items', ->
-      assert.throws -> type.apply [0, 'hi'], [1, p:0, 'x', d:0]
-      assert.throws -> type.apply {}, [p:0, 'a', d:0]
-
-    it 'throws if the type is missing', ->
-      assert.throws -> type.apply {}, [et:'missing', e:{}]
-
-
-  describe 'apply path', ->
-    it 'does not modify path when op is unrelated', ->
-      path ['a', 'b', 'c'], op: null
-      path ['a', 'b', 'c'], op: ['x', i:5]
-      path ['a', 'b', 'c'], op: ['x', r:true]
-      path ['a', 'b', 'c'], op: [['x', p:0], ['y', d:0]]
-      path [1,2,3], op: [2, i:5]
-      path [1,2,3], op: [1, 2, 4, i:5]
-      path [1], op: [1, 2, r:true]
-      path ['x'], op: ['x', 'y', r:true]
-
-    it 'adjusts list indicies', ->
-      path [2], op: [1, i:5], expect: [3]
-      path [2], op: [2, i:5], expect: [3]
-      path [2], op: [1, r:true], expect: [1]
-      path [2], op: [[1, p:0], [3, d:0]], expect: [1]
-      path [2], op: [[1, d:0], [3, p:0]], expect: [3]
-      path [2], op: [[2, d:0], [3, p:0]], expect: [3]
-
-    it 'returns null when the object at the path was removed', ->
-      path ['x'], op: [r:true], expect: null
-      path ['x'], op: ['x', r:true], expect: null
-      path [1], op: [r:true], expect: null
-      path [1], op: [1, r:true], expect: null
-
-    it 'moves the path', ->
-      path ['a', 'z'], op: [['a', p:0], ['y', d:0]], expect: ['y', 'z']
-      path ['a', 'b'], op: [['a', 'b', p:0], ['z', d:0]], expect: ['z']
-      path ['a', 'b'], op: [['a', 'b', 'c', p:0], ['z', d:0]]
-      path [1,2], op: [[1, p:0], [10, d:0]], expect: [10, 2]
-      path [1,2], op: [[1, 2, p:0], [10, d:0]], expect: [10]
-      path [1,2], op: [1, [1, d:0], [2, p:0]], expect: [1, 1]
-      path [1,2], op: [[1, 2, 3, p:0], [10, d:0]]
-
-    it 'handles pick parent and move', ->
-      path ['a', 'b', 'c'], op: [['a', r:true, 'b', p:0], ['x', d:0]], expect: ['x', 'c']
-
-    it 'adjusts indicies under a pick', ->
-      path ['a', 'b', 10], op: [['a', p:0, 'b', 1, r:true], ['x', d:0]], expect: ['x', 'b', 9]
-
-    it.skip 'gen ops', ->
-      # This should do something like:
-      # - Generate a document
-      # - Generate op, a random operation
-      # - Generate a path to somewhere in the document and an edit we can do there -> op2
-      # - Check that transform(op2, op) == op2 at transformPosition(path) or something like that.
-
-    it 'calls transformPosition with embedded string edits if available', ->
-      # For embedded string operations (and other things that have
-      # transformPosition or transformPosition or whatever) we should call that.
-      path ['x','y','z', 1], op: ['x','y','z', es:['abc']], expect: ['x','y','z', 4]
-      path ['x','y','z', 1], op: ['x','y','z', es:['💃']], expect: ['x','y','z', 2]
-      path ['x','y','z'], op: ['x','y','z', es:['💃']], expect: ['x','y','z']
-
-
-# ******* Compose *******
-
-  describe 'compose', ->
-    it 'composes empty ops to nothing', -> compose
-      op1: null
-      op2: null
-      expect: null
-
-    describe 'op1 drop', ->
-      it 'vs remove', -> compose
-        op1: [['x', p:0], ['y', d:0]]
-        op2: ['y', r:true]
-        expect: ['x', r:true]
-
-      it 'vs remove parent', -> compose
-        op1: [['x', p:0], ['y', 0, d:0]]
-        op2: ['y', r:true]
-        expect: [['x', r:true], ['y', r:true]]
-
-      it 'vs remove child', -> compose
-        op1: [['x', p:0], ['y', d:0]]
-        op2: ['y', 'a', r:true]
-        expect: [['x', p:0, 'a', r:true], ['y', d:0]]
-
-      it 'vs remove and pick child', -> compose
-        op1: [['x', p:0], ['y', d:0]]
-        op2: [['y', r:true, 'a', p:0], ['z', d:0]]
-        expect: [['x', r:true, 'a', p:0], ['z', d:0]]
-
-      it 'vs pick', -> compose
-        op1: [['x', p:0], ['y', d:0]]
-        op2: [['y', p:0], ['z', d:0]]
-        expect: [['x', p:0], ['z', d:0]]
-
-      it 'is transformed by op2 picks', -> compose
-        op1: [['x', p:0], ['y', 10, d:0]]
-        op2: ['y', 0, r:true]
-        expect: [['x', p:0], ['y', [0, r:true], [9, d:0]]]
-
-    describe 'op1 insert', ->
-      it 'vs remove', -> compose
-        op1: ['x', i:{a:'hi'}]
-        op2: ['x', r:true]
-        expect: null
-
-      it 'vs remove parent', -> compose
-        op1: ['x', 0, i:{a:'hi'}]
-        op2: ['x', r:true]
-        expect: ['x', r:true]
-
-      it 'vs remove child', -> compose
-        op1: ['x', i:{a:'hi', b:'woo'}]
-        op2: ['x', 'a', r:true]
-        expect: ['x', i:{b:'woo'}]
-
-      it 'vs remove and pick child', -> compose
-        op1: ['x', i:{a:'hi', b:'woo'}]
-        op2: [['x', r:true, 'a', p:0], ['y', d:0]]
-        expect: ['y', i:'hi']
-
-      it 'vs remove an embedded insert', -> compose
-        op1: ['x', i:{}, 'y', i:'hi']
-        op2: ['x', 'y', r:true]
-        expect: ['x', i:{}]
-
-      it 'vs remove from an embedded insert', -> compose
-        op1: ['x', i:{}, 'y', i:[1,2,3]]
-        op2: ['x', 'y', 1, r:true]
-        expect: ['x', i:{}, 'y', i:[1, 3]]
-
-      it 'picks the correct element of an embedded insert', -> compose
-        op1: ['x', i:['a', 'b', 'c'], 1, i:'XX']
-        op2: [['x', 1, p:0], ['y', d:0]]
-        expect: [['x', i:['a', 'b', 'c']], ['y', i:'XX']]
-
-      it 'picks the correct element of an embedded insert 2', -> compose
-        op1: ['x', i:['a', 'b', 'c'], 1, i:'XX']
-        op2: [['x', 3, p:0], ['y', d:0]] # should grab 'c'.
-        expect: [['x', i:['a', 'b'], 1, i:'XX'], ['y', i:'c']]
-
-
-      it 'moves all children', -> compose
-        op1: ['x', i:{}, 'y', i:[1,2,3]]
-        op2: [['x', p:0], ['z', d:0]]
-        expect: ['z', i:{}, 'y', i:[1,2,3]]
-
-      it 'removes all children', -> compose
-        op1: ['x', i:{}, 'y', i:[1,2,3]]
-        op2: ['x', r:true]
-        expect: null
-
-      it 'removes all children when removed at the destination', -> compose
-        op1: [['x', p:0], ['y', d:0, 0, i:'hi']]
-        op2: ['y', r:true]
-        expect: ['x', r:true]
-
-      it 'vs op2 insert', -> compose # Inserts aren't folded together.
-        op1: [i:{}]
-        op2: ['x', i:'hi']
-        expect: [i:{}, 'x', i:'hi']
-
-      it 'vs op2 string edit', -> compose
-        op1: [i:'hi']
-        op2: [es:[2, ' there']]
-        expect: [i:'hi', es:[2, ' there']]
-
-      it 'vs op2 number edit', -> compose
-        op1: [i:10]
-        op2: [ena:20]
-        expect: [i:10, ena:20]
-
-    describe 'op1 edit', ->
-      it 'removes the edit if the edited object is deleted', -> compose
-        op1: ['x', es:['hi']]
-        op2: ['x', r:true]
-        expect: ['x', r:true]
-
-      it 'removes the edit in an embedded insert 1', -> compose
-        op1: ['x', i:'', es:['hi']]
-        op2: ['x', r:true]
-        expect: null
-
-      it 'removes the edit in an embedded insert 2', -> compose
-        op1: ['x', i:[''], 0, es:['hi']]
-        op2: ['x', 0, r:true]
-        expect: ['x', i:[]]
-
-      it 'composes string edits', -> compose
-        op1: [es:['hi']]
-        op2: [es:[2, ' there']]
-        expect: [es:['hi there']]
-
-      it 'composes number edits', -> compose
-        op1: [ena:10]
-        op2: [ena:-8]
-        expect: [ena:2]
-
-      it 'transforms and composes edits', -> compose
-        op1: ['x', es:['hi']]
-        op2: [['x', p:0], ['y', d:0, es:[2, ' there']]]
-        expect: [['x', p:0], ['y', d:0, es:['hi there']]]
-
-      it 'preserves inserts with edits', -> compose
-        op1: ['x', i:'hi']
-        op2: [['x', p:0], ['y', d:0, es:[' there']]]
-        expect: ['y', i:'hi', es:[' there']]
-
-      it 'allows a different edit in the same location', -> compose
-        op1: ['x', es:['hi']]
-        op2: ['x', r:true, i:'yo', es:[2, ' there']]
-        expect: ['x', r:true, i:'yo', es:[2, ' there']]
-
-      it 'throws if the type is missing', ->
-        assert.throws -> type.compose [et:'missing', e:{}], [et:'missing', e:{}]
-
-    describe 'op2 pick', ->
-      it 'gets untransformed by op1 drops', ->
-        op1: [5, i:'hi']
-        op2: [6, r:true]
-        expect: [5, r:true, i:'hi']
-
-    describe 'op1 insert containing a drop', ->
-      it 'vs pick at insert', -> compose
-        op1: [['x', p:0], ['y', i:{}, 'x', d:0]]
-        op2: [['y', p:0], ['z', d:0]]
-        expect: [['x', p:0], ['z', i:{}, 'x', d:0]]
-
-    describe 'fuzzer tests', ->
-      it 'complicated transform of indicies', -> compose
-        op1: [ 0, { p: 0 }, 'x', 2, { d: 0 } ]
-        op2: [ 0, 'x', 0, { r: true } ]
-        expect: [
-          [0, p:0, 'x', 1, d:0],
-          [1, 'x', 0, r:true]
-        ]
-
-    describe 'setnull interaction', ->
-      # Currently failing.
-      it 'reorders items inside a setnull region', -> compose
-        op1: [{i:[]}, [0, {i:'a'}], [1, {i:'b'}]]
-        op2: [[0, {p:0}], [1, {d:0}]]
-        expect: [{i:[]}, [0, {i:'b'}], [1, {i:'a'}]]
-
-      it 'lets a setnull child be moved', -> compose
-        op1: ['list', {i:[]}, 0, {i:'hi'}]
-        op2: [['list', 0, p:0], ['z', d:0]]
-        expect: [['list', {i:[]}], ['z', i:'hi']]
-
-      it 'lets a setnull child get modified', -> compose
-        op1: [{i:[]}, 0, {i:['a']}]
-        op2: [0, 0, {r:'a', i:'b'}]
-        expect: [{i:[]}, 0, {i: []}, 0, {i: 'b'}]
-        #expect: [{i:[]}, 0, {i:['b']}] # Maybe better??
-
-    describe 'regression', ->
-      it 'skips op2 drops when calculating op1 drop index simple', -> compose
-        op1: [[ 0, { p: 0 } ], [ 2, { d: 0 } ]]
-        op2: [[ 0, { p: 0 } ], [ 1, { d: 0 } ]]
-        expect: [ [ 0, { p: 1 } ], [ 1, { p: 0, d: 0 } ], [ 2, { d: 1 } ] ]
-
-      it 'skips op2 drops when calculating op1 drop index complex', -> compose
-        op1: [[0, {p:0, d:1}], [1, p:1], [2, d:0]]
-        op2: [[0, p:0], [1, d:0]]
-        # expect: [[0, {p:1}], [1, {d:0, p:0}], [2, d:1]]
-        expect: [[0, p:1], [1, {p:0, d:0}], [2, d:1]]
-
-      it '3', -> compose
-        op1: [ { i: [ null, [] ] }, 0, { i: '' } ]
-        op2: [ 1, { p: 0 }, 0, { d: 0 } ]
-        # ... it'd be way more consistent to drop the null separately rather than merging it??
-        expect: [ { i: [ [] ] }, [ 0, { i: '' } ], [ 1, 0, { i: null } ] ]
-
-      it '4', -> compose # This one triggered a bug in cursor!
-        op1: [ 0,
-          [ 0, [ 'a', { r: true } ], [ 'b', { d: 0 } ] ],
-          [ 2, { p: 0 } ] ]
-        op2: [ 0, 0, 'c', { i: 'd' } ]
-        expect: [ 0,
-          [ 0, [ 'a', { r: true } ], [ 'b', { d: 0 } ], [ 'c', { i: 'd' } ] ],
-          [ 2, { p: 0 } ]
-        ]
-
-  # *** Old stuff
-  describe 'old compose', ->
-    it 'gloms together unrelated edits', ->
-      compose
-        op1: [['a', p:0], ['b', d:0]]
-        op2: [['x', p:0], ['y', d:0]]
-        expect: [['a', p:0], ['b', d:0], ['x', p:1], ['y', d:1]]
-
-      compose
-        op1: [2, i:'hi']
-        op2: [0, 'x', r:true]
-        expect: [[0, 'x', r:true], [2, i:"hi"]]
-
-    it 'translates drops in objects', -> compose
-      op1: ['x', ['a', p:0], ['b', d:0]] # x.a -> x.b
-      op2: [['x', p:0], ['y', d:0]] # x -> y
-      expect: [['x', {p:0}, 'a', {p:1}], ['y', {d:0}, 'b', {d:1}]] # x.a -> y.b, x -> y
-
-    it 'untranslates picks in objects', -> compose
-      op1: [['x', p:0], ['y', d:0]] # x -> y
-      op2: [['y', 'a', p:0], ['z', d:0]] # y.a -> z
-      expect: [['x',p:0,'a',p:1], ['y',d:0], ['z',d:1]] # x.a -> z, x -> y
-
-    it 'insert gets carried wholesale', -> compose
-      op1: ['x', i:'hi there']
-      op2: [['x', p:0], ['y', d:0]] # x -> y
-      expect: ['y', i:'hi there']
-
-    it 'insert gets edited by the op', -> compose
-      op1: ['x', {i:{a:1, b:2, c:3}}]
-      op2: [['x', 'a', p:0], ['y', d:0]]
-      expect: [['x', {i:{b:2, c:3}}], ['y', i:1]]
-
-    it 'does not merge mutual inserts', -> compose
-      op1: [i:{}]
-      op2: ['x', i:"hi"]
-      expect: [i:{}, 'x', i:'hi']
-
-    # TODO: List nonsense.
-
-    # TODO: Edits.
-
-
-# ****** Transform ******
-
-  describe 'transform', ->
-    describe 'op1 pick', ->
-      it 'vs delete', -> xf
-        op1: [['x', p:0], ['y', d:0]]
-        op2: ['x', r:true]
-        expect: null
-      it 'vs delete parent', -> xf
-        op1: [['x', 'a', p:0], ['y', d:0]]
-        op2: ['x', r:true]
-        expect: null
-      it 'vs delete parent 2', -> xf
-        op1: ['x', ['a', p:0], ['b', d:0]]
-        op2: ['x', r:true]
-        expect: null
-
-      it 'vs pick', -> xf
-        op1: [['x', p:0], ['z', d:0]]
-        op2: [['x', p:0], ['y', d:0]]
-        # Consider adding a conflict for this case.
-        expectLeft: [['y', p:0], ['z', d:0]]
-        expectRight: null
-      it 'vs pick parent', -> xf
-        op1: [['x', 'a', p:0], ['z', d:0]]
-        op2: [['x', p:0], ['y', d:0]]
-        expect: [['y', 'a', p:0], ['z', d:0]]
-
-      it 'vs pick and pick child', -> xf # regression
-        op1: [ # a -> xa, a.c -> xc
-          ['a', p:0, 'c', p:1]
-          ['xa', d:0]
-          ['xc', d:1]
-        ]
-        op2: [['a', p:0], ['b', d:0]] # a -> b
-        expectLeft: [
-          ['b', p:0, 'c', p:1]
-          ['xa', d:0]
-          ['xc', d:1]
-        ]
-        expectRight: [
-          ['b', 'c', p:0]
-          ['xc', d:0]
-        ]
-
-      it 'vs edit', -> xf
-        op1: [['x', p:0], ['z', d:0]]
-        op2: ['x', es:['hi']]
-        expect: [['x', p:0], ['z', d:0]]
-
-      it 'vs delete, drop', -> xf
-        op1: [['x', p:0], ['y', d:0]]
-        op2: [['a', p:0], ['x', r:0, d:0]]
-        expect: null
-
-      it 'vs delete, insert', -> xf
-        op1: [['x', p:0], ['y', d:0]]
-        op2: ['x', r:0, i:5]
-        expect: null
-
-      it 'vs pick, drop to self',
-        -> xf
-          op1: [['x', p:0], ['y', d:0]]
-          op2: [['x', p:0], ['y', d:0]]
-          expect: null
-
-        -> xf
-          op1: [['a', 1, p:0], ['y', d:0]]
-          op2: [['a', 1, p:0], ['y', d:0]]
-          expect: null
-
-      it 'vs pick, drop', -> xf
-        op1: [['x', p:0], ['z', d:0]] # x->z
-        op2: [['a', p:0], ['x', p:1, d:0], ['y', d:1]] # a->x, x->y
-        expectLeft: [['y', p:0], ['z', d:0]]
-        expectRight: null
-
-      it 'vs pick, insert', -> xf
-        op1: [['x', p:0], ['z', d:0]]
-        op2: [['x', p:0, i:5], ['y', d:0]]
-        expectLeft: [['y', p:0], ['z', d:0]]
-        expectRight: null
-
-      it 'vs pick, edit', ->
-        op1: [['x', p:0], ['z', d:0]]
-        op2: [['x', es:['hi'], p:0], ['y', d:0]]
-        expectLeft: [['y', p:0], ['z', d:0]]
-        expectRight: null
-
-    describe 'op1 delete', ->
-      it 'vs delete', -> xf
-        op1: ['x', r:true]
-        op2: ['x', r:true]
-        expect: null
-      it 'vs delete parent', -> xf
-        op1: ['x', 'a', r:true]
-        op2: ['x', r:true]
-        expect: null
-
-      it 'vs pick', -> xf
-        op1: ['x', r:true]
-        op2: [['x', p:0], ['y', d:0]]
-        expect: ['y', r:true]
-      it 'vs pick parent', -> xf
-        op1: ['x', 'a', r:true]
-        op2: [['x', p:0], ['y', d:0]]
-        expect: ['y', 'a', r:true]
-
-      it 'vs pick and drop', -> xf
-        op1: ['x', r:true]
-        op2: [['a', p:0], ['x', d:0, p:1], ['z', d:1]]
-        expect: ['z', r:true]
-
-      it 'vs edit', -> xf
-        op1: ['x', r:true]
-        op2: ['x', es:['hi']]
-        conflict: type: RM_UNEXPECTED_CONTENT
-        expect: ['x', r:true]
-
-      it 'vs move and insert', -> xf
-        op1: [ 'a', 1, { r: true } ]
-        op2: [
-          [ 'a', { p: 0 } ],
-          [ 'b', { d: 0 }, [ 0, { i: 5 } ], [ 1, { i: 5 } ] ]
-        ]
-        expect: ['b', 3, r:true]
-
-      describe 'vs pick child', ->
-        it 'move in', -> xf
-          op1: ['x', r:true]
-          op2: [['a', p:0], ['x', 'y', d:0]]
-          conflict: type: RM_UNEXPECTED_CONTENT
-          expect: ['x', r:true, 'y', r:true] # Also ok if its just x, r:true
-
-        it 'move across', -> xf
-          op1: ['x', r:true] # delete doc.x
-          op2: ['x', ['y', p:0], ['z', d:0]]
-          expect: ['x', r:true]
-
-        it 'move out', -> xf
-          op1: ['x', r:true]
-          op2: [['x', 'y', p:0], ['y', d:0]] # move doc.x.y -> doc.y
-          expect: [['x', r:true], ['y', r:true]] # delete doc.x and doc.y
-
-        it 'multiple out', -> xf
-          op1: ['x', r:true]
-          op2: [['x', 'y', p:0, 'z', p:1], ['y', d:0], ['z', d:1]]
-          expect: [['x', r:true], ['y', r:true], ['z', r:true]]
-
-        it 'chain out', -> xf
-          op1: ['x', r:true]
-          op2: [['x', 'y', p:0], ['y', p:1], ['z', d:0, 'a', d:1]]
-          conflict:
-            type: RM_UNEXPECTED_CONTENT
-            op2: [['y', p:0], ['z', 'a', d:0]] # cMv(['y'], ['z', 'a'])
-          expect: [['x', r:true], ['z', r:true, 'a', r:true]]
-
-        it 'mess', -> xf
-          # yeesh
-          op1: [['x', r:true, 'y', 'z', p:0], ['z', d:0]]
-          op2: [['x', 'y', p:0], ['y', d:0]]
-          expect: [['x', r:true], ['y', r:true, 'z', p:0], ['z', d:0]]
-
-    describe 'op1 drop', ->
-      it 'vs delete parent', -> xf
-        op1: [['x', p:0], ['y', 'a', d:0]]
-        op2: ['y', r:true]
-        conflict: type: RM_UNEXPECTED_CONTENT
-        expect: ['x', r:true]
-
-      it 'vs a cancelled parent', -> xf
-        # This is actually a really complicated case.
-        op1: [['x', 'y', p:0], ['y', p:1], ['z', d:0, 'a', d:1]]
-        op2: ['x', r:true]
-        conflict:
-          type: RM_UNEXPECTED_CONTENT
-          op1: [['y', p:0], ['z', 'a', d:0]] # c1: cMv(['y'], ['z', 'a'])
-        expect: ['y', r:true]
-
-      it 'vs pick parent', -> xf
-        op1: [['x', p:0], ['y', 'a', d:0]]
-        op2: [['y', p:0], ['z', d:0]]
-        expect: [['x', p:0], ['z', 'a', d:0]]
-
-      it 'vs drop', -> xf
-        op1: [['x', p:0], ['z', d:0]]
-        op2: [['y', p:0], ['z', d:0]]
-        conflict: type: DROP_COLLISION
-        expectLeft: [['x', p:0], ['z', r:true, d:0]]
-        expectRight: ['x', r:true]
-
-      it 'vs drop (list)', -> xf
-        op1: [[0, p:0], [4, d:0]]
-        op2: [[5, d:0], [10, p:0]]
-        expectLeft: [[0, p:0], [4, d:0]]
-        expectRight: [[0, p:0], [5, d:0]]
-
-      it 'vs drop (chained)', -> xf
-        op1: [['a', p:1], ['x', p:0], ['z', d:0, 'a', d:1]]
-        op2: [['y', p:0], ['z', d:0]]
-        conflict:
-          type: DROP_COLLISION
-          op1: [['x', p:0], ['z', d:0]] #cMv(['x'], ['z'])
-        expectLeft: [['a', p:0], ['x', p:1], ['z', r:true, d:1, 'a', d:0]]
-        expectRight: [['a', r:true], ['x', r:true]]
-
-      it 'vs insert', -> xf
-        op1: [['x', p:0], ['z', d:0]]
-        op2: ['z', i:5]
-        conflict: type: DROP_COLLISION
-        expectLeft: [['x', p:0], ['z', r:true, d:0]]
-        expectRight: ['x', r:true]
-
-      it 'vs pick (a->b->c vs b->x)', -> xf
-        op1: [['a', p:0], ['b', {p:1, d:0}], ['c', d:1]]
-        op2: [['b', p:0], ['x', d:0]]
-        expectLeft: [['a', p:0], ['b', d:0], ['c', d:1], ['x', p:1]]
-        expectRight: [['a', p:0], ['b', d:0]]
-
-      describe.skip 'vs move inside me', ->
-        # Note: This is *not* blackholeing! The edits are totally fine; we
-        # just need one edit to win.
-        # The current behaviour just nukes both.
-        it 'in objects', -> xf
-          op1: [['x', p:0], ['y', 'a', d:0]]
-          op2: [['x', 'a', d:0], ['y', p:0]]
-          expectLeft: [['x', p:0, 'a', p:1], ['y', d:1, 'x', d:0]]
-          expectRight: null
-
-        it 'in lists', -> xf
-          op1: [0, p:0, 'x', d:0]
-          op2: [[0, 'y', d:0], [1, p:0]]
-          expectLeft: [0, {p:0, d:1}, ['x', d:0], ['y', p:1]]
-          expectRight: null
-
-        it 'multiple', -> xf
-          # a->x.a, b->x.b
-          op1: [['a', p:0], ['b', p:1], ['x', 'a', d:0, 'b', d:1]]
-          op2: [['a', 'x', d:0], ['x', p:0]] # x->a.x
-          expectLeft: [['a', p:0, 'x', p:1], ['b', p:2],
-            ['x', d:1, ['a', d:0], ['b', d:2]]]
-          expectRight: null
-
-    describe 'op1 insert', ->
-      it 'vs delete parent', -> xf
-        op1: ['y', 'a', i:5]
-        op2: ['y', r:true]
-        conflict: type: RM_UNEXPECTED_CONTENT
-        expect: null
-
-      it 'vs pick parent', -> xf
-        op1: ['y', 'a', i:5]
-        op2: [['y', p:0], ['z', d:0]]
-        expect: ['z', 'a', i:5]
-
-      it 'vs drop', -> xf
-        op1: ['z', i:5]
-        op2: [['y', p:0], ['z', d:0]]
-        conflict: type: DROP_COLLISION
-        expectLeft: ['z', r:true, i:5]
-        expectRight: null
-
-      it 'vs insert', -> xf
-        op1: ['z', i:5]
-        op2: ['z', i:10]
-        conflict: type: DROP_COLLISION
-        expectLeft: ['z', r:true, i:5]
-        expectRight: null
-
-      it 'vs insert at list position', -> xf
-        op1: [5, i:'hi']
-        op2: [5, i:'there']
-        expectLeft: [5, i:'hi']
-        expectRight: [6, i:'hi']
-
-      it 'vs identical insert', -> xf
-        op1: ['z', i:5]
-        op2: ['z', i:5]
-        expect: null
-
-      # This is the new setNull for setting up schemas
-      it 'vs embedded inserts', ->
-        xf
-          op1: ['x', i:{}]
-          op2: ['x', i:{}, 'y', i:5]
-          expect: null
-
-        xf
-          op1: ['x', i:{}, 'y', i:5]
-          op2: ['x', i:{}]
-          expect: ['x', 'y', i:5]
-
-        xf
-          op1: ['x', i:{}, 'y', i:5]
-          op2: ['x', i:{}, 'y', i:5]
-          expect: null
-
-        xf
-          op1: ['x', i:{}, 'y', i:5]
-          op2: ['x', i:{}, 'y', i:6]
-          conflict:
-            type: DROP_COLLISION
-            op1: ['x', 'y', i:5]
-            op2: ['x', 'y', i:6]
-          expectLeft: ['x', 'y', r:true, i:5]
-          expectRight: null
-
-      it 'with embedded edits', -> xf
-        op1: [i:'', es:['aaa']]
-        op2: [i:'', es:['bbb']]
-        expectLeft: [es:['aaa']]
-        expectRight: [es:[3, 'aaa']]
-
-    describe 'op1 edit', ->
-      it 'vs delete', -> xf
-        op1: ['x', es:['hi']]
-        op2: ['x', r:true]
-        conflict: type: RM_UNEXPECTED_CONTENT
-        expect: null
-
-      it 'vs delete parent', -> xf
-        op1: ['x', 'y', es:['hi']]
-        op2: ['x', r:true]
-        conflict: type: RM_UNEXPECTED_CONTENT
-        expect: null
-
-      it 'vs pick', -> xf
-        op1: ['x', es:['hi']]
-        op2: [['x', p:0], ['y', d:0]]
-        expect: ['y', es:['hi']]
-
-      it 'vs edit string', -> xf
-        op1: ['x', es:['ab']]
-        op2: ['x', es:['cd']]
-        expectLeft: ['x', es:['ab']]
-        expectRight: ['x', es:[2, 'ab']]
-
-      it 'vs edit number', -> xf
-        op1: [ena:5]
-        op2: [ena:100]
-        expect: [ena:5]
-
-      it 'throws if edit types arent compatible', ->
-        assert.throws -> type.transform [es:[]], [ena:5], 'left'
-
-      it 'vs move and edit', -> xf
-        op1: ['x', es:[1, 'ab']]
-        op2: [['x', p:0], ['y', d:0, es:[d:1, 'cd']]]
-        expectLeft: ['y', es:['ab']]
-        expectRight: ['y', es:[2, 'ab']]
-
-      it 'throws if the type is missing', ->
-        assert.throws -> type.transform [et:'missing', e:{}], [et:'missing', e:{}], 'left'
-
-    describe 'op2 cancel move', ->
-      it 'and insert', -> xf
-        op1: ['x', r:true]
-        op2: [['x', 'a', p:0], ['y', d:0, 'b', i:5]]
-        conflict:
-          type: RM_UNEXPECTED_CONTENT
-          op2: ['y', 'b', i:5]
-        expect: [['x', r:true], ['y', r:true, 'b', r:true]]
-
-      it 'and another move (rm x vs x.a -> y, q -> y.b)', -> xf
-        op1: ['x', r:true]
-        op2: [['q', p:1], ['x', 'a', p:0], ['y', d:0, 'b', d:1]]
-        conflict:
-          type: RM_UNEXPECTED_CONTENT
-          op2: [['q', p:0], ['y', 'b', d:0]]
-        expect: [['x', r:true], ['y', r:true, 'b', r:true]]
-
-    describe 'op2 list move an op1 drop', ->
-      it 'vs op1 remove', -> xf
-        op1: [[0, r:true, 'a', i:'hi'], [5, r:true]]
-        op2: [[1, p:0], [4, d:0]]
-        expect: [[0, r:true], [3, 'a', i:'hi'], [5, r:true]]
-
-      it 'vs op1 remove 2', -> xf
-        op1: [[0, r:true, 'a', i:'hi'], [1, r:true], [2, r:true]]
-        op2: [[3, p:0], [4, d:0]]
-        expect: [[0, r:true], [1, r:true, 'a', i:'hi'], [2, r:true]]
-
-      it 'vs op1 insert before', -> xf
-        op1: [[0, i:'a'], [1, i:'b'], [2, 'a', i:'hi']]
-        op2: [[0, p:0], [1, d:0]]
-        expect: [[0, i:'a'], [1, i:'b'], [3, 'a', i:'hi']]
-
-
-      it 'vs op1 insert before and replace', -> xf
-        op1: [[0, i:'xx', 'a', r:true], [1, 'a', i:'hi']]
-        op2: [[0, p:0], [3, d:0]]
-        expect: [[0, i:'xx'], [3, 'a', r:true], [4, 'a', i:'hi']]
-
-
-    describe 'list', ->
-      describe 'drop', ->
-        it 'transforms by p1 drops', -> xf
-          op1: [[5, i:5], [10, i:10]]
-          op2: [9, i:9]
-          expectLeft: [[5, i:5], [10, i:10]]
-          expectRight: [[5, i:5], [11, i:10]]
-
-        it 'transforms by p1 picks'
-        it 'transforms by p2 picks'
-        it 'transforms by p2 drops'
-
-  describe 'conflicts', ->
-    describe 'drop into remove / rm unexpected', ->
-      # xfConflict does both xf(op1, op2, left) and xf(op2, op1, right), and
-      # uses invConflict. So this also tests RM_UNEXPECTED_CONTENT with each
-      # test case.
-      it 'errors if you insert', -> xf
-        op1: ['a', 'b', i:5]
-        op2: ['a', r:true]
-        conflict: type: RM_UNEXPECTED_CONTENT
-        expect: null
-
-      it 'errors if you drop', -> xf
-        op1: [['a', p:0], ['x', 'b', d:0]]
-        op2: ['x', r:true]
-        conflict: type: RM_UNEXPECTED_CONTENT
-        expect: ['a', r:true]
-
-      it 'errors if you rm then insert in a child', -> xf
-        op1: ['a', 'b', r:true, i:5]
-        op2: ['a', r:true]
-        conflict:
-          type: RM_UNEXPECTED_CONTENT
-          op1: ['a', 'b', i:5]
-        expect: null
-
-      it 'errors if the object is replaced', -> xf
-        op1: ['a', 'b', i:5]
-        op2: ['a', r:true, i:10]
-        conflict:
-          type: RM_UNEXPECTED_CONTENT
-          op2: ['a', r:true]
-        expect: null
-
-      it 'handles a delete of the source parent by op2', -> xf
-        op1: [['a', p:0], ['b', 'b', d:0]]
-        op2: [['a', p:0], ['b', r:true, 'c', d:0]]
-        conflictLeft:
-          type: RM_UNEXPECTED_CONTENT
-          op2: ['b', r:true]
-        expectLeft: ['b', 'c', r:true]
-        expectRight: null
-
-      it.skip 'returns symmetric errors when both ops delete the other', -> xf
-        # The problem here is that there's two conflicts we want to return.
-        # Which one should be returned first? It'd be nice for the order of
-        # conflict returning to be symmetric - that is, if we know multiple
-        # conflicts happen, order them based on left/right. But I haven't done
-        # that, so we get different conflicts out of this in a first pass.
-        op1: [ [ 'x', { r: true } ], [ 'y', 'a', { i: {} } ] ]
-        op2: [ [ 'x', 'a', { i: {} } ], [ 'y', { r: true } ] ]
-        conflict: type: RM_UNEXPECTED_CONTENT
-        expect: ['x', r:true]
-
-    describe 'overlapping drop', ->
-      it 'errors if two ops insert different content into the same place in an object', -> xf
-        op1: ['x', i:'hi']
-        op2: ['x', i:'yo']
-        conflict: type: DROP_COLLISION
-        expectLeft: ['x', r:true, i:'hi']
-        expectRight: null
-
-      it 'does not conflict if inserts are identical', -> xf
-        op1: ['x', i:'hi']
-        op2: ['x', i:'hi']
-        expectLeft: null
-        expectRight: null
-
-      it 'does not conflict if the two operations make identical moves', -> xf
-        op1: [['a', p:0], ['x', d:0]]
-        op2: [['a', p:0], ['x', d:0]]
-        expect: null # ??? Also ok for left: ['x', p:0, d:0]
-
-      it 'does not conflict if inserts are into a list', -> xf
-        op1: [1, i:'hi']
-        op2: [1, i:'yo']
-        expectLeft: [1, i:'hi']
-        expectRight: [2, i:'hi']
-
-      it 'errors if the inserts are at the root', -> xf
-        op1: [i:1]
-        op2: [i:2]
-        conflict: type: DROP_COLLISION
-        expectLeft: [r:true, i:1]
-        expectRight: null
-
-      it 'errors with insert vs drop', -> xf
-        op1: ['x', i:'hi']
-        op2: [['a', p:0], ['x', d:0]]
-        # ????
-        conflict: type: DROP_COLLISION
-        expectLeft: ['x', r:true, i:'hi']
-        expectRight: null
-
-      it 'errors with drop vs insert', -> xf
-        op1: [['a', p:0], ['x', d:0]]
-        op2: ['x', i:'hi']
-        conflict: type: DROP_COLLISION
-        expectLeft: [['a', p:0], ['x', r:true, d:0]]
-        expectRight: ['a', r:true]
-
-      it 'errors with drop vs drop', -> xf
-        op1: [['a', p:0], ['x', d:0]]
-        op2: [['b', p:0], ['x', d:0]]
-        conflict: type: DROP_COLLISION
-        expectLeft: [['a', p:0], ['x', r:true, d:0]]
-        expectRight: ['a', r:true]
-
-      it 'errors if the two sides insert in the vacuum', -> xf
-        op1: [['a', p:0], ['b', d:0], ['c', i:5]]
-        op2: [['a', p:0], ['b', i:6], ['c', d:0]]
-        conflictLeft:
-          type: DROP_COLLISION
-          op1: [['a', p:0], ['b', d:0]]
-          op2: ['b', i:6]
-        expectLeft: [['b', r:true, d:0], ['c', p:0, i:5]]
-        conflictRight:
-          type: DROP_COLLISION
-          op1: ['c', i:5]
-          op2: [['a', p:0], ['c', d:0]]
-        expectRight: null
-
-
-    describe 'discarded edit', ->
-      it 'edit removed directly', -> xf
-        op1: ['a', es:[]]
-        op2: ['a', r:true]
-        conflict: type: RM_UNEXPECTED_CONTENT
-        expect: null
-
-      it 'edit inside new content throws RM_UNEXPECTED_CONTENT', -> xf
-        op1: ['a', 'b', i: 'hi', es:[]]
-        op2: ['a', r:true]
-        conflict:
-          type: RM_UNEXPECTED_CONTENT
-          op1: ['a', 'b', i:'hi']
-        expect: null
-
-    describe 'blackhole', ->
-      it 'detects and errors', -> xf
-        op1: [['x', p:0], ['y', 'a', d:0]]
-        op2: [['x', 'a', d:0], ['y', p:0]]
-        conflict: type: BLACKHOLE
-        expect: ['x', r:true, 'a', r:true] # Also equivalent: ['x', r:true]
-
-      it 'blackhole logic does not apply when op2 removes parent', -> xf
-        # TODO: Although you wouldn't know it, since this result is very similar.
-        op1: [['x', p:0], ['y', 'xx', 'a', d:0]]
-        op2: [['x', 'a', d:0], ['y', p:0, 'xx', r:true]]
-        conflict:
-          type: RM_UNEXPECTED_CONTENT
-          op2: ['y', 'xx', r:true]
-        expect: ['x', r:true, 'a', r:true] # Also ok: ['x', r:true]
-
-      it 'blackhole logic still applies when op2 inserts', -> xf
-        op1: [['x', p:0], ['y', 'a', i:{}, 'b', d:0]]
-        op2: [['x', 'a', i:{}, 'b', d:0], ['y', p:0]]
-        conflict:
-          type: BLACKHOLE
-          op1: [['x', p:0], ['y', 'a', 'b', d:0]]
-          op2: [['x', 'a', 'b', d:0], ['y', p:0]]
-        expect: ['x', r:true, 'a', r:true, 'b', r:true]
-
-      it 'blackholes items in lists correctly', -> xf
-        op1: [1, p:0, 'a', d:0]
-        op2: [[1, 'b', d:0], [2, p:0]]
-        conflict: type: BLACKHOLE
-        expect: [1, r:true, 'b', r:true]
-
-      it 'blackholes items despite scrambled pick and drop slots', -> xf
-        op1: [ [ 'a', { p: 1, d: 1 } ], [ 'x', { p: 0 } ], [ 'y', 'a', { d: 0 } ] ]
-        op2: [ [ 'x', 'a', { d: 0 } ], [ 'y', { p: 0 } ] ]
-        conflict:
-          type: BLACKHOLE
-          op1: [ [ 'x', { p: 0 } ], [ 'y', 'a', { d: 0 } ] ]
-        expect: [['a', p:0, d:0], ['x', r:true, 'a', r:true]]
-
-      it 'handles chained blackholes', -> xf
-        op1: [ [ 'a', { p: 0 } ], # a->b.b, c->d.d
-          [ 'b', 'b', { d: 0 } ],
-          [ 'c', { p: 1 } ],
-          [ 'd', 'd', { d: 1 } ]
-        ]
-        op2: [ [ 'a', 'a', { d: 1 } ], # b->c.c, d->a.a
-          [ 'b', { p: 0 } ],
-          [ 'c', 'c', { d: 0 } ],
-          [ 'd', { p: 1 } ]
-        ]
-        conflict: type: BLACKHOLE
-          # c1: cMv(['a'], ['b', 'b'])
-          # c2: cMv(['b'], ['c', 'c'])
-        expect: [['a', r:true, 'a', r:true], ['c', r:true, 'c', r:true]]
-
-      it 'creates conflict return values with valid slot ids', -> xf
-        op1: [['a', p:0], ['b', d:0], ['x', p:1], ['y', 'a', d:1]]
-        op2: [['x', 'a', d:0], ['y', p:0]]
-        conflict:
-          type: BLACKHOLE
-          op1: [['x', p:0], ['y', 'a', d:0]]
-        expect: [['a', p:0], ['b', d:0], ['x', r:true, 'a', r:true]]
-
-
-  describe 'transform-old', ->
-    it 'foo', ->
-      xf
-        op1: [
-          ['x', ['a', {p:0}], ['b', {d:0}]],
-          ['y', ['a', {p:1}], ['b', {d:1}]]
-        ]
-        op2: ['x', {r:true}]
-        expect: ['y', ['a', {p:0}], ['b', {d:0}]]
-
-    # it 'hard', ->
-    #   op1: ['x', [1, r:true], [2, r:true, es:['hi']]] # Edit at index 4 originally.
-    #   # move the edited string to .y[4] which
-    #   op2: [['x', 4, p:0], ['y', [2, r:true], [4, d:0]]]
-    #   expect:
-
-    describe 'object edits', ->
-      it 'can reparent with some extra junk', -> xf
-        op1: [['x', p:0], ['y', d:0]]
-        op2: [
-          ['_a', d:1]
-          ['_x', d:0]
-          ['x', p:0, 'a', p:1]
-        ]
-        expectLeft: [['_x', p:0], ['y', d:0]]
-        expectRight: null # the object was moved fair and square.
-
-    describe 'deletes', ->
-
-      it.skip 'delete parent of a move', -> xf
-        # The current logic of transform actually just burns everything (in a
-        # consistant way of course). I'm not sure if this is better or worse -
-        # basically we'd be saying that if a move could end up in one of two places,
-        # put it in the place where it won't be killed forever. But that introduces new
-        # complexity, so I'm going to skip this for now.
-
-        # x.a -> a, delete x
-        op1: [['x', r:true, 'a', p:0], ['z', d:0]]
-        # x.a -> x.b.
-        op2: ['x', ['a', p:0], ['b', d:0]]
-        expect: [['x', r:true, 'b', p:0], ['z', d:0]] # TODO: It would be better to do this in both cases.
-        #expectRight: ['x', r:true]
-
-      it 'awful delete nonsense', ->
-        xf
-          op1: [['x', r:true], ['y', i:'hi']] # delete doc.x, insert doc.y
-          op2: [['x', 'a', p:0], ['y', d:0]] # move doc.x.a -> doc.y
-          expect: [['x', r:true], ['y', r:true, i:'hi']] # del doc.x and doc.y, insert doc.y
-
-        xf
-          op1: [['x', 'a', p:0], ['y', d:0]] # x.a -> y
-          op2: [['x', r:true], ['y', i:'hi']] # delete x, ins y
-          expect: null
-
-        xf
-          op1: [10, r:true]
-          op2: [[5, d:0], [10, 1, p:0]]
-          expect: [[5, r:true], [11, r:true]]
-        # And how do those indexes interact with pick / drop operations??
-
-
-    describe 'swap', ->
-      swap = [
-        ['a', p:0, 'b', p:1]
-        ['b', d:1, 'a', d:0]
-      ]
-
-      it 'noop vs swap', -> xf
-        op1: null
-        op2: swap
-        expect: null
-
-      it 'can swap two edits', -> xf
-        op1: ['a', es:['a edit'], 'b', es:['b edit']]
-        op2: swap
-        expect: ['b', es:['b edit'], 'a', es:['a edit']]
-
-    describe 'lists', ->
-      it 'can rewrite simple list indexes', ->
-        xf
-          op1: [10, es:['edit']]
-          op2: [0, i:'oh hi']
-          expect: [11, es:['edit']]
-
-        xf
-          op1: [10, r:true]
-          op2: [0, i:'oh hi']
-          expect: [11, r:true]
-
-        xf
-          op1: [10, i:{}]
-          op2: [0, i:'oh hi']
-          expect: [11, i:{}]
-
-      it 'can change the root from an object to a list', -> xf
-        op1: ['a', es:['hi']]
-        op2: [{i:[], r:true}, [0, d:0], ['a', p:0]]
-        expect: [0, es:['hi']]
-
-      it 'can handle adjacent drops', -> xf
-        op1: [[11, i:1], [12, i:2], [13, i:3]]
-        op2: [0, r:true]
-        expect: [[10, i:1], [11, i:2], [12, i:3]]
-
-      it 'fixes drop indexes correctly 1', -> xf
-        op1: [[0, r:true], [1, i:'hi']]
-        op2: [1, r:true]
-        expect: [0, r:true, i:'hi']
-
-      it 'list drop vs delete uses the correct result index', ->
-        xf
-          op1: [2, i:'hi']
-          op2: [2, r:true]
-          expect: [2, i:'hi']
-
-        xf
-          op1: [3, i:'hi']
-          op2: [2, r:true]
-          expect: [2, i:'hi']
-
-      it 'list drop vs drop uses the correct result index', -> xf
-        op1: [2, i:'hi']
-        op2: [2, i:'other']
-        expectLeft: [2, i:'hi']
-        expectRight: [3, i:'hi']
-
-      it 'list drop vs delete and drop', ->
-        xf
-          op1: [2, i:'hi']
-          op2: [2, r:true, i:'other']
-          expectLeft: [2, i:'hi']
-          expectRight: [3, i:'hi']
-
-        xf
-          op1: [3, i:'hi']
-          op2: [[2, r:true], [3, i:'other']]
-          expect: [2, i:'hi']
-
-        xf
-          op1: [4, i:'hi']
-          op2: [[2, r:true], [3, i:'other']]
-          expectLeft: [3, i:'hi']
-          expectRight: [4, i:'hi']
-
-      it 'list delete vs drop', ->
-        xf
-          op1: [1, r:true]
-          op2: [2, i:'hi']
-          expect: [1, r:true]
-
-        xf
-          op1: [2, r:true]
-          op2: [2, i:'hi']
-          expect: [3, r:true]
-
-        xf
-          op1: [3, r:true]
-          op2: [2, i:'hi']
-          expect: [4, r:true]
-
-      it 'list delete vs delete', ->
-        xf
-          op1: [1, r:true]
-          op2: [1, r:true]
-          expect: null # It was already deleted.
-
-      it 'fixes drop indexes correctly 2', -> xf
-        op1: [[0, r:true], [1, i:'hi']]
-        op2: [2, r:true] # Shouldn't affect the op.
-        expect: [[0, r:true], [1, i:'hi']]
-
-      it 'insert vs delete parent', -> xf
-        op1: [2, 'x', i:'hi']
-        op2: [2, r:true]
-        conflict: type: RM_UNEXPECTED_CONTENT
-        expect: null
-
-      it 'transforms against inserts in my own list', ->
-        xf #[0,1,2,3] -> [a,0,b,1,2,3...]
-          op1: [[0, i:'a'], [2, i:'b']]
-          op2: [1, r:true]
-          expect: [[0, i:'a'], [2, i:'b']]
-
-      it 'vs cancelled op2 drop', -> xf
-        doc: {x:{a:'x.a'}, y:['a','b','c']}
-        op1: [['x', r:true], ['y', 3, i:5]]
-        op2: [['x', 'a', p:0], ['y', 2, d:0]]
-        expect: [['x', r:true], ['y', [2, r:true], [3, i:5]]]
-
-      it 'vs cancelled op1 drop', -> xf
-        op1: [['x', p:0], ['y', [3, d:0], [4, i:5]]]
-        op2: ['x', r:true]
-        expect: ['y', 3, i:5]
-
-      it 'vs cancelled op1 pick', -> xf
-        doc: Array.from 'abcdefg'
-        op1: [[1, p:0], [4, r:true, i:4], [6, d:0]]
-        op2: [1, r:true]
-        expect: [[3, r:true], [4, i:4]]
-
-      it 'xxxxx 1', -> diamond # TODO Regression.
-        doc: Array.from('abcdef')
-        op1: [[1, {p:0, i:'AAA'}], [3, {i:'BBB'}], [5, {d:0}]]
-        op2: [1, {r:true}]
-
-      it 'xxxxx 2', -> diamond
-        doc: Array.from('abcdef')
-        op1: [[1, {p:0, i:'AAA'}], [3, {d:0}], [5, {i:'CCC'}]]
-        op2: [1, {r:true}]
-
-
-    describe 'edit', ->
-      it 'transforms edits by one another', -> xf
-        op1: [1, es:[2, 'hi']]
-        op2: [1, es:['yo']]
-        expect: [1, es:[4, 'hi']]
-
-      it 'copies in ops otherwise', -> xf
-        op1: ['x', {e:{position:2, text:'wai'}, et:'simple'}]
-        op2: ['y', r:true]
-        expect: ['x', {e:{position:2, text:'wai'}, et:'simple'}]
-
-      it 'allows edits at the root', -> xf
-        op1: [{e:{position:2, text:'wai'}, et:'simple'}]
-        op2: [{e:{position:0, text:'omg'}, et:'simple'}]
-        expect: [{e:{position:5, text:'wai'}, et:'simple'}]
-
-      it 'applies edits in the right order', -> xf
-        # Edits happen *after* the drop phase.
-        op1: [1, es:[2, 'hi']]
-        op2: [[1, i:{}], [2, es:['yo']]]
-        expect: [2, es:[4, 'hi']]
-
-      it 'an edit on a deleted object goes away', -> xf
-        op1: [1, es:[2, 'hi']]
-        op2: [1, r:"yo"]
-        conflict:
-          type: RM_UNEXPECTED_CONTENT
-          op2: [1, r:true] # .... It'd be better if this copied the remove.
-        expect: null
-
-      # TODO Numbers
-
-
-# ***** Test cases found by the fuzzer which have caused issues
-  describe 'fuzzer tests', ->
-    it 'asdf', -> apply
-      doc: { the: '', Twas: 'the' }
-      op: [ 'the', { es: [] } ]
-      expect: { the: '', Twas: 'the' }
-
-    it 'does not duplicate list items from edits', -> apply
-      doc: ['eyes']
-      op: [ 0, { es: [] } ]
-      expect: ['eyes']
-
-    it 'will edit the root document', -> apply
-      doc: ''
-      op: [es:[]]
-      expect: ''
-
-    # ------ These have nothing to do with apply. TODO: Move them out of this grouping.
-
-    it 'diamond', ->
-      # TODO: Do this for all combinations.
-      diamond
-        doc: Array.from 'abcde'
-        op1: [ [ 0, { p: 0 } ], [ 1, { d: 0 } ] ]
-        op2: [ [ 0, { p: 0 } ], [ 4, { d: 0 } ] ]
-
-    it 'shuffles lists correctly', -> xf
-      op1: [ [ 0, { p: 0 } ], [ 1, { d: 0 } ] ]
-      op2: [ [ 0, { p: 0 } ], [ 10, { d: 0 } ] ]
-      expectLeft: [ [ 1, { d: 0 } ], [ 10, { p: 0 } ] ]
-      expectRight: null
-
-    it 'inserts before edits', ->
-      xf
-        op1: [0, 'x', i:5]
-        op2: [0, i:35]
-        expect: [1, 'x', i:5]
-
-      xf
-        op1: [0, es:[]]
-        op2: [0, i:35]
-        expect: [1, es:[]]
-
-    it 'duplicates become noops in a list',
-      -> xf
-        op1: [0,{"p":0,"d":0}]
-        op2: [0,{"p":0,"d":0}]
-        expectLeft: [0,{"p":0,"d":0}] # This is a bit weird.
-        expectRight: null
-
-      -> xf
-        op1: [0, r:true, i:'a']
-        op2: [0, i:'b']
-        expectLeft: [[0, i:'a'], [1, r:true]]
-        expectRight: [1, r:true, i:'a']
-
-      -> xf
-        op1: [0, r:true, i:5]
-        op2: [0, r:true]
-        expect: [0, i:5]
-
-    it 'p1 pick descends correctly', ->
-      xf
-        op1: [2, r:true, 1, es:['hi']]
-        op2: [3, 1, r:true]
-        conflict:
-          type: RM_UNEXPECTED_CONTENT
-          op1: [2, 1, es:['hi']]
-        expect: [2, r:true]
-
-      xf
-        op1: [[2, r:true, 1, es:['hi']], [3, 1, r:true]]
-        op2: [3, 2, r:true]
-        conflict:
-          type: RM_UNEXPECTED_CONTENT
-          op1: [2, 1, es:['hi']]
-        expect: [[2, r:true], [3, 1, r:true]]
-
-    it 'transforms picks correctly', -> xf
-      op1: [1, 1, r:true]
-      op2: [0, p:0, d:0]
-      expect: [1, 1, r:true]
-
-    it 'pick & drop vs insert after the picked item', -> xf
-      op1: [0, p:0,d:0] # Remove / insert works the same.
-      op2: [1, i:"hi"]
-      expectLeft: [0, p:0,d:0]
-      expectRight: [[0, p:0], [1, d:0]]
-
-    it 'pick same item vs shuffle list', -> xf
-      op1: [1, ['x', p:0], ['y', d:0]]
-      op2: [1, {d:0}, 'x', {p:0}]
-      expectLeft: [1, p:0, 'y', d:0]
-      expectRight: null
-
-    it 'remove the same item in a list', -> xf
-      op1: [ 0, { r: true } ]
-      op2: [ 0, { r: true } ]
-      expect: null
-
-    it 'rm vs hold item', -> xf
-      op1: [ 0, { r: true } ]
-      op2: [ 0, { p: 0, d: 0 } ]
-      expect: [ 0, { r: true } ]
-
-    it 'moves child elements correctly', -> xf
-      doc: ['a', [0,10,20], 'c']
-      op1: [ 1, 0, { p: 0, d: 0 } ]
-      op2: [ [ 1, { d: 0 } ], [ 2, { p: 0 } ] ]
-      expect: [ 2, 0, { d:0, p:0 } ]
-
-    it 'moves list indexes', -> xf
-      doc: [[], 'b', 'c']
-      op1: [ [ 0, 'hi', { d: 0 } ], [ 1, { p: 0 } ] ]
-      op2: [ [ 0, { p: 0 } ], [ 20, { d: 0 } ] ]
-      expect: [[0, p:0], [19, 'hi', d:0]]
-
-    it 'insert empty string vs insert null', -> xf
-      doc: undefined
-      op1: [i:'hi']
-      op2: [i:null]
-      conflict: type: DROP_COLLISION
-      expectLeft: [r:true, i:'hi']
-      expectRight: null
-
-    it 'move vs emplace', -> xf
-      doc: ['a', 'b']
-      op1: [[0, p:0], [1, d:0]]
-      op2: [1, {p:0, d:0}]
-      expectLeft: [0, {p:0, d:0}]
-      expectRight: [[0, p:0], [1, d:0]]
-
-    it 'rm chases a subdocument that was moved out', -> xf
-      doc: [ [ 'aaa' ] ]
-      op1: [ 0, { r: true } ]
-      op2: [ 0, { d: 0 }, 0, { p: 0 } ] # Valid because lists.
-      expect: [[0, r:true], [1, r:true]]
-
-    it 'colliding drops', -> xf
-      doc: [ 'a', 'b', {} ]
-      op1: [[0, p:0], [1, 'x', d:0]] # -> ['b', x:'a']
-      op2: [1, p:0, 'x', d:0] # -> ['a', x:'b']
-      conflict: type: DROP_COLLISION
-      expectLeft: [[0, p:0, 'x', d:0], [1, 'x', r:true]]
-      expectRight: [0, r:true]
-
-    it 'transform crash', -> xf
-      op1: [ [ 'the', { r: true, d: 0 } ], [ 'whiffling', { p: 0 } ] ]
-      op2: [ 'the', { p: 0, d: 0 } ]
-      expect: [ [ 'the', { d: 0, r: true } ], [ 'whiffling', { p: 0 } ] ]
-
-    it 'transforms drops when the parent is moved by a remove', -> xf
-      op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]]
-      op2: ['a', 0, {r:1}]
-      expect: [['a', {p:0}], ['b', {d:0}, 0, {i:2}]]
-
-    it 'transforms drops when the parent is moved by a drop', -> xf
-      op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]]
-      op2: ['a', 0, {i:1}]
-      expect: [['a', {p:0}], ['b', {d:0}, 2, {i:2}]]
-
-    it 'transforms conflicting drops obfuscated by a move', -> xf
-      op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]]
-      op2: ['a', 1, {i:1}]
-      expectLeft: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]]
-      expectRight: [['a', {p:0}], ['b', {d:0}, 2, {i:2}]]
-
-    it 'transforms edits when the parent is moved', -> xf
-      op1: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 1, 'xxx' ] } ] ]
-      op2: [ 'x', { es: [ d: 1, 'Z' ] } ]
-      expectLeft: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 'xxx' ] } ] ]
-      expectRight: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 1, 'xxx' ] } ] ]
-
-    it 'xf lots', -> xf
-      op1: [['a', p:0], ['b', d:0, es:['hi']]]
-      op2: [['a', p:0], ['c', d:0]]
-      expectLeft: [['b', d:0, es:['hi']], ['c', p:0]]
-      expectRight: ['c', es:['hi']]
-
-    it 'inserts are moved back by the other op', -> xf
-      op1: [['a', p:0], ['b', d:0, 'x', i:'hi']]
-      op2: [['a', p:0], ['c', d:0]]
-      expectLeft: [['b', d:0, 'x', i:'hi'], ['c', p:0]]
-      expectRight: ['c', 'x', i:'hi']
-
-    it 'more awful edit moves', -> xf
-      op1: [['a', p:0], ['c', d:0, 'x', es:[]]]
-      op2: ['a', ['b', d:0], ['x', p:0]]
-      expect: [['a', p:0], ['c', d:0, 'b', es:[]]]
-
-    it 'inserts null', -> xf
-      op1: [ 'x', 'a', { i: null } ]
-      op2: [ [ 'x', { p: 0 } ], [ 'y', { d: 0 } ] ]
-      expect: [ 'y', 'a', { i: null } ]
-
-    it 'preserves local insert if both sides delete', -> xf
-      op1: [ { i: {}, r: true }, 'x', { i: 'yo' } ]
-      op2: [ { r: true } ]
-      expect: [ { i: {} }, 'x', { i: 'yo' } ]
-
-    it 'handles insert/delete vs move', -> xf
-      op1: [ 'a', { i: {}, r: true }, 'x', { i: 'yo' } ]
-      op2: [ [ 'a', { p: 0 } ], [ 'b', { d: 0 } ] ]
-      expect: [ [ 'a', { i: {} }, 'x', { i: 'yo' } ], [ 'b', { r: true }, ] ]
-
-    it 'insert pushes edit target', -> xf
-      op1: [[ 0, { i: "yo" } ], [ 1, 'a', { es: [] }]]
-      op2: [0, [ 'a', { p: 0 } ], [ 'b', { d: 0 } ]]
-      expect: [[0, { i: 'yo' }], [1, 'b', { es: [] }]]
-
-    it 'composes simple regression', ->
-      compose
-        op1: [ 0, { p: 0, d: 0 } ]
-        op2: [ { r: true } ]
-        expect: [ { r: true }, 0, { r: true } ]
-
-      compose
-        op1: [ 'a', 1, { r: true } ]
-        op2: [ 'a', { r: true } ]
-        expect: [ 'a', { r: true }, 1, { r: true } ]
-
-    it 'ignores op2 inserts for index position after op1 insert', -> xf
-      op1: [ { r:true, i: [] }, 0, { i: '' } ]
-      op2: [ 0, { i: 0 } ],
-      conflict:
-        type: RM_UNEXPECTED_CONTENT
-        op1: [r:true]
-      expect: [ { r: true, i: [] }, 0, { r:true, i: '' } ]
-
-    it 'edit moved inside a removed area should be removed', -> xf
-      op1: [[ 0, { r: true } ], [ 2, { es: [ ] } ]]
-      op2: [[ 0, 'x', { d: 0 } ], [ 3, { p: 0 } ]]
-      conflict:
-        type: RM_UNEXPECTED_CONTENT
-        op1: [0, r:true]
-      expect: [ 0, { r: true }, 'x', {r:true} ]
-
-    it 'advances indexes correctly with mixed numbers', -> xf
-      op1: [ [ 'x', [ 0, { p: 0 } ], [ 1, { d: 1 } ] ], [ 'y', { p: 1 } ], [ 'zzz', { d: 0 } ] ]
-      op2: [ [ 'x', 2, { i: 'hi' } ], [ 'y', { p: 0 } ], [ 'z', { d: 0 } ] ]
-      expectLeft: [ [ 'x', [ 0, { p: 1 } ], [ 1, { d: 0 } ] ], [ 'z', { p: 0 } ], [ 'zzz', { d: 1 } ] ]
-      expectRight: [ [ 'x', 0, { p: 0 } ], [ 'zzz', { d: 0 } ] ]
-
-    it 'handles index positions past cancelled drops 1', -> xf
-      op1: [ 0, { r: true, i: [ '' ] } ],
-      op2: [ [ 0, { p: 0, d: 0 } ], [ 1, { i: 23 } ] ]
-      expectLeft: [ 0, { r: true, i: [ '' ] } ]
-      expectRight: [ [ 0, { r: true } ], [ 1, { i: [ '' ] } ] ]
-
-    it 'handles index positions past cancelled drops 2', -> xf
-      # This looks more complicated, but its a simpler version of the above test.
-      op1: [ [ 'a', { r: true } ], [ 'b', 0, { i: 'hi' } ] ]
-      op2: [ [ 'a', { p: 0 } ], [ 'b', [ 0, { d: 0 } ], [ 1, { i: 'yo' } ] ] ]
-      expectLeft: [ 'b', 0, { i: 'hi', r: true } ]
-      expectRight: [ 'b', [ 0, { r: true } ], [ 1, { i: 'hi' } ] ]
-
-    it 'calculates removed drop indexes correctly', -> xf
-      op1: [ [ 0, { i: 'hi', p: 0 } ], [ 1, 1, { d: 0 } ], [ 2, { r: true } ] ]
-      op2: [ [ 0, { i: 'yo', p: 0 } ], [ 1, 1, { d: 0 } ] ]
-      expectLeft: [ [ 0, { i: 'hi' } ], [ 1, 1, { p: 0 } ], [ 2, { r: true }, 1, { d: 0 } ] ]
-      expectRight: [ [ 1, { i: 'hi' } ], [ 2, { r: true } ] ]
-
-    it 'removed drop indexes calc regression', -> xf
-      op1: [ [ 1, { p: 0 }, 'burbled', { d: 0 } ], [ 3, { r: true } ] ]
-      op2: [ [ 0, { i: 'to', r: true } ], [ 1, { p: 1 }, [ 'its', { d: 0 } ], [ 'thought', { d: 1 } ] ], [ 3, { p: 0 } ] ]
-      expectLeft: [ 1, [ 'burbled', { d: 0 } ], [ 'its', { r: true } ], [ 'thought', { p: 0 } ] ]
-      expectRight: [ 1, 'its', { r: true } ]
-
-    it 'removed drop indexes tele to op1 pick', -> xf
-      op1: [ 'a', 0, [ 0, { es: [] } ], [ 2, { r: true } ] ]
-      op2: [ [ 'a', { p: 0 }, 0, 0, { p: 1 } ], [ 'b', { d: 0 }, 0, 1, 0, { d: 1 } ] ]
-      conflict:
-        type: RM_UNEXPECTED_CONTENT
-        op1: ['a', 0, 2, r:true]
-        op2: [ [ 'a', 0, 0, { p: 0 } ], [ 'b', 0, 1, 0, { d: 0 } ] ]
-      expect: [ 'b', 0, 1, { r: true }, 0, { r: true } ]
-
-    it 'tracks removed drop index teleports', -> xf
-      # rm 0.a, move 0.b -> 0.c
-      doc: [{a:['a'], b:'b'}]
-      op1: [ 0, [ 'a', { r: true } ], [ 'b', { p: 0 } ], [ 'c', { d: 0 } ] ] # [{c:'b'}]
-      op2: [ 0, { d: 0, p: 1 }, [ 0, { d: 1 } ], [ 'a', { p: 0 } ] ] # [[{b:'b'}, 'a']]
-      conflict:
-        type: RM_UNEXPECTED_CONTENT
-        op1: [0, 'a', r:true]
-        op2: [0, p:0, 0, d:0]
-      expect: [ 0, { r: true }, 0, { r: true } ]
-
-    it 'handles transforming past cancelled move', -> xf
-      op1: [ [ 0, { r: true } ], [ 10, { i: [ '' ] } ] ]
-      op2: [ 0, { p: 0, d: 0 } ]
-      expect: [ [ 0, { r: true } ], [ 10, { i: [ '' ] } ] ]
-
-    it 'correctly adjusts indexes in another fuzzer great', -> xf
-      op1: [ [ 0, { d: 0, r: true } ], [ 3, { p: 0 } ] ]
-      op2: [ [ 0, { p: 0 } ], [ 3, { d: 0 } ] ]
-      expect: [[0, d:0], [2, p:0], [3, r:true]]
-
-    it 'op2 moves into something op1 removes and op1 moves into that', -> xf
-      op1: [ [ 'a', { r: true }, 'aa', { p: 0 } ], [ 'b', 'x', { d: 0 } ] ]
-      op2: [ [ 'a', 'bb', { d: 0 } ], [ 'b', { p: 0 } ] ]
-      conflict:
-        type: RM_UNEXPECTED_CONTENT
-        op1: ['a', r:true]
-      expect: [ 'a', { r: true }, ['aa', r:true], ['bb', r:true]] # Also ok if we miss the second rs.
-
-    it 'op2 moves into op1 remove edge cases', ->
-      # Sorry not minified.
-      xf
-        op1: [ 'Came', 0, [ 0, { r: true }, 'he', { p: 0 } ], [ 1, { d: 0 }, 0, { i: 'time' } ] ]
-        op2: [ 'Came', 0, [ 0, 'he', [ 0, { d: 0 } ], [ 1, { es: [] } ] ], [ 1, { p: 0 } ] ],
-        expectLeft: [ 'Came', 0, 0, { r: true, d: 0 }, [ 0, { i: 'time' } ], [ 'he', { p: 0 } ] ]
-        expectRight: [ 'Came', 0, 0, { r: true, d: 0 }, [ 1, { i: 'time' } ], [ 'he', { p: 0 } ] ]
-
-      xf
-        op1: [ [ 0, [ 1, { p: 0 } ], [ 2, { r: true } ] ], [ 1, 'xxx', { d: 0 } ] ]
-        op2: [ 0, 1, { i: {}, p: 0 }, 'b', { d: 0 } ]
-        expectLeft: [ [ 0, [ 1, 'b', { p: 0 } ], [ 2, { r: true } ] ], [ 1, 'xxx', { d: 0 } ] ]
-        expectRight: [ 0, 2, { r: true } ]
-
-    it 'translates indexes correctly in this fuzzer find', -> xf
-      op1: [ 0, { p: 0 }, 'x', { d: 0 } ]
-      op2: [ [ 0, { p: 0, d: 0 } ], [ 1, { i: 'y' } ] ]
-      expectLeft: [[0, { p: 0 }], [1, 'x', { d: 0 }]]
-      expectRight: null
-
-    it 'buries children of blackholed values', -> xf
-      op1: [ [ 0, [ 'a', { p: 0 } ], [ 'b', { d: 0 } ], [ 'c', { d: 1 } ] ], [ 1, { p: 1 } ] ]
-      op2: [ 0, { p: 0 }, 'x', { d: 0 } ]
-      # This is a bit interesting. The question is, which op2 picks and drops
-      # should we include in the output? For now the answer is that we include
-      # anything in both ops thats going to end up inside the blackholed
-      # content.
-      conflict: type: BLACKHOLE
-
-        # op1: [[0, 'c', d:0], [1, p:0]]
-      expect: [ 0, r: true, 'x', r:true ]
-
-    it 'does not conflict when removed target gets moved inside removed container', ->
-      # This edge case is interesting because we don't generate the same
-      # conflicts on left and right. We want our move of a.x to escape the
-      # object before removing it, but when we're right, the other operation's
-      # move holds the object and we get an unexpected rm conflict.
-      xf
-        op1: [ [ 'a', { r: true }, 'x', { p: 0 } ], [ 'b', { d: 0 } ] ]
-        op2: [ 'a', [ 'x', { p: 0 } ], [ 'y', { d: 0 } ] ]
-        conflictRight:
-          type: RM_UNEXPECTED_CONTENT
-          op1: ['a', r:true]
-        expectLeft: [ [ 'a', { r: true }, 'y', { p: 0 } ], [ 'b', { d: 0 } ] ]
-        expectRight: [ 'a', { r: true }, 'y', {r:true}]
-
-      xf
-        op1: [ [ 'a', { r: true }, 1, { p: 0 } ], [ 'b', { d: 0 } ] ]
-        op2: [ 'a', [ 0, { d: 0 } ], [ 1, { p: 0 } ] ]
-        expectLeft: [ [ 'a', { r: true }, 0, { p: 0 } ], [ 'b', { d: 0 } ] ]
-        conflictRight:
-          type: RM_UNEXPECTED_CONTENT
-          op1: ['a', r:true]
-        expectRight: [ 'a', { r: true }, 0, {r:true}]
-
-      expect: [ [ 'a', { r: true }, 0, { p: 0 } ], [ 'b', { d: 0 } ] ]
-
-    it 'compose copies op2 edit data', -> compose
-      op1: [ 'a', { r: true } ]
-      op2: [ [ 'x', { p: 0 } ], [ 'y', { d: 0 }, 'b', { es: [] } ] ]
-      expect: [
-        ['a', r:true]
-        ['x', {p:0}]
-        ['y', {d: 0}, 'b', {es: []}]
-      ]
-
-    it 'does not conflict when the dest is salvaged', -> xf
-      op1: [ [ 'a', { p: 0 } ], [ 'b', { i: 'hi' } ], [ 'c', { d: 0 } ] ]
-      op2: [ [ 'a', { p: 0 } ], [ 'b', { d: 0 } ] ]
-      expectLeft: [['b', {p:0, i:'hi'}], ['c', d:0]]
-      conflictRight:
-        type: DROP_COLLISION
-        op1: [ 'b', { i: 'hi' } ]
-      expectRight: null
-
-    it 'does not conflict on identical r/i pairs', -> xf
-      op1: [{ i: [], r: true }]
-      op2: [{ i: [], r: true }]
-      expect: null
-
-    it 'allows embedded edits in identical r/i', -> xf
-      op1: [ { r: true, i: '', es: [] } ]
-      op2: [ { r: true, i: '' } ]
-      expect: [es:[]]
-
-    it 'does not conflict on identical r/i pairs with identical drops inside', -> xf
-      op1: [ { i: {}, r: true }, 'a', { i: 'a' } ]
-      op2: [ { i: {}, r: true }, 'a', { i: 'a' } ]
-      expect: null
-
-    it 'generates a DROP_COLLISION on children', -> xf
-      op1: [ { i: {}, r: true }, 'a', { i: 'a' } ]
-      op2: [ { i: {}, r: true }, 'a', { i: 'b' } ]
-      conflict:
-        type: DROP_COLLISION
-        op1: ['a', { i: 'a' } ]
-        op2: ['a', { i: 'b' } ]
-      expectLeft: ['a', r:true, i:'a']
-      expectRight: null
-
-    it 'Transforms edit moves into the right dest', -> xf
-      op1: [ 0, { p: 0, d: 0 },
-        # These parts are all needed for some reason.
-        [ 0, { i: 1 } ],
-        [ 1, { r: true } ],
-        [ 3, { es: [] } ]
-      ]
-      op2: [ 0, [ 0, { d: 0 } ], [ 3, { p: 0 } ] ]
-      expectLeft: [ 0, {p:0, d:0},
-        [0, i:1],
-        [1, es:[]],
-        [2, r:true]
-      ]
-      expectRight: [0, {p:0, d:0}
-        [0, es:[]],
-        [1, i:1],
-        [2, r:true]
-      ]
-
-    it 'adjusts indexes of pick -> drop', -> xf
-      op1: [ 0, { p: 0, d: 0 } ]
-      op2: [ [ 0, { i: 'yo', p: 0 } ], [ 1, { d: 0 } ] ],
-      expectLeft: [ [ 0, { d: 0 } ], [ 1, { p: 0 } ] ]
-      expectRight: null
-
-    it 'clears output outDrop when theres no pick', -> xf
-      # Again, not minimized. We return the right data, we were just double-
-      # descending into outDrop.
-      op1: [ [ 'the', { d: 0, p: 0 } ], [ 'toves', { r: true } ] ]
-      op2: [
-        [ 'bird', { d: 0 } ],
-        [ 'slain', { d: 1 } ],
-        [ 'the', { p: 1 } ],
-        [ 'toves', { p: 0 } ]
-      ]
-      expectLeft: [
-        [ 'bird', { r: true } ],
-        [ 'slain', { p: 0 } ],
-        [ 'the', { d: 0 } ]
-      ]
-      expectRight: [ 'bird', { r: true } ]
-
-    it 'pushes drop indexes by other held items', -> xf
-      op1: [ [ 0, { p: 0 }],
-        [ 1,
-          [ 0, { i: 'hi' } ],
-          [ 1, { d: 0, es: [] } ] ]
-      ]
-      op2: [
-        [ 0, { p: 1 }, 1, { d: 0 }, 2, { d: 1 } ],
-        [ 2, { p: 0 } ]
-      ]
-      expectLeft: [ 0, 1,
-        [ 0, { i: 'hi' } ],
-        [ 1, { d: 0, es: [] } ],
-        [ 2, { p: 0 } ]
-      ]
-      expectRight: [ 0, 1, [ 0, { i: 'hi' } ], [ 3, { es: [] } ] ]
-
-    it 'composes correctly with lots of removes', -> compose
-      op1: [ 3, 1, { r: true } ],
-      op2: [
-        [ 0, { es: [] } ],
-        [ 1, { r: true, es: [] } ],
-        [ 2, { r: true } ]
-      ]
-      expect: [
-        [ 0, { es: [] } ],
-        [ 1, { es: [], r: true } ],
-        [ 2, { r: true } ],
-        [ 3, 1, { r: true } ]
-      ]
-
-    it 'does not descend twice when p/r on an identical insert', -> xf
-      op1: [ [ 'a', { p: 0, i: '' } ], [ 'b', { d: 0 } ] ]
-      op2: [ 'a', { r: true, i: '' } ]
-      expect: null
-
-    it 'conflicts underneath a moved / inserted child', -> xf
-      op1: [ [ 'a', { p: 0, i: {} }, 'x', {i:5} ], [ 'b', { d: 0 } ] ]
-      op2: [ 'a', { r: true, i: {} }, 'x', {i:6} ]
-      conflict:
-        type: DROP_COLLISION
-        op1: ['a', 'x', i:5]
-        op2: ['a', 'x', i:6]
-      expectLeft: ['a', 'x', {r:true, i:5}]
-      expectRight: null
-
-    it 'clears drop2 in transform moves', -> xf
-      doc: [b: a: 'hi']
-      op1: [0, d:0,
-        [ 'a', { es: [] } ],
-        [ 'b', { p: 0 } ]
-      ]
-      op2: [ 0, 'b',
-        [ 'a', { p: 0 } ],
-        [ 'b', { d: 0 } ]
-      ]
-      expect: [0, d:0, 'b', { p: 0, es:[] }]
-
-    it 'descends correctly when op2 picks and drops', -> xf
-      op1: [
-        [ 'b', { d: 0 }, [ 1, { es: [] } ], [ 2, { i: null } ] ],
-        [ 'e', { p: 0 } ]
-      ]
-      op2: [ { p: 0, d: 0 }, 'e', 1, { p: 1, d: 1 } ]
-      expectLeft: [
-        [ 'b', { d: 0 }, [ 1, { i: null } ], [ 2, { es: [] } ]],
-        [ 'e', { p: 0 } ]
-      ]
-      expectRight: [
-        [ 'b', { d: 0 }, [ 1, { es: [] } ], [ 2, { i: null } ] ],
-        [ 'e', { p: 0 } ]
-      ]
-
-    it 'composes a pick out of the insert', -> compose
-      op1: [ { i: [ 5, { x: 6 } ] } ]
-      op2: [ [ 0, { r: true }, 'c', { d: 0 } ], [ 1, 'x', { p: 0 } ] ]
-      # expect: [{i: [{c: 6}]}]
-      expect: [ { i: [ {} ] }, 0, 'c', { i: 6 } ]
-
-    it 'is not overeager to remove intermediate literal array items', -> compose
-      op1: [ [ 0, { i: [ 'a', 'b' ] }, 0, { p: 0 } ], [ 1, 0, { d: 0 } ] ]
-      op2: [ 0, { r: ['a'] }, 1, { r: 'b' } ]
-      expect: [ 0, 0, { d: 0, p: 0 } ]
-
-    it 'descends down insert indexes correctly', -> compose
-      op1: [ { i: [ {}, 'a' ] }, 1, { i: 'b' } ]
-      op2: [ [ 1, { r: 'b' } ], [ 2, { r: 'a' } ] ]
-      expect: [ { i: [ {} ] } ]
-
-    it 'handles composes with ena: 0', -> compose
-      op1: [i:10]
-      op2: [ena:0]
-      expect: [i:10, ena:0] # Also ok: just discarding the ena:0.
-
-    it 'handles rm parent with cross move', -> compose
-      op1: [ [ 'a', { p: 0 } ], [ 'b', 1, { d: 0 } ] ]
-      op2: [ [ 'b', { r: true }, 1, { p: 0 } ], [ 'c', { d: 0 } ]]
-      expect: [ [ 'a', { p: 0 } ], [ 'b', { r: true } ], [ 'c', { d: 0 } ] ]
-
-    it 'lets you remove children of an op at 2 levels', -> compose
-      op1: [ { i: [ 'a', { x: 'hi' } ] } ]
-      op2: [ { r: true }, 1, 'x', { r: true } ]
-      expect: null
-
-    it 'discards op1 inserts inside a removed chunk', -> compose
-      op1: [ 'y', [ 1, { i: 'x' } ], [ 2, { i: [ 'a', 'b' ] } ] ]
-      op2: [ { r: true }, 'y', 2, 0, { r: true } ]
-      expect: [ { r: true } ]
-
-    it 'handles deeply nested blackhole operations', -> xf
-      op1: [
-        [ 'x', { p: 0 } ],
-        [ 'y',
-          [ 'a',
-            [ 'j', { p: 1 } ],
-            [ 'k', { d: 1 } ]
-          ],
-          [ 'b', { d: 0 }]
-        ]
-      ]
-      op2: [
-        [ 'x', 'xx', { d: 0 }, 'j', 'jj', { d: 1 } ],
-        [ 'y', { p: 1 }, 'a', { p: 0 } ]
-      ]
-      conflict: type: BLACKHOLE
-      expect: ['x', r:true, 'xx', r:true, 'j', 'jj', r:true]
-
-    it 'does not list removed op1 moves in the blackhole info', -> xf
-      op1: [
-        [ 'a', [ 'j', { d: 0 } ], [ 'k', { d: 1 } ] ],
-        [ 'b', { p: 0 }, 'z', 0, { p: 1 } ]
-      ]
-      op2: [
-        [ 'a', { p: 0 } ],
-        [ 'b', [ 'y', { d: 0 } ], [ 'z', { r: true } ] ]
-      ]
-      conflict:
-        type: BLACKHOLE
-        op1: [ [ 'a', 'j', { d: 0 } ], [ 'b', { p: 0 } ] ]
-        op2: [ [ 'a', { p: 0 } ], [ 'b', 'y', { d: 0 } ] ]
-      expect: ['b', r:true, 'y', r:true]
-
-    it 'handles overlapping pick in blackholes', -> xf
-      # This looks complicated, but its really not so bad. Its:
-      # a->b.0, a.x -> z
-      # vs
-      # b -> a.x -> a.y
-      #
-      # Its a bit twisty because we're both picking up the same element and
-      # putting it in different places. This is why we have different left and
-      # right results.
-      op1: [
-        [ 'a', { p: 1 }, 'x', { p: 0 } ],
-        [ 'b', 0, { d: 1 } ],
-        [ 'z', { d: 0 } ]
-      ]
-      op2: [
-        [ 'a', [ 'x', { d: 0, p: 1 } ], [ 'y', { d: 1 } ] ],
-        [ 'b', { p: 0 } ]
-      ]
-      conflictLeft:
-        type: BLACKHOLE
-        op1: [['a', p:0], ['b', 0, d:0]]
-        op2: [['a', 'x', d:0], ['b', p:0]]
-      expectLeft: [
-        [ 'a', { r: true },
-          [ 'x', { r: true } ],
-          [ 'y', { p: 0 } ]
-        ],
-        [ 'z', { d: 0 } ]
-      ]
-      conflictRight:
-        type: BLACKHOLE
-        op1: [['a', p:0], ['b', 0, d:0]]
-      expectRight: [ 'a', { r: true },
-        [ 'x', { r: true } ],
-        [ 'y', { r: true } ]
-      ]
-    
\ No newline at end of file
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..e59b242
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,3078 @@
+// Unit tests for the JSON1 OT type.
+//
+// These tests are quite unstructured. You can see the skeletons of a few
+// organizing systems, but ultimately there's just lots of test cases to run.
+//
+// Cleanups welcome, so long as you don't remove any tests.
+
+const assert = require('assert')
+const { type } = require('../lib/json1')
+const log = require('../lib/log')
+const deepClone = require('../lib/deepClone')
+
+const { transform } = type
+const { DROP_COLLISION, RM_UNEXPECTED_CONTENT, BLACKHOLE } = type
+
+const apply = ({ doc: snapshot, op, expect }) => {
+  type.setDebug(false)
+
+  const orig = deepClone(snapshot)
+  try {
+    const result = type.apply(snapshot, op)
+    assert.deepStrictEqual(snapshot, orig, 'Original snapshot was mutated')
+    return assert.deepStrictEqual(result, expect)
+  } catch (e) {
+    console.log(
+      [
+        'Apply failed! Repro ',
+        `apply( ${JSON.stringify(snapshot)}, ${JSON.stringify(op)} )`
+      ].join('')
+    )
+    console.log(`expected output: ${JSON.stringify(expect)}`)
+    throw e
+  }
+}
+
+const d = fn => {
+  type.setDebug(true)
+  fn()
+  return type.setDebug(false)
+}
+
+const compose = ({ op1, op2, expect }) => {
+  try {
+    const result = type.compose(
+      op1,
+      op2
+    )
+    return assert.deepStrictEqual(result, expect)
+  } catch (e) {
+    d(() => {
+      console.error('FAIL! Repro with:')
+      console.log(`compose( ${JSON.stringify(op1)}, ${JSON.stringify(op2)} )`)
+      console.log(`expected output: ${JSON.stringify(expect)}`)
+      return type.compose(
+        op1,
+        op2
+      )
+    })
+    throw e
+  }
+}
+
+const invConflict = ({ type, op1, op2 }) => ({ type, op1: op2, op2: op1 })
+
+const otherSide = side => (side === 'left' ? 'right' : 'left')
+
+const checkConflict = ({
+  op1,
+  op2,
+  side,
+  conflict: expectConflict,
+  expect
+}) => {
+  // We should get the same conflict with xf(op1, op2, left) and xf(op2, op1, right).
+  if (expectConflict != null) {
+    if (!expectConflict.op1) {
+      expectConflict.op1 = type.normalize(op1)
+    }
+    if (!expectConflict.op2) {
+      expectConflict.op2 = type.normalize(op2)
+    }
+  }
+
+  const result = []
+  for (let [side_, op1_, op2_, ec] of [
+    [side, op1, op2, expectConflict],
+    [
+      otherSide(side),
+      op2,
+      op1,
+      expectConflict ? invConflict(expectConflict) : null
+    ]
+  ]) {
+    try {
+      // d -> log('tryTransform', side_, op1_, op2_)
+      const { ok, conflict } = type.tryTransform(op1_, op2_, side_)
+      if (ec == null) {
+        // We don't care what the result is here; just that it doesn't conflict.
+        result.push(assert(ok))
+      } else {
+        assert(!ok, `Conflict erroneously succeeded (${side_})`)
+        // d -> log('conflict', conflict)
+        conflict.op1 = type.normalize(conflict.op1)
+        conflict.op2 = type.normalize(conflict.op2)
+        result.push(assert.deepStrictEqual(conflict, ec))
+      }
+    } catch (e) {
+      d(() => {
+        console.error('FAIL! Repro with:')
+        console.log(
+          `tryTransform(${JSON.stringify(op1_)}, ${JSON.stringify(
+            op2_
+          )}, '${side_}')`
+        )
+        return type.tryTransform(op1_, op2_, side_)
+      })
+      throw e
+    }
+  }
+}
+
+const xf = ({
+  op1,
+  op2,
+  conflict,
+  conflictLeft,
+  conflictRight,
+  expect,
+  expectLeft,
+  expectRight
+}) => {
+  if (expect !== undefined) {
+    expectLeft = expectRight = expect
+  }
+  if (conflict !== undefined) {
+    conflictLeft = conflictRight = conflict
+  }
+
+  for (let [side, e, c] of [
+    ['left', expectLeft, conflictLeft],
+    ['right', expectRight, conflictRight]
+  ]) {
+    checkConflict({ op1, op2, side, conflict: c, expect: e })
+
+    try {
+      const result =
+        c != null
+          ? type.transformNoConflict(op1, op2, side)
+          : transform(op1, op2, side)
+      assert.deepStrictEqual(result, e)
+    } catch (error) {
+      e = error
+      d(() => {
+        console.error('FAIL! Repro with:')
+        console.log(
+          `transform(${JSON.stringify(op1)}, ${JSON.stringify(op2)}, '${side}')`
+        )
+      })
+      // if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side
+      throw e
+    }
+  }
+}
+
+const diamond = ({ doc, op1, op2 }) => {
+  let doc1, doc12, doc2, doc21, op1_, op2_
+  type.setDebug(false)
+
+  try {
+    // Test that the diamond property holds
+    op1_ = transform(op1, op2, 'left')
+    op2_ = transform(op2, op1, 'right')
+
+    doc1 = type.apply(doc, op1)
+    doc2 = type.apply(doc, op2)
+
+    doc12 = type.apply(doc1, op2_)
+    doc21 = type.apply(doc2, op1_)
+
+    return assert.deepStrictEqual(doc12, doc21)
+  } catch (e) {
+    log.quiet = false
+    log('\nOops! Diamond property does not hold. Given document', doc)
+    log('op1 ', op1, '   /    op2', op2)
+    log('op1_', op1_, '   /    op2_', op2_)
+    log('---- 1')
+    log('op1', op1, '->', doc1)
+    log('op2', op2_, '->', doc12)
+    log('---- 2')
+    log('op2', op2, '->', doc2)
+    log('op1', op1_, '->', doc21)
+    log('----------')
+    log(doc12, '!=', doc21)
+    throw e
+  }
+}
+
+const path = (path, { op, expect }) => {
+  if (expect === undefined) {
+    expect = path.slice()
+  }
+
+  const result = type.transformPosition(path, op)
+  assert.deepStrictEqual(result, expect)
+
+  // Also check that path+X = expect+X
+  const path2 = path.concat('x')
+  const expect2 = expect != null ? expect.concat('x') : null
+
+  const result2 = type.transformPosition(path2, op)
+  assert.deepStrictEqual(result2, expect2)
+}
+
+describe('json1', () => {
+  before(() => {
+    type.registerSubtype(require('ot-simple'))
+    return type.setDebug(true)
+  })
+  after(() => type.setDebug(false))
+
+  describe('checkOp', () => {
+    const pass = op => {
+      try {
+        return type.checkValidOp(op)
+      } catch (e) {
+        console.log(`FAIL! Repro with:\ncheckOp( ${JSON.stringify(op)} )`)
+        throw e
+      }
+    }
+
+    const fail = op => {
+      try {
+        return assert.throws(() => type.checkValidOp(op))
+      } catch (e) {
+        console.log(`FAIL! Repro with:\ncheckOp( ${JSON.stringify(op)} )`)
+        console.log('Should throw!')
+        throw e
+      }
+    }
+
+    it('allows some simple valid ops', () => {
+      pass(null)
+      pass([{ i: [1, 2, 3] }])
+      pass([{ r: {} }])
+      pass([['x', { p: 0 }], ['y', { d: 0 }]])
+      pass([[0, { p: 0 }], [10, { d: 0 }]])
+      pass([['a', { p: 0 }], ['b', { d: 0 }], ['x', { p: 1 }], ['y', { d: 1 }]])
+      pass([{ e: 'hi', et: 'simple' }])
+      pass([{ es: ['hi'] }])
+      pass([{ ena: 5 }])
+    })
+
+    it('disallows invalid syntax', () => {
+      fail(undefined)
+      fail({})
+      fail('hi')
+      fail(true)
+      fail(false)
+      fail(0)
+      fail(10)
+      fail([{}])
+      fail([{ invalid: true }])
+      fail([10, {}])
+      fail([10, { invalid: true }])
+      fail([10, 'hi'])
+    })
+
+    it('throws if there is any empty leaves', () => {
+      fail([])
+      fail(['x'])
+      fail(['x', {}])
+      fail(['x', []])
+      fail([10])
+      fail([10, {}])
+      fail([10, []])
+    })
+
+    it('ensures path components are non-zero integers or strings', () => {
+      fail([-1, { r: {} }])
+      fail([0.5, { r: {} }])
+      fail([true, { r: {} }])
+      fail([false, { r: {} }])
+      fail([null, { r: {} }])
+      fail([undefined, { r: {} }])
+    })
+
+    it('does not allow two pickups or two drops in a component', () => {
+      fail([{ p: 0, r: {} }])
+      fail([{ p: 1, r: {} }])
+      fail(['x', { p: 0, r: {} }])
+      fail(['x', { p: 1, r: {} }])
+
+      fail([{ d: 0, i: 'hi' }])
+      fail([{ d: 1, i: 'hi' }])
+      fail([10, { d: 0, i: 'hi' }])
+      fail([10, { d: 1, i: 'hi' }])
+    })
+
+    it('throws if there are mismatched pickups / drops', () => {
+      fail([{ p: 0 }])
+      fail([{ d: 0 }])
+      fail(['x', { p: 0 }])
+      fail([10, { p: 0 }])
+      fail(['x', { d: 0 }])
+      fail([10, { d: 0 }])
+    })
+
+    it('throws if pick/drop indexes dont start at 0', () => {
+      fail([['x', { p: 1 }], ['y', { d: 1 }]])
+      fail([[10, { p: 1 }], [20, { d: 1 }]])
+    })
+
+    it('throws if a descent starts with an edit', () =>
+      fail([10, [{ i: 'hi' }]]))
+
+    it('throws if descents are out of order', () => {
+      fail(['x', ['b', { r: {} }], ['a', { r: {} }]])
+      fail(['x', [10, { r: {} }], [5, { r: {} }]])
+      fail(['x', ['a', { r: {} }], [5, { r: {} }]])
+      fail(['x', ['a', { r: {} }], ['a', { r: {} }]])
+      fail(['x', [10, { r: {} }], [10, { r: {} }]])
+    })
+
+    it('throws if descents start with the same scalar', () =>
+      fail(['x', ['a', { r: {} }], ['a', { e: {} }]]))
+
+    it('throws if descents have two adjacent edits', () => {
+      fail([{ r: {} }, { p: 0 }])
+      fail(['x', { r: {} }, { p: 0 }])
+      fail(['x', { r: {} }, { p: 0 }, 'y', { r: {} }])
+    })
+
+    it.skip('does not allow ops to overwrite their own inserted data', () => {
+      fail([{ i: { x: 5 } }, 'x', { i: 6 }])
+      fail([{ i: ['hi'] }, 0, { i: 'omg' }])
+    })
+
+    it.skip('does not allow immediate data directly parented in other immediate data', () => {
+      fail([{ i: {} }, 'x', { i: 5 }])
+      fail([{ i: { x: 5 } }, 'x', 'y', { i: 6 }])
+      fail([{ i: [] }, 0, { i: 5 }])
+    })
+
+    it('does not allow the final item to be a single descent', () =>
+      fail(['a', ['b', { r: {} }]])) // It should be ['a', 'b', r:{}]
+
+    it('does not allow anything after the descents at the end', () => {
+      fail([[1, { r: {} }], [2, { r: {} }], 5])
+      fail([[1, { r: {} }], [2, { r: {} }], 5, { r: {} }])
+      fail([[1, { r: {} }], [2, { r: {} }], { r: {} }])
+    })
+
+    it('allows removes inside removes', () => {
+      pass(['x', { r: true }, 'y', { r: true }])
+      pass(['x', { r: {} }, 'y', { r: true }])
+      pass([
+        ['x', { r: true }, 'y', { p: 0 }, 'z', { r: true }],
+        ['y', { d: 0 }]
+      ])
+      pass([['x', { r: {} }, 'y', { p: 0 }, 'z', { r: true }], ['y', { d: 0 }]])
+    })
+
+    it('allows inserts inside inserts', () => {
+      pass([1, { i: {} }, 'x', { i: 10 }])
+      pass([[0, 'x', { p: 0 }], [1, { i: {} }, 'x', { d: 0 }, 'y', { i: 10 }]])
+    })
+
+    it.skip('fails if the operation drops items inside something it picked up', () => {
+      fail(['x', { r: true }, 1, { i: 'hi' }])
+      fail(['x', { d: 0 }, 1, { p: 0 }])
+      fail([{ r: true }, 1, { p: 0, d: 0 }])
+    })
+
+    describe('edit', () => {
+      it('requires all edits to specify their type', () => {
+        fail([{ e: {} }])
+        fail([5, { e: {} }])
+        pass([{ e: {}, et: 'simple' }])
+      })
+
+      it('allows edits to have null or false for the operation', () => {
+        // These aren't valid operations according to the simple type, but the
+        // type doesn't define a checkValidOp so we wouldn't be able to tell
+        // anyway.
+        pass([{ e: null, et: 'simple' }])
+        pass([5, { e: null, et: 'simple' }])
+        pass([{ e: false, et: 'simple' }])
+        pass([5, { e: false, et: 'simple' }])
+      })
+
+      it('does not allow an edit to use an unregistered type', () => {
+        fail([{ e: {}, et: 'an undefined type' }])
+        fail([{ e: null, et: 'an undefined type' }])
+      })
+
+      it('does not allow two edits in the same operation', () => {
+        fail([{ e: {}, et: 'simple', es: [1, 2, 3] }])
+        fail([{ es: [], ena: 5 }])
+        fail([{ e: {}, et: 'simple', ena: 5 }])
+      })
+
+      it('fails if the type is missing', () => fail([{ et: 'missing', e: {} }]))
+
+      it('does not allow anything inside an edited subtree')
+
+      it.skip('does not allow an edit inside removed or picked up content', () => {
+        fail([{ r: true }, 1, { es: ['hi'] }])
+        pass([1, { r: true }, 1, { es: ['hi'] }])
+        fail(['x', { r: true }, 1, { es: ['hi'] }])
+        pass([[1, { p: 0 }, 1, { es: ['hi'] }], [2, { d: 0 }]])
+        fail([['x', { p: 0 }, 1, { es: ['hi'] }], ['y', { d: 0 }]])
+
+        // This is actually ok.
+        pass([0, { p: 0 }, ['a', { es: [], r: true }], ['x', { d: 0 }]])
+      })
+
+      it.skip('does not allow you to drop inside something that was removed', () => {
+        // These insert into the next list item
+        pass([[1, { r: true }, 1, { d: 0 }], [2, { p: 0 }]])
+        pass([1, { p: 0 }, 'x', { d: 0 }])
+
+        // But this is not ok.
+        fail(['x', { p: 0 }, 'a', { d: 0 }])
+      })
+    })
+  })
+
+  describe('normalize', () => {
+    const n = (opIn, expect) => {
+      if (expect === undefined) {
+        expect = opIn
+      }
+      const op = type.normalize(opIn)
+      assert.deepStrictEqual(op, expect)
+    }
+
+    it('does the right thing for noops', () => {
+      n(null)
+      n([], null)
+    })
+
+    it('normalizes some regular ops', () => {
+      n([{ i: 'hi' }])
+      n([{ i: 'hi' }, 1, 2, 3], [{ i: 'hi' }])
+      n([[1, 2, 3, { p: 0 }], [1, 2, 3, { d: 0 }]], [1, 2, 3, { p: 0, d: 0 }])
+      n(
+        [[1, 2, 3, { p: 0 }], [1, 2, 30, { d: 0 }]],
+        [1, 2, [3, { p: 0 }], [30, { d: 0 }]]
+      )
+      n(
+        [[1, 2, 30, { p: 0 }], [1, 2, 3, { d: 0 }]],
+        [1, 2, [3, { d: 0 }], [30, { p: 0 }]]
+      )
+    })
+
+    it('will let you insert null', () => n([{ i: null }]))
+
+    it('normalizes embedded ops when available', () => {
+      n([{ es: [0, 'hi'] }], [{ es: ['hi'] }])
+      n([{ et: 'text-unicode', e: ['hi'] }], [{ es: ['hi'] }])
+      n([{ et: 'text-unicode', e: [0, 'hi'] }], [{ es: ['hi'] }])
+      n([{ et: 'simple', e: {} }])
+      n([{ et: 'number', e: 5 }], [{ ena: 5 }])
+      n([{ ena: 5 }])
+    })
+
+    it.skip('normalizes embedded removes', () => {
+      n([1, { r: true }, 2, { r: true }], [1, { r: true }])
+      n([{ r: true }, 2, { r: true }], [{ r: true }])
+    })
+
+    it('throws if the type is missing', () =>
+      // Not sure if this is the best behaviour but ... eh.
+      assert.throws(() => n([{ et: 'missing', e: {} }])))
+
+    it('corrects weird pick and drop ids', () =>
+      n([['x', { p: 1 }], ['y', { d: 1 }]], [['x', { p: 0 }], ['y', { d: 0 }]]))
+  })
+
+  // ****** Apply ******
+
+  describe('apply', () => {
+    it('Can set properties', () => {
+      apply({
+        doc: [],
+        op: [0, { i: 17 }],
+        expect: [17]
+      })
+
+      apply({
+        doc: {},
+        op: ['x', { i: 5 }],
+        expect: { x: 5 }
+      })
+    })
+
+    it('can edit the root', () => {
+      apply({
+        doc: { x: 5 },
+        op: [{ r: true }],
+        expect: undefined
+      })
+
+      apply({
+        doc: '',
+        op: [{ r: true }],
+        expect: undefined
+      })
+
+      apply({
+        doc: 'hi',
+        op: [{ r: true, i: null }],
+        expect: null
+      })
+
+      apply({
+        doc: 'hi',
+        op: [{ es: [2, ' there'] }],
+        expect: 'hi there'
+      })
+
+      assert.throws(() => type.apply(null, [{ i: 5 }]))
+
+      apply({
+        doc: undefined,
+        op: [{ i: 5 }],
+        expect: 5
+      })
+
+      apply({
+        doc: { x: 5 },
+        op: [{ r: {}, i: [1, 2, 3] }],
+        expect: [1, 2, 3]
+      })
+    })
+
+    // TODO: And an edit of the root.
+
+    it('can move 1', () =>
+      apply({
+        doc: { x: 5 },
+        op: [['x', { p: 0 }], ['y', { d: 0 }]],
+        expect: { y: 5 }
+      }))
+
+    it('can move 2', () =>
+      apply({
+        doc: [0, 1, 2],
+        op: [[1, { p: 0 }], [2, { d: 0 }]],
+        expect: [0, 2, 1]
+      }))
+
+    it('can handle complex list index stuff', () =>
+      apply({
+        doc: [0, 1, 2, 3, 4, 5],
+        op: [[1, { r: {}, i: 11 }], [2, { r: {}, i: 12 }]],
+        expect: [0, 11, 12, 3, 4, 5]
+      }))
+
+    it('correctly handles interspersed descent and edits', () =>
+      apply({
+        doc: { x: { y: { was: 'y' }, was: 'x' } },
+        op: [['X', { d: 0 }, 'Y', { d: 1 }], ['x', { p: 0 }, 'y', { p: 1 }]],
+        expect: { X: { Y: { was: 'y' }, was: 'x' } }
+      }))
+
+    it('can edit strings', () =>
+      apply({
+        doc: 'errd',
+        op: [{ es: [2, 'maghe'] }],
+        expect: 'ermagherd'
+      }))
+
+    it('can edit numbers', () =>
+      apply({
+        doc: 5,
+        op: [{ ena: 10 }],
+        expect: 15
+      }))
+
+    it('can edit child numbers', () =>
+      apply({
+        doc: [20],
+        op: [0, { ena: -100 }],
+        expect: [-80]
+      }))
+
+    it('can edit subdocuments using an embedded type', () =>
+      apply({
+        doc: { str: 'hai' },
+        op: [{ e: { position: 2, text: 'wai' }, et: 'simple' }],
+        expect: { str: 'hawaii' }
+      }))
+
+    it('applies edits after drops', () =>
+      apply({
+        doc: { x: 'yooo' },
+        op: [['x', { p: 0 }], ['y', { d: 0, es: ['sup'] }]],
+        expect: { y: 'supyooo' }
+      }))
+
+    it('throws when the op traverses missing items', () => {
+      assert.throws(() => type.apply([0, 'hi'], [1, { p: 0 }, 'x', { d: 0 }]))
+      assert.throws(() => type.apply({}, [{ p: 0 }, 'a', { d: 0 }]))
+    })
+
+    it('throws if the type is missing', () =>
+      assert.throws(() => type.apply({}, [{ et: 'missing', e: {} }])))
+  })
+
+  describe('apply path', () => {
+    it('does not modify path when op is unrelated', () => {
+      path(['a', 'b', 'c'], { op: null })
+      path(['a', 'b', 'c'], { op: ['x', { i: 5 }] })
+      path(['a', 'b', 'c'], { op: ['x', { r: true }] })
+      path(['a', 'b', 'c'], { op: [['x', { p: 0 }], ['y', { d: 0 }]] })
+      path([1, 2, 3], { op: [2, { i: 5 }] })
+      path([1, 2, 3], { op: [1, 2, 4, { i: 5 }] })
+      path([1], { op: [1, 2, { r: true }] })
+      path(['x'], { op: ['x', 'y', { r: true }] })
+    })
+
+    it('adjusts list indicies', () => {
+      path([2], { op: [1, { i: 5 }], expect: [3] })
+      path([2], { op: [2, { i: 5 }], expect: [3] })
+      path([2], { op: [1, { r: true }], expect: [1] })
+      path([2], { op: [[1, { p: 0 }], [3, { d: 0 }]], expect: [1] })
+      path([2], { op: [[1, { d: 0 }], [3, { p: 0 }]], expect: [3] })
+      path([2], { op: [[2, { d: 0 }], [3, { p: 0 }]], expect: [3] })
+    })
+
+    it('returns null when the object at the path was removed', () => {
+      path(['x'], { op: [{ r: true }], expect: null })
+      path(['x'], { op: ['x', { r: true }], expect: null })
+      path([1], { op: [{ r: true }], expect: null })
+      path([1], { op: [1, { r: true }], expect: null })
+    })
+
+    it('moves the path', () => {
+      path(['a', 'z'], {
+        op: [['a', { p: 0 }], ['y', { d: 0 }]],
+        expect: ['y', 'z']
+      })
+      path(['a', 'b'], {
+        op: [['a', 'b', { p: 0 }], ['z', { d: 0 }]],
+        expect: ['z']
+      })
+      path(['a', 'b'], { op: [['a', 'b', 'c', { p: 0 }], ['z', { d: 0 }]] })
+      path([1, 2], { op: [[1, { p: 0 }], [10, { d: 0 }]], expect: [10, 2] })
+      path([1, 2], { op: [[1, 2, { p: 0 }], [10, { d: 0 }]], expect: [10] })
+      path([1, 2], { op: [1, [1, { d: 0 }], [2, { p: 0 }]], expect: [1, 1] })
+      path([1, 2], { op: [[1, 2, 3, { p: 0 }], [10, { d: 0 }]] })
+    })
+
+    it('handles pick parent and move', () =>
+      path(['a', 'b', 'c'], {
+        op: [['a', { r: true }, 'b', { p: 0 }], ['x', { d: 0 }]],
+        expect: ['x', 'c']
+      }))
+
+    it('adjusts indicies under a pick', () =>
+      path(['a', 'b', 10], {
+        op: [['a', { p: 0 }, 'b', 1, { r: true }], ['x', { d: 0 }]],
+        expect: ['x', 'b', 9]
+      }))
+
+    it.skip('gen ops', () => {})
+    // This should do something like:
+    // - Generate a document
+    // - Generate op, a random operation
+    // - Generate a path to somewhere in the document and an edit we can do there -> op2
+    // - Check that transform(op2, op) == op2 at transformPosition(path) or something like that.
+
+    it('calls transformPosition with embedded string edits if available', () => {
+      // For embedded string operations (and other things that have
+      // transformPosition or transformPosition or whatever) we should call that.
+      path(['x', 'y', 'z', 1], {
+        op: ['x', 'y', 'z', { es: ['abc'] }],
+        expect: ['x', 'y', 'z', 4]
+      })
+      path(['x', 'y', 'z', 1], {
+        op: ['x', 'y', 'z', { es: ['💃'] }],
+        expect: ['x', 'y', 'z', 2]
+      })
+      path(['x', 'y', 'z'], {
+        op: ['x', 'y', 'z', { es: ['💃'] }],
+        expect: ['x', 'y', 'z']
+      })
+    })
+  })
+
+  // ******* Compose *******
+
+  describe('compose', () => {
+    it('composes empty ops to nothing', () =>
+      compose({
+        op1: null,
+        op2: null,
+        expect: null
+      }))
+
+    describe('op1 drop', () => {
+      it('vs remove', () =>
+        compose({
+          op1: [['x', { p: 0 }], ['y', { d: 0 }]],
+          op2: ['y', { r: true }],
+          expect: ['x', { r: true }]
+        }))
+
+      it('vs remove parent', () =>
+        compose({
+          op1: [['x', { p: 0 }], ['y', 0, { d: 0 }]],
+          op2: ['y', { r: true }],
+          expect: [['x', { r: true }], ['y', { r: true }]]
+        }))
+
+      it('vs remove child', () =>
+        compose({
+          op1: [['x', { p: 0 }], ['y', { d: 0 }]],
+          op2: ['y', 'a', { r: true }],
+          expect: [['x', { p: 0 }, 'a', { r: true }], ['y', { d: 0 }]]
+        }))
+
+      it('vs remove and pick child', () =>
+        compose({
+          op1: [['x', { p: 0 }], ['y', { d: 0 }]],
+          op2: [['y', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]],
+          expect: [['x', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]]
+        }))
+
+      it('vs pick', () =>
+        compose({
+          op1: [['x', { p: 0 }], ['y', { d: 0 }]],
+          op2: [['y', { p: 0 }], ['z', { d: 0 }]],
+          expect: [['x', { p: 0 }], ['z', { d: 0 }]]
+        }))
+
+      it('is transformed by op2 picks', () =>
+        compose({
+          op1: [['x', { p: 0 }], ['y', 10, { d: 0 }]],
+          op2: ['y', 0, { r: true }],
+          expect: [['x', { p: 0 }], ['y', [0, { r: true }], [9, { d: 0 }]]]
+        }))
+    })
+
+    describe('op1 insert', () => {
+      it('vs remove', () =>
+        compose({
+          op1: ['x', { i: { a: 'hi' } }],
+          op2: ['x', { r: true }],
+          expect: null
+        }))
+
+      it('vs remove parent', () =>
+        compose({
+          op1: ['x', 0, { i: { a: 'hi' } }],
+          op2: ['x', { r: true }],
+          expect: ['x', { r: true }]
+        }))
+
+      it('vs remove child', () =>
+        compose({
+          op1: ['x', { i: { a: 'hi', b: 'woo' } }],
+          op2: ['x', 'a', { r: true }],
+          expect: ['x', { i: { b: 'woo' } }]
+        }))
+
+      it('vs remove and pick child', () =>
+        compose({
+          op1: ['x', { i: { a: 'hi', b: 'woo' } }],
+          op2: [['x', { r: true }, 'a', { p: 0 }], ['y', { d: 0 }]],
+          expect: ['y', { i: 'hi' }]
+        }))
+
+      it('vs remove an embedded insert', () =>
+        compose({
+          op1: ['x', { i: {} }, 'y', { i: 'hi' }],
+          op2: ['x', 'y', { r: true }],
+          expect: ['x', { i: {} }]
+        }))
+
+      it('vs remove from an embedded insert', () =>
+        compose({
+          op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }],
+          op2: ['x', 'y', 1, { r: true }],
+          expect: ['x', { i: {} }, 'y', { i: [1, 3] }]
+        }))
+
+      it('picks the correct element of an embedded insert', () =>
+        compose({
+          op1: ['x', { i: ['a', 'b', 'c'] }, 1, { i: 'XX' }],
+          op2: [['x', 1, { p: 0 }], ['y', { d: 0 }]],
+          expect: [['x', { i: ['a', 'b', 'c'] }], ['y', { i: 'XX' }]]
+        }))
+
+      it('picks the correct element of an embedded insert 2', () =>
+        compose({
+          op1: ['x', { i: ['a', 'b', 'c'] }, 1, { i: 'XX' }],
+          op2: [['x', 3, { p: 0 }], ['y', { d: 0 }]], // should grab 'c'.
+          expect: [['x', { i: ['a', 'b'] }, 1, { i: 'XX' }], ['y', { i: 'c' }]]
+        }))
+
+      it('moves all children', () =>
+        compose({
+          op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }],
+          op2: [['x', { p: 0 }], ['z', { d: 0 }]],
+          expect: ['z', { i: {} }, 'y', { i: [1, 2, 3] }]
+        }))
+
+      it('removes all children', () =>
+        compose({
+          op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }],
+          op2: ['x', { r: true }],
+          expect: null
+        }))
+
+      it('removes all children when removed at the destination', () =>
+        compose({
+          op1: [['x', { p: 0 }], ['y', { d: 0 }, 0, { i: 'hi' }]],
+          op2: ['y', { r: true }],
+          expect: ['x', { r: true }]
+        }))
+
+      it('vs op2 insert', () =>
+        compose({
+          // Inserts aren't folded together.
+          op1: [{ i: {} }],
+          op2: ['x', { i: 'hi' }],
+          expect: [{ i: {} }, 'x', { i: 'hi' }]
+        }))
+
+      it('vs op2 string edit', () =>
+        compose({
+          op1: [{ i: 'hi' }],
+          op2: [{ es: [2, ' there'] }],
+          expect: [{ i: 'hi', es: [2, ' there'] }]
+        }))
+
+      it('vs op2 number edit', () =>
+        compose({
+          op1: [{ i: 10 }],
+          op2: [{ ena: 20 }],
+          expect: [{ i: 10, ena: 20 }]
+        }))
+    })
+
+    describe('op1 edit', () => {
+      it('removes the edit if the edited object is deleted', () =>
+        compose({
+          op1: ['x', { es: ['hi'] }],
+          op2: ['x', { r: true }],
+          expect: ['x', { r: true }]
+        }))
+
+      it('removes the edit in an embedded insert 1', () =>
+        compose({
+          op1: ['x', { i: '', es: ['hi'] }],
+          op2: ['x', { r: true }],
+          expect: null
+        }))
+
+      it('removes the edit in an embedded insert 2', () =>
+        compose({
+          op1: ['x', { i: [''] }, 0, { es: ['hi'] }],
+          op2: ['x', 0, { r: true }],
+          expect: ['x', { i: [] }]
+        }))
+
+      it('composes string edits', () =>
+        compose({
+          op1: [{ es: ['hi'] }],
+          op2: [{ es: [2, ' there'] }],
+          expect: [{ es: ['hi there'] }]
+        }))
+
+      it('composes number edits', () =>
+        compose({
+          op1: [{ ena: 10 }],
+          op2: [{ ena: -8 }],
+          expect: [{ ena: 2 }]
+        }))
+
+      it('transforms and composes edits', () =>
+        compose({
+          op1: ['x', { es: ['hi'] }],
+          op2: [['x', { p: 0 }], ['y', { d: 0, es: [2, ' there'] }]],
+          expect: [['x', { p: 0 }], ['y', { d: 0, es: ['hi there'] }]]
+        }))
+
+      it('preserves inserts with edits', () =>
+        compose({
+          op1: ['x', { i: 'hi' }],
+          op2: [['x', { p: 0 }], ['y', { d: 0, es: [' there'] }]],
+          expect: ['y', { i: 'hi', es: [' there'] }]
+        }))
+
+      it('allows a different edit in the same location', () =>
+        compose({
+          op1: ['x', { es: ['hi'] }],
+          op2: ['x', { r: true, i: 'yo', es: [2, ' there'] }],
+          expect: ['x', { r: true, i: 'yo', es: [2, ' there'] }]
+        }))
+
+      it('throws if the type is missing', () =>
+        assert.throws(() =>
+          type.compose(
+            [{ et: 'missing', e: {} }],
+            [{ et: 'missing', e: {} }]
+          )
+        ))
+    })
+
+    describe('op2 pick', () =>
+      it('gets untransformed by op1 drops', () => ({
+        op1: [5, { i: 'hi' }],
+        op2: [6, { r: true }],
+        expect: [5, { r: true, i: 'hi' }]
+      })))
+
+    describe('op1 insert containing a drop', () =>
+      it('vs pick at insert', () =>
+        compose({
+          op1: [['x', { p: 0 }], ['y', { i: {} }, 'x', { d: 0 }]],
+          op2: [['y', { p: 0 }], ['z', { d: 0 }]],
+          expect: [['x', { p: 0 }], ['z', { i: {} }, 'x', { d: 0 }]]
+        })))
+
+    describe('fuzzer tests', () =>
+      it('complicated transform of indicies', () =>
+        compose({
+          op1: [0, { p: 0 }, 'x', 2, { d: 0 }],
+          op2: [0, 'x', 0, { r: true }],
+          expect: [[0, { p: 0 }, 'x', 1, { d: 0 }], [1, 'x', 0, { r: true }]]
+        })))
+
+    describe('setnull interaction', () => {
+      // Currently failing.
+      it('reorders items inside a setnull region', () =>
+        compose({
+          op1: [{ i: [] }, [0, { i: 'a' }], [1, { i: 'b' }]],
+          op2: [[0, { p: 0 }], [1, { d: 0 }]],
+          expect: [{ i: [] }, [0, { i: 'b' }], [1, { i: 'a' }]]
+        }))
+
+      it('lets a setnull child be moved', () =>
+        compose({
+          op1: ['list', { i: [] }, 0, { i: 'hi' }],
+          op2: [['list', 0, { p: 0 }], ['z', { d: 0 }]],
+          expect: [['list', { i: [] }], ['z', { i: 'hi' }]]
+        }))
+
+      it('lets a setnull child get modified', () =>
+        compose({
+          op1: [{ i: [] }, 0, { i: ['a'] }],
+          op2: [0, 0, { r: 'a', i: 'b' }],
+          expect: [{ i: [] }, 0, { i: [] }, 0, { i: 'b' }]
+        }))
+    })
+    //expect: [{i:[]}, 0, {i:['b']}] # Maybe better??
+
+    describe('regression', () => {
+      it('skips op2 drops when calculating op1 drop index simple', () =>
+        compose({
+          op1: [[0, { p: 0 }], [2, { d: 0 }]],
+          op2: [[0, { p: 0 }], [1, { d: 0 }]],
+          expect: [[0, { p: 1 }], [1, { p: 0, d: 0 }], [2, { d: 1 }]]
+        }))
+
+      it('skips op2 drops when calculating op1 drop index complex', () =>
+        compose({
+          op1: [[0, { p: 0, d: 1 }], [1, { p: 1 }], [2, { d: 0 }]],
+          op2: [[0, { p: 0 }], [1, { d: 0 }]],
+          // expect: [[0, {p:1}], [1, {d:0, p:0}], [2, d:1]]
+          expect: [[0, { p: 1 }], [1, { p: 0, d: 0 }], [2, { d: 1 }]]
+        }))
+
+      it('3', () =>
+        compose({
+          op1: [{ i: [null, []] }, 0, { i: '' }],
+          op2: [1, { p: 0 }, 0, { d: 0 }],
+          // ... it'd be way more consistent to drop the null separately rather than merging it??
+          expect: [{ i: [[]] }, [0, { i: '' }], [1, 0, { i: null }]]
+        }))
+
+      it('4', () =>
+        compose({
+          // This one triggered a bug in cursor!
+          op1: [0, [0, ['a', { r: true }], ['b', { d: 0 }]], [2, { p: 0 }]],
+          op2: [0, 0, 'c', { i: 'd' }],
+          expect: [
+            0,
+            [0, ['a', { r: true }], ['b', { d: 0 }], ['c', { i: 'd' }]],
+            [2, { p: 0 }]
+          ]
+        }))
+    })
+  })
+
+  // *** Old stuff
+  describe('old compose', () => {
+    it('gloms together unrelated edits', () => {
+      compose({
+        op1: [['a', { p: 0 }], ['b', { d: 0 }]],
+        op2: [['x', { p: 0 }], ['y', { d: 0 }]],
+        expect: [
+          ['a', { p: 0 }],
+          ['b', { d: 0 }],
+          ['x', { p: 1 }],
+          ['y', { d: 1 }]
+        ]
+      })
+
+      compose({
+        op1: [2, { i: 'hi' }],
+        op2: [0, 'x', { r: true }],
+        expect: [[0, 'x', { r: true }], [2, { i: 'hi' }]]
+      })
+    })
+
+    it('translates drops in objects', () =>
+      compose({
+        op1: ['x', ['a', { p: 0 }], ['b', { d: 0 }]], // x.a -> x.b
+        op2: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y
+        expect: [['x', { p: 0 }, 'a', { p: 1 }], ['y', { d: 0 }, 'b', { d: 1 }]]
+      })) // x.a -> y.b, x -> y
+
+    it('untranslates picks in objects', () =>
+      compose({
+        op1: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y
+        op2: [['y', 'a', { p: 0 }], ['z', { d: 0 }]], // y.a -> z
+        expect: [
+          ['x', { p: 0 }, 'a', { p: 1 }],
+          ['y', { d: 0 }],
+          ['z', { d: 1 }]
+        ]
+      })) // x.a -> z, x -> y
+
+    it('insert gets carried wholesale', () =>
+      compose({
+        op1: ['x', { i: 'hi there' }],
+        op2: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y
+        expect: ['y', { i: 'hi there' }]
+      }))
+
+    it('insert gets edited by the op', () =>
+      compose({
+        op1: ['x', { i: { a: 1, b: 2, c: 3 } }],
+        op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }]],
+        expect: [['x', { i: { b: 2, c: 3 } }], ['y', { i: 1 }]]
+      }))
+
+    it('does not merge mutual inserts', () =>
+      compose({
+        op1: [{ i: {} }],
+        op2: ['x', { i: 'hi' }],
+        expect: [{ i: {} }, 'x', { i: 'hi' }]
+      }))
+  })
+
+  // TODO: List nonsense.
+
+  // TODO: Edits.
+
+  // ****** Transform ******
+
+  describe('transform', () => {
+    describe('op1 pick', () => {
+      it('vs delete', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['y', { d: 0 }]],
+          op2: ['x', { r: true }],
+          expect: null
+        }))
+      it('vs delete parent', () =>
+        xf({
+          op1: [['x', 'a', { p: 0 }], ['y', { d: 0 }]],
+          op2: ['x', { r: true }],
+          expect: null
+        }))
+      it('vs delete parent 2', () =>
+        xf({
+          op1: ['x', ['a', { p: 0 }], ['b', { d: 0 }]],
+          op2: ['x', { r: true }],
+          expect: null
+        }))
+
+      it('vs pick', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['z', { d: 0 }]],
+          op2: [['x', { p: 0 }], ['y', { d: 0 }]],
+          // Consider adding a conflict for this case.
+          expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]],
+          expectRight: null
+        }))
+      it('vs pick parent', () =>
+        xf({
+          op1: [['x', 'a', { p: 0 }], ['z', { d: 0 }]],
+          op2: [['x', { p: 0 }], ['y', { d: 0 }]],
+          expect: [['y', 'a', { p: 0 }], ['z', { d: 0 }]]
+        }))
+
+      it('vs pick and pick child', () =>
+        xf({
+          // regression
+          op1: [
+            // a -> xa, a.c -> xc
+            ['a', { p: 0 }, 'c', { p: 1 }],
+            ['xa', { d: 0 }],
+            ['xc', { d: 1 }]
+          ],
+          op2: [['a', { p: 0 }], ['b', { d: 0 }]], // a -> b
+          expectLeft: [
+            ['b', { p: 0 }, 'c', { p: 1 }],
+            ['xa', { d: 0 }],
+            ['xc', { d: 1 }]
+          ],
+          expectRight: [['b', 'c', { p: 0 }], ['xc', { d: 0 }]]
+        }))
+
+      it('vs edit', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['z', { d: 0 }]],
+          op2: ['x', { es: ['hi'] }],
+          expect: [['x', { p: 0 }], ['z', { d: 0 }]]
+        }))
+
+      it('vs delete, drop', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['y', { d: 0 }]],
+          op2: [['a', { p: 0 }], ['x', { r: 0, d: 0 }]],
+          expect: null
+        }))
+
+      it('vs delete, insert', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['y', { d: 0 }]],
+          op2: ['x', { r: 0, i: 5 }],
+          expect: null
+        }))
+
+      it(
+        'vs pick, drop to self',
+        () =>
+          xf({
+            op1: [['x', { p: 0 }], ['y', { d: 0 }]],
+            op2: [['x', { p: 0 }], ['y', { d: 0 }]],
+            expect: null
+          }),
+
+        () =>
+          xf({
+            op1: [['a', 1, { p: 0 }], ['y', { d: 0 }]],
+            op2: [['a', 1, { p: 0 }], ['y', { d: 0 }]],
+            expect: null
+          })
+      )
+
+      it('vs pick, drop', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['z', { d: 0 }]], // x->z
+          op2: [['a', { p: 0 }], ['x', { p: 1, d: 0 }], ['y', { d: 1 }]], // a->x, x->y
+          expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]],
+          expectRight: null
+        }))
+
+      it('vs pick, insert', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['z', { d: 0 }]],
+          op2: [['x', { p: 0, i: 5 }], ['y', { d: 0 }]],
+          expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]],
+          expectRight: null
+        }))
+
+      it('vs pick, edit', () => ({
+        op1: [['x', { p: 0 }], ['z', { d: 0 }]],
+        op2: [['x', { es: ['hi'], p: 0 }], ['y', { d: 0 }]],
+        expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]],
+        expectRight: null
+      }))
+    })
+
+    describe('op1 delete', () => {
+      it('vs delete', () =>
+        xf({
+          op1: ['x', { r: true }],
+          op2: ['x', { r: true }],
+          expect: null
+        }))
+      it('vs delete parent', () =>
+        xf({
+          op1: ['x', 'a', { r: true }],
+          op2: ['x', { r: true }],
+          expect: null
+        }))
+
+      it('vs pick', () =>
+        xf({
+          op1: ['x', { r: true }],
+          op2: [['x', { p: 0 }], ['y', { d: 0 }]],
+          expect: ['y', { r: true }]
+        }))
+      it('vs pick parent', () =>
+        xf({
+          op1: ['x', 'a', { r: true }],
+          op2: [['x', { p: 0 }], ['y', { d: 0 }]],
+          expect: ['y', 'a', { r: true }]
+        }))
+
+      it('vs pick and drop', () =>
+        xf({
+          op1: ['x', { r: true }],
+          op2: [['a', { p: 0 }], ['x', { d: 0, p: 1 }], ['z', { d: 1 }]],
+          expect: ['z', { r: true }]
+        }))
+
+      it('vs edit', () =>
+        xf({
+          op1: ['x', { r: true }],
+          op2: ['x', { es: ['hi'] }],
+          conflict: { type: RM_UNEXPECTED_CONTENT },
+          expect: ['x', { r: true }]
+        }))
+
+      it('vs move and insert', () =>
+        xf({
+          op1: ['a', 1, { r: true }],
+          op2: [['a', { p: 0 }], ['b', { d: 0 }, [0, { i: 5 }], [1, { i: 5 }]]],
+          expect: ['b', 3, { r: true }]
+        }))
+
+      return describe('vs pick child', function() {
+        it('move in', () =>
+          xf({
+            op1: ['x', { r: true }],
+            op2: [['a', { p: 0 }], ['x', 'y', { d: 0 }]],
+            conflict: { type: RM_UNEXPECTED_CONTENT },
+            expect: ['x', { r: true }, 'y', { r: true }]
+          })) // Also ok if its just x, r:true
+
+        it('move across', () =>
+          xf({
+            op1: ['x', { r: true }], // delete doc.x
+            op2: ['x', ['y', { p: 0 }], ['z', { d: 0 }]],
+            expect: ['x', { r: true }]
+          }))
+
+        it('move out', () =>
+          xf({
+            op1: ['x', { r: true }],
+            op2: [['x', 'y', { p: 0 }], ['y', { d: 0 }]], // move doc.x.y -> doc.y
+            expect: [['x', { r: true }], ['y', { r: true }]]
+          })) // delete doc.x and doc.y
+
+        it('multiple out', () =>
+          xf({
+            op1: ['x', { r: true }],
+            op2: [
+              ['x', 'y', { p: 0 }, 'z', { p: 1 }],
+              ['y', { d: 0 }],
+              ['z', { d: 1 }]
+            ],
+            expect: [['x', { r: true }], ['y', { r: true }], ['z', { r: true }]]
+          }))
+
+        it('chain out', () =>
+          xf({
+            op1: ['x', { r: true }],
+            op2: [
+              ['x', 'y', { p: 0 }],
+              ['y', { p: 1 }],
+              ['z', { d: 0 }, 'a', { d: 1 }]
+            ],
+            conflict: {
+              type: RM_UNEXPECTED_CONTENT,
+              op2: [['y', { p: 0 }], ['z', 'a', { d: 0 }]]
+            }, // cMv(['y'], ['z', 'a'])
+            expect: [['x', { r: true }], ['z', { r: true }, 'a', { r: true }]]
+          }))
+
+        it('mess', () =>
+          xf({
+            // yeesh
+            op1: [['x', { r: true }, 'y', 'z', { p: 0 }], ['z', { d: 0 }]],
+            op2: [['x', 'y', { p: 0 }], ['y', { d: 0 }]],
+            expect: [
+              ['x', { r: true }],
+              ['y', { r: true }, 'z', { p: 0 }],
+              ['z', { d: 0 }]
+            ]
+          }))
+      })
+    })
+
+    describe('op1 drop', () => {
+      it('vs delete parent', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]],
+          op2: ['y', { r: true }],
+          conflict: { type: RM_UNEXPECTED_CONTENT },
+          expect: ['x', { r: true }]
+        }))
+
+      it('vs a cancelled parent', () =>
+        xf({
+          // This is actually a really complicated case.
+          op1: [
+            ['x', 'y', { p: 0 }],
+            ['y', { p: 1 }],
+            ['z', { d: 0 }, 'a', { d: 1 }]
+          ],
+          op2: ['x', { r: true }],
+          conflict: {
+            type: RM_UNEXPECTED_CONTENT,
+            op1: [['y', { p: 0 }], ['z', 'a', { d: 0 }]]
+          }, // c1: cMv(['y'], ['z', 'a'])
+          expect: ['y', { r: true }]
+        }))
+
+      it('vs pick parent', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]],
+          op2: [['y', { p: 0 }], ['z', { d: 0 }]],
+          expect: [['x', { p: 0 }], ['z', 'a', { d: 0 }]]
+        }))
+
+      it('vs drop', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['z', { d: 0 }]],
+          op2: [['y', { p: 0 }], ['z', { d: 0 }]],
+          conflict: { type: DROP_COLLISION },
+          expectLeft: [['x', { p: 0 }], ['z', { r: true, d: 0 }]],
+          expectRight: ['x', { r: true }]
+        }))
+
+      it('vs drop (list)', () =>
+        xf({
+          op1: [[0, { p: 0 }], [4, { d: 0 }]],
+          op2: [[5, { d: 0 }], [10, { p: 0 }]],
+          expectLeft: [[0, { p: 0 }], [4, { d: 0 }]],
+          expectRight: [[0, { p: 0 }], [5, { d: 0 }]]
+        }))
+
+      it('vs drop (chained)', () =>
+        xf({
+          op1: [
+            ['a', { p: 1 }],
+            ['x', { p: 0 }],
+            ['z', { d: 0 }, 'a', { d: 1 }]
+          ],
+          op2: [['y', { p: 0 }], ['z', { d: 0 }]],
+          conflict: {
+            type: DROP_COLLISION,
+            op1: [['x', { p: 0 }], ['z', { d: 0 }]]
+          }, //cMv(['x'], ['z'])
+          expectLeft: [
+            ['a', { p: 0 }],
+            ['x', { p: 1 }],
+            ['z', { r: true, d: 1 }, 'a', { d: 0 }]
+          ],
+          expectRight: [['a', { r: true }], ['x', { r: true }]]
+        }))
+
+      it('vs insert', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['z', { d: 0 }]],
+          op2: ['z', { i: 5 }],
+          conflict: { type: DROP_COLLISION },
+          expectLeft: [['x', { p: 0 }], ['z', { r: true, d: 0 }]],
+          expectRight: ['x', { r: true }]
+        }))
+
+      it('vs pick (a->b->c vs b->x)', () =>
+        xf({
+          op1: [['a', { p: 0 }], ['b', { p: 1, d: 0 }], ['c', { d: 1 }]],
+          op2: [['b', { p: 0 }], ['x', { d: 0 }]],
+          expectLeft: [
+            ['a', { p: 0 }],
+            ['b', { d: 0 }],
+            ['c', { d: 1 }],
+            ['x', { p: 1 }]
+          ],
+          expectRight: [['a', { p: 0 }], ['b', { d: 0 }]]
+        }))
+
+      return describe.skip('vs move inside me', function() {
+        // Note: This is *not* blackholeing! The edits are totally fine; we
+        // just need one edit to win.
+        // The current behaviour just nukes both.
+        it('in objects', () =>
+          xf({
+            op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]],
+            op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]],
+            expectLeft: [
+              ['x', { p: 0 }, 'a', { p: 1 }],
+              ['y', { d: 1 }, 'x', { d: 0 }]
+            ],
+            expectRight: null
+          }))
+
+        it('in lists', () =>
+          xf({
+            op1: [0, { p: 0 }, 'x', { d: 0 }],
+            op2: [[0, 'y', { d: 0 }], [1, { p: 0 }]],
+            expectLeft: [0, { p: 0, d: 1 }, ['x', { d: 0 }], ['y', { p: 1 }]],
+            expectRight: null
+          }))
+
+        it('multiple', () =>
+          xf({
+            // a->x.a, b->x.b
+            op1: [
+              ['a', { p: 0 }],
+              ['b', { p: 1 }],
+              ['x', 'a', { d: 0 }, 'b', { d: 1 }]
+            ],
+            op2: [['a', 'x', { d: 0 }], ['x', { p: 0 }]], // x->a.x
+            expectLeft: [
+              ['a', { p: 0 }, 'x', { p: 1 }],
+              ['b', { p: 2 }],
+              ['x', { d: 1 }, ['a', { d: 0 }], ['b', { d: 2 }]]
+            ],
+            expectRight: null
+          }))
+      })
+    })
+
+    describe('op1 insert', () => {
+      it('vs delete parent', () =>
+        xf({
+          op1: ['y', 'a', { i: 5 }],
+          op2: ['y', { r: true }],
+          conflict: { type: RM_UNEXPECTED_CONTENT },
+          expect: null
+        }))
+
+      it('vs pick parent', () =>
+        xf({
+          op1: ['y', 'a', { i: 5 }],
+          op2: [['y', { p: 0 }], ['z', { d: 0 }]],
+          expect: ['z', 'a', { i: 5 }]
+        }))
+
+      it('vs drop', () =>
+        xf({
+          op1: ['z', { i: 5 }],
+          op2: [['y', { p: 0 }], ['z', { d: 0 }]],
+          conflict: { type: DROP_COLLISION },
+          expectLeft: ['z', { r: true, i: 5 }],
+          expectRight: null
+        }))
+
+      it('vs insert', () =>
+        xf({
+          op1: ['z', { i: 5 }],
+          op2: ['z', { i: 10 }],
+          conflict: { type: DROP_COLLISION },
+          expectLeft: ['z', { r: true, i: 5 }],
+          expectRight: null
+        }))
+
+      it('vs insert at list position', () =>
+        xf({
+          op1: [5, { i: 'hi' }],
+          op2: [5, { i: 'there' }],
+          expectLeft: [5, { i: 'hi' }],
+          expectRight: [6, { i: 'hi' }]
+        }))
+
+      it('vs identical insert', () =>
+        xf({
+          op1: ['z', { i: 5 }],
+          op2: ['z', { i: 5 }],
+          expect: null
+        }))
+
+      // This is the new setNull for setting up schemas
+      it('vs embedded inserts', function() {
+        xf({
+          op1: ['x', { i: {} }],
+          op2: ['x', { i: {} }, 'y', { i: 5 }],
+          expect: null
+        })
+
+        xf({
+          op1: ['x', { i: {} }, 'y', { i: 5 }],
+          op2: ['x', { i: {} }],
+          expect: ['x', 'y', { i: 5 }]
+        })
+
+        xf({
+          op1: ['x', { i: {} }, 'y', { i: 5 }],
+          op2: ['x', { i: {} }, 'y', { i: 5 }],
+          expect: null
+        })
+
+        return xf({
+          op1: ['x', { i: {} }, 'y', { i: 5 }],
+          op2: ['x', { i: {} }, 'y', { i: 6 }],
+          conflict: {
+            type: DROP_COLLISION,
+            op1: ['x', 'y', { i: 5 }],
+            op2: ['x', 'y', { i: 6 }]
+          },
+          expectLeft: ['x', 'y', { r: true, i: 5 }],
+          expectRight: null
+        })
+      })
+
+      it('with embedded edits', () =>
+        xf({
+          op1: [{ i: '', es: ['aaa'] }],
+          op2: [{ i: '', es: ['bbb'] }],
+          expectLeft: [{ es: ['aaa'] }],
+          expectRight: [{ es: [3, 'aaa'] }]
+        }))
+    })
+
+    describe('op1 edit', () => {
+      it('vs delete', () =>
+        xf({
+          op1: ['x', { es: ['hi'] }],
+          op2: ['x', { r: true }],
+          conflict: { type: RM_UNEXPECTED_CONTENT },
+          expect: null
+        }))
+
+      it('vs delete parent', () =>
+        xf({
+          op1: ['x', 'y', { es: ['hi'] }],
+          op2: ['x', { r: true }],
+          conflict: { type: RM_UNEXPECTED_CONTENT },
+          expect: null
+        }))
+
+      it('vs pick', () =>
+        xf({
+          op1: ['x', { es: ['hi'] }],
+          op2: [['x', { p: 0 }], ['y', { d: 0 }]],
+          expect: ['y', { es: ['hi'] }]
+        }))
+
+      it('vs edit string', () =>
+        xf({
+          op1: ['x', { es: ['ab'] }],
+          op2: ['x', { es: ['cd'] }],
+          expectLeft: ['x', { es: ['ab'] }],
+          expectRight: ['x', { es: [2, 'ab'] }]
+        }))
+
+      it('vs edit number', () =>
+        xf({
+          op1: [{ ena: 5 }],
+          op2: [{ ena: 100 }],
+          expect: [{ ena: 5 }]
+        }))
+
+      it('throws if edit types arent compatible', () =>
+        assert.throws(() => type.transform([{ es: [] }], [{ ena: 5 }], 'left')))
+
+      it('vs move and edit', () =>
+        xf({
+          op1: ['x', { es: [1, 'ab'] }],
+          op2: [['x', { p: 0 }], ['y', { d: 0, es: [{ d: 1 }, 'cd'] }]],
+          expectLeft: ['y', { es: ['ab'] }],
+          expectRight: ['y', { es: [2, 'ab'] }]
+        }))
+
+      it('throws if the type is missing', () =>
+        assert.throws(() =>
+          type.transform(
+            [{ et: 'missing', e: {} }],
+            [{ et: 'missing', e: {} }],
+            'left'
+          )
+        ))
+    })
+
+    describe('op2 cancel move', () => {
+      it('and insert', () =>
+        xf({
+          op1: ['x', { r: true }],
+          op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }, 'b', { i: 5 }]],
+          conflict: {
+            type: RM_UNEXPECTED_CONTENT,
+            op2: ['y', 'b', { i: 5 }]
+          },
+          expect: [['x', { r: true }], ['y', { r: true }, 'b', { r: true }]]
+        }))
+
+      it('and another move (rm x vs x.a -> y, q -> y.b)', () =>
+        xf({
+          op1: ['x', { r: true }],
+          op2: [
+            ['q', { p: 1 }],
+            ['x', 'a', { p: 0 }],
+            ['y', { d: 0 }, 'b', { d: 1 }]
+          ],
+          conflict: {
+            type: RM_UNEXPECTED_CONTENT,
+            op2: [['q', { p: 0 }], ['y', 'b', { d: 0 }]]
+          },
+          expect: [['x', { r: true }], ['y', { r: true }, 'b', { r: true }]]
+        }))
+    })
+
+    describe('op2 list move an op1 drop', () => {
+      it('vs op1 remove', () =>
+        xf({
+          op1: [[0, { r: true }, 'a', { i: 'hi' }], [5, { r: true }]],
+          op2: [[1, { p: 0 }], [4, { d: 0 }]],
+          expect: [[0, { r: true }], [3, 'a', { i: 'hi' }], [5, { r: true }]]
+        }))
+
+      it('vs op1 remove 2', () =>
+        xf({
+          op1: [
+            [0, { r: true }, 'a', { i: 'hi' }],
+            [1, { r: true }],
+            [2, { r: true }]
+          ],
+          op2: [[3, { p: 0 }], [4, { d: 0 }]],
+          expect: [
+            [0, { r: true }],
+            [1, { r: true }, 'a', { i: 'hi' }],
+            [2, { r: true }]
+          ]
+        }))
+
+      it('vs op1 insert before', () =>
+        xf({
+          op1: [[0, { i: 'a' }], [1, { i: 'b' }], [2, 'a', { i: 'hi' }]],
+          op2: [[0, { p: 0 }], [1, { d: 0 }]],
+          expect: [[0, { i: 'a' }], [1, { i: 'b' }], [3, 'a', { i: 'hi' }]]
+        }))
+
+      it('vs op1 insert before and replace', () =>
+        xf({
+          op1: [[0, { i: 'xx' }, 'a', { r: true }], [1, 'a', { i: 'hi' }]],
+          op2: [[0, { p: 0 }], [3, { d: 0 }]],
+          expect: [
+            [0, { i: 'xx' }],
+            [3, 'a', { r: true }],
+            [4, 'a', { i: 'hi' }]
+          ]
+        }))
+    })
+
+    describe('list', () =>
+      describe('drop', () => {
+        it('transforms by p1 drops', () =>
+          xf({
+            op1: [[5, { i: 5 }], [10, { i: 10 }]],
+            op2: [9, { i: 9 }],
+            expectLeft: [[5, { i: 5 }], [10, { i: 10 }]],
+            expectRight: [[5, { i: 5 }], [11, { i: 10 }]]
+          }))
+
+        it('transforms by p1 picks')
+        it('transforms by p2 picks')
+        it('transforms by p2 drops')
+      }))
+  })
+
+  describe('conflicts', () => {
+    describe('drop into remove / rm unexpected', () => {
+      // xfConflict does both xf(op1, op2, left) and xf(op2, op1, right), and
+      // uses invConflict. So this also tests RM_UNEXPECTED_CONTENT with each
+      // test case.
+      it('errors if you insert', () =>
+        xf({
+          op1: ['a', 'b', { i: 5 }],
+          op2: ['a', { r: true }],
+          conflict: { type: RM_UNEXPECTED_CONTENT },
+          expect: null
+        }))
+
+      it('errors if you drop', () =>
+        xf({
+          op1: [['a', { p: 0 }], ['x', 'b', { d: 0 }]],
+          op2: ['x', { r: true }],
+          conflict: { type: RM_UNEXPECTED_CONTENT },
+          expect: ['a', { r: true }]
+        }))
+
+      it('errors if you rm then insert in a child', () =>
+        xf({
+          op1: ['a', 'b', { r: true, i: 5 }],
+          op2: ['a', { r: true }],
+          conflict: {
+            type: RM_UNEXPECTED_CONTENT,
+            op1: ['a', 'b', { i: 5 }]
+          },
+          expect: null
+        }))
+
+      it('errors if the object is replaced', () =>
+        xf({
+          op1: ['a', 'b', { i: 5 }],
+          op2: ['a', { r: true, i: 10 }],
+          conflict: {
+            type: RM_UNEXPECTED_CONTENT,
+            op2: ['a', { r: true }]
+          },
+          expect: null
+        }))
+
+      it('handles a delete of the source parent by op2', () =>
+        xf({
+          op1: [['a', { p: 0 }], ['b', 'b', { d: 0 }]],
+          op2: [['a', { p: 0 }], ['b', { r: true }, 'c', { d: 0 }]],
+          conflictLeft: {
+            type: RM_UNEXPECTED_CONTENT,
+            op2: ['b', { r: true }]
+          },
+          expectLeft: ['b', 'c', { r: true }],
+          expectRight: null
+        }))
+
+      it.skip('returns symmetric errors when both ops delete the other', () =>
+        xf({
+          // The problem here is that there's two conflicts we want to return.
+          // Which one should be returned first? It'd be nice for the order of
+          // conflict returning to be symmetric - that is, if we know multiple
+          // conflicts happen, order them based on left/right. But I haven't done
+          // that, so we get different conflicts out of this in a first pass.
+          op1: [['x', { r: true }], ['y', 'a', { i: {} }]],
+          op2: [['x', 'a', { i: {} }], ['y', { r: true }]],
+          conflict: { type: RM_UNEXPECTED_CONTENT },
+          expect: ['x', { r: true }]
+        }))
+    })
+
+    describe('overlapping drop', () => {
+      it('errors if two ops insert different content into the same place in an object', () =>
+        xf({
+          op1: ['x', { i: 'hi' }],
+          op2: ['x', { i: 'yo' }],
+          conflict: { type: DROP_COLLISION },
+          expectLeft: ['x', { r: true, i: 'hi' }],
+          expectRight: null
+        }))
+
+      it('does not conflict if inserts are identical', () =>
+        xf({
+          op1: ['x', { i: 'hi' }],
+          op2: ['x', { i: 'hi' }],
+          expectLeft: null,
+          expectRight: null
+        }))
+
+      it('does not conflict if the two operations make identical moves', () =>
+        xf({
+          op1: [['a', { p: 0 }], ['x', { d: 0 }]],
+          op2: [['a', { p: 0 }], ['x', { d: 0 }]],
+          expect: null
+        })) // ??? Also ok for left: ['x', p:0, d:0]
+
+      it('does not conflict if inserts are into a list', () =>
+        xf({
+          op1: [1, { i: 'hi' }],
+          op2: [1, { i: 'yo' }],
+          expectLeft: [1, { i: 'hi' }],
+          expectRight: [2, { i: 'hi' }]
+        }))
+
+      it('errors if the inserts are at the root', () =>
+        xf({
+          op1: [{ i: 1 }],
+          op2: [{ i: 2 }],
+          conflict: { type: DROP_COLLISION },
+          expectLeft: [{ r: true, i: 1 }],
+          expectRight: null
+        }))
+
+      it('errors with insert vs drop', () =>
+        xf({
+          op1: ['x', { i: 'hi' }],
+          op2: [['a', { p: 0 }], ['x', { d: 0 }]],
+          // ????
+          conflict: { type: DROP_COLLISION },
+          expectLeft: ['x', { r: true, i: 'hi' }],
+          expectRight: null
+        }))
+
+      it('errors with drop vs insert', () =>
+        xf({
+          op1: [['a', { p: 0 }], ['x', { d: 0 }]],
+          op2: ['x', { i: 'hi' }],
+          conflict: { type: DROP_COLLISION },
+          expectLeft: [['a', { p: 0 }], ['x', { r: true, d: 0 }]],
+          expectRight: ['a', { r: true }]
+        }))
+
+      it('errors with drop vs drop', () =>
+        xf({
+          op1: [['a', { p: 0 }], ['x', { d: 0 }]],
+          op2: [['b', { p: 0 }], ['x', { d: 0 }]],
+          conflict: { type: DROP_COLLISION },
+          expectLeft: [['a', { p: 0 }], ['x', { r: true, d: 0 }]],
+          expectRight: ['a', { r: true }]
+        }))
+
+      it('errors if the two sides insert in the vacuum', () =>
+        xf({
+          op1: [['a', { p: 0 }], ['b', { d: 0 }], ['c', { i: 5 }]],
+          op2: [['a', { p: 0 }], ['b', { i: 6 }], ['c', { d: 0 }]],
+          conflictLeft: {
+            type: DROP_COLLISION,
+            op1: [['a', { p: 0 }], ['b', { d: 0 }]],
+            op2: ['b', { i: 6 }]
+          },
+          expectLeft: [['b', { r: true, d: 0 }], ['c', { p: 0, i: 5 }]],
+          conflictRight: {
+            type: DROP_COLLISION,
+            op1: ['c', { i: 5 }],
+            op2: [['a', { p: 0 }], ['c', { d: 0 }]]
+          },
+          expectRight: null
+        }))
+    })
+
+    describe('discarded edit', () => {
+      it('edit removed directly', () =>
+        xf({
+          op1: ['a', { es: [] }],
+          op2: ['a', { r: true }],
+          conflict: { type: RM_UNEXPECTED_CONTENT },
+          expect: null
+        }))
+
+      it('edit inside new content throws RM_UNEXPECTED_CONTENT', () =>
+        xf({
+          op1: ['a', 'b', { i: 'hi', es: [] }],
+          op2: ['a', { r: true }],
+          conflict: {
+            type: RM_UNEXPECTED_CONTENT,
+            op1: ['a', 'b', { i: 'hi' }]
+          },
+          expect: null
+        }))
+    })
+
+    describe('blackhole', () => {
+      it('detects and errors', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]],
+          op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]],
+          conflict: { type: BLACKHOLE },
+          expect: ['x', { r: true }, 'a', { r: true }]
+        })) // Also equivalent: ['x', r:true]
+
+      it('blackhole logic does not apply when op2 removes parent', () =>
+        xf({
+          // TODO: Although you wouldn't know it, since this result is very similar.
+          op1: [['x', { p: 0 }], ['y', 'xx', 'a', { d: 0 }]],
+          op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }, 'xx', { r: true }]],
+          conflict: {
+            type: RM_UNEXPECTED_CONTENT,
+            op2: ['y', 'xx', { r: true }]
+          },
+          expect: ['x', { r: true }, 'a', { r: true }]
+        })) // Also ok: ['x', r:true]
+
+      it('blackhole logic still applies when op2 inserts', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['y', 'a', { i: {} }, 'b', { d: 0 }]],
+          op2: [['x', 'a', { i: {} }, 'b', { d: 0 }], ['y', { p: 0 }]],
+          conflict: {
+            type: BLACKHOLE,
+            op1: [['x', { p: 0 }], ['y', 'a', 'b', { d: 0 }]],
+            op2: [['x', 'a', 'b', { d: 0 }], ['y', { p: 0 }]]
+          },
+          expect: ['x', { r: true }, 'a', { r: true }, 'b', { r: true }]
+        }))
+
+      it('blackholes items in lists correctly', () =>
+        xf({
+          op1: [1, { p: 0 }, 'a', { d: 0 }],
+          op2: [[1, 'b', { d: 0 }], [2, { p: 0 }]],
+          conflict: { type: BLACKHOLE },
+          expect: [1, { r: true }, 'b', { r: true }]
+        }))
+
+      it('blackholes items despite scrambled pick and drop slots', () =>
+        xf({
+          op1: [['a', { p: 1, d: 1 }], ['x', { p: 0 }], ['y', 'a', { d: 0 }]],
+          op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]],
+          conflict: {
+            type: BLACKHOLE,
+            op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]]
+          },
+          expect: [['a', { p: 0, d: 0 }], ['x', { r: true }, 'a', { r: true }]]
+        }))
+
+      it('handles chained blackholes', () =>
+        xf({
+          op1: [
+            ['a', { p: 0 }], // a->b.b, c->d.d
+            ['b', 'b', { d: 0 }],
+            ['c', { p: 1 }],
+            ['d', 'd', { d: 1 }]
+          ],
+          op2: [
+            ['a', 'a', { d: 1 }], // b->c.c, d->a.a
+            ['b', { p: 0 }],
+            ['c', 'c', { d: 0 }],
+            ['d', { p: 1 }]
+          ],
+          conflict: { type: BLACKHOLE },
+          // c1: cMv(['a'], ['b', 'b'])
+          // c2: cMv(['b'], ['c', 'c'])
+          expect: [
+            ['a', { r: true }, 'a', { r: true }],
+            ['c', { r: true }, 'c', { r: true }]
+          ]
+        }))
+
+      it('creates conflict return values with valid slot ids', () =>
+        xf({
+          op1: [
+            ['a', { p: 0 }],
+            ['b', { d: 0 }],
+            ['x', { p: 1 }],
+            ['y', 'a', { d: 1 }]
+          ],
+          op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]],
+          conflict: {
+            type: BLACKHOLE,
+            op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]]
+          },
+          expect: [
+            ['a', { p: 0 }],
+            ['b', { d: 0 }],
+            ['x', { r: true }, 'a', { r: true }]
+          ]
+        }))
+    })
+  })
+
+  describe('transform-old', () => {
+    it('foo', () =>
+      xf({
+        op1: [
+          ['x', ['a', { p: 0 }], ['b', { d: 0 }]],
+          ['y', ['a', { p: 1 }], ['b', { d: 1 }]]
+        ],
+        op2: ['x', { r: true }],
+        expect: ['y', ['a', { p: 0 }], ['b', { d: 0 }]]
+      }))
+
+    // it 'hard', ->
+    //   op1: ['x', [1, r:true], [2, r:true, es:['hi']]] # Edit at index 4 originally.
+    //   # move the edited string to .y[4] which
+    //   op2: [['x', 4, p:0], ['y', [2, r:true], [4, d:0]]]
+    //   expect:
+
+    describe('object edits', () =>
+      it('can reparent with some extra junk', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['y', { d: 0 }]],
+          op2: [
+            ['_a', { d: 1 }],
+            ['_x', { d: 0 }],
+            ['x', { p: 0 }, 'a', { p: 1 }]
+          ],
+          expectLeft: [['_x', { p: 0 }], ['y', { d: 0 }]],
+          expectRight: null
+        }))) // the object was moved fair and square.
+
+    describe('deletes', () => {
+      it.skip('delete parent of a move', () =>
+        xf({
+          // The current logic of transform actually just burns everything (in a
+          // consistant way of course). I'm not sure if this is better or worse -
+          // basically we'd be saying that if a move could end up in one of two places,
+          // put it in the place where it won't be killed forever. But that introduces new
+          // complexity, so I'm going to skip this for now.
+
+          // x.a -> a, delete x
+          op1: [['x', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]],
+          // x.a -> x.b.
+          op2: ['x', ['a', { p: 0 }], ['b', { d: 0 }]],
+          expect: [['x', { r: true }, 'b', { p: 0 }], ['z', { d: 0 }]]
+        })) // TODO: It would be better to do this in both cases.
+      //expectRight: ['x', r:true]
+
+      it('awful delete nonsense', () => {
+        xf({
+          op1: [['x', { r: true }], ['y', { i: 'hi' }]], // delete doc.x, insert doc.y
+          op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], // move doc.x.a -> doc.y
+          expect: [['x', { r: true }], ['y', { r: true, i: 'hi' }]]
+        }) // del doc.x and doc.y, insert doc.y
+
+        xf({
+          op1: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], // x.a -> y
+          op2: [['x', { r: true }], ['y', { i: 'hi' }]], // delete x, ins y
+          expect: null
+        })
+
+        xf({
+          op1: [10, { r: true }],
+          op2: [[5, { d: 0 }], [10, 1, { p: 0 }]],
+          expect: [[5, { r: true }], [11, { r: true }]]
+        })
+      })
+    })
+    // And how do those indexes interact with pick / drop operations??
+
+    describe('swap', () => {
+      const swap = [
+        ['a', { p: 0 }, 'b', { p: 1 }],
+        ['b', { d: 1 }, 'a', { d: 0 }]
+      ]
+
+      it('noop vs swap', () =>
+        xf({
+          op1: null,
+          op2: swap,
+          expect: null
+        }))
+
+      it('can swap two edits', () =>
+        xf({
+          op1: ['a', { es: ['a edit'] }, 'b', { es: ['b edit'] }],
+          op2: swap,
+          expect: ['b', { es: ['b edit'] }, 'a', { es: ['a edit'] }]
+        }))
+    })
+
+    describe('lists', () => {
+      it('can rewrite simple list indexes', () => {
+        xf({
+          op1: [10, { es: ['edit'] }],
+          op2: [0, { i: 'oh hi' }],
+          expect: [11, { es: ['edit'] }]
+        })
+
+        xf({
+          op1: [10, { r: true }],
+          op2: [0, { i: 'oh hi' }],
+          expect: [11, { r: true }]
+        })
+
+        xf({
+          op1: [10, { i: {} }],
+          op2: [0, { i: 'oh hi' }],
+          expect: [11, { i: {} }]
+        })
+      })
+
+      it('can change the root from an object to a list', () =>
+        xf({
+          op1: ['a', { es: ['hi'] }],
+          op2: [{ i: [], r: true }, [0, { d: 0 }], ['a', { p: 0 }]],
+          expect: [0, { es: ['hi'] }]
+        }))
+
+      it('can handle adjacent drops', () =>
+        xf({
+          op1: [[11, { i: 1 }], [12, { i: 2 }], [13, { i: 3 }]],
+          op2: [0, { r: true }],
+          expect: [[10, { i: 1 }], [11, { i: 2 }], [12, { i: 3 }]]
+        }))
+
+      it('fixes drop indexes correctly 1', () =>
+        xf({
+          op1: [[0, { r: true }], [1, { i: 'hi' }]],
+          op2: [1, { r: true }],
+          expect: [0, { r: true, i: 'hi' }]
+        }))
+
+      it('list drop vs delete uses the correct result index', () => {
+        xf({
+          op1: [2, { i: 'hi' }],
+          op2: [2, { r: true }],
+          expect: [2, { i: 'hi' }]
+        })
+
+        xf({
+          op1: [3, { i: 'hi' }],
+          op2: [2, { r: true }],
+          expect: [2, { i: 'hi' }]
+        })
+      })
+
+      it('list drop vs drop uses the correct result index', () =>
+        xf({
+          op1: [2, { i: 'hi' }],
+          op2: [2, { i: 'other' }],
+          expectLeft: [2, { i: 'hi' }],
+          expectRight: [3, { i: 'hi' }]
+        }))
+
+      it('list drop vs delete and drop', () => {
+        xf({
+          op1: [2, { i: 'hi' }],
+          op2: [2, { r: true, i: 'other' }],
+          expectLeft: [2, { i: 'hi' }],
+          expectRight: [3, { i: 'hi' }]
+        })
+
+        xf({
+          op1: [3, { i: 'hi' }],
+          op2: [[2, { r: true }], [3, { i: 'other' }]],
+          expect: [2, { i: 'hi' }]
+        })
+
+        xf({
+          op1: [4, { i: 'hi' }],
+          op2: [[2, { r: true }], [3, { i: 'other' }]],
+          expectLeft: [3, { i: 'hi' }],
+          expectRight: [4, { i: 'hi' }]
+        })
+      })
+
+      it('list delete vs drop', () => {
+        xf({
+          op1: [1, { r: true }],
+          op2: [2, { i: 'hi' }],
+          expect: [1, { r: true }]
+        })
+
+        xf({
+          op1: [2, { r: true }],
+          op2: [2, { i: 'hi' }],
+          expect: [3, { r: true }]
+        })
+
+        xf({
+          op1: [3, { r: true }],
+          op2: [2, { i: 'hi' }],
+          expect: [4, { r: true }]
+        })
+      })
+
+      it('list delete vs delete', () =>
+        xf({
+          op1: [1, { r: true }],
+          op2: [1, { r: true }],
+          expect: null
+        })) // It was already deleted.
+
+      it('fixes drop indexes correctly 2', () =>
+        xf({
+          op1: [[0, { r: true }], [1, { i: 'hi' }]],
+          op2: [2, { r: true }], // Shouldn't affect the op.
+          expect: [[0, { r: true }], [1, { i: 'hi' }]]
+        }))
+
+      it('insert vs delete parent', () =>
+        xf({
+          op1: [2, 'x', { i: 'hi' }],
+          op2: [2, { r: true }],
+          conflict: { type: RM_UNEXPECTED_CONTENT },
+          expect: null
+        }))
+
+      it('transforms against inserts in my own list', () =>
+        xf({
+          //[0,1,2,3] -> [a,0,b,1,2,3...]
+          op1: [[0, { i: 'a' }], [2, { i: 'b' }]],
+          op2: [1, { r: true }],
+          expect: [[0, { i: 'a' }], [2, { i: 'b' }]]
+        }))
+
+      it('vs cancelled op2 drop', () =>
+        xf({
+          doc: { x: { a: 'x.a' }, y: ['a', 'b', 'c'] },
+          op1: [['x', { r: true }], ['y', 3, { i: 5 }]],
+          op2: [['x', 'a', { p: 0 }], ['y', 2, { d: 0 }]],
+          expect: [['x', { r: true }], ['y', [2, { r: true }], [3, { i: 5 }]]]
+        }))
+
+      it('vs cancelled op1 drop', () =>
+        xf({
+          op1: [['x', { p: 0 }], ['y', [3, { d: 0 }], [4, { i: 5 }]]],
+          op2: ['x', { r: true }],
+          expect: ['y', 3, { i: 5 }]
+        }))
+
+      it('vs cancelled op1 pick', () =>
+        xf({
+          doc: Array.from('abcdefg'),
+          op1: [[1, { p: 0 }], [4, { r: true, i: 4 }], [6, { d: 0 }]],
+          op2: [1, { r: true }],
+          expect: [[3, { r: true }], [4, { i: 4 }]]
+        }))
+
+      it('xxxxx 1', () =>
+        diamond({
+          // TODO Regression.
+          doc: Array.from('abcdef'),
+          op1: [[1, { p: 0, i: 'AAA' }], [3, { i: 'BBB' }], [5, { d: 0 }]],
+          op2: [1, { r: true }]
+        }))
+
+      it('xxxxx 2', () =>
+        diamond({
+          doc: Array.from('abcdef'),
+          op1: [[1, { p: 0, i: 'AAA' }], [3, { d: 0 }], [5, { i: 'CCC' }]],
+          op2: [1, { r: true }]
+        }))
+    })
+
+    describe('edit', () => {
+      it('transforms edits by one another', () =>
+        xf({
+          op1: [1, { es: [2, 'hi'] }],
+          op2: [1, { es: ['yo'] }],
+          expect: [1, { es: [4, 'hi'] }]
+        }))
+
+      it('copies in ops otherwise', () =>
+        xf({
+          op1: ['x', { e: { position: 2, text: 'wai' }, et: 'simple' }],
+          op2: ['y', { r: true }],
+          expect: ['x', { e: { position: 2, text: 'wai' }, et: 'simple' }]
+        }))
+
+      it('allows edits at the root', () =>
+        xf({
+          op1: [{ e: { position: 2, text: 'wai' }, et: 'simple' }],
+          op2: [{ e: { position: 0, text: 'omg' }, et: 'simple' }],
+          expect: [{ e: { position: 5, text: 'wai' }, et: 'simple' }]
+        }))
+
+      it('applies edits in the right order', () =>
+        xf({
+          // Edits happen *after* the drop phase.
+          op1: [1, { es: [2, 'hi'] }],
+          op2: [[1, { i: {} }], [2, { es: ['yo'] }]],
+          expect: [2, { es: [4, 'hi'] }]
+        }))
+
+      it('an edit on a deleted object goes away', () =>
+        xf({
+          op1: [1, { es: [2, 'hi'] }],
+          op2: [1, { r: 'yo' }],
+          conflict: {
+            type: RM_UNEXPECTED_CONTENT,
+            op2: [1, { r: true }]
+          }, // .... It'd be better if this copied the remove.
+          expect: null
+        }))
+    })
+  })
+
+  // TODO Numbers
+
+  // ***** Test cases found by the fuzzer which have caused issues
+  describe('fuzzer tests', () => {
+    it('asdf', () =>
+      apply({
+        doc: { the: '', Twas: 'the' },
+        op: ['the', { es: [] }],
+        expect: { the: '', Twas: 'the' }
+      }))
+
+    it('does not duplicate list items from edits', () =>
+      apply({
+        doc: ['eyes'],
+        op: [0, { es: [] }],
+        expect: ['eyes']
+      }))
+
+    it('will edit the root document', () =>
+      apply({
+        doc: '',
+        op: [{ es: [] }],
+        expect: ''
+      }))
+
+    // ------ These have nothing to do with apply. TODO: Move them out of this grouping.
+
+    it('diamond', () =>
+      // TODO: Do this for all combinations.
+      diamond({
+        doc: Array.from('abcde'),
+        op1: [[0, { p: 0 }], [1, { d: 0 }]],
+        op2: [[0, { p: 0 }], [4, { d: 0 }]]
+      }))
+
+    it('shuffles lists correctly', () =>
+      xf({
+        op1: [[0, { p: 0 }], [1, { d: 0 }]],
+        op2: [[0, { p: 0 }], [10, { d: 0 }]],
+        expectLeft: [[1, { d: 0 }], [10, { p: 0 }]],
+        expectRight: null
+      }))
+
+    it('inserts before edits', () => {
+      xf({
+        op1: [0, 'x', { i: 5 }],
+        op2: [0, { i: 35 }],
+        expect: [1, 'x', { i: 5 }]
+      })
+
+      xf({
+        op1: [0, { es: [] }],
+        op2: [0, { i: 35 }],
+        expect: [1, { es: [] }]
+      })
+    })
+
+    it(
+      'duplicates become noops in a list',
+      () =>
+        xf({
+          op1: [0, { p: 0, d: 0 }],
+          op2: [0, { p: 0, d: 0 }],
+          expectLeft: [0, { p: 0, d: 0 }], // This is a bit weird.
+          expectRight: null
+        }),
+
+      () =>
+        xf({
+          op1: [0, { r: true, i: 'a' }],
+          op2: [0, { i: 'b' }],
+          expectLeft: [[0, { i: 'a' }], [1, { r: true }]],
+          expectRight: [1, { r: true, i: 'a' }]
+        }),
+
+      () =>
+        xf({
+          op1: [0, { r: true, i: 5 }],
+          op2: [0, { r: true }],
+          expect: [0, { i: 5 }]
+        })
+    )
+
+    it('p1 pick descends correctly', () => {
+      xf({
+        op1: [2, { r: true }, 1, { es: ['hi'] }],
+        op2: [3, 1, { r: true }],
+        conflict: {
+          type: RM_UNEXPECTED_CONTENT,
+          op1: [2, 1, { es: ['hi'] }]
+        },
+        expect: [2, { r: true }]
+      })
+
+      xf({
+        op1: [[2, { r: true }, 1, { es: ['hi'] }], [3, 1, { r: true }]],
+        op2: [3, 2, { r: true }],
+        conflict: {
+          type: RM_UNEXPECTED_CONTENT,
+          op1: [2, 1, { es: ['hi'] }]
+        },
+        expect: [[2, { r: true }], [3, 1, { r: true }]]
+      })
+    })
+
+    it('transforms picks correctly', () =>
+      xf({
+        op1: [1, 1, { r: true }],
+        op2: [0, { p: 0, d: 0 }],
+        expect: [1, 1, { r: true }]
+      }))
+
+    it('pick & drop vs insert after the picked item', () =>
+      xf({
+        op1: [0, { p: 0, d: 0 }], // Remove / insert works the same.
+        op2: [1, { i: 'hi' }],
+        expectLeft: [0, { p: 0, d: 0 }],
+        expectRight: [[0, { p: 0 }], [1, { d: 0 }]]
+      }))
+
+    it('pick same item vs shuffle list', () =>
+      xf({
+        op1: [1, ['x', { p: 0 }], ['y', { d: 0 }]],
+        op2: [1, { d: 0 }, 'x', { p: 0 }],
+        expectLeft: [1, { p: 0 }, 'y', { d: 0 }],
+        expectRight: null
+      }))
+
+    it('remove the same item in a list', () =>
+      xf({
+        op1: [0, { r: true }],
+        op2: [0, { r: true }],
+        expect: null
+      }))
+
+    it('rm vs hold item', () =>
+      xf({
+        op1: [0, { r: true }],
+        op2: [0, { p: 0, d: 0 }],
+        expect: [0, { r: true }]
+      }))
+
+    it('moves child elements correctly', () =>
+      xf({
+        doc: ['a', [0, 10, 20], 'c'],
+        op1: [1, 0, { p: 0, d: 0 }],
+        op2: [[1, { d: 0 }], [2, { p: 0 }]],
+        expect: [2, 0, { d: 0, p: 0 }]
+      }))
+
+    it('moves list indexes', () =>
+      xf({
+        doc: [[], 'b', 'c'],
+        op1: [[0, 'hi', { d: 0 }], [1, { p: 0 }]],
+        op2: [[0, { p: 0 }], [20, { d: 0 }]],
+        expect: [[0, { p: 0 }], [19, 'hi', { d: 0 }]]
+      }))
+
+    it('insert empty string vs insert null', () =>
+      xf({
+        doc: undefined,
+        op1: [{ i: 'hi' }],
+        op2: [{ i: null }],
+        conflict: { type: DROP_COLLISION },
+        expectLeft: [{ r: true, i: 'hi' }],
+        expectRight: null
+      }))
+
+    it('move vs emplace', () =>
+      xf({
+        doc: ['a', 'b'],
+        op1: [[0, { p: 0 }], [1, { d: 0 }]],
+        op2: [1, { p: 0, d: 0 }],
+        expectLeft: [0, { p: 0, d: 0 }],
+        expectRight: [[0, { p: 0 }], [1, { d: 0 }]]
+      }))
+
+    it('rm chases a subdocument that was moved out', () =>
+      xf({
+        doc: [['aaa']],
+        op1: [0, { r: true }],
+        op2: [0, { d: 0 }, 0, { p: 0 }], // Valid because lists.
+        expect: [[0, { r: true }], [1, { r: true }]]
+      }))
+
+    it('colliding drops', () =>
+      xf({
+        doc: ['a', 'b', {}],
+        op1: [[0, { p: 0 }], [1, 'x', { d: 0 }]], // -> ['b', x:'a']
+        op2: [1, { p: 0 }, 'x', { d: 0 }], // -> ['a', x:'b']
+        conflict: { type: DROP_COLLISION },
+        expectLeft: [[0, { p: 0 }, 'x', { d: 0 }], [1, 'x', { r: true }]],
+        expectRight: [0, { r: true }]
+      }))
+
+    it('transform crash', () =>
+      xf({
+        op1: [['the', { r: true, d: 0 }], ['whiffling', { p: 0 }]],
+        op2: ['the', { p: 0, d: 0 }],
+        expect: [['the', { d: 0, r: true }], ['whiffling', { p: 0 }]]
+      }))
+
+    it('transforms drops when the parent is moved by a remove', () =>
+      xf({
+        op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]],
+        op2: ['a', 0, { r: 1 }],
+        expect: [['a', { p: 0 }], ['b', { d: 0 }, 0, { i: 2 }]]
+      }))
+
+    it('transforms drops when the parent is moved by a drop', () =>
+      xf({
+        op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]],
+        op2: ['a', 0, { i: 1 }],
+        expect: [['a', { p: 0 }], ['b', { d: 0 }, 2, { i: 2 }]]
+      }))
+
+    it('transforms conflicting drops obfuscated by a move', () =>
+      xf({
+        op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]],
+        op2: ['a', 1, { i: 1 }],
+        expectLeft: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]],
+        expectRight: [['a', { p: 0 }], ['b', { d: 0 }, 2, { i: 2 }]]
+      }))
+
+    it('transforms edits when the parent is moved', () =>
+      xf({
+        op1: [['x', { p: 0 }], ['y', { d: 0, es: [1, 'xxx'] }]],
+        op2: ['x', { es: [{ d: 1 }, 'Z'] }],
+        expectLeft: [['x', { p: 0 }], ['y', { d: 0, es: ['xxx'] }]],
+        expectRight: [['x', { p: 0 }], ['y', { d: 0, es: [1, 'xxx'] }]]
+      }))
+
+    it('xf lots', () =>
+      xf({
+        op1: [['a', { p: 0 }], ['b', { d: 0, es: ['hi'] }]],
+        op2: [['a', { p: 0 }], ['c', { d: 0 }]],
+        expectLeft: [['b', { d: 0, es: ['hi'] }], ['c', { p: 0 }]],
+        expectRight: ['c', { es: ['hi'] }]
+      }))
+
+    it('inserts are moved back by the other op', () =>
+      xf({
+        op1: [['a', { p: 0 }], ['b', { d: 0 }, 'x', { i: 'hi' }]],
+        op2: [['a', { p: 0 }], ['c', { d: 0 }]],
+        expectLeft: [['b', { d: 0 }, 'x', { i: 'hi' }], ['c', { p: 0 }]],
+        expectRight: ['c', 'x', { i: 'hi' }]
+      }))
+
+    it('more awful edit moves', () =>
+      xf({
+        op1: [['a', { p: 0 }], ['c', { d: 0 }, 'x', { es: [] }]],
+        op2: ['a', ['b', { d: 0 }], ['x', { p: 0 }]],
+        expect: [['a', { p: 0 }], ['c', { d: 0 }, 'b', { es: [] }]]
+      }))
+
+    it('inserts null', () =>
+      xf({
+        op1: ['x', 'a', { i: null }],
+        op2: [['x', { p: 0 }], ['y', { d: 0 }]],
+        expect: ['y', 'a', { i: null }]
+      }))
+
+    it('preserves local insert if both sides delete', () =>
+      xf({
+        op1: [{ i: {}, r: true }, 'x', { i: 'yo' }],
+        op2: [{ r: true }],
+        expect: [{ i: {} }, 'x', { i: 'yo' }]
+      }))
+
+    it('handles insert/delete vs move', () =>
+      xf({
+        op1: ['a', { i: {}, r: true }, 'x', { i: 'yo' }],
+        op2: [['a', { p: 0 }], ['b', { d: 0 }]],
+        expect: [['a', { i: {} }, 'x', { i: 'yo' }], ['b', { r: true }]]
+      }))
+
+    it('insert pushes edit target', () =>
+      xf({
+        op1: [[0, { i: 'yo' }], [1, 'a', { es: [] }]],
+        op2: [0, ['a', { p: 0 }], ['b', { d: 0 }]],
+        expect: [[0, { i: 'yo' }], [1, 'b', { es: [] }]]
+      }))
+
+    it('composes simple regression', () => {
+      compose({
+        op1: [0, { p: 0, d: 0 }],
+        op2: [{ r: true }],
+        expect: [{ r: true }, 0, { r: true }]
+      })
+
+      compose({
+        op1: ['a', 1, { r: true }],
+        op2: ['a', { r: true }],
+        expect: ['a', { r: true }, 1, { r: true }]
+      })
+    })
+
+    it('ignores op2 inserts for index position after op1 insert', () =>
+      xf({
+        op1: [{ r: true, i: [] }, 0, { i: '' }],
+        op2: [0, { i: 0 }],
+        conflict: {
+          type: RM_UNEXPECTED_CONTENT,
+          op1: [{ r: true }]
+        },
+        expect: [{ r: true, i: [] }, 0, { r: true, i: '' }]
+      }))
+
+    it('edit moved inside a removed area should be removed', () =>
+      xf({
+        op1: [[0, { r: true }], [2, { es: [] }]],
+        op2: [[0, 'x', { d: 0 }], [3, { p: 0 }]],
+        conflict: {
+          type: RM_UNEXPECTED_CONTENT,
+          op1: [0, { r: true }]
+        },
+        expect: [0, { r: true }, 'x', { r: true }]
+      }))
+
+    it('advances indexes correctly with mixed numbers', () =>
+      xf({
+        op1: [
+          ['x', [0, { p: 0 }], [1, { d: 1 }]],
+          ['y', { p: 1 }],
+          ['zzz', { d: 0 }]
+        ],
+        op2: [['x', 2, { i: 'hi' }], ['y', { p: 0 }], ['z', { d: 0 }]],
+        expectLeft: [
+          ['x', [0, { p: 1 }], [1, { d: 0 }]],
+          ['z', { p: 0 }],
+          ['zzz', { d: 1 }]
+        ],
+        expectRight: [['x', 0, { p: 0 }], ['zzz', { d: 0 }]]
+      }))
+
+    it('handles index positions past cancelled drops 1', () =>
+      xf({
+        op1: [0, { r: true, i: [''] }],
+        op2: [[0, { p: 0, d: 0 }], [1, { i: 23 }]],
+        expectLeft: [0, { r: true, i: [''] }],
+        expectRight: [[0, { r: true }], [1, { i: [''] }]]
+      }))
+
+    it('handles index positions past cancelled drops 2', () =>
+      xf({
+        // This looks more complicated, but its a simpler version of the above test.
+        op1: [['a', { r: true }], ['b', 0, { i: 'hi' }]],
+        op2: [['a', { p: 0 }], ['b', [0, { d: 0 }], [1, { i: 'yo' }]]],
+        expectLeft: ['b', 0, { i: 'hi', r: true }],
+        expectRight: ['b', [0, { r: true }], [1, { i: 'hi' }]]
+      }))
+
+    it('calculates removed drop indexes correctly', () =>
+      xf({
+        op1: [[0, { i: 'hi', p: 0 }], [1, 1, { d: 0 }], [2, { r: true }]],
+        op2: [[0, { i: 'yo', p: 0 }], [1, 1, { d: 0 }]],
+        expectLeft: [
+          [0, { i: 'hi' }],
+          [1, 1, { p: 0 }],
+          [2, { r: true }, 1, { d: 0 }]
+        ],
+        expectRight: [[1, { i: 'hi' }], [2, { r: true }]]
+      }))
+
+    it('removed drop indexes calc regression', () =>
+      xf({
+        op1: [[1, { p: 0 }, 'burbled', { d: 0 }], [3, { r: true }]],
+        op2: [
+          [0, { i: 'to', r: true }],
+          [1, { p: 1 }, ['its', { d: 0 }], ['thought', { d: 1 }]],
+          [3, { p: 0 }]
+        ],
+        expectLeft: [
+          1,
+          ['burbled', { d: 0 }],
+          ['its', { r: true }],
+          ['thought', { p: 0 }]
+        ],
+        expectRight: [1, 'its', { r: true }]
+      }))
+
+    it('removed drop indexes tele to op1 pick', () =>
+      xf({
+        op1: ['a', 0, [0, { es: [] }], [2, { r: true }]],
+        op2: [
+          ['a', { p: 0 }, 0, 0, { p: 1 }],
+          ['b', { d: 0 }, 0, 1, 0, { d: 1 }]
+        ],
+        conflict: {
+          type: RM_UNEXPECTED_CONTENT,
+          op1: ['a', 0, 2, { r: true }],
+          op2: [['a', 0, 0, { p: 0 }], ['b', 0, 1, 0, { d: 0 }]]
+        },
+        expect: ['b', 0, 1, { r: true }, 0, { r: true }]
+      }))
+
+    it('tracks removed drop index teleports', () =>
+      xf({
+        // rm 0.a, move 0.b -> 0.c
+        doc: [{ a: ['a'], b: 'b' }],
+        op1: [0, ['a', { r: true }], ['b', { p: 0 }], ['c', { d: 0 }]], // [{c:'b'}]
+        op2: [0, { d: 0, p: 1 }, [0, { d: 1 }], ['a', { p: 0 }]], // [[{b:'b'}, 'a']]
+        conflict: {
+          type: RM_UNEXPECTED_CONTENT,
+          op1: [0, 'a', { r: true }],
+          op2: [0, { p: 0 }, 0, { d: 0 }]
+        },
+        expect: [0, { r: true }, 0, { r: true }]
+      }))
+
+    it('handles transforming past cancelled move', () =>
+      xf({
+        op1: [[0, { r: true }], [10, { i: [''] }]],
+        op2: [0, { p: 0, d: 0 }],
+        expect: [[0, { r: true }], [10, { i: [''] }]]
+      }))
+
+    it('correctly adjusts indexes in another fuzzer great', () =>
+      xf({
+        op1: [[0, { d: 0, r: true }], [3, { p: 0 }]],
+        op2: [[0, { p: 0 }], [3, { d: 0 }]],
+        expect: [[0, { d: 0 }], [2, { p: 0 }], [3, { r: true }]]
+      }))
+
+    it('op2 moves into something op1 removes and op1 moves into that', () =>
+      xf({
+        op1: [['a', { r: true }, 'aa', { p: 0 }], ['b', 'x', { d: 0 }]],
+        op2: [['a', 'bb', { d: 0 }], ['b', { p: 0 }]],
+        conflict: {
+          type: RM_UNEXPECTED_CONTENT,
+          op1: ['a', { r: true }]
+        },
+        expect: ['a', { r: true }, ['aa', { r: true }], ['bb', { r: true }]]
+      })) // Also ok if we miss the second rs.
+
+    it('op2 moves into op1 remove edge cases', () => {
+      // Sorry not minified.
+      xf({
+        op1: [
+          'Came',
+          0,
+          [0, { r: true }, 'he', { p: 0 }],
+          [1, { d: 0 }, 0, { i: 'time' }]
+        ],
+        op2: [
+          'Came',
+          0,
+          [0, 'he', [0, { d: 0 }], [1, { es: [] }]],
+          [1, { p: 0 }]
+        ],
+        expectLeft: [
+          'Came',
+          0,
+          0,
+          { r: true, d: 0 },
+          [0, { i: 'time' }],
+          ['he', { p: 0 }]
+        ],
+        expectRight: [
+          'Came',
+          0,
+          0,
+          { r: true, d: 0 },
+          [1, { i: 'time' }],
+          ['he', { p: 0 }]
+        ]
+      })
+
+      xf({
+        op1: [[0, [1, { p: 0 }], [2, { r: true }]], [1, 'xxx', { d: 0 }]],
+        op2: [0, 1, { i: {}, p: 0 }, 'b', { d: 0 }],
+        expectLeft: [
+          [0, [1, 'b', { p: 0 }], [2, { r: true }]],
+          [1, 'xxx', { d: 0 }]
+        ],
+        expectRight: [0, 2, { r: true }]
+      })
+    })
+
+    it('translates indexes correctly in this fuzzer find', () =>
+      xf({
+        op1: [0, { p: 0 }, 'x', { d: 0 }],
+        op2: [[0, { p: 0, d: 0 }], [1, { i: 'y' }]],
+        expectLeft: [[0, { p: 0 }], [1, 'x', { d: 0 }]],
+        expectRight: null
+      }))
+
+    it('buries children of blackholed values', () =>
+      xf({
+        op1: [
+          [0, ['a', { p: 0 }], ['b', { d: 0 }], ['c', { d: 1 }]],
+          [1, { p: 1 }]
+        ],
+        op2: [0, { p: 0 }, 'x', { d: 0 }],
+        // This is a bit interesting. The question is, which op2 picks and drops
+        // should we include in the output? For now the answer is that we include
+        // anything in both ops thats going to end up inside the blackholed
+        // content.
+        conflict: { type: BLACKHOLE },
+
+        // op1: [[0, 'c', d:0], [1, p:0]]
+        expect: [0, { r: true }, 'x', { r: true }]
+      }))
+
+    it('does not conflict when removed target gets moved inside removed container', () => {
+      // This edge case is interesting because we don't generate the same
+      // conflicts on left and right. We want our move of a.x to escape the
+      // object before removing it, but when we're right, the other operation's
+      // move holds the object and we get an unexpected rm conflict.
+      xf({
+        op1: [['a', { r: true }, 'x', { p: 0 }], ['b', { d: 0 }]],
+        op2: ['a', ['x', { p: 0 }], ['y', { d: 0 }]],
+        conflictRight: {
+          type: RM_UNEXPECTED_CONTENT,
+          op1: ['a', { r: true }]
+        },
+        expectLeft: [['a', { r: true }, 'y', { p: 0 }], ['b', { d: 0 }]],
+        expectRight: ['a', { r: true }, 'y', { r: true }]
+      })
+
+      xf({
+        op1: [['a', { r: true }, 1, { p: 0 }], ['b', { d: 0 }]],
+        op2: ['a', [0, { d: 0 }], [1, { p: 0 }]],
+        expectLeft: [['a', { r: true }, 0, { p: 0 }], ['b', { d: 0 }]],
+        conflictRight: {
+          type: RM_UNEXPECTED_CONTENT,
+          op1: ['a', { r: true }]
+        },
+        expectRight: ['a', { r: true }, 0, { r: true }]
+      })
+
+      // TODO have a look at this - doesn't seem right to just define an object
+      {
+        expect: [['a', { r: true }, 0, { p: 0 }], ['b', { d: 0 }]]
+      }
+    })
+
+    it('compose copies op2 edit data', () =>
+      compose({
+        op1: ['a', { r: true }],
+        op2: [['x', { p: 0 }], ['y', { d: 0 }, 'b', { es: [] }]],
+        expect: [
+          ['a', { r: true }],
+          ['x', { p: 0 }],
+          ['y', { d: 0 }, 'b', { es: [] }]
+        ]
+      }))
+
+    it('does not conflict when the dest is salvaged', () =>
+      xf({
+        op1: [['a', { p: 0 }], ['b', { i: 'hi' }], ['c', { d: 0 }]],
+        op2: [['a', { p: 0 }], ['b', { d: 0 }]],
+        expectLeft: [['b', { p: 0, i: 'hi' }], ['c', { d: 0 }]],
+        conflictRight: {
+          type: DROP_COLLISION,
+          op1: ['b', { i: 'hi' }]
+        },
+        expectRight: null
+      }))
+
+    it('does not conflict on identical r/i pairs', () =>
+      xf({
+        op1: [{ i: [], r: true }],
+        op2: [{ i: [], r: true }],
+        expect: null
+      }))
+
+    it('allows embedded edits in identical r/i', () =>
+      xf({
+        op1: [{ r: true, i: '', es: [] }],
+        op2: [{ r: true, i: '' }],
+        expect: [{ es: [] }]
+      }))
+
+    it('does not conflict on identical r/i pairs with identical drops inside', () =>
+      xf({
+        op1: [{ i: {}, r: true }, 'a', { i: 'a' }],
+        op2: [{ i: {}, r: true }, 'a', { i: 'a' }],
+        expect: null
+      }))
+
+    it('generates a DROP_COLLISION on children', () =>
+      xf({
+        op1: [{ i: {}, r: true }, 'a', { i: 'a' }],
+        op2: [{ i: {}, r: true }, 'a', { i: 'b' }],
+        conflict: {
+          type: DROP_COLLISION,
+          op1: ['a', { i: 'a' }],
+          op2: ['a', { i: 'b' }]
+        },
+        expectLeft: ['a', { r: true, i: 'a' }],
+        expectRight: null
+      }))
+
+    it('Transforms edit moves into the right dest', () =>
+      xf({
+        op1: [
+          0,
+          { p: 0, d: 0 },
+          // These parts are all needed for some reason.
+          [0, { i: 1 }],
+          [1, { r: true }],
+          [3, { es: [] }]
+        ],
+        op2: [0, [0, { d: 0 }], [3, { p: 0 }]],
+        expectLeft: [
+          0,
+          { p: 0, d: 0 },
+          [0, { i: 1 }],
+          [1, { es: [] }],
+          [2, { r: true }]
+        ],
+        expectRight: [
+          0,
+          { p: 0, d: 0 },
+          [0, { es: [] }],
+          [1, { i: 1 }],
+          [2, { r: true }]
+        ]
+      }))
+
+    it('adjusts indexes of pick -> drop', () =>
+      xf({
+        op1: [0, { p: 0, d: 0 }],
+        op2: [[0, { i: 'yo', p: 0 }], [1, { d: 0 }]],
+        expectLeft: [[0, { d: 0 }], [1, { p: 0 }]],
+        expectRight: null
+      }))
+
+    it('clears output outDrop when theres no pick', () =>
+      xf({
+        // Again, not minimized. We return the right data, we were just double-
+        // descending into outDrop.
+        op1: [['the', { d: 0, p: 0 }], ['toves', { r: true }]],
+        op2: [
+          ['bird', { d: 0 }],
+          ['slain', { d: 1 }],
+          ['the', { p: 1 }],
+          ['toves', { p: 0 }]
+        ],
+        expectLeft: [
+          ['bird', { r: true }],
+          ['slain', { p: 0 }],
+          ['the', { d: 0 }]
+        ],
+        expectRight: ['bird', { r: true }]
+      }))
+
+    it('pushes drop indexes by other held items', () =>
+      xf({
+        op1: [[0, { p: 0 }], [1, [0, { i: 'hi' }], [1, { d: 0, es: [] }]]],
+        op2: [[0, { p: 1 }, 1, { d: 0 }, 2, { d: 1 }], [2, { p: 0 }]],
+        expectLeft: [
+          0,
+          1,
+          [0, { i: 'hi' }],
+          [1, { d: 0, es: [] }],
+          [2, { p: 0 }]
+        ],
+        expectRight: [0, 1, [0, { i: 'hi' }], [3, { es: [] }]]
+      }))
+
+    it('composes correctly with lots of removes', () =>
+      compose({
+        op1: [3, 1, { r: true }],
+        op2: [[0, { es: [] }], [1, { r: true, es: [] }], [2, { r: true }]],
+        expect: [
+          [0, { es: [] }],
+          [1, { es: [], r: true }],
+          [2, { r: true }],
+          [3, 1, { r: true }]
+        ]
+      }))
+
+    it('does not descend twice when p/r on an identical insert', () =>
+      xf({
+        op1: [['a', { p: 0, i: '' }], ['b', { d: 0 }]],
+        op2: ['a', { r: true, i: '' }],
+        expect: null
+      }))
+
+    it('conflicts underneath a moved / inserted child', () =>
+      xf({
+        op1: [['a', { p: 0, i: {} }, 'x', { i: 5 }], ['b', { d: 0 }]],
+        op2: ['a', { r: true, i: {} }, 'x', { i: 6 }],
+        conflict: {
+          type: DROP_COLLISION,
+          op1: ['a', 'x', { i: 5 }],
+          op2: ['a', 'x', { i: 6 }]
+        },
+        expectLeft: ['a', 'x', { r: true, i: 5 }],
+        expectRight: null
+      }))
+
+    it('clears drop2 in transform moves', () =>
+      xf({
+        doc: [{ b: { a: 'hi' } }],
+        op1: [0, { d: 0 }, ['a', { es: [] }], ['b', { p: 0 }]],
+        op2: [0, 'b', ['a', { p: 0 }], ['b', { d: 0 }]],
+        expect: [0, { d: 0 }, 'b', { p: 0, es: [] }]
+      }))
+
+    it('descends correctly when op2 picks and drops', () =>
+      xf({
+        op1: [
+          ['b', { d: 0 }, [1, { es: [] }], [2, { i: null }]],
+          ['e', { p: 0 }]
+        ],
+        op2: [{ p: 0, d: 0 }, 'e', 1, { p: 1, d: 1 }],
+        expectLeft: [
+          ['b', { d: 0 }, [1, { i: null }], [2, { es: [] }]],
+          ['e', { p: 0 }]
+        ],
+        expectRight: [
+          ['b', { d: 0 }, [1, { es: [] }], [2, { i: null }]],
+          ['e', { p: 0 }]
+        ]
+      }))
+
+    it('composes a pick out of the insert', () =>
+      compose({
+        op1: [{ i: [5, { x: 6 }] }],
+        op2: [[0, { r: true }, 'c', { d: 0 }], [1, 'x', { p: 0 }]],
+        // expect: [{i: [{c: 6}]}]
+        expect: [{ i: [{}] }, 0, 'c', { i: 6 }]
+      }))
+
+    it('is not overeager to remove intermediate literal array items', () =>
+      compose({
+        op1: [[0, { i: ['a', 'b'] }, 0, { p: 0 }], [1, 0, { d: 0 }]],
+        op2: [0, { r: ['a'] }, 1, { r: 'b' }],
+        expect: [0, 0, { d: 0, p: 0 }]
+      }))
+
+    it('descends down insert indexes correctly', () =>
+      compose({
+        op1: [{ i: [{}, 'a'] }, 1, { i: 'b' }],
+        op2: [[1, { r: 'b' }], [2, { r: 'a' }]],
+        expect: [{ i: [{}] }]
+      }))
+
+    it('handles composes with ena: 0', () =>
+      compose({
+        op1: [{ i: 10 }],
+        op2: [{ ena: 0 }],
+        expect: [{ i: 10, ena: 0 }]
+      })) // Also ok: just discarding the ena:0.
+
+    it('handles rm parent with cross move', () =>
+      compose({
+        op1: [['a', { p: 0 }], ['b', 1, { d: 0 }]],
+        op2: [['b', { r: true }, 1, { p: 0 }], ['c', { d: 0 }]],
+        expect: [['a', { p: 0 }], ['b', { r: true }], ['c', { d: 0 }]]
+      }))
+
+    it('lets you remove children of an op at 2 levels', () =>
+      compose({
+        op1: [{ i: ['a', { x: 'hi' }] }],
+        op2: [{ r: true }, 1, 'x', { r: true }],
+        expect: null
+      }))
+
+    it('discards op1 inserts inside a removed chunk', () =>
+      compose({
+        op1: ['y', [1, { i: 'x' }], [2, { i: ['a', 'b'] }]],
+        op2: [{ r: true }, 'y', 2, 0, { r: true }],
+        expect: [{ r: true }]
+      }))
+
+    it('handles deeply nested blackhole operations', () =>
+      xf({
+        op1: [
+          ['x', { p: 0 }],
+          ['y', ['a', ['j', { p: 1 }], ['k', { d: 1 }]], ['b', { d: 0 }]]
+        ],
+        op2: [
+          ['x', 'xx', { d: 0 }, 'j', 'jj', { d: 1 }],
+          ['y', { p: 1 }, 'a', { p: 0 }]
+        ],
+        conflict: { type: BLACKHOLE },
+        expect: ['x', { r: true }, 'xx', { r: true }, 'j', 'jj', { r: true }]
+      }))
+
+    it('does not list removed op1 moves in the blackhole info', () =>
+      xf({
+        op1: [
+          ['a', ['j', { d: 0 }], ['k', { d: 1 }]],
+          ['b', { p: 0 }, 'z', 0, { p: 1 }]
+        ],
+        op2: [['a', { p: 0 }], ['b', ['y', { d: 0 }], ['z', { r: true }]]],
+        conflict: {
+          type: BLACKHOLE,
+          op1: [['a', 'j', { d: 0 }], ['b', { p: 0 }]],
+          op2: [['a', { p: 0 }], ['b', 'y', { d: 0 }]]
+        },
+        expect: ['b', { r: true }, 'y', { r: true }]
+      }))
+
+    it('handles overlapping pick in blackholes', () =>
+      xf({
+        // This looks complicated, but its really not so bad. Its:
+        // a->b.0, a.x -> z
+        // vs
+        // b -> a.x -> a.y
+        //
+        // Its a bit twisty because we're both picking up the same element and
+        // putting it in different places. This is why we have different left and
+        // right results.
+        op1: [
+          ['a', { p: 1 }, 'x', { p: 0 }],
+          ['b', 0, { d: 1 }],
+          ['z', { d: 0 }]
+        ],
+        op2: [['a', ['x', { d: 0, p: 1 }], ['y', { d: 1 }]], ['b', { p: 0 }]],
+        conflictLeft: {
+          type: BLACKHOLE,
+          op1: [['a', { p: 0 }], ['b', 0, { d: 0 }]],
+          op2: [['a', 'x', { d: 0 }], ['b', { p: 0 }]]
+        },
+        expectLeft: [
+          ['a', { r: true }, ['x', { r: true }], ['y', { p: 0 }]],
+          ['z', { d: 0 }]
+        ],
+        conflictRight: {
+          type: BLACKHOLE,
+          op1: [['a', { p: 0 }], ['b', 0, { d: 0 }]]
+        },
+        expectRight: ['a', { r: true }, ['x', { r: true }], ['y', { r: true }]]
+      }))
+  })
+})
diff --git a/yarn.lock b/yarn.lock
index e106f3f..eec7e90 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,11 @@
 # yarn lockfile v1
 
 
+ansi-regex@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+  integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
+
 balanced-match@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -25,20 +30,28 @@ buffer-from@^1.0.0:
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
   integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
 
-coffeescript@^2.3.2:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-2.3.2.tgz#e854a7020dfe47b7cf4dd412042e32ef1e269810"
-  integrity sha512-YObiFDoukx7qPBi/K0kUKyntEZDfBQiqs/DbrR1xzASKOBjGT7auD85/DiPeRr9k++lRj7l3uA9TNMLfyfcD/Q==
+cli-progress@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-2.1.1.tgz#45ee1b143487c19043a3262131ccb4676f87f032"
+  integrity sha512-TSJw3LY9ZRSis7yYzQ7flIdtQMbacd9oYoiFphJhI4SzgmqF0zErO+uNv0lbUjk1L4AGfHQJ4OVYYzW+JV66KA==
+  dependencies:
+    colors "^1.1.2"
+    string-width "^2.1.1"
+
+colors@^1.1.2:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
+  integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==
 
 commander@2.15.1:
   version "2.15.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
   integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
 
-commander@~2.17.1:
-  version "2.17.1"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
-  integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
+commander@^2.19.0:
+  version "2.20.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
+  integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
 
 concat-map@0.0.1:
   version "0.0.1"
@@ -107,6 +120,11 @@ inherits@2:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
   integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
 
+is-fullwidth-code-point@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+  integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
+
 minimatch@3.0.4, minimatch@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -156,9 +174,12 @@ once@^1.3.0:
     wrappy "1"
 
 ot-fuzzer@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/ot-fuzzer/-/ot-fuzzer-1.1.0.tgz#b556a799117ceeb458794ef376f34b985f62dc8d"
-  integrity sha512-48w+h509Sq48JMsit5/qPCxhWIzztS52DkhIztqiFV0UT2MA6BWu2JiLHSw6cLren1O5IWhNjU7LObOmVOAFGg==
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/ot-fuzzer/-/ot-fuzzer-1.2.1.tgz#41f70305fdd1d55268f6cc169c14c0eb9e5fb31c"
+  integrity sha512-dOm+Wb1Mqrw8ql5ksiZFf3Bdsruj5r4mHaa3COqtbg0ClkGjxZXwMGgMLjWslq/b0maeiw5yvYwIdH6As4svHg==
+  dependencies:
+    cli-progress "^2.1.1"
+    seedrandom "^2.4.4"
 
 ot-simple@^1.0.0:
   version "1.0.0"
@@ -182,10 +203,15 @@ path-is-absolute@^1.0.0:
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
   integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
 
-source-map-support@~0.5.6:
-  version "0.5.9"
-  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f"
-  integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==
+seedrandom@^2.4.4:
+  version "2.4.4"
+  resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.4.tgz#b25ea98632c73e45f58b77cfaa931678df01f9ba"
+  integrity sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==
+
+source-map-support@~0.5.10:
+  version "0.5.12"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
+  integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==
   dependencies:
     buffer-from "^1.0.0"
     source-map "^0.6.0"
@@ -195,6 +221,21 @@ source-map@^0.6.0, source-map@~0.6.1:
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
+string-width@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+  dependencies:
+    is-fullwidth-code-point "^2.0.0"
+    strip-ansi "^4.0.0"
+
+strip-ansi@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+  integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
+  dependencies:
+    ansi-regex "^3.0.0"
+
 supports-color@5.4.0:
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
@@ -203,13 +244,13 @@ supports-color@5.4.0:
     has-flag "^3.0.0"
 
 terser@^3.11.0:
-  version "3.11.0"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-3.11.0.tgz#60782893e1f4d6788acc696351f40636d0e37af0"
-  integrity sha512-5iLMdhEPIq3zFWskpmbzmKwMQixKmTYwY3Ox9pjtSklBLnHiuQ0GKJLhL1HSYtyffHM3/lDIFBnb82m9D7ewwQ==
+  version "3.17.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2"
+  integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==
   dependencies:
-    commander "~2.17.1"
+    commander "^2.19.0"
     source-map "~0.6.1"
-    source-map-support "~0.5.6"
+    source-map-support "~0.5.10"
 
 unicount@^1.0.0:
   version "1.0.0"
