Skip to content

Commit

Permalink
fix(mock-doc): fully svg compliant (#1739)
Browse files Browse the repository at this point in the history
  • Loading branch information
manucorporat authored Jul 19, 2019
1 parent 5f2fc80 commit ff46cbc
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 24 deletions.
9 changes: 1 addition & 8 deletions src/mock-doc/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@ export function createElement(ownerDocument: any, tagName: string) {
return new MockCanvasElement(ownerDocument);
}

if (SVG_TAGS.has(tagName)) {
return new MockSVGElement(ownerDocument, tagName);
}

if (ownerDocument != null && tagName.includes('-')) {
const win = ownerDocument.defaultView;
if (win != null && win.customElements != null) {
Expand All @@ -73,9 +69,6 @@ export function createElementNS(ownerDocument: any, namespaceURI: string, tagNam
}
}

// This set is intentionally incomplete. More tags can be added as needed.
const SVG_TAGS = new Set(['circle', 'line', 'g', 'path', 'svg', 'symbol', 'viewbox']);

class MockAnchorElement extends MockHTMLElement {
constructor(ownerDocument: any) {
super(ownerDocument, 'a');
Expand Down Expand Up @@ -215,7 +208,7 @@ patchPropAttributes(MockScriptElement.prototype, {
type: String
});

class MockSVGElement extends MockElement {
export class MockSVGElement extends MockElement {
// SVGElement properties and methods
get ownerSVGElement(): SVGSVGElement { return null; }
get viewportElement(): SVGElement { return null; }
Expand Down
17 changes: 15 additions & 2 deletions src/mock-doc/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export class MockElement extends MockNode {
super(
ownerDocument,
NODE_TYPES.ELEMENT_NODE,
typeof nodeName === 'string' ? nodeName.toUpperCase() : null,
typeof nodeName === 'string' ? nodeName : null,
null
);
this.namespaceURI = null;
Expand Down Expand Up @@ -542,7 +542,7 @@ export class MockElement extends MockNode {
set tabIndex(value: number) { this.setAttributeNS(null, 'tabindex', value); }

get tagName() { return this.nodeName; }
set tagName(value: string) { this.nodeName = value.toUpperCase(); }
set tagName(value: string) { this.nodeName = value; }

get textContent() {
const text: string[] = [];
Expand Down Expand Up @@ -679,6 +679,19 @@ function insertBefore(parentNode: MockNode, newNode: MockNode, referenceNode: Mo
}

export class MockHTMLElement extends MockElement {

namespaceURI = 'http://www.w3.org/1999/xhtml';

constructor(ownerDocument: any, nodeName: string) {
super(
ownerDocument,
typeof nodeName === 'string' ? nodeName.toUpperCase() : null,
);
}

get tagName() { return this.nodeName; }
set tagName(value: string) { this.nodeName = value; }

get attributes() {
let attrs = attrsMap.get(this);
if (attrs == null) {
Expand Down
10 changes: 6 additions & 4 deletions src/mock-doc/parse-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ function getParser(ownerDocument: MockDocument) {
},

createElement(tagName: string, namespaceURI: string, attrs: Attribute[]) {
const elm = ownerDocument.createElement(tagName);
elm.namespaceURI = namespaceURI;

const elm = ownerDocument.createElementNS(namespaceURI, tagName);
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];

Expand Down Expand Up @@ -175,7 +173,11 @@ function getParser(ownerDocument: MockDocument) {
},

getTagName(element: MockElement) {
return element.nodeName.toLowerCase();
if (element.namespaceURI === 'http://www.w3.org/1999/xhtml') {
return element.nodeName.toLowerCase();
} else {
return element.nodeName;
}
},

getNamespaceURI(element: MockElement) {
Expand Down
10 changes: 9 additions & 1 deletion src/mock-doc/serialize-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function serializeNodeToHtml(elm: Node | MockNode, opts: SerializeNodeToH

function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: SerializeOutput, isShadowRoot: boolean) {
if (node.nodeType === NODE_TYPES.ELEMENT_NODE || isShadowRoot) {
const tagName = isShadowRoot ? 'mock:shadow-root' : node.nodeName.toLowerCase();
const tagName = isShadowRoot ? 'mock:shadow-root' : getTagName(node as Element);

if (tagName === 'body') {
output.isWithinBody = true;
Expand Down Expand Up @@ -402,6 +402,14 @@ const LT_REGEX = /</g;
const GT_REGEX = />/g;
const CAN_REMOVE_ATTR_QUOTES = /^[^ \t\n\f\r"'`=<>\/\\-]+$/;

function getTagName(element: Element) {
if (element.namespaceURI === 'http://www.w3.org/1999/xhtml') {
return element.nodeName.toLowerCase();
} else {
return element.nodeName;
}
}

function escapeString(str: string, attrMode: boolean) {
str = str.replace(AMP_REGEX, '&amp;').replace(NBSP_REGEX, '&nbsp;');

Expand Down
2 changes: 1 addition & 1 deletion src/mock-doc/test/custom-elements.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('customElements', () => {
}
});

const cmpA = document.createElement('cmp-a');
const cmpA = document.createElement('CMP-a');
cmpA.setAttribute('attr-a', 'value-a');
expect(attrName).toBe('attr-a');
expect(oldValue).toBe(null);
Expand Down
64 changes: 64 additions & 0 deletions src/mock-doc/test/element.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MockDocument } from '../document';
import { MockWindow, cloneWindow } from '../window';
import { MockElement, MockHTMLElement } from '../node';
import { XLINK_NS } from '../../runtime/runtime-constants';
import { MockSVGElement } from '../element';


describe('element', () => {
Expand Down Expand Up @@ -119,6 +120,69 @@ describe('element', () => {
});
});

describe('namespaceURI', () => {
it('HTMLElement namespaceURI is always http://www.w3.org/1999/xhtml', () => {
const htmlElement = new MockHTMLElement(doc, 'svg');
expect(htmlElement.namespaceURI).toEqual('http://www.w3.org/1999/xhtml');

const createdElement1 = doc.createElement('div');
expect(createdElement1.namespaceURI).toEqual('http://www.w3.org/1999/xhtml');

const createdElement2 = doc.createElement('svg');
expect(createdElement2.namespaceURI).toEqual('http://www.w3.org/1999/xhtml');
expect(createdElement2 instanceof MockHTMLElement).toBe(true);

const createdElement3 = doc.createElementNS('http://www.w3.org/1999/xhtml', 'svg');
expect(createdElement3.namespaceURI).toEqual('http://www.w3.org/1999/xhtml');
expect(createdElement3 instanceof MockHTMLElement).toBe(true);
});

it('Element namespace is null by defualt', () => {
const element = new MockElement(doc, 'svg');
expect(element.namespaceURI).toEqual(null);
});

it('createElementNS sets the namespace', () => {
const element = doc.createElementNS('random', 'svg');
expect(element.namespaceURI).toEqual('random');
expect(element instanceof MockSVGElement).toBe(false);

const element1 = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
expect(element1.namespaceURI).toEqual('http://www.w3.org/2000/svg');
expect(element1 instanceof MockSVGElement).toBe(true);
});
});

describe('tagName', () => {
it('Element tagName/nodeName is case sensible', () => {
const element = new MockElement(doc, 'myElement');
expect(element.tagName).toEqual('myElement');
expect(element.nodeName).toEqual('myElement');

const foreignObject = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
expect(foreignObject.tagName).toEqual('foreignObject');
expect(foreignObject.nodeName).toEqual('foreignObject');
});

it('HTMLElement tagName/nodeName is case insensible', () => {
const element = new MockHTMLElement(doc, 'myElement');
expect(element.tagName).toEqual('MYELEMENT');
expect(element.nodeName).toEqual('MYELEMENT');

const foreignObject = document.createElement('foreignObject');
expect(foreignObject.tagName).toEqual('FOREIGNOBJECT');
expect(foreignObject.nodeName).toEqual('FOREIGNOBJECT');

const foreignObject2 = document.createElementNS('http://www.w3.org/1999/xhtml', 'foreignObject');
expect(foreignObject2.tagName).toEqual('FOREIGNOBJECT');
expect(foreignObject2.nodeName).toEqual('FOREIGNOBJECT');

const createdElement = document.createElement('myElement');
expect(createdElement.tagName).toEqual('MYELEMENT');
expect(createdElement.nodeName).toEqual('MYELEMENT');
});
});

describe('attributes', () => {
it('attributes are case sensible in Element', () => {
const element = new MockElement(doc, 'div');
Expand Down
41 changes: 40 additions & 1 deletion src/mock-doc/test/html-parse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,45 @@ describe('parseHtml', () => {
expect(article.tagName).toBe('CMP-A');
});

it('svg', () => {
doc = new MockDocument(`
<div>
<svg>
<a>Hello</a>
<feImage></feImage>
<foreignObject>
<a>Hello</a>
<feImage></feImage>
</foreignObject>
<svg>
</div>
`);
expect(doc.body.firstElementChild.tagName).toEqual('DIV');
expect(doc.body.firstElementChild.firstElementChild.tagName).toEqual('svg');
expect(doc.body.firstElementChild.firstElementChild.children[0].tagName).toEqual('a');
expect(doc.body.firstElementChild.firstElementChild.children[1].tagName).toEqual('feImage');
expect(doc.body.firstElementChild.firstElementChild.children[2].tagName).toEqual('foreignObject');
expect(doc.body.firstElementChild.firstElementChild.children[2].children[0].tagName).toEqual('A');
expect(doc.body.firstElementChild.firstElementChild.children[2].children[1].tagName).toEqual('FEIMAGE');
expect(doc.body).toEqualHtml(`
<div>
<svg>
<a>
Hello
</a>
<feImage></feImage>
<foreignObject>
<a>
Hello
</a>
<feimage></feimage>
</foreignObject>
<svg></svg>
</svg>
</div>
`);
});

it('template', () => {
doc = new MockDocument(`
<template>text</template>
Expand Down Expand Up @@ -215,7 +254,7 @@ describe('parseHtml', () => {

it('should mock canvas api', () => {
const elm = parseHtmlToFragment('<canvas id="canvas" width="300" height="300"></canvas>');
const canvas = elm.children[0];
const canvas = elm.children[0];
const ctx: CanvasRenderingContext2D = canvas.getContext('2d');
expect(elm.children.length).toBe(1);
expect(elm.children[0].nodeName).toBe('CANVAS');
Expand Down
23 changes: 16 additions & 7 deletions src/runtime/vdom/test/patch-svg.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('renderer', () => {
describe('created element', () => {

it('has tag', () => {
patch(vnode0, h('div', null), document);
patch(vnode0, h('div', null));
expect(hostElm.tagName).toEqual('DIV');
});

Expand All @@ -29,10 +29,19 @@ describe('renderer', () => {
h('foreignObject', null,
h('div', null, 'I am HTML embedded in SVG')
)
), document);
));

expect(svgElm.firstChild.namespaceURI).toEqual(SVG_NS);
expect(svgElm.firstChild.firstChild.namespaceURI).not.toEqual(SVG_NS);
expect(svgElm).toEqualHtml(`
<svg>
<foreignObject>
<div>
I am HTML embedded in SVG
</div>
</foreignObject>
</svg>`
);
});

it('should not affect subsequence element', () => {
Expand All @@ -42,11 +51,11 @@ describe('renderer', () => {
h('circle', null)
] as any),
h('div', null)
] as any), document);
] as any));

expect(hostElm.tagName).toEqual('DIV');
expect(hostElm.namespaceURI).not.toEqual(SVG_NS);
expect(hostElm.firstElementChild.tagName).toEqual('SVG');
expect(hostElm.firstElementChild.tagName).toEqual('svg');
expect(hostElm.firstElementChild.namespaceURI).toEqual(SVG_NS);
expect(hostElm.firstElementChild.firstChild.namespaceURI).toEqual(SVG_NS);
expect(hostElm.firstElementChild.lastChild.namespaceURI).toEqual(SVG_NS);
Expand All @@ -62,7 +71,7 @@ describe('renderer', () => {
h('div', null,
h('svg', null)
)
), document);
));

const vnode1 = toVNode(vnode0.$elm$);

Expand All @@ -72,11 +81,11 @@ describe('renderer', () => {
),
h('div', null)
] as any
), document);
));

const vnode2 = toVNode(vnode1.$elm$);
expect(vnode2.$children$[0].$elm$.tagName).toEqual('DIV');
expect(vnode2.$children$[0].$children$[0].$elm$.tagName).toEqual('SVG');
expect(vnode2.$children$[0].$children$[0].$elm$.tagName).toEqual('svg');
expect(vnode2.$children$[1].$elm$.tagName).toEqual('DIV');
});
});
Expand Down

0 comments on commit ff46cbc

Please sign in to comment.