zefyr

document_test.dart 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. // Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file
  2. // for details. All rights reserved. Use of this source code is governed by a
  3. // BSD-style license that can be found in the LICENSE file.
  4. import 'dart:convert';
  5. import 'package:notus/notus.dart';
  6. import 'package:quill_delta/quill_delta.dart';
  7. import 'package:test/test.dart';
  8. import 'matchers.dart';
  9. NotusDocument dartconfDoc() {
  10. final delta = Delta()..insert('DartConf\nLos Angeles\n');
  11. return NotusDocument.fromDelta(delta);
  12. }
  13. NotusDocument dartconfEmbedDoc() {
  14. final hr = NotusAttribute.embed.horizontalRule.toJson();
  15. final delta = Delta()
  16. ..insert('DartConf\n')
  17. ..insert(kZeroWidthSpace, hr)
  18. ..insert('\n')
  19. ..insert('Los Angeles\n');
  20. return NotusDocument.fromDelta(delta);
  21. }
  22. final ul = NotusAttribute.ul.toJson();
  23. final h1 = NotusAttribute.h1.toJson();
  24. void main() {
  25. group('$NotusDocument', () {
  26. test('validates for doc delta', () {
  27. var badDelta = Delta()
  28. ..insert('Text')
  29. ..retain(5)
  30. ..insert('\n');
  31. expect(() {
  32. NotusDocument.fromDelta(badDelta);
  33. }, throwsArgumentError);
  34. });
  35. test('empty document contains single empty line', () {
  36. final doc = NotusDocument();
  37. expect(doc.toPlainText(), '\n');
  38. });
  39. test('json serialization', () {
  40. final original = dartconfDoc();
  41. final jsonData = json.encode(original);
  42. final doc = NotusDocument.fromJson(json.decode(jsonData) as List);
  43. expect(doc.toDelta(), original.toDelta());
  44. expect(json.encode(doc), jsonData);
  45. });
  46. test('length', () {
  47. final doc = dartconfDoc();
  48. expect(doc.length, 21);
  49. });
  50. test('toString', () {
  51. final doc = dartconfDoc();
  52. expect(doc.toString(), doc.toString());
  53. });
  54. test('load non-empty document', () {
  55. final doc = dartconfDoc();
  56. expect(doc.toPlainText(), 'DartConf\nLos Angeles\n');
  57. });
  58. test('document delta must end with line-break character', () {
  59. final delta = Delta()..insert('DartConf\nLos Angeles');
  60. expect(() {
  61. NotusDocument.fromDelta(delta);
  62. }, throwsA(const TypeMatcher<AssertionError>()));
  63. });
  64. test('lookupLine', () {
  65. final doc = dartconfDoc();
  66. doc.format(20, 1, NotusAttribute.bq);
  67. var line1 = doc.lookupLine(3);
  68. var line2 = doc.lookupLine(13);
  69. expect(line1.node, const TypeMatcher<LineNode>());
  70. expect(line1.node.toPlainText(), 'DartConf\n');
  71. expect(line2.node, const TypeMatcher<LineNode>());
  72. expect(line2.node.toPlainText(), 'Los Angeles\n');
  73. });
  74. test('format applies heuristics', () {
  75. final doc = dartconfDoc();
  76. doc.format(0, 15, NotusAttribute.ul);
  77. expect(doc.root.children, hasLength(1));
  78. expect(doc.root.children.first, const TypeMatcher<BlockNode>());
  79. });
  80. test('format ignores empty changes', () async {
  81. final doc = dartconfDoc();
  82. var changeList = doc.changes.toList();
  83. var change = doc.format(1, 0, NotusAttribute.bold);
  84. doc.close();
  85. var changes = await changeList;
  86. expect(change, isEmpty);
  87. expect(changes, isEmpty);
  88. });
  89. test('format returns actual change delta', () {
  90. final doc = dartconfDoc();
  91. final change = doc.format(0, 15, NotusAttribute.ul);
  92. final expectedChange = Delta()
  93. ..retain(8)
  94. ..retain(1, ul)
  95. ..retain(11)
  96. ..retain(1, ul);
  97. expect(change, expectedChange);
  98. });
  99. test('format updates document delta', () {
  100. final doc = dartconfDoc();
  101. doc.format(0, 15, NotusAttribute.ul);
  102. final expectedDoc = Delta()
  103. ..insert('DartConf')
  104. ..insert('\n', ul)
  105. ..insert('Los Angeles')
  106. ..insert('\n', ul);
  107. expect(doc.toDelta(), expectedDoc);
  108. });
  109. test('format allows zero-length updates', () {
  110. final doc = dartconfDoc();
  111. doc.format(0, 0, NotusAttribute.ul);
  112. final expectedDoc = Delta()
  113. ..insert('DartConf')
  114. ..insert('\n', ul)
  115. ..insert('Los Angeles')
  116. ..insert('\n');
  117. expect(doc.toDelta(), expectedDoc);
  118. });
  119. test('insert applies heuristics', () {
  120. final doc = dartconfDoc();
  121. doc.format(0, 15, NotusAttribute.ul);
  122. doc.insert(8, '\n');
  123. expect(doc.root.children, hasLength(1));
  124. expect(doc.root.children.first, const TypeMatcher<BlockNode>());
  125. });
  126. test('insert returns actual change delta', () {
  127. final doc = dartconfDoc();
  128. doc.format(0, 15, NotusAttribute.ul);
  129. final change = doc.insert(8, '\n');
  130. final expectedChange = Delta()
  131. ..retain(8)
  132. ..insert('\n', ul);
  133. expect(change, expectedChange);
  134. });
  135. test('insert updates document delta', () {
  136. final doc = dartconfDoc();
  137. doc.format(0, 15, NotusAttribute.ul);
  138. doc.insert(8, '\n');
  139. final expectedDoc = Delta()
  140. ..insert('DartConf')
  141. ..insert('\n\n', ul)
  142. ..insert('Los Angeles')
  143. ..insert('\n', ul);
  144. expect(doc.toDelta(), expectedDoc);
  145. });
  146. test('insert throws assert error if change is empty', () {
  147. final doc = dartconfDoc();
  148. expect(() {
  149. doc.insert(8, '');
  150. }, throwsA(const TypeMatcher<AssertionError>()));
  151. });
  152. test('replace throws assert error if change is empty', () {
  153. final doc = dartconfDoc();
  154. expect(() {
  155. doc.replace(8, 0, '');
  156. }, throwsA(const TypeMatcher<AssertionError>()));
  157. });
  158. test('compose throws assert error if change is empty', () {
  159. final doc = dartconfDoc();
  160. expect(() {
  161. doc.compose(Delta()..retain(1), ChangeSource.local);
  162. }, throwsA(const TypeMatcher<AssertionError>()));
  163. });
  164. test('replace applies heuristic rules', () {
  165. final doc = dartconfDoc();
  166. doc.format(0, 0, NotusAttribute.h1);
  167. doc.replace(8, 1, ' ');
  168. expect(doc.root.children, hasLength(1));
  169. LineNode line = doc.root.children.first;
  170. expect(line.style.get(NotusAttribute.heading), NotusAttribute.h1);
  171. expect(line.toPlainText(), 'DartConf Los Angeles\n');
  172. });
  173. test('delete applies heuristic rules', () {
  174. final doc = dartconfDoc();
  175. doc.format(0, 0, NotusAttribute.h1);
  176. doc.delete(8, 1);
  177. expect(doc.root.children, hasLength(1));
  178. LineNode line = doc.root.children.first;
  179. expect(line.style.get(NotusAttribute.heading), NotusAttribute.h1);
  180. });
  181. test('delete which results in an empty change', () {
  182. // This test relies on a delete rule which ensures line-breaks around
  183. // and embed.
  184. final doc = dartconfEmbedDoc();
  185. doc.delete(8, 1);
  186. expect(doc.toPlainText(), 'DartConf\n${kZeroWidthSpace}\nLos Angeles\n');
  187. });
  188. test('checks for closed state', () {
  189. final doc = dartconfDoc();
  190. expect(doc.isClosed, isFalse);
  191. doc.close();
  192. expect(doc.isClosed, isTrue);
  193. expect(() {
  194. doc.compose(Delta()..insert('a'), ChangeSource.local);
  195. }, throwsAssertionError);
  196. expect(() {
  197. doc.insert(0, 'a');
  198. }, throwsAssertionError);
  199. expect(() {
  200. doc.format(0, 1, NotusAttribute.bold);
  201. }, throwsAssertionError);
  202. expect(() {
  203. doc.delete(0, 1);
  204. }, throwsAssertionError);
  205. });
  206. test('collectStyle', () {
  207. final doc = dartconfDoc();
  208. final style = doc.collectStyle(0, 10);
  209. expect(style, isNotNull);
  210. });
  211. test('insert embed after line-break', () {
  212. final doc = dartconfDoc();
  213. doc.format(9, 0, NotusAttribute.embed.horizontalRule);
  214. expect(doc.root.children, hasLength(3));
  215. expect(doc.root.first.toPlainText(), 'DartConf\n');
  216. expect(doc.root.last.toPlainText(), 'Los Angeles\n');
  217. LineNode line = doc.root.children.elementAt(1);
  218. EmbedNode embed = line.first;
  219. expect(embed.toPlainText(), EmbedNode.kPlainTextPlaceholder);
  220. final style = NotusStyle().merge(NotusAttribute.embed.horizontalRule);
  221. expect(embed.style, style);
  222. });
  223. test('insert embed before line-break', () {
  224. final doc = dartconfDoc();
  225. doc.format(8, 0, NotusAttribute.embed.horizontalRule);
  226. expect(doc.root.children, hasLength(3));
  227. expect(doc.root.first.toPlainText(), 'DartConf\n');
  228. expect(doc.root.last.toPlainText(), 'Los Angeles\n');
  229. LineNode line = doc.root.children.elementAt(1);
  230. EmbedNode embed = line.first;
  231. expect(embed.toPlainText(), EmbedNode.kPlainTextPlaceholder);
  232. final style = NotusStyle().merge(NotusAttribute.embed.horizontalRule);
  233. expect(embed.style, style);
  234. });
  235. test('insert embed in the middle of a line', () {
  236. final doc = dartconfDoc();
  237. doc.format(4, 0, NotusAttribute.embed.horizontalRule);
  238. expect(doc.root.children, hasLength(4));
  239. expect(doc.root.children.elementAt(0).toPlainText(), 'Dart\n');
  240. expect(doc.root.children.elementAt(1).toPlainText(),
  241. '${EmbedNode.kPlainTextPlaceholder}\n');
  242. expect(doc.root.children.elementAt(2).toPlainText(), 'Conf\n');
  243. expect(doc.root.children.elementAt(3).toPlainText(), 'Los Angeles\n');
  244. LineNode line = doc.root.children.elementAt(1);
  245. EmbedNode embed = line.first;
  246. expect(embed.toPlainText(), EmbedNode.kPlainTextPlaceholder);
  247. final style = NotusStyle().merge(NotusAttribute.embed.horizontalRule);
  248. expect(embed.style, style);
  249. });
  250. test('delete embed', () {
  251. final doc = dartconfDoc();
  252. doc.format(8, 0, NotusAttribute.embed.horizontalRule);
  253. expect(doc.root.children, hasLength(3));
  254. doc.delete(9, 1);
  255. expect(doc.root.children, hasLength(3));
  256. LineNode line = doc.root.children.elementAt(1);
  257. expect(line, isEmpty);
  258. });
  259. test('insert text containing zero-width space', () {
  260. final doc = dartconfDoc();
  261. final change = doc.insert(0, EmbedNode.kPlainTextPlaceholder);
  262. expect(change, isEmpty);
  263. expect(doc.length, 21);
  264. });
  265. test('insert text before embed', () {
  266. final doc = dartconfDoc();
  267. doc.format(8, 0, NotusAttribute.embed.horizontalRule);
  268. expect(doc.root.children, hasLength(3));
  269. doc.insert(9, 'text');
  270. expect(doc.root.children, hasLength(4));
  271. expect(doc.root.children.elementAt(1).toPlainText(), 'text\n');
  272. expect(doc.root.children.elementAt(2).toPlainText(),
  273. '${EmbedNode.kPlainTextPlaceholder}\n');
  274. });
  275. test('insert text after embed', () {
  276. final doc = dartconfDoc();
  277. doc.format(8, 0, NotusAttribute.embed.horizontalRule);
  278. expect(doc.root.children, hasLength(3));
  279. doc.insert(10, 'text');
  280. expect(doc.root.children, hasLength(4));
  281. expect(doc.root.children.elementAt(1).toPlainText(),
  282. '${EmbedNode.kPlainTextPlaceholder}\n');
  283. expect(doc.root.children.elementAt(2).toPlainText(), 'text\n');
  284. });
  285. test('replace text with embed', () {
  286. final doc = dartconfDoc();
  287. doc.format(4, 4, NotusAttribute.embed.horizontalRule);
  288. expect(doc.root.children, hasLength(3));
  289. expect(doc.root.children.elementAt(0).toPlainText(), 'Dart\n');
  290. expect(doc.root.children.elementAt(1).toPlainText(),
  291. '${EmbedNode.kPlainTextPlaceholder}\n');
  292. expect(doc.root.children.elementAt(2).toPlainText(), 'Los Angeles\n');
  293. });
  294. test('replace embed with embed', () {
  295. final doc = dartconfDoc();
  296. doc.format(4, 4, NotusAttribute.embed.horizontalRule);
  297. doc.format(5, 1, NotusAttribute.embed.horizontalRule);
  298. expect(doc.root.children, hasLength(3));
  299. expect(doc.root.children.elementAt(0).toPlainText(), 'Dart\n');
  300. expect(doc.root.children.elementAt(1).toPlainText(),
  301. '${EmbedNode.kPlainTextPlaceholder}\n');
  302. expect(doc.root.children.elementAt(2).toPlainText(), 'Los Angeles\n');
  303. });
  304. });
  305. }