doc_test.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. (function() {
  2. // A minilanguage for instantiating linked CodeMirror instances and Docs
  3. function instantiateSpec(spec, place, opts) {
  4. var names = {}, pos = 0, l = spec.length, editors = [];
  5. while (spec) {
  6. var m = spec.match(/^(\w+)(\*?)(?:='([^\']*)'|<(~?)(\w+)(?:\/(\d+)-(\d+))?)\s*/);
  7. var name = m[1], isDoc = m[2], cur;
  8. if (m[3]) {
  9. cur = isDoc ? CodeMirror.Doc(m[3]) : CodeMirror(place, clone(opts, {value: m[3]}));
  10. } else {
  11. var other = m[5];
  12. if (!names.hasOwnProperty(other)) {
  13. names[other] = editors.length;
  14. editors.push(CodeMirror(place, opts));
  15. }
  16. var doc = editors[names[other]].linkedDoc({
  17. sharedHist: !m[4],
  18. from: m[6] ? Number(m[6]) : null,
  19. to: m[7] ? Number(m[7]) : null
  20. });
  21. cur = isDoc ? doc : CodeMirror(place, clone(opts, {value: doc}));
  22. }
  23. names[name] = editors.length;
  24. editors.push(cur);
  25. spec = spec.slice(m[0].length);
  26. }
  27. return editors;
  28. }
  29. function clone(obj, props) {
  30. if (!obj) return;
  31. clone.prototype = obj;
  32. var inst = new clone();
  33. if (props) for (var n in props) if (props.hasOwnProperty(n))
  34. inst[n] = props[n];
  35. return inst;
  36. }
  37. function eqAll(val) {
  38. var end = arguments.length, msg = null;
  39. if (typeof arguments[end-1] == "string")
  40. msg = arguments[--end];
  41. if (i == end) throw new Error("No editors provided to eqAll");
  42. for (var i = 1; i < end; ++i)
  43. eq(arguments[i].getValue(), val, msg)
  44. }
  45. function testDoc(name, spec, run, opts, expectFail) {
  46. if (!opts) opts = {};
  47. return test("doc_" + name, function() {
  48. var place = document.getElementById("testground");
  49. var editors = instantiateSpec(spec, place, opts);
  50. var successful = false;
  51. try {
  52. run.apply(null, editors);
  53. successful = true;
  54. } finally {
  55. if (!successful || verbose) {
  56. place.style.visibility = "visible";
  57. } else {
  58. for (var i = 0; i < editors.length; ++i)
  59. if (editors[i] instanceof CodeMirror)
  60. place.removeChild(editors[i].getWrapperElement());
  61. }
  62. }
  63. }, expectFail);
  64. }
  65. var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
  66. function testBasic(a, b) {
  67. eqAll("x", a, b);
  68. a.setValue("hey");
  69. eqAll("hey", a, b);
  70. b.setValue("wow");
  71. eqAll("wow", a, b);
  72. a.replaceRange("u\nv\nw", Pos(0, 3));
  73. b.replaceRange("i", Pos(0, 4));
  74. b.replaceRange("j", Pos(2, 1));
  75. eqAll("wowui\nv\nwj", a, b);
  76. }
  77. testDoc("basic", "A='x' B<A", testBasic);
  78. testDoc("basicSeparate", "A='x' B<~A", testBasic);
  79. testDoc("sharedHist", "A='ab\ncd\nef' B<A", function(a, b) {
  80. a.replaceRange("x", Pos(0));
  81. b.replaceRange("y", Pos(1));
  82. a.replaceRange("z", Pos(2));
  83. eqAll("abx\ncdy\nefz", a, b);
  84. a.undo();
  85. a.undo();
  86. eqAll("abx\ncd\nef", a, b);
  87. a.redo();
  88. eqAll("abx\ncdy\nef", a, b);
  89. b.redo();
  90. eqAll("abx\ncdy\nefz", a, b);
  91. a.undo(); b.undo(); a.undo(); a.undo();
  92. eqAll("ab\ncd\nef", a, b);
  93. }, null, ie_lt8);
  94. testDoc("undoIntact", "A='ab\ncd\nef' B<~A", function(a, b) {
  95. a.replaceRange("x", Pos(0));
  96. b.replaceRange("y", Pos(1));
  97. a.replaceRange("z", Pos(2));
  98. a.replaceRange("q", Pos(0));
  99. eqAll("abxq\ncdy\nefz", a, b);
  100. a.undo();
  101. a.undo();
  102. eqAll("abx\ncdy\nef", a, b);
  103. b.undo();
  104. eqAll("abx\ncd\nef", a, b);
  105. a.redo();
  106. eqAll("abx\ncd\nefz", a, b);
  107. a.redo();
  108. eqAll("abxq\ncd\nefz", a, b);
  109. a.undo(); a.undo(); a.undo(); a.undo();
  110. eqAll("ab\ncd\nef", a, b);
  111. b.redo();
  112. eqAll("ab\ncdy\nef", a, b);
  113. });
  114. testDoc("undoConflict", "A='ab\ncd\nef' B<~A", function(a, b) {
  115. a.replaceRange("x", Pos(0));
  116. a.replaceRange("z", Pos(2));
  117. // This should clear the first undo event in a, but not the second
  118. b.replaceRange("y", Pos(0));
  119. a.undo(); a.undo();
  120. eqAll("abxy\ncd\nef", a, b);
  121. a.replaceRange("u", Pos(2));
  122. a.replaceRange("v", Pos(0));
  123. // This should clear both events in a
  124. b.replaceRange("w", Pos(0));
  125. a.undo(); a.undo();
  126. eqAll("abxyvw\ncd\nefu", a, b);
  127. });
  128. testDoc("doubleRebase", "A='ab\ncd\nef\ng' B<~A C<B", function(a, b, c) {
  129. c.replaceRange("u", Pos(3));
  130. a.replaceRange("", Pos(0, 0), Pos(1, 0));
  131. c.undo();
  132. eqAll("cd\nef\ng", a, b, c);
  133. });
  134. testDoc("undoUpdate", "A='ab\ncd\nef' B<~A", function(a, b) {
  135. a.replaceRange("x", Pos(2));
  136. b.replaceRange("u\nv\nw\n", Pos(0, 0));
  137. a.undo();
  138. eqAll("u\nv\nw\nab\ncd\nef", a, b);
  139. a.redo();
  140. eqAll("u\nv\nw\nab\ncd\nefx", a, b);
  141. a.undo();
  142. eqAll("u\nv\nw\nab\ncd\nef", a, b);
  143. b.undo();
  144. a.redo();
  145. eqAll("ab\ncd\nefx", a, b);
  146. a.undo();
  147. eqAll("ab\ncd\nef", a, b);
  148. });
  149. testDoc("undoKeepRanges", "A='abcdefg' B<A", function(a, b) {
  150. var m = a.markText(Pos(0, 1), Pos(0, 3), {className: "foo"});
  151. b.replaceRange("x", Pos(0, 0));
  152. eqCharPos(m.find().from, Pos(0, 2));
  153. b.replaceRange("yzzy", Pos(0, 1), Pos(0));
  154. eq(m.find(), null);
  155. b.undo();
  156. eqCharPos(m.find().from, Pos(0, 2));
  157. b.undo();
  158. eqCharPos(m.find().from, Pos(0, 1));
  159. });
  160. testDoc("longChain", "A='uv' B<A C<B D<C", function(a, b, c, d) {
  161. a.replaceSelection("X");
  162. eqAll("Xuv", a, b, c, d);
  163. d.replaceRange("Y", Pos(0));
  164. eqAll("XuvY", a, b, c, d);
  165. });
  166. testDoc("broadCast", "B<A C<A D<A E<A", function(a, b, c, d, e) {
  167. b.setValue("uu");
  168. eqAll("uu", a, b, c, d, e);
  169. a.replaceRange("v", Pos(0, 1));
  170. eqAll("uvu", a, b, c, d, e);
  171. });
  172. // A and B share a history, C and D share a separate one
  173. testDoc("islands", "A='x\ny\nz' B<A C<~A D<C", function(a, b, c, d) {
  174. a.replaceRange("u", Pos(0));
  175. d.replaceRange("v", Pos(2));
  176. b.undo();
  177. eqAll("x\ny\nzv", a, b, c, d);
  178. c.undo();
  179. eqAll("x\ny\nz", a, b, c, d);
  180. a.redo();
  181. eqAll("xu\ny\nz", a, b, c, d);
  182. d.redo();
  183. eqAll("xu\ny\nzv", a, b, c, d);
  184. });
  185. testDoc("unlink", "B<A C<A D<B", function(a, b, c, d) {
  186. a.setValue("hi");
  187. b.unlinkDoc(a);
  188. d.setValue("aye");
  189. eqAll("hi", a, c);
  190. eqAll("aye", b, d);
  191. a.setValue("oo");
  192. eqAll("oo", a, c);
  193. eqAll("aye", b, d);
  194. });
  195. testDoc("bareDoc", "A*='foo' B*<A C<B", function(a, b, c) {
  196. is(a instanceof CodeMirror.Doc);
  197. is(b instanceof CodeMirror.Doc);
  198. is(c instanceof CodeMirror);
  199. eqAll("foo", a, b, c);
  200. a.replaceRange("hey", Pos(0, 0), Pos(0));
  201. c.replaceRange("!", Pos(0));
  202. eqAll("hey!", a, b, c);
  203. b.unlinkDoc(a);
  204. b.setValue("x");
  205. eqAll("x", b, c);
  206. eqAll("hey!", a);
  207. });
  208. testDoc("swapDoc", "A='a' B*='b' C<A", function(a, b, c) {
  209. var d = a.swapDoc(b);
  210. d.setValue("x");
  211. eqAll("x", c, d);
  212. eqAll("b", a, b);
  213. });
  214. testDoc("docKeepsScroll", "A='x' B*='y'", function(a, b) {
  215. addDoc(a, 200, 200);
  216. a.scrollIntoView(Pos(199, 200));
  217. var c = a.swapDoc(b);
  218. a.swapDoc(c);
  219. var pos = a.getScrollInfo();
  220. is(pos.left > 0, "not at left");
  221. is(pos.top > 0, "not at top");
  222. });
  223. testDoc("copyDoc", "A='u'", function(a) {
  224. var copy = a.getDoc().copy(true);
  225. a.setValue("foo");
  226. copy.setValue("bar");
  227. var old = a.swapDoc(copy);
  228. eq(a.getValue(), "bar");
  229. a.undo();
  230. eq(a.getValue(), "u");
  231. a.swapDoc(old);
  232. eq(a.getValue(), "foo");
  233. eq(old.historySize().undo, 1);
  234. eq(old.copy(false).historySize().undo, 0);
  235. });
  236. testDoc("docKeepsMode", "A='1+1'", function(a) {
  237. var other = CodeMirror.Doc("hi", "text/x-markdown");
  238. a.setOption("mode", "text/javascript");
  239. var old = a.swapDoc(other);
  240. eq(a.getOption("mode"), "text/x-markdown");
  241. eq(a.getMode().name, "markdown");
  242. a.swapDoc(old);
  243. eq(a.getOption("mode"), "text/javascript");
  244. eq(a.getMode().name, "javascript");
  245. });
  246. testDoc("subview", "A='1\n2\n3\n4\n5' B<~A/1-3", function(a, b) {
  247. eq(b.getValue(), "2\n3");
  248. eq(b.firstLine(), 1);
  249. b.setCursor(Pos(4));
  250. eqCharPos(b.getCursor(), Pos(2, 1));
  251. a.replaceRange("-1\n0\n", Pos(0, 0));
  252. eq(b.firstLine(), 3);
  253. eqCharPos(b.getCursor(), Pos(4, 1));
  254. a.undo();
  255. eqCharPos(b.getCursor(), Pos(2, 1));
  256. b.replaceRange("oyoy\n", Pos(2, 0));
  257. eq(a.getValue(), "1\n2\noyoy\n3\n4\n5");
  258. b.undo();
  259. eq(a.getValue(), "1\n2\n3\n4\n5");
  260. });
  261. testDoc("subviewEditOnBoundary", "A='11\n22\n33\n44\n55' B<~A/1-4", function(a, b) {
  262. a.replaceRange("x\nyy\nz", Pos(0, 1), Pos(2, 1));
  263. eq(b.firstLine(), 2);
  264. eq(b.lineCount(), 2);
  265. eq(b.getValue(), "z3\n44");
  266. a.replaceRange("q\nrr\ns", Pos(3, 1), Pos(4, 1));
  267. eq(b.firstLine(), 2);
  268. eq(b.getValue(), "z3\n4q");
  269. eq(a.getValue(), "1x\nyy\nz3\n4q\nrr\ns5");
  270. a.execCommand("selectAll");
  271. a.replaceSelection("!");
  272. eqAll("!", a, b);
  273. });
  274. testDoc("sharedMarker", "A='ab\ncd\nef\ngh' B<A C<~A/1-2", function(a, b, c) {
  275. var mark = b.markText(Pos(0, 1), Pos(3, 1),
  276. {className: "cm-searching", shared: true});
  277. var found = a.findMarksAt(Pos(0, 2));
  278. eq(found.length, 1);
  279. eq(found[0], mark);
  280. eq(c.findMarksAt(Pos(1, 1)).length, 1);
  281. eqCharPos(mark.find().from, Pos(0, 1));
  282. eqCharPos(mark.find().to, Pos(3, 1));
  283. b.replaceRange("x\ny\n", Pos(0, 0));
  284. eqCharPos(mark.find().from, Pos(2, 1));
  285. eqCharPos(mark.find().to, Pos(5, 1));
  286. var cleared = 0;
  287. CodeMirror.on(mark, "clear", function() {++cleared;});
  288. b.operation(function(){mark.clear();});
  289. eq(a.findMarksAt(Pos(3, 1)).length, 0);
  290. eq(b.findMarksAt(Pos(3, 1)).length, 0);
  291. eq(c.findMarksAt(Pos(3, 1)).length, 0);
  292. eq(mark.find(), null);
  293. eq(cleared, 1);
  294. });
  295. testDoc("sharedMarkerCopy", "A='abcde'", function(a) {
  296. var shared = a.markText(Pos(0, 1), Pos(0, 3), {shared: true});
  297. var b = a.linkedDoc();
  298. var found = b.findMarksAt(Pos(0, 2));
  299. eq(found.length, 1);
  300. eq(found[0], shared);
  301. shared.clear();
  302. eq(b.findMarksAt(Pos(0, 2)), 0);
  303. });
  304. testDoc("sharedMarkerDetach", "A='abcde' B<A C<B", function(a, b, c) {
  305. var shared = a.markText(Pos(0, 1), Pos(0, 3), {shared: true});
  306. a.unlinkDoc(b);
  307. var inB = b.findMarksAt(Pos(0, 2));
  308. eq(inB.length, 1);
  309. is(inB[0] != shared);
  310. var inC = c.findMarksAt(Pos(0, 2));
  311. eq(inC.length, 1);
  312. is(inC[0] != shared);
  313. inC[0].clear();
  314. is(shared.find());
  315. });
  316. testDoc("sharedBookmark", "A='ab\ncd\nef\ngh' B<A C<~A/1-2", function(a, b, c) {
  317. var mark = b.setBookmark(Pos(1, 1), {shared: true});
  318. var found = a.findMarksAt(Pos(1, 1));
  319. eq(found.length, 1);
  320. eq(found[0], mark);
  321. eq(c.findMarksAt(Pos(1, 1)).length, 1);
  322. eqCharPos(mark.find(), Pos(1, 1));
  323. b.replaceRange("x\ny\n", Pos(0, 0));
  324. eqCharPos(mark.find(), Pos(3, 1));
  325. var cleared = 0;
  326. CodeMirror.on(mark, "clear", function() {++cleared;});
  327. b.operation(function() {mark.clear();});
  328. eq(a.findMarks(Pos(0, 0), Pos(5)).length, 0);
  329. eq(b.findMarks(Pos(0, 0), Pos(5)).length, 0);
  330. eq(c.findMarks(Pos(0, 0), Pos(5)).length, 0);
  331. eq(mark.find(), null);
  332. eq(cleared, 1);
  333. });
  334. testDoc("undoInSubview", "A='line 0\nline 1\nline 2\nline 3\nline 4' B<A/1-4", function(a, b) {
  335. b.replaceRange("x", Pos(2, 0));
  336. a.undo();
  337. eq(a.getValue(), "line 0\nline 1\nline 2\nline 3\nline 4");
  338. eq(b.getValue(), "line 1\nline 2\nline 3");
  339. });
  340. })();