Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 80 additions & 11 deletions lib/core/utils/dq-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,98 @@ import getNodeFromTree from './get-node-from-tree';
import AbstractVirtualNode from '../base/virtual-node/abstract-virtual-node';
import cache from '../base/cache';
import memoize from './memoize';
import getNodeAttributes from './get-node-attributes';
import VirtualNode from '../../core/base/virtual-node/virtual-node';

const CACHE_KEY = 'DqElm.RunOptions';

function truncate(str, maxLength) {
maxLength = maxLength || 300;
function getOuterHtml(element) {
let source = element.outerHTML;

if (!source && typeof window.XMLSerializer === 'function') {
source = new window.XMLSerializer().serializeToString(element);
}

return source || '';
}

/**
* Truncates the outerHTML property of an element
* @param {Node} element the element node which needs to be truncated
*/

export function truncateElement(element) {
const maxLen = 300;
const maxAttrNameOrValueLen = 20;

const deepStr = getOuterHtml(element);
let vNode = getNodeFromTree(element);
if (!vNode) {
vNode = new VirtualNode(element);
}
const { nodeName } = vNode.props;

if (deepStr.length < maxLen) {
return deepStr;
}

if (str.length > maxLength) {
const index = str.indexOf('>');
str = str.substring(0, index + 1);
const attributeStrList = [];
const shallowNode = element.cloneNode(false);
const elementNodeMap = getNodeAttributes(shallowNode);

let str = getOuterHtml(shallowNode);

if (str.length < maxLen) {
let attrString = '';
for (const { name, value } of elementNodeMap) {
const attr = { name, value };
attrString += ` ${attr.name}="${attr.value}"`;
}

str = `<${nodeName}${attrString}>`;
return str;
}
let strLen = `<${nodeName}>`.length;

for (const { name, value } of elementNodeMap) {
if (strLen > maxLen) {
break;
}

const attr = { name, value };
let attrName = attr.name;
let attrValue = attr.value;

attrName =
attrName.length > maxAttrNameOrValueLen
? attrName.substring(0, maxAttrNameOrValueLen) + '...'
: attrName;
attrValue =
attrValue.length > maxAttrNameOrValueLen
? attrValue.substring(0, maxAttrNameOrValueLen) + '...'
: attrValue;

const strAttr = `${attrName}="${attrValue}"`;
strLen += (' ' + strAttr).length;
attributeStrList.push(strAttr);
}

str = `<${nodeName} ${attributeStrList.join(' ')}>`;
if (str.length > maxLen) {
str = str.substring(0, maxLen) + ' ...>';
} else if (attributeStrList.length < elementNodeMap.length) {
str = str.substring(0, str.length - 1) + ' ...>';
}

return str;
}

function getSource(element) {
if (!element?.outerHTML) {
if (!element) {
return '';
}
let source = element.outerHTML;
if (!source && typeof window.XMLSerializer === 'function') {
source = new window.XMLSerializer().serializeToString(element);
}
return truncate(source || '');

return truncateElement(element);
}

/**
Expand Down
55 changes: 55 additions & 0 deletions test/core/utils/dq-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,61 @@ describe('DqElement', () => {
assert.equal(result.source, '<div class="foo" id="target">');
});

it('should truncate large attributes of large element', () => {
const el = document.createElement('div');
let attributeName = 'data-';
let attributeValue = '';
for (let i = 0; i < 500; i++) {
attributeName += 'foo';
attributeValue += i;
}
el.setAttribute(attributeName, attributeValue);

const vNode = new DqElement(el);
assert.equal(
vNode.source,
`<div ${attributeName.substring(0, 20)}...="${attributeValue.substring(0, 20)}...">`
);
});

it('should remove attributes for a large element having a large number of attributes', () => {
let customElement = '<div id="target"';

for (let i = 0; i < 100; i++) {
customElement += ` attr${i}="value${i}"`;
}

customElement += `><div>`;
const vNode = queryFixture(customElement);
const result = new DqElement(vNode);
const truncatedAttrCount = (result.source.match(/attr/g) || []).length;
assert.isBelow(truncatedAttrCount, 100);
assert.isAtLeast(truncatedAttrCount, 10);
});

it('should truncate a large element with long custom tag name', () => {
let longCustomElementTagName = new Array(300).join('b');
let customElement = `<${longCustomElementTagName} id="target">A</${longCustomElementTagName}>`;
const vNode = queryFixture(customElement);
const result = new DqElement(vNode);
assert.equal(result.source, `${customElement.substring(0, 300)} ...>`);
});

it('should not truncate attributes if children are long but attribute itself is within limits', () => {
let el = document.createElement('div');
let attributeValue = '';
let innerHtml = '';
for (let i = 0; i < 50; i++) {
attributeValue += 'a';
innerHtml += 'foobar';
}
el.setAttribute('long-attribute', attributeValue);
el.innerHTML = innerHtml;

const vNode = new DqElement(el);
assert.equal(vNode.source, `<div long-attribute="${attributeValue}">`);
});

it('should use spec object over passed element', () => {
const vNode = queryFixture('<div id="target" class="bar">Hello!</div>');
const spec = { source: 'woot' };
Expand Down