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(icon): remove IDs from source icon set from rendered output #8266

Merged
merged 1 commit into from
Nov 10, 2017
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
21 changes: 11 additions & 10 deletions src/lib/icon/fake-svgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,40 @@

/**
* Fake URLs and associated SVG documents used by tests.
* The ID attribute is used to load the icons, the name attribute is only used for testing.
* @docs-private
*/
export const FAKE_SVGS = {
cat: '<svg><path id="meow"></path></svg>',
dog: '<svg><path id="woof"></path></svg>',
cat: '<svg><path id="meow" name="meow"></path></svg>',
dog: '<svg><path id="woof" name="woof"></path></svg>',
farmSet1: `
<svg>
<defs>
<g id="pig"><path id="oink"></path></g>
<g id="cow"><path id="moo"></path></g>
<g id="pig" name="pig"><path name="oink"></path></g>
<g id="cow" name="cow"><path name="moo"></path></g>
</defs>
</svg>
`,
farmSet2: `
<svg>
<defs>
<g id="cow"><path id="moo moo"></path></g>
<g id="sheep"><path id="baa"></path></g>
<g id="cow" name="cow"><path name="moo moo"></path></g>
<g id="sheep" name="sheep"><path name="baa"></path></g>
</defs>
</svg>
`,
farmSet3: `
<svg>
<symbol id="duck">
<path id="quack"></path>
<symbol id="duck" name="duck">
<path id="quack" name="quack"></path>
</symbol>
</svg>
`,
arrows: `
<svg>
<defs>
<svg id="left-arrow"><path id="left"></path></svg>
<svg id="right-arrow"><path id="right"></path></svg>
<svg id="left-arrow" name="left-arrow"><path name="left"></path></svg>
<svg id="right-arrow" name="right-arrow"><path name="right"></path></svg>
</defs>
</svg> `
};
21 changes: 12 additions & 9 deletions src/lib/icon/icon-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,23 +365,28 @@ export class MatIconRegistry {
* returns it. Returns null if no matching element is found.
*/
private _extractSvgIconFromSet(iconSet: SVGElement, iconName: string): SVGElement | null {
const iconNode = iconSet.querySelector('#' + iconName);
const iconSource = iconSet.querySelector('#' + iconName);

if (!iconNode) {
if (!iconSource) {
return null;
}

// Clone the element and remove the ID to prevent multiple elements from being added
// to the page with the same ID.
const iconElement = iconSource.cloneNode(true) as Element;
iconElement.id = '';

// If the icon node is itself an <svg> node, clone and return it directly. If not, set it as
// the content of a new <svg> node.
if (iconNode.tagName.toLowerCase() === 'svg') {
return this._setSvgAttributes(iconNode.cloneNode(true) as SVGElement);
if (iconElement.nodeName.toLowerCase() === 'svg') {
return this._setSvgAttributes(iconElement as SVGElement);
}

// If the node is a <symbol>, it won't be rendered so we have to convert it into <svg>. Note
// that the same could be achieved by referring to it via <use href="#id">, however the <use>
// tag is problematic on Firefox, because it needs to include the current page path.
if (iconNode.nodeName.toLowerCase() === 'symbol') {
return this._setSvgAttributes(this._toSvgElement(iconNode));
if (iconElement.nodeName.toLowerCase() === 'symbol') {
return this._setSvgAttributes(this._toSvgElement(iconElement));
}

// createElement('SVG') doesn't work as expected; the DOM ends up with
Expand All @@ -391,7 +396,7 @@ export class MatIconRegistry {
// http://stackoverflow.com/questions/23003278/svg-innerhtml-in-firefox-can-not-display
const svg = this._svgElementFromString('<svg></svg>');
// Clone the node so we don't remove it from the parent icon set element.
svg.appendChild(iconNode.cloneNode(true));
svg.appendChild(iconElement);

return this._setSvgAttributes(svg);
}
Expand All @@ -400,8 +405,6 @@ export class MatIconRegistry {
* Creates a DOM element from the given SVG string.
*/
private _svgElementFromString(str: string): SVGElement {
// TODO: Is there a better way than innerHTML? Renderer doesn't appear to have a method for
// creating an element from an HTML string.
const div = document.createElement('DIV');
div.innerHTML = str;
const svg = div.querySelector('svg') as SVGElement;
Expand Down
16 changes: 10 additions & 6 deletions src/lib/icon/icon.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function sortedClassNames(element: Element): string[] {
* Verifies that an element contains a single <svg> child element, and returns that child.
*/
function verifyAndGetSingleSvgChild(element: SVGElement): SVGElement {
expect(element.id).toBeFalsy();
expect(element.childNodes.length).toBe(1);
const svgChild = element.childNodes[0] as SVGElement;
expect(svgChild.tagName.toLowerCase()).toBe('svg');
Expand All @@ -31,7 +32,9 @@ function verifyPathChildElement(element: Element, attributeValue: string): void
expect(element.childNodes.length).toBe(1);
const pathElement = element.childNodes[0] as SVGPathElement;
expect(pathElement.tagName.toLowerCase()).toBe('path');
expect(pathElement.getAttribute('id')).toBe(attributeValue);

// The testing data SVGs have the name attribute set for verification.
expect(pathElement.getAttribute('name')).toBe(attributeValue);
}


Expand Down Expand Up @@ -190,7 +193,7 @@ describe('MatIcon', () => {
svgChild = svgElement.childNodes[0];
// The first <svg> child should be the <g id="pig"> element.
expect(svgChild.tagName.toLowerCase()).toBe('g');
expect(svgChild.getAttribute('id')).toBe('pig');
expect(svgChild.getAttribute('name')).toBe('pig');
verifyPathChildElement(svgChild, 'oink');

// Change the icon, and the SVG element should be replaced.
Expand All @@ -200,7 +203,7 @@ describe('MatIcon', () => {
svgChild = svgElement.childNodes[0];
// The first <svg> child should be the <g id="cow"> element.
expect(svgChild.tagName.toLowerCase()).toBe('g');
expect(svgChild.getAttribute('id')).toBe('cow');
expect(svgChild.getAttribute('name')).toBe('cow');
verifyPathChildElement(svgChild, 'moo');
});

Expand All @@ -224,7 +227,8 @@ describe('MatIcon', () => {
svgChild = svgElement.childNodes[0];
// The <svg> child should be the <g id="pig"> element.
expect(svgChild.tagName.toLowerCase()).toBe('g');
expect(svgChild.getAttribute('id')).toBe('pig');
expect(svgChild.getAttribute('name')).toBe('pig');
expect(svgChild.getAttribute('id')).toBe('');
expect(svgChild.childNodes.length).toBe(1);
verifyPathChildElement(svgChild, 'oink');

Expand All @@ -237,7 +241,7 @@ describe('MatIcon', () => {
svgChild = svgElement.childNodes[0];
// The first <svg> child should be the <g id="cow"> element.
expect(svgChild.tagName.toLowerCase()).toBe('g');
expect(svgChild.getAttribute('id')).toBe('cow');
expect(svgChild.getAttribute('name')).toBe('cow');
expect(svgChild.childNodes.length).toBe(1);
verifyPathChildElement(svgChild, 'moo moo');
});
Expand All @@ -259,7 +263,7 @@ describe('MatIcon', () => {
expect(svgElement.querySelector('symbol')).toBeFalsy();
expect(svgElement.childNodes.length).toBe(1);
expect(firstChild.nodeName.toLowerCase()).toBe('path');
expect((firstChild as HTMLElement).getAttribute('id')).toBe('quack');
expect((firstChild as HTMLElement).getAttribute('name')).toBe('quack');
});

it('should not wrap <svg> elements in icon sets in another svg tag', () => {
Expand Down
1 change: 0 additions & 1 deletion src/lib/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1870,7 +1870,6 @@ describe('MatSelect', () => {

// There appears to be a small rounding error on IE, so we verify that the value is close,
// not exact.
let platform = new Platform();
if (platform.TRIDENT) {
let difference =
Math.abs(optionTop + (menuItemHeight - triggerHeight) / 2 - triggerTop);
Expand Down