unit.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import isEqual from 'lodash.isequal';
  2. import Editor from '../../core/editor';
  3. import Emitter from '../../core/emitter';
  4. import Selection from '../../core/selection';
  5. import Scroll from '../../blots/scroll';
  6. import Quill from '../../core/quill';
  7. const div = document.createElement('div');
  8. div.id = 'test-container';
  9. document.body.appendChild(div);
  10. window.onerror = function(msg) {
  11. return msg === 'Script error.';
  12. };
  13. beforeEach(function() {
  14. jasmine.addMatchers({
  15. toEqualHTML() {
  16. return { compare: compareHTML };
  17. },
  18. toBeApproximately() {
  19. return { compare: compareApproximately };
  20. },
  21. });
  22. div.innerHTML = '<div></div>';
  23. this.container = div.firstChild;
  24. this.initialize = initialize.bind(this);
  25. });
  26. function compareApproximately(actual, expected, tolerance) {
  27. const pass = Math.abs(actual - expected) <= tolerance;
  28. return {
  29. pass,
  30. message: `${actual} is ${pass ? '' : 'not'} approximately ${expected}`,
  31. };
  32. }
  33. function compareHTML(actual, expected, ignoreClassId, ignoreUI = true) {
  34. const [div1, div2] = [actual, expected].map(function(html) {
  35. if (html instanceof HTMLElement) {
  36. html = html.innerHTML;
  37. }
  38. const container = document.createElement('div');
  39. container.innerHTML = html.replace(/\n\s*/g, '');
  40. // Heuristic for if DOM 'fixed' our input HTML
  41. if (
  42. container.innerHTML.replace(/\s/g, '').length !==
  43. html.replace(/\s/g, '').length
  44. ) {
  45. console.error('Invalid markup', html); // eslint-disable-line no-console
  46. throw new Error('Invalid markup passed to compareHTML');
  47. }
  48. if (ignoreUI) {
  49. Array.from(container.querySelectorAll('.ql-ui')).forEach(node => {
  50. node.remove();
  51. });
  52. }
  53. return container;
  54. });
  55. let ignoredAttributes = ['width', 'height', 'data-row', 'contenteditable'];
  56. if (ignoreClassId) {
  57. ignoredAttributes = ignoredAttributes.concat(['class', 'id']);
  58. }
  59. const message = compareNodes(div1, div2, ignoredAttributes);
  60. if (message != null) {
  61. console.error(div1.innerHTML); // eslint-disable-line no-console
  62. console.error(div2.innerHTML); // eslint-disable-line no-console
  63. return { pass: false, message };
  64. }
  65. return { pass: true, message: 'HTMLs equal' };
  66. }
  67. function compareNodes(node1, node2, ignoredAttributes = []) {
  68. if (node1.nodeType !== node2.nodeType) {
  69. return `Expected nodeType '${node1.nodeType}' to equal '${node2.nodeType}'`;
  70. }
  71. if (node1.nodeType === node1.ELEMENT_NODE) {
  72. if (node1.tagName !== node2.tagName) {
  73. return `Expected tagName '${node1.tagName}' to equal '${node2.tagName}'`;
  74. }
  75. const [attr1, attr2] = [node1, node2].map(function(node) {
  76. return Array.from(node.attributes || []).reduce(function(attr, elem) {
  77. if (ignoredAttributes.indexOf(elem.name) < 0) {
  78. attr[elem.name] =
  79. elem.name === 'style' ? elem.value.trim() : elem.value;
  80. }
  81. return attr;
  82. }, {});
  83. });
  84. if (!isEqual(attr1, attr2)) {
  85. return `Expected attributes ${jasmine.pp(attr1)} to equal ${jasmine.pp(
  86. attr2,
  87. )}`;
  88. }
  89. if (node1.childNodes.length !== node2.childNodes.length) {
  90. return `Expected node childNodes length '${
  91. node1.childNodes.length
  92. }' to equal '${node2.childNodes.length}'`;
  93. }
  94. if (node1.childNodes.length === 0) return null;
  95. let message = '';
  96. if (
  97. Array.from(node1.childNodes).some(function(child1, i) {
  98. message = compareNodes(child1, node2.childNodes[i], ignoredAttributes);
  99. return message;
  100. })
  101. ) {
  102. return message;
  103. }
  104. } else if (node1.data !== node2.data) {
  105. return `Expected node text '${node1.data}' to equal '${node2.data}'`;
  106. }
  107. return null;
  108. }
  109. /* eslint-disable-next-line react/no-this-in-sfc */
  110. function initialize(klass, html, container = this.container, options = {}) {
  111. if (typeof html === 'object') {
  112. container.innerHTML = html.html;
  113. } else {
  114. container.innerHTML = html.replace(/\n\s*/g, '');
  115. }
  116. if (klass === HTMLElement) return container;
  117. if (klass === Quill) return new Quill(container, options);
  118. const emitter = new Emitter();
  119. const scroll = new Scroll(Quill.getNamespace().registry, container, {
  120. emitter,
  121. });
  122. if (klass === Scroll) return scroll;
  123. if (klass === Editor) return new Editor(scroll);
  124. if (klass === Selection) return new Selection(scroll, emitter);
  125. if (klass[0] === Editor && klass[1] === Selection) {
  126. return [new Editor(scroll), new Selection(scroll, emitter)];
  127. }
  128. return null;
  129. }