Validating SVG Component Rendering in PreactJS
This test suite validates SVG rendering and attribute handling in Preact components. It ensures proper SVG namespace management, attribute transformation, and DOM integration while verifying both static and dynamic SVG content rendering.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
preactjs/preact
test/browser/svg.test.js
import { createElement, Component, render } from 'preact';
import { setupRerender } from 'preact/test-utils';
import { setupScratch, teardown, sortAttributes } from '../_util/helpers';
/** @jsx createElement */
describe('svg', () => {
let scratch;
beforeEach(() => {
scratch = setupScratch();
});
afterEach(() => {
teardown(scratch);
});
it('should render SVG to string', () => {
render(
<svg viewBox="0 0 360 360">
<path
stroke="white"
fill="black"
d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z"
/>
</svg>,
scratch
);
let html = sortAttributes(
String(scratch.innerHTML).replace(
' xmlns="http://www.w3.org/2000/svg"',
''
)
);
expect(html).to.equal(
sortAttributes(
`
<svg viewBox="0 0 360 360">
<path d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z" fill="black" stroke="white"></path>
</svg>
`.replace(/[\n\t]+/g, '')
)
);
});
it('should support svg xlink:href attribute', () => {
render(
createElement('svg', {}, createElement('use', { 'xlink:href': '#foo' })),
scratch
);
expect(scratch.innerHTML).to.contain(` href="#foo"`);
});
it('should support svg attributes', () => {
const Demo = ({ url }) => (
<svg viewBox="0 0 360 360" xlinkHref={url}>
<path
d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z"
fill="black"
stroke="white"
/>
</svg>
);
render(<Demo url="www.preact.com" />, scratch);
let html = String(scratch.innerHTML).replace(
' xmlns="http://www.w3.org/2000/svg"',
''
);
html = sortAttributes(
html.replace(' xmlns:xlink="http://www.w3.org/1999/xlink"', '')
);
expect(html).to.equal(
sortAttributes(
`
<svg viewBox="0 0 360 360" href="www.preact.com">
<path d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z" fill="black" stroke="white"></path>
</svg>
`.replace(/[\n\t]+/g, '')
)
);
render(<Demo />, scratch);
html = String(scratch.innerHTML).replace(
' xmlns="http://www.w3.org/2000/svg"',
''
);
html = sortAttributes(
html.replace(' xmlns:xlink="http://www.w3.org/1999/xlink"', '')
);
expect(html).to.equal(
sortAttributes(
`
<svg viewBox="0 0 360 360">
<path d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z" fill="black" stroke="white"></path>
</svg>
`.replace(/[\n\t]+/g, '')
)
);
});
it('should render SVG to DOM', () => {
const Demo = () => (
<svg viewBox="0 0 360 360">
<path
d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z"
fill="black"
stroke="white"
/>
</svg>
);
render(<Demo />, scratch);
let html = sortAttributes(
String(scratch.innerHTML).replace(
' xmlns="http://www.w3.org/2000/svg"',
''
)
);
expect(html).to.equal(
sortAttributes(
'<svg viewBox="0 0 360 360"><path stroke="white" fill="black" d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z"></path></svg>'
)
);
});
it('should render with the correct namespace URI', () => {
render(<svg />, scratch);
let namespace = scratch.querySelector('svg').namespaceURI;
expect(namespace).to.equal('http://www.w3.org/2000/svg');
});
it('should render children with the correct namespace URI', () => {
render(
<svg>
<text />
</svg>,
scratch
);
let namespace = scratch.querySelector('text').namespaceURI;
expect(namespace).to.equal('http://www.w3.org/2000/svg');
});
it('should inherit correct namespace URI from parent', () => {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
scratch.appendChild(svg);
render(<text />, scratch.firstChild);
let namespace = scratch.querySelector('text').namespaceURI;
expect(namespace).to.equal('http://www.w3.org/2000/svg');
});
it('should inherit correct namespace URI from parent upon updating', () => {
setupRerender();
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
scratch.appendChild(svg);
class App extends Component {
state = { show: true };
componentDidMount() {
// eslint-disable-next-line
this.setState({ show: false }, () => {
expect(scratch.querySelector('circle').namespaceURI).to.equal(
'http://www.w3.org/2000/svg'
);
});
}
render() {
return this.state.show ? <text /> : <circle />;
}
}
render(<App />, scratch.firstChild);
});
it('should use attributes for className', () => {
const Demo = ({ c }) => (
<svg viewBox="0 0 360 360" {...(c ? { class: 'foo_' + c } : {})}>
<path
class={c && 'bar_' + c}
stroke="white"
fill="black"
d="M347.1 357.9L183.3 256.5 13 357.9V1.7h334.1v356.2zM58.5 47.2v231.4l124.8-74.1 118.3 72.8V47.2H58.5z"
/>
</svg>
);
render(<Demo c="1" />, scratch);
let root = scratch.firstChild;
sinon.spy(root, 'removeAttribute');
render(<Demo />, scratch);
expect(root.removeAttribute).to.have.been.calledOnce.and.calledWith(
'class'
);
root.removeAttribute.restore();
render(<div />, scratch);
render(<Demo />, scratch);
root = scratch.firstChild;
sinon.spy(root, 'setAttribute');
render(<Demo c="2" />, scratch);
expect(root.setAttribute).to.have.been.calledOnce.and.calledWith(
'class',
'foo_2'
);
root.setAttribute.restore();
});
it('should still support class attribute', () => {
render(<svg viewBox="0 0 1 1" class="foo bar" />, scratch);
expect(scratch.innerHTML).to.contain(` class="foo bar"`);
});
it('should still support className attribute', () => {
render(<svg viewBox="0 0 1 1" className="foo bar" />, scratch);
expect(scratch.innerHTML).to.contain(` class="foo bar"`);
});
it('should switch back to HTML for <foreignObject>', () => {
render(
<svg>
<g>
<foreignObject>
<a href="#foo">test</a>
</foreignObject>
</g>
</svg>,
scratch
);
expect(scratch.getElementsByTagName('a'))
.to.have.property('0')
.that.is.a('HTMLAnchorElement');
});
it('should render foreignObject as an svg element', () => {
render(
<svg>
<g>
<foreignObject>
<a href="#foo">test</a>
</foreignObject>
</g>
</svg>,
scratch
);
expect(scratch.querySelector('foreignObject').localName).to.equal(
'foreignObject'
);
});
it('should transition from DOM to SVG and back', () => {
render(
<div>
<svg
id="svg1923"
width="700"
xmlns="http://www.w3.org/2000/svg"
height="700"
>
<circle cy="333" cx="333" r="333" />
<circle cy="333" cx="333" r="333" fill="#fede58" />
</svg>
</div>,
scratch
);
expect(scratch.firstChild).to.be.an('HTMLDivElement');
expect(scratch.firstChild.firstChild).to.be.an('SVGSVGElement');
});
});