Skip to content

Commit ebf32d0

Browse files
Merge pull request #311 from gpoitch/gp/pretty-attrs
2 parents 1403064 + 4010ba0 commit ebf32d0

File tree

7 files changed

+497
-418
lines changed

7 files changed

+497
-418
lines changed

.changeset/plenty-cycles-work.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"preact-render-to-string": patch
3+
---
4+
5+
Apply attribute name handling in pretty mode

src/index.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { encodeEntities, styleObjToCss, UNSAFE_NAME } from './util.js';
1+
import {
2+
encodeEntities,
3+
styleObjToCss,
4+
UNSAFE_NAME,
5+
NAMESPACE_REPLACE_REGEX,
6+
HTML_LOWER_CASE,
7+
SVG_CAMEL_CASE
8+
} from './util.js';
29
import { options, h, Fragment } from 'preact';
310
import {
411
CHILDREN,
@@ -454,9 +461,6 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
454461
return s + '>' + html + '</' + type + '>';
455462
}
456463

457-
const HTML_LOWER_CASE = /^accessK|^auto[A-Z]|^ch|^col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|readO|rowS|spellC|src[A-Z]|tabI|item[A-Z]/;
458-
const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/;
459-
const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)(:|[A-Z])/;
460464
const SELF_CLOSING = new Set([
461465
'area',
462466
'base',

src/pretty.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import {
66
getChildren,
77
createComponent,
88
UNSAFE_NAME,
9-
XLINK,
10-
VOID_ELEMENTS
9+
VOID_ELEMENTS,
10+
NAMESPACE_REPLACE_REGEX,
11+
HTML_LOWER_CASE,
12+
SVG_CAMEL_CASE
1113
} from './util.js';
1214
import { COMMIT, DIFF, DIFFED, RENDER, SKIP_EFFECTS } from './constants.js';
1315
import { options, Fragment } from 'preact';
@@ -243,8 +245,21 @@ function _renderToStringPretty(
243245
} else if (name === 'className') {
244246
if (typeof props.class !== 'undefined') continue;
245247
name = 'class';
246-
} else if (isSvgMode && XLINK.test(name)) {
247-
name = name.toLowerCase().replace(/^xlink:?/, 'xlink:');
248+
} else if (name === 'acceptCharset') {
249+
name = 'accept-charset';
250+
} else if (name === 'httpEquiv') {
251+
name = 'http-equiv';
252+
} else if (NAMESPACE_REPLACE_REGEX.test(name)) {
253+
name = name.replace(NAMESPACE_REPLACE_REGEX, '$1:$2').toLowerCase();
254+
} else if (isSvgMode) {
255+
if (SVG_CAMEL_CASE.test(name)) {
256+
name =
257+
name === 'panose1'
258+
? 'panose-1'
259+
: name.replace(/([A-Z])/g, '-$1').toLowerCase();
260+
}
261+
} else if (HTML_LOWER_CASE.test(name)) {
262+
name = name.toLowerCase();
248263
}
249264

250265
if (name === 'htmlFor') {

src/util.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
22
export const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
3-
export const XLINK = /^xlink:?./;
3+
export const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)(:|[A-Z])/;
4+
export const HTML_LOWER_CASE = /^accessK|^auto[A-Z]|^ch|^col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|readO|rowS|spellC|src[A-Z]|tabI|item[A-Z]/;
5+
export const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/;
46

57
// DOM properties that should NOT have "px" added when numeric
68
const ENCODED_ENTITIES = /["&<]/;

test/pretty.test.js

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import basicRender from '../src/index.js';
22
import { render } from '../src/jsx.js';
33
import { h, Fragment } from 'preact';
44
import { expect } from 'chai';
5-
import { dedent } from './utils.js';
5+
import { dedent, svgAttributes, htmlAttributes } from './utils.js';
66

77
describe('pretty', () => {
8-
let prettyRender = (jsx) => render(jsx, {}, { pretty: true });
8+
let prettyRender = (jsx, opts) => render(jsx, {}, { pretty: true, ...opts });
99

1010
it('should render no whitespace by default', () => {
1111
let rendered = basicRender(
@@ -196,4 +196,56 @@ describe('pretty', () => {
196196
it('should not render function children', () => {
197197
expect(prettyRender(<div>{() => {}}</div>)).to.equal('<div></div>');
198198
});
199+
200+
it('should render SVG elements', () => {
201+
let rendered = prettyRender(
202+
<svg>
203+
<image xlinkHref="#" />
204+
<foreignObject>
205+
<div xlinkHref="#" />
206+
</foreignObject>
207+
<g>
208+
<image xlinkHref="#" />
209+
</g>
210+
</svg>
211+
);
212+
213+
expect(rendered).to.equal(
214+
`<svg>\n\t<image xlink:href="#"></image>\n\t<foreignObject>\n\t\t<div xlink:href="#"></div>\n\t</foreignObject>\n\t<g>\n\t\t<image xlink:href="#"></image>\n\t</g>\n</svg>`
215+
);
216+
});
217+
218+
describe('Attribute casing', () => {
219+
it('should have correct SVG casing', () => {
220+
for (let name in svgAttributes) {
221+
let value = svgAttributes[name];
222+
223+
let rendered = prettyRender(
224+
<svg>
225+
<path {...{ [name]: 'foo' }} />
226+
</svg>
227+
);
228+
expect(rendered).to.equal(
229+
`<svg>\n\t<path ${value}="foo"></path>\n</svg>`
230+
);
231+
}
232+
});
233+
234+
it('should have correct HTML casing', () => {
235+
for (let name in htmlAttributes) {
236+
let value = htmlAttributes[name];
237+
238+
if (name === 'checked') {
239+
let rendered = prettyRender(<input type="checkbox" checked />, {
240+
jsx: false
241+
});
242+
expect(rendered).to.equal(`<input type="checkbox" checked />`);
243+
continue;
244+
} else {
245+
let rendered = prettyRender(<div {...{ [name]: 'foo' }} />);
246+
expect(rendered).to.equal(`<div ${value}="foo"></div>`);
247+
}
248+
}
249+
});
250+
});
199251
});

0 commit comments

Comments
 (0)