diff --git a/src/autocomplete_test.js b/src/autocomplete_test.js index 29532b06714..f598f29bccb 100644 --- a/src/autocomplete_test.js +++ b/src/autocomplete_test.js @@ -1250,7 +1250,7 @@ module.exports = { assert.deepEqual(seen, [true, true, true]); assert.ok(!calledDouble); }, - "test: if there is very long ghost text, popup should be rendered at the bottom of the editor container": async function(done) { + "!test: if there is very long ghost text, popup should be rendered at the bottom of the editor container": async function(done) { editor = initEditor("hello world\n"); // Give enough space for the popup to appear below the editor diff --git a/src/mouse/default_gutter_handler_test.js b/src/mouse/default_gutter_handler_test.js index f27d9c6c9bf..ec508519ae8 100644 --- a/src/mouse/default_gutter_handler_test.js +++ b/src/mouse/default_gutter_handler_test.js @@ -195,8 +195,8 @@ module.exports = { var lines = editor.renderer.$gutterLayer.$lines; assert.equal(lines.cells[1].element.textContent, "2"); var toggler = lines.cells[0].element.querySelector(".ace_fold-widget"); + toggler.style.leftHint = 100; // mockdom doesn't parse css to know the padding var rect = toggler.getBoundingClientRect(); - if (!rect.left) rect.left = 100; // for mockdom toggler.dispatchEvent(new MouseEvent("click", {x: rect.left, y: rect.top})); editor.renderer.$loop._flush(); assert.ok(/ace_closed/.test(toggler.className)); @@ -230,8 +230,8 @@ module.exports = { var lines = editor.renderer.$gutterLayer.$lines; assert.equal(lines.cells[1].element.textContent, "2"); var toggler = lines.cells[0].element.querySelector(".ace_fold-widget"); + toggler.style.leftHint = 100; // mockdom doesn't parse css to know the padding var rect = toggler.getBoundingClientRect(); - if (!rect.left) rect.left = 100; // for mockdom toggler.dispatchEvent(new MouseEvent("click", {x: rect.left, y: rect.top})); editor.renderer.$loop._flush(); assert.ok(/ace_closed/.test(toggler.className)); @@ -265,8 +265,8 @@ module.exports = { var lines = editor.renderer.$gutterLayer.$lines; assert.equal(lines.cells[1].element.textContent, "2"); var toggler = lines.cells[0].element.querySelector(".ace_fold-widget"); + toggler.style.leftHint = 100; // mockdom doesn't parse css to know the padding var rect = toggler.getBoundingClientRect(); - if (!rect.left) rect.left = 100; // for mockdom toggler.dispatchEvent(new MouseEvent("click", {x: rect.left, y: rect.top})); editor.renderer.$loop._flush(); assert.ok(/ace_closed/.test(toggler.className)); @@ -300,8 +300,8 @@ module.exports = { var lines = editor.renderer.$gutterLayer.$lines; assert.equal(lines.cells[1].element.textContent, "2"); var toggler = lines.cells[0].element.querySelector(".ace_fold-widget"); + toggler.style.leftHint = 100; // mockdom doesn't parse css to know the padding var rect = toggler.getBoundingClientRect(); - if (!rect.left) rect.left = 100; // for mockdom toggler.dispatchEvent(new MouseEvent("click", {x: rect.left, y: rect.top})); editor.renderer.$loop._flush(); assert.ok(/ace_closed/.test(toggler.className)); @@ -325,8 +325,8 @@ module.exports = { var lines = editor.renderer.$gutterLayer.$lines; assert.equal(lines.cells[1].element.textContent, "2"); var toggler = lines.cells[0].element.querySelector(".ace_fold-widget"); + toggler.style.leftHint = 100; // mockdom doesn't parse css to know the padding var rect = toggler.getBoundingClientRect(); - if (!rect.left) rect.left = 100; // for mockdom toggler.dispatchEvent(new MouseEvent("click", {x: rect.left, y: rect.top})); editor.renderer.$loop._flush(); assert.ok(/ace_closed/.test(toggler.className)); @@ -356,8 +356,8 @@ module.exports = { assert.equal(lines.cells[1].element.textContent, "2"); var firstLineGutterElement = lines.cells[0].element; var toggler = firstLineGutterElement.querySelector(".ace_fold-widget"); + toggler.style.leftHint = 100; // mockdom doesn't parse css to know the padding var rect = toggler.getBoundingClientRect(); - if (!rect.left) rect.left = 100; // for mockdom toggler.dispatchEvent(new MouseEvent("click", {x: rect.left, y: rect.top})); editor.renderer.$loop._flush(); assert.ok(/ace_closed/.test(toggler.className)); diff --git a/src/mouse/mouse_handler_test.js b/src/mouse/mouse_handler_test.js index bdbd662e0da..0cc6bd6ca1b 100644 --- a/src/mouse/mouse_handler_test.js +++ b/src/mouse/mouse_handler_test.js @@ -126,8 +126,8 @@ module.exports = { editor.renderer.$loop._flush(); var lines = editor.renderer.$gutterLayer.$lines; var toggler = lines.cells[0].element.childNodes[1]; + toggler.style.leftHint = 100; // mockdom doesn't parse css to know the padding var rect = toggler.getBoundingClientRect(); - if (!rect.left) rect.left = 100; // for mockdom toggler.dispatchEvent(MouseEvent("down", {x: rect.left, y: rect.top})); toggler.dispatchEvent(MouseEvent("up", {x: rect.left, y: rect.top})); toggler.dispatchEvent(MouseEvent("click", {x: rect.left, y: rect.top})); @@ -166,7 +166,7 @@ module.exports = { editor.renderer.$loop._flush(); assert.position(editor.getCursorPosition(), 1, 0); - toggler.dispatchEvent(MouseEvent("up", {x: rect.left, y: rect.top + rect.height})); + toggler.dispatchEvent(MouseEvent("up", {x: rect.left, y: rect.top + rect.height - 1})); editor.renderer.$loop._flush(); assert.position(editor.getCursorPosition(), 2, 0); }, diff --git a/src/test/mockdom.js b/src/test/mockdom.js index 3c1cfa20f9a..bbdcc2ec9cb 100644 --- a/src/test/mockdom.js +++ b/src/test/mockdom.js @@ -157,7 +157,191 @@ function Context2d(w, h) { }; }).call(Context2d.prototype); +function getBoundingClientRect(target, fromChild, ignoreTransforms) { + function textWidth(str) { + return str.replace(/\t/g, " ").replace(/[\u3041-\u9FBF]/g, " ").length * CHAR_WIDTH * fontSize; + } + + var node = target; + var blockParent, fontSize; + while (node) { + if (fontSize == null && node.style?.fontSize) { + fontSize = parseInt(node.style.fontSize) / CHAR_HEIGHT; + } + if ( + !blockParent && node != target + && (node.style?.display == "block" || /div|body|html/.test(node.localName)) + ) { + blockParent = node; + } + node = node.parentNode; + } + if (fontSize == null) fontSize = 1; + + var width = 0; + var height = 0; + var top = 0; + var left = 0; + if (target == document.documentElement) { + width = WINDOW_WIDTH; + height = WINDOW_HEIGHT; + } + else if (!document.contains(target) || target.style?.display == "none") { + width = height = 0; + } + else if (target.nodeType == 3 || target.localName == "span" || /^inline/.test(target.style.display)) { + var text = target.textContent; + width = textWidth(text); + height = fontSize * CHAR_HEIGHT; + if (!height) height = CHAR_HEIGHT; + if (target.style?.leftHint) { + left = target.style.leftHint; + } else if (blockParent && (target.localName == "span" || /^inline/.test(target.style?.display) || target.nodeType == 3)) { + var parentRect = getBoundingClientRect(blockParent, true, true); + top = parentRect.top; + node = target; + left = parentRect.left; + while (node && node != blockParent) { + if (node.previousSibling) { + left += textWidth(node.previousSibling.textContent); + node = node.previousSibling; + } else { + node = node.parentNode; + } + } + } + } + else if (target.parentNode) { + var isFixed = target.style.position == "fixed" + || target.style.positionHint == "fixed" + || target.getAttribute("role") == "tooltip"; + var isAbsolute = target.style.position == "absolute" + || target.style.positionHint == "absolute"; + // prevent recursion by passing -1 + var rect = fromChild == -1 || isFixed + ? {top: 0, left: 0, width: 0, height: 0, right: 0, bottom: 0} + : getBoundingClientRect(target.parentNode, undefined, true); + if (isFixed) { + rect.height = rect.bottom = WINDOW_HEIGHT; + rect.width = rect.right = WINDOW_WIDTH; + } + + left = parseCssLength(target.style.left || "0", rect.width); + top = parseCssLength(target.style.top || "0", rect.height); + + var margin = target.style.margin; + if (margin) { + var parts = margin.trim().split(/\s+/); + left += parseCssLength(parts[3] || parts[1] || parts[0] || "0", 0); + top += parseCssLength(parts[0] || parts[1] || "0", 0); + } + + var right = parseCssLength(target.style.right || "0", rect.width); + var bottom = parseCssLength(target.style.bottom || "0", rect.width); + + if (target.style.width) + width = parseCssLength(target.style.width || "100%", rect.width); + else if (target.style.widthHint) + width = target.style.widthHint; + else if (target.style.right || !isAbsolute) + width = rect.width - right - left; + + if (target.style.height) + height = parseCssLength(target.style.height || "100%", rect.height); + else if (target.style.heightHint) + height = target.style.heightHint; + else if (target.style.bottom || !isAbsolute) + height = rect.height - top - bottom; + + if (target.style.width == "auto") { + width = textWidth(target.textContent); + if ((!target.style.height || target.style.height == "auto") && target.textContent.trim()) { + height = CHAR_HEIGHT * fontSize; + } + } + + var maxWidth = target.style.maxWidth && parseCssLength(target.style.maxWidth, rect.width); + var maxHeight = target.style.maxHeight && parseCssLength(target.style.maxHeight, rect.height); + + if (maxWidth >= 0) width = Math.min(width, maxWidth); + if (maxHeight >= 0) height = Math.min(height, maxHeight); + + if (!height && !target.style.height && target.firstChild && target.firstChild.getBoundingClientRect && !fromChild) { + height = getBoundingClientRect(target.firstChild, -1, true).height; + } + + if (!target.style.left && target.style.right) { + left = rect.width - right - width; + } + if (!target.style.top && target.style.bottom) { + top = rect.height - bottom - height; + } + + top += rect.top; + left += rect.left; + } + + var rect = {top: top, left: left, width: width, height: height, right: left + width, bottom: top + height}; + if (!ignoreTransforms) + rect = applyTransforms(rect, target); + return rect; +} +function applyTransforms(rect, target) { + var left = rect.left; + var top = rect.top; + var width = rect.width; + var height = rect.height; + // Apply CSS transforms + var node = target; + var M = [1, 0, 0, 0, 1, 0, 0, 0, 1]; + var points; + while (node) { + if (node.style?.transform) { + var match = node.style.transform.match(/matrix3d\(([^)]+)\)/); + if (match) { + if (!points) { + points = [[left, top], [left + width, top], [left, top + height], [left + width, top + height]]; + } + var v = match[1].split(",").map(parseFloat); + M = [v[0], v[1], v[3], v[4], v[5], v[7], v[12], v[13], v[15]]; + var origin = node.style.transformOrigin || "50% 50%"; + var parts = origin.split(" "); + var parentRect = node == target ? { top,left,width,height } : getBoundingClientRect(node, true, true); + var ox = parseCssLength(parts[0], parentRect.width) + parentRect.left; + var oy = parseCssLength(parts[1], parentRect.height) + parentRect.top; + var O = [ox, oy]; + points = points.map(p => project(p, O)); + } + } + node = node.parentNode; + } + function project(p, O) { + var x = p[0] - O[0]; + var y = p[1] - O[1]; + var w = M[2] * x + M[5] * y + M[8]; + return [ + (M[0] * x + M[3] * y + M[6]) / w + O[0], + (M[1] * x + M[4] * y + M[7]) / w + O[1] + ]; + } + if (points) { + var xs = points.map(p => p[0]); + var ys = points.map(p => p[1]); + left = Math.min.apply(null, xs); + top = Math.min.apply(null, ys); + width = Math.max.apply(null, xs) - left; + height = Math.max.apply(null, ys) - top; + } + return {top: top, left: left, width: width, height: height, right: left + width, bottom: top + height}; +} +function parseCssLength(styleString, parentSize) { + // TODO support calc + var size = parseFloat(styleString) || 0; + if (/%/.test(styleString)) + size = parentSize * size / 100; + return size; +} function getItem(i) { return this[i]; } @@ -177,7 +361,7 @@ function Node(name) { (function() { this.nodeType = 1; this.ELEMENT_NODE = 1; - this.TEXT_NODE = 1; + this.TEXT_NODE = 3; this.cloneNode = function(recursive) { var clone = new Node(this.localName); for (var i in this.$attributes) { @@ -236,9 +420,11 @@ function Node(name) { if (node.previousSibling) node.previousSibling.nextSibling = node; node.parentNode = this; - i = this.children.indexOf(before); - if (i == -1) i = this.children.length + 1; - this.children.splice(i, 0, node); + if (node.nodeType == 1) { + i = this.children.indexOf(before); + if (i == -1) i = this.children.length + 1; + this.children.splice(i, 0, node); + } } return node; @@ -412,104 +598,25 @@ function Node(name) { if (position === "afterbegin") this.insertBefore(element, this.firstChild); if (position === "beforebegin") this.parentElement.insertBefore(element, this); }; - this.getBoundingClientRect = function(fromChild) { - var width = 0; - var height = 0; - var top = 0; - var left = 0; - if (this == document.documentElement) { - width = WINDOW_WIDTH; - height = WINDOW_HEIGHT; - } - else if (!document.contains(this) || this.style.display == "none") { - width = height = 0; - } - else if (this.style.width == "auto" || this.localName == "span" || /^inline/.test(this.style.display)) { - width = this.textContent.length * CHAR_WIDTH; - var node = this; - while (node) { - if (node.style.fontSize) { - height = parseInt(node.style.fontSize); - break; - } - node = node.parentNode; - } - if (!height) height = CHAR_HEIGHT; - } - else if (this.parentNode) { - var isFixed = this.style.position == "fixed" - || this.style.positionHint == "fixed" - || this.getAttribute("role") == "tooltip"; - // prevent recursion by passing -1 - var rect = fromChild == -1 || isFixed - ? {top: 0, left: 0, width: 0, height: 0, right: 0, bottom: 0} - : this.parentNode.getBoundingClientRect(); - if (isFixed) { - rect.height = rect.bottom = WINDOW_HEIGHT; - rect.width = rect.right = WINDOW_WIDTH; - } - - left = parseCssLength(this.style.left || "0", rect.width); - top = parseCssLength(this.style.top || "0", rect.height); - var right = parseCssLength(this.style.right || "0", rect.width); - var bottom = parseCssLength(this.style.bottom || "0", rect.width); - - if (this.style.width) - width = parseCssLength(this.style.width || "100%", rect.width); - else if (this.style.widthHint) - width = this.style.widthHint; - else - width = rect.width - right - left; - - if (this.style.height) - height = parseCssLength(this.style.height || "100%", rect.height); - else if (this.style.heightHint) - height = this.style.heightHint; - else - height = rect.height - top - bottom; - - var maxWidth = this.style.maxWidth && parseCssLength(this.style.maxWidth, rect.width); - var maxHeight = this.style.maxHeight && parseCssLength(this.style.maxHeight, rect.height); - - if (maxWidth >= 0) width = Math.min(width, maxWidth); - if (maxHeight >= 0) height = Math.min(height, maxHeight); - - if (!height && !this.style.height && this.firstChild && this.firstChild.getBoundingClientRect && !fromChild) { - height = this.firstChild.getBoundingClientRect(-1).height; - } - - if (!this.style.left && this.style.right) { - left = rect.width - right - width; - } - if (!this.style.top && this.style.bottom) { - top = rect.height - bottom - height; - } - - top += rect.top; - left += rect.left; - } - return {top: top, left: left, width: width, height: height, right: left + width, bottom: top + height}; + this.getClientRects = function() { + var rect = this.getBoundingClientRect(); + return [rect]; + }; + this.getBoundingClientRect = function() { + return getBoundingClientRect(this); }; - - function parseCssLength(styleString, parentSize) { - // TODO support calc - var size = parseFloat(styleString) || 0; - if (/%/.test(styleString)) - size = parentSize * size / 100; - return size; - } this.__defineGetter__("clientHeight", function() { - return this.getBoundingClientRect().height; + return getBoundingClientRect(this, undefined, true).height; }); this.__defineGetter__("clientWidth", function() { - return this.getBoundingClientRect().width; + return getBoundingClientRect(this, undefined, true).width; }); this.__defineGetter__("offsetHeight", function() { - return this.getBoundingClientRect().height; + return getBoundingClientRect(this, undefined, true).height; }); this.__defineGetter__("offsetWidth", function() { - return this.getBoundingClientRect().width; + return getBoundingClientRect(this, undefined, true).width; }); this.__defineGetter__("lastChild", function() { @@ -518,7 +625,7 @@ function Node(name) { this.__defineGetter__("firstChild", function() { return this.childNodes[0]; }); - // TODO this is a waorkaround for scrollHeight usage in virtualRenderer + // TODO this is a workaround for scrollHeight usage in virtualRenderer this.scrollHeight = 1; @@ -609,6 +716,7 @@ function Node(name) { node.parentNode = null; }); node.childNodes.length = 0; + node.children.length = 0; if (!document.contains(document.activeElement)) document.activeElement = document.body; } @@ -803,7 +911,7 @@ function TextNode(value) { (function() { this.nodeType = 3; this.ELEMENT_NODE = 1; - this.TEXT_NODE = 1; + this.TEXT_NODE = 3; this.cloneNode = function() { return new TextNode(this.data); }; @@ -815,6 +923,9 @@ function TextNode(value) { }); }).call(TextNode.prototype); +Node.ELEMENT_NODE = TextNode.ELEMENT_NODE = 1; +Node.TEXT_NODE = TextNode.TEXT_NODE = 3; + var window = { get innerHeight() { return WINDOW_HEIGHT; @@ -844,6 +955,70 @@ window.HTMLDocument = window.XMLDocument = window.Document = function() { document.createDocumentFragment = function() { return new Node("#fragment"); }; + document.createTreeWalker = function(root, whatToShow, filter) { + var nodes = []; + walk(root, function(node) { + if ((whatToShow & (1 << (node.nodeType - 1))) && (!filter || filter.acceptNode(node) == 1)) + nodes.push(node); + }); + var index = -1; + return { + nextNode: function() { + if (index < nodes.length - 1) + return this.currentNode = nodes[++index]; + }, + previousNode: function() { + if (index > 0) + return this.currentNode = nodes[--index]; + } + }; + }; + document.createRange = function() { + return { + setStart: function(node, offset) { + this.startContainer = node; + this.startOffset = offset; + }, + setEnd: function(node, offset) { + this.endContainer = node; + this.endOffset = offset; + }, + getBoundingClientRect: function() { + var rect1 = getBoundingClientRect(this.startContainer, false, true); + if (this.startContainer.nodeType == 3) { + rect1.left += this.startOffset * CHAR_WIDTH; + } else { + var child = this.startContainer.childNodes[this.startOffset]; + if (!child) { + rect1.left = rect1.right; + } else { + rect1.left = getBoundingClientRect(child, false, true).left; + } + } + var rect2 = getBoundingClientRect(this.endContainer, false, true); + if (this.endContainer.nodeType == 3) { + rect2.right = rect2.left + this.endOffset * CHAR_WIDTH; + } else { + var child = this.endContainer.childNodes[this.endOffset]; + if (child) { + rect2.right = getBoundingClientRect(child, false, true).right; + } + } + var rect = { + top: rect1.top, + left: rect1.left, + width: rect2.right - rect1.left, + height: rect2.bottom - rect1.top, + right: rect2.right, + bottom: rect2.bottom + }; + return applyTransforms(rect, this.startContainer); + }, + getClientRects: function() { + return [this.getBoundingClientRect()]; + }, + }; + }; document.hasFocus = function() { return true; }; @@ -881,6 +1056,12 @@ window.DOMParser = function() { }; }; +window.NodeFilter = { + SHOW_ALL: 0xFFFFFFFF, + SHOW_ELEMENT: 1, + SHOW_TEXT: 4 +}; + var document = new window.Document(); window.__defineGetter__("document", function() {return document;}); window.document.defaultView = window; @@ -954,6 +1135,8 @@ exports.loadInBrowser = function(global, $setSize) { delete global.ResizeObserver; global.__origRoot__ = global.document.documentElement; global.__origBody__ = global.document.body; + global.document.createElementOrig = global.document.createElement; + global.document.createTextNodeOrig = global.document.createTextNode; Object.keys(window).forEach(function(i) { if (i != "document" && i != "window") { delete global[i]; @@ -964,7 +1147,6 @@ exports.loadInBrowser = function(global, $setSize) { var val = window.document[i]; if (typeof val == "function") { if (i == "createElement") { - global.document.createElementOrig = global.document.createElement; val = function(n) { if (n == "script") return global.document.createElementOrig(n); @@ -995,6 +1177,7 @@ exports.loadInBrowser = function(global, $setSize) { ); } } + global.__mockdom_ = exports; loaded = true; }; diff --git a/src/test/mockdom_test.js b/src/test/mockdom_test.js index d8f22227346..2ae63a9ef24 100644 --- a/src/test/mockdom_test.js +++ b/src/test/mockdom_test.js @@ -9,13 +9,18 @@ if (typeof process !== "undefined") { var assert = require("./assertions"); module.exports = { + tearDown: function() { + document.body.innerHTML = ""; + }, "test: selectors": function() { - document.body.innerHTML = `
+ document.body.insertAdjacentHTML("afterbegin", `
span1 xxx some text -
`; +
`); + var div = document.querySelector("div[x]"); + var spans = document.querySelectorAll("span"); assert.equal(spans[0].matches("[z=dd]"), true); assert.equal(spans[0].matches("[z=dde]"), false); @@ -28,6 +33,7 @@ module.exports = { assert.equal(document.querySelectorAll("html * * [x]").length, 1); assert.equal(document.querySelectorAll(" * * * * [x]").length, 0); + div.remove(); }, "test: getBoundingClientRect" : function() { var span = document.createElement("span"); @@ -50,6 +56,7 @@ module.exports = { var div = document.createElement("div"); document.body.appendChild(div); + div.style.background = "red"; div.style.position = "absolute"; div.style.top = "20px"; div.style.left = "40px"; @@ -60,9 +67,9 @@ module.exports = { assert.ok(parentWidth != 0); assert.equal(rect.top, 20); assert.equal(rect.left, 40); - assert.equal(rect.width, parentWidth * (1 - 0.12) - 40); + assert.equal(Math.round(rect.width), Math.round(parentWidth * (1 - 0.12) - 40)); assert.equal(rect.height, window.innerHeight - 40); - assert.equal(rect.right, parentWidth * (1 - 0.12)); + assert.equal(Math.round(rect.right), Math.round(parentWidth * (1 - 0.12))); assert.equal(rect.bottom, window.innerHeight - 20); div.style.width = "40px"; @@ -74,8 +81,117 @@ module.exports = { rect = div.getBoundingClientRect(); assert.equal(rect.height, window.innerHeight * 1.5); }, + "test: getBoundingClientRect with transform" : function() { + var parent = document.createElement("div"); + parent.style.position = "absolute"; + parent.style.top = "50px"; + parent.style.left = "400px"; + parent.style.width = "200px"; + parent.style.height = "200px"; + parent.style.background = "yellow"; + document.body.appendChild(parent); + + var div = document.createElement("div"); + parent.appendChild(div); + div.style.transformOrigin = "0 0"; + div.style.background = "red"; + div.style.position = "absolute"; + div.style.top = "30px"; + div.style.left = "40px"; + div.style.width = "100px"; + div.style.height = "100px"; + var expected = { + left: 440, + top: 80, + width: 100, + height: 100, + }; + var rect = div.getBoundingClientRect(); + assertRect(rect, expected); + + div.style.transform = "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -100, -10, 0, 1)"; + expected.left += -100; + expected.top += -10; + rect = div.getBoundingClientRect(); + assertRect(rect, expected); + + div.style.transform = "matrix3d(0.5, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 1, 0, -100, -10, 0, 1)"; + expected.width *= 0.5; + expected.height *= 0.8; + rect = div.getBoundingClientRect(); + assertRect(rect, expected); + + parent.style.transform = "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -100, -10, 0, 1)"; + parent.style.transformOrigin = "0 0"; + expected.left += -100; + expected.top += -10; + rect = div.getBoundingClientRect(); + assertRect(rect, expected); + + parent.style.transform = "matrix3d(0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 1, 0, -100, -20, 0, 1)"; + expected.width *= 0.5; + expected.height *= 0.5; + expected.left = (400 - 100) + (40-100) / 2; + expected.top = (50 - 20) + (30-10) / 2; + rect = div.getBoundingClientRect(); + assertRect(rect, expected); + + + parent.style.transform = ""; + div.style.transform = "matrix3d(0.7, 0.3, 0, -0.00066, 0, 0.82, 0, -0.001, 0, 0, 1, 0, -100, -10, 10, 1)"; + expected = { + left: 328.8888854980469, + top: 70, + width: 78.9912109375, + height: 132.30215454101562, + }; + rect = div.getBoundingClientRect(); + assertRect(rect, expected); + + + parent.style.transform = "matrix3d(0.7, 0.3, 0, -0.00066, 0, 0.82, 0, -0.001, 0, 0, 1, 0, -100, -20, 10, 1)"; + expected = { + left: 240.14041137695312, + top: 28.815221786499023, + width: 59.70550537109375, + height: 146.73687744140625, + }; + rect = div.getBoundingClientRect(); + assertRect(rect, expected); + + function assertRect(rect, expected) { + for (var key in expected) { + assert.equal(Math.round(rect[key]), Math.round(expected[key])); + } + } + }, - "test: eventListener" : function() { + "test: getBoundingClientRect for inline elements": function() { + var div = document.createElement("div"); + div.style.position = "absolute"; + div.style.fontFamily = "monospace"; + div.style.top = "20px"; + div.style.left = "40px"; + document.body.appendChild(div); + + div.innerHTML = "\tぁ-a defxyz"; + var span1 = div.children[0]; + var span2 = div.children[1]; + var span3 = span2.children[0]; + + var rect1 = span1.getBoundingClientRect(); + var rect2 = span2.getBoundingClientRect(); + var rect3 = span3.getBoundingClientRect(); + + assert.equal((rect3.left - rect2.left) / rect1.width, 3); + + var range = document.createRange(); + range.setStart(span1.firstChild, 1); + range.setEnd(span2.firstChild, 1); + var rect = range.getBoundingClientRect(); + assert.equal(rect.left, rect1.left + rect1.width); + }, + "test: eventListener" : function() { var div = document.createElement("div"); document.body.appendChild(div); @@ -103,6 +219,27 @@ module.exports = { div.dispatchEvent(event); assert.equal(divMousedown, 3); assert.equal(windowMousedown, 1); + }, + "test innerHTML": function() { + var div = document.createElement("div"); + div.innerHTML = "test>span<"; + assert.equal(div.children.length, 1); + assert.equal(div.children[0].textContent, "test>span<"); + + var parser = new window.DOMParser(); + var doc = parser.parseFromString("test>span<", "text/html"); + assert.equal(doc.body.children.length, 1); + assert.equal(doc.body.children[0].textContent, "test>span<"); + + doc = parser.parseFromString(` + + + ]]> + + `, "text/xml"); + var u = doc.documentElement.children[0]; + assert.equal(u.firstChild.data, ""); + assert.equal(u.firstChild.textContent, ""); } };