| 1 | /** |
| 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | * |
| 4 | * This source code is licensed under the MIT license found in the |
| 5 | * LICENSE file in the root directory of this source tree. |
| 6 | * |
| 7 | * @emails react-core |
| 8 | */ |
| 9 | 'use strict'; |
| 10 | |
| 11 | let React; |
| 12 | let ReactDOMClient; |
| 13 | let ReactDOMServer; |
| 14 | let act; |
| 15 | |
| 16 | describe('ReactDOMSVG', () => { |
| 17 | beforeEach(() => { |
| 18 | React = require('react'); |
| 19 | ReactDOMClient = require('react-dom/client'); |
| 20 | ReactDOMServer = require('react-dom/server'); |
| 21 | act = require('internal-test-utils').act; |
| 22 | }); |
| 23 | |
| 24 | it('creates initial namespaced markup', () => { |
| 25 | const markup = ReactDOMServer.renderToString( |
| 26 | <svg> |
| 27 | <image xlinkHref="http://i.imgur.com/w7GCRPb.png" /> |
| 28 | </svg>, |
| 29 | ); |
| 30 | expect(markup).toContain('xlink:href="http://i.imgur.com/w7GCRPb.png"'); |
| 31 | }); |
| 32 | |
| 33 | it('creates elements with SVG namespace inside SVG tag during mount', async () => { |
| 34 | const node = document.createElement('div'); |
| 35 | let div, |
| 36 | div2, |
| 37 | div3, |
| 38 | foreignObject, |
| 39 | foreignObject2, |
| 40 | g, |
| 41 | image, |
| 42 | image2, |
| 43 | image3, |
| 44 | p, |
| 45 | svg, |
| 46 | svg2, |
| 47 | svg3, |
| 48 | svg4; |
| 49 | const root = ReactDOMClient.createRoot(node); |
| 50 | await act(() => { |
| 51 | root.render( |
| 52 | <div> |
| 53 | <svg ref={el => (svg = el)}> |
| 54 | <g ref={el => (g = el)} strokeWidth="5"> |
| 55 | <svg ref={el => (svg2 = el)}> |
| 56 | <foreignObject ref={el => (foreignObject = el)}> |
| 57 | <svg ref={el => (svg3 = el)}> |
| 58 | <svg ref={el => (svg4 = el)} /> |
| 59 | <image |
| 60 | ref={el => (image = el)} |
| 61 | xlinkHref="http://i.imgur.com/w7GCRPb.png" |
| 62 | /> |
| 63 | </svg> |
| 64 | <div ref={el => (div = el)} /> |
| 65 | </foreignObject> |
| 66 | </svg> |
| 67 | <image |
| 68 | ref={el => (image2 = el)} |
| 69 | xlinkHref="http://i.imgur.com/w7GCRPb.png" |
| 70 | /> |
| 71 | <foreignObject ref={el => (foreignObject2 = el)}> |
| 72 | <div ref={el => (div2 = el)} /> |
| 73 | </foreignObject> |
| 74 | </g> |
| 75 | </svg> |
| 76 | <p ref={el => (p = el)}> |
| 77 | <svg> |
| 78 | <image |
| 79 | ref={el => (image3 = el)} |
| 80 | xlinkHref="http://i.imgur.com/w7GCRPb.png" |
| 81 | /> |
| 82 | </svg> |
| 83 | </p> |
| 84 | <div ref={el => (div3 = el)} /> |
| 85 | </div>, |
| 86 | ); |
| 87 | }); |
| 88 | [svg, svg2, svg3, svg4].forEach(el => { |
| 89 | expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg'); |
| 90 | // SVG tagName is case sensitive. |
| 91 | expect(el.tagName).toBe('svg'); |
| 92 | }); |
| 93 | expect(g.namespaceURI).toBe('http://www.w3.org/2000/svg'); |
| 94 | expect(g.tagName).toBe('g'); |
| 95 | expect(g.getAttribute('stroke-width')).toBe('5'); |
| 96 | expect(p.namespaceURI).toBe('http://www.w3.org/1999/xhtml'); |
| 97 | // DOM tagName is capitalized by browsers. |
| 98 | expect(p.tagName).toBe('P'); |
| 99 | [image, image2, image3].forEach(el => { |
| 100 | expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg'); |
| 101 | expect(el.tagName).toBe('image'); |
| 102 | expect(el.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe( |
| 103 | 'http://i.imgur.com/w7GCRPb.png', |
| 104 | ); |
| 105 | }); |
| 106 | [foreignObject, foreignObject2].forEach(el => { |
| 107 | expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg'); |
| 108 | expect(el.tagName).toBe('foreignObject'); |
| 109 | }); |
| 110 | [div, div2, div3].forEach(el => { |
| 111 | expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml'); |
| 112 | expect(el.tagName).toBe('DIV'); |
| 113 | }); |
| 114 | }); |
| 115 | |
| 116 | it('creates elements with SVG namespace inside SVG tag during update', async () => { |
| 117 | let inst, |
| 118 | div, |
| 119 | div2, |
| 120 | foreignObject, |
| 121 | foreignObject2, |
| 122 | g, |
| 123 | image, |
| 124 | image2, |
| 125 | svg, |
| 126 | svg2, |
| 127 | svg3, |
| 128 | svg4; |
| 129 | |
| 130 | class App extends React.Component { |
| 131 | state = {step: 0}; |
| 132 | |
| 133 | render() { |
| 134 | inst = this; |
| 135 | const {step} = this.state; |
| 136 | if (step === 0) { |
| 137 | return null; |
| 138 | } |
| 139 | return ( |
| 140 | <g ref={el => (g = el)} strokeWidth="5"> |
| 141 | <svg ref={el => (svg2 = el)}> |
| 142 | <foreignObject ref={el => (foreignObject = el)}> |
| 143 | <svg ref={el => (svg3 = el)}> |
| 144 | <svg ref={el => (svg4 = el)} /> |
| 145 | <image |
| 146 | ref={el => (image = el)} |
| 147 | xlinkHref="http://i.imgur.com/w7GCRPb.png" |
| 148 | /> |
| 149 | </svg> |
| 150 | <div ref={el => (div = el)} /> |
| 151 | </foreignObject> |
| 152 | </svg> |
| 153 | <image |
| 154 | ref={el => (image2 = el)} |
| 155 | xlinkHref="http://i.imgur.com/w7GCRPb.png" |
| 156 | /> |
| 157 | <foreignObject ref={el => (foreignObject2 = el)}> |
| 158 | <div ref={el => (div2 = el)} /> |
| 159 | </foreignObject> |
| 160 | </g> |
| 161 | ); |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | const node = document.createElement('div'); |
| 166 | const root = ReactDOMClient.createRoot(node); |
| 167 | await act(() => { |
| 168 | root.render( |
| 169 | <svg ref={el => (svg = el)}> |
| 170 | <App /> |
| 171 | </svg>, |
| 172 | ); |
| 173 | }); |
| 174 | await act(() => { |
| 175 | inst.setState({step: 1}); |
| 176 | }); |
| 177 | |
| 178 | [svg, svg2, svg3, svg4].forEach(el => { |
| 179 | expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg'); |
| 180 | // SVG tagName is case sensitive. |
| 181 | expect(el.tagName).toBe('svg'); |
| 182 | }); |
| 183 | expect(g.namespaceURI).toBe('http://www.w3.org/2000/svg'); |
| 184 | expect(g.tagName).toBe('g'); |
| 185 | expect(g.getAttribute('stroke-width')).toBe('5'); |
| 186 | [image, image2].forEach(el => { |
| 187 | expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg'); |
| 188 | expect(el.tagName).toBe('image'); |
| 189 | expect(el.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe( |
| 190 | 'http://i.imgur.com/w7GCRPb.png', |
| 191 | ); |
| 192 | }); |
| 193 | [foreignObject, foreignObject2].forEach(el => { |
| 194 | expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg'); |
| 195 | expect(el.tagName).toBe('foreignObject'); |
| 196 | }); |
| 197 | [div, div2].forEach(el => { |
| 198 | expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml'); |
| 199 | // DOM tagName is capitalized by browsers. |
| 200 | expect(el.tagName).toBe('DIV'); |
| 201 | }); |
| 202 | }); |
| 203 | |
| 204 | it('can render SVG into a non-React SVG tree', async () => { |
| 205 | const outerSVGRoot = document.createElementNS( |
| 206 | 'http://www.w3.org/2000/svg', |
| 207 | 'svg', |
| 208 | ); |
| 209 | const container = document.createElementNS( |
| 210 | 'http://www.w3.org/2000/svg', |
| 211 | 'g', |
| 212 | ); |
| 213 | outerSVGRoot.appendChild(container); |
| 214 | let image; |
| 215 | const root = ReactDOMClient.createRoot(container); |
| 216 | await act(() => { |
| 217 | root.render(<image ref={el => (image = el)} />); |
| 218 | }); |
| 219 | expect(image.namespaceURI).toBe('http://www.w3.org/2000/svg'); |
| 220 | expect(image.tagName).toBe('image'); |
| 221 | }); |
| 222 | |
| 223 | it('can render HTML into a foreignObject in non-React SVG tree', async () => { |
| 224 | const outerSVGRoot = document.createElementNS( |
| 225 | 'http://www.w3.org/2000/svg', |
| 226 | 'svg', |
| 227 | ); |
| 228 | const container = document.createElementNS( |
| 229 | 'http://www.w3.org/2000/svg', |
| 230 | 'foreignObject', |
| 231 | ); |
| 232 | outerSVGRoot.appendChild(container); |
| 233 | let div; |
| 234 | const root = ReactDOMClient.createRoot(container); |
| 235 | await act(() => { |
| 236 | root.render(<div ref={el => (div = el)} />); |
| 237 | }); |
| 238 | expect(div.namespaceURI).toBe('http://www.w3.org/1999/xhtml'); |
| 239 | expect(div.tagName).toBe('DIV'); |
| 240 | }); |
| 241 | }); |