zefyr

document_test.dart 10KB

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