Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(mock-doc): fully svg compliant #1739

Merged
merged 5 commits into from
Jul 19, 2019
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
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