Skip to content

Commit a9adfd4

Browse files
fix: primitives rendering to equal react output (#190)
* fix: do not convert boolean to string * feat: match react output --------- Co-authored-by: Arthur Fiorette <47537704+arthurfiorette@users.noreply.github.com> Co-authored-by: Arthur Fiorette <me@arthur.place>
1 parent 88ea4f4 commit a9adfd4

File tree

5 files changed

+200
-98
lines changed

5 files changed

+200
-98
lines changed

.changeset/witty-timers-reply.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@kitajs/html': patch
3+
---
4+
5+
Fixed primitives rendering to equal react output

packages/html/index.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -386,10 +386,13 @@ function contentsToString(contents, escape) {
386386
switch (typeof content) {
387387
case 'string':
388388
case 'number':
389-
case 'boolean':
389+
// Bigint is the only case where it differs from React.
390+
// where React renders a empty string and we render the whole number.
390391
case 'bigint':
391392
result += content;
392393
continue;
394+
case 'boolean':
395+
continue;
393396
}
394397

395398
if (!content) {
@@ -402,11 +405,15 @@ function contentsToString(contents, escape) {
402405
continue;
403406
}
404407

405-
// @ts-ignore - Type instantiation is excessively deep and possibly infinite.
406-
return Promise.all(contents.slice(index)).then(function resolveContents(resolved) {
407-
resolved.unshift(result);
408-
return contentsToString(resolved, escape);
409-
});
408+
if (typeof content.then === 'function') {
409+
// @ts-ignore - Type instantiation is excessively deep and possibly infinite.
410+
return Promise.all(contents.slice(index)).then(function resolveContents(resolved) {
411+
resolved.unshift(result);
412+
return contentsToString(resolved, escape);
413+
});
414+
}
415+
416+
throw new Error('Objects are not valid as a KitaJSX child');
410417
}
411418

412419
// escapeHtml is faster with longer strings, that's
@@ -427,11 +434,13 @@ function contentToString(content, safe) {
427434
switch (typeof content) {
428435
case 'string':
429436
return safe ? escapeHtml(content) : content;
430-
431437
case 'number':
432-
case 'boolean':
438+
// Bigint is the only case where it differs from React.
439+
// where React renders a empty string and we render the whole number.
433440
case 'bigint':
434441
return content.toString();
442+
case 'boolean':
443+
return '';
435444
}
436445

437446
if (!content) {
@@ -442,9 +451,13 @@ function contentToString(content, safe) {
442451
return contentsToString(content, safe);
443452
}
444453

445-
return content.then(function resolveContent(resolved) {
446-
return contentToString(resolved, safe);
447-
});
454+
if (typeof content.then === 'function') {
455+
return content.then(function resolveContent(resolved) {
456+
return contentToString(resolved, safe);
457+
});
458+
}
459+
460+
throw new Error('Objects are not valid as a KitaJSX child');
448461
}
449462

450463
/**

packages/html/jsx.d.ts

Lines changed: 69 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -855,73 +855,73 @@ declare namespace JSX {
855855

856856
// All the WAI-ARIA 1.1 role attribute values from https://www.w3.org/TR/wai-aria-1.1/#role_definitions
857857
type AriaRole =
858-
| "alert"
859-
| "alertdialog"
860-
| "application"
861-
| "article"
862-
| "banner"
863-
| "button"
864-
| "cell"
865-
| "checkbox"
866-
| "columnheader"
867-
| "combobox"
868-
| "complementary"
869-
| "contentinfo"
870-
| "definition"
871-
| "dialog"
872-
| "directory"
873-
| "document"
874-
| "feed"
875-
| "figure"
876-
| "form"
877-
| "grid"
878-
| "gridcell"
879-
| "group"
880-
| "heading"
881-
| "img"
882-
| "link"
883-
| "list"
884-
| "listbox"
885-
| "listitem"
886-
| "log"
887-
| "main"
888-
| "marquee"
889-
| "math"
890-
| "menu"
891-
| "menubar"
892-
| "menuitem"
893-
| "menuitemcheckbox"
894-
| "menuitemradio"
895-
| "navigation"
896-
| "none"
897-
| "note"
898-
| "option"
899-
| "presentation"
900-
| "progressbar"
901-
| "radio"
902-
| "radiogroup"
903-
| "region"
904-
| "row"
905-
| "rowgroup"
906-
| "rowheader"
907-
| "scrollbar"
908-
| "search"
909-
| "searchbox"
910-
| "separator"
911-
| "slider"
912-
| "spinbutton"
913-
| "status"
914-
| "switch"
915-
| "tab"
916-
| "table"
917-
| "tablist"
918-
| "tabpanel"
919-
| "term"
920-
| "textbox"
921-
| "timer"
922-
| "toolbar"
923-
| "tooltip"
924-
| "tree"
925-
| "treegrid"
926-
| "treeitem"
858+
| 'alert'
859+
| 'alertdialog'
860+
| 'application'
861+
| 'article'
862+
| 'banner'
863+
| 'button'
864+
| 'cell'
865+
| 'checkbox'
866+
| 'columnheader'
867+
| 'combobox'
868+
| 'complementary'
869+
| 'contentinfo'
870+
| 'definition'
871+
| 'dialog'
872+
| 'directory'
873+
| 'document'
874+
| 'feed'
875+
| 'figure'
876+
| 'form'
877+
| 'grid'
878+
| 'gridcell'
879+
| 'group'
880+
| 'heading'
881+
| 'img'
882+
| 'link'
883+
| 'list'
884+
| 'listbox'
885+
| 'listitem'
886+
| 'log'
887+
| 'main'
888+
| 'marquee'
889+
| 'math'
890+
| 'menu'
891+
| 'menubar'
892+
| 'menuitem'
893+
| 'menuitemcheckbox'
894+
| 'menuitemradio'
895+
| 'navigation'
896+
| 'none'
897+
| 'note'
898+
| 'option'
899+
| 'presentation'
900+
| 'progressbar'
901+
| 'radio'
902+
| 'radiogroup'
903+
| 'region'
904+
| 'row'
905+
| 'rowgroup'
906+
| 'rowheader'
907+
| 'scrollbar'
908+
| 'search'
909+
| 'searchbox'
910+
| 'separator'
911+
| 'slider'
912+
| 'spinbutton'
913+
| 'status'
914+
| 'switch'
915+
| 'tab'
916+
| 'table'
917+
| 'tablist'
918+
| 'tabpanel'
919+
| 'term'
920+
| 'textbox'
921+
| 'timer'
922+
| 'toolbar'
923+
| 'tooltip'
924+
| 'tree'
925+
| 'treegrid'
926+
| 'treeitem'
927927
| (string & {});

packages/html/test/attributes.test.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ describe('Attributes', () => {
4848
assert.equal('<form novalidate></form>', <form novalidate></form>);
4949
});
5050

51+
test('Undefined', () => {
52+
assert.equal(
53+
'<div></div>',
54+
<div hidden={undefined} translate={undefined}>
55+
{undefined}
56+
</div>
57+
);
58+
});
59+
5160
test('Dates & Objects', () => {
5261
const date = new Date();
5362
assert.equal(<del datetime={date} />, `<del datetime="${date.toISOString()}"></del>`);

packages/html/test/misc.test.tsx

Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,110 @@ describe('Miscellaneous', () => {
1919
assert.equal(<div hx-target="find "></div>, '<div hx-target="find "></div>');
2020
});
2121

22-
test('Falsy values', () => {
22+
test('Primitive values renders exactly like React', () => {
23+
assert.equal(<div>{false}</div>, <div></div>);
24+
assert.equal(<div>{null}</div>, <div></div>);
25+
assert.equal(<div>{undefined}</div>, <div></div>);
26+
assert.equal(<div>{0}</div>, <div>0</div>);
27+
assert.equal(<div>{432}</div>, <div>432</div>);
28+
assert.equal(<div>{NaN}</div>, <div>NaN</div>);
29+
assert.equal(<div>{true}</div>, <div></div>);
30+
assert.equal(<div>{Infinity}</div>, <div>Infinity</div>);
31+
assert.equal(<div>{-Infinity}</div>, <div>-Infinity</div>);
32+
assert.equal(<div>{[1, 2, 3]}</div>, <div>123</div>);
33+
2334
assert.equal(
24-
<>
35+
<div>
36+
{false}
2537
{false}
38+
</div>,
39+
<div></div>
40+
);
41+
assert.equal(
42+
<div>
2643
{null}
44+
{null}
45+
</div>,
46+
<div></div>
47+
);
48+
assert.equal(
49+
<div>
50+
{undefined}
2751
{undefined}
52+
</div>,
53+
<div></div>
54+
);
55+
assert.equal(
56+
<div>
2857
{0}
58+
{0}
59+
</div>,
60+
<div>00</div>
61+
);
62+
assert.equal(
63+
<div>
64+
{432}
65+
{432}
66+
</div>,
67+
<div>432432</div>
68+
);
69+
assert.equal(
70+
<div>
71+
{NaN}
2972
{NaN}
73+
</div>,
74+
<div>NaNNaN</div>
75+
);
76+
assert.equal(
77+
<div>
3078
{true}
79+
{true}
80+
</div>,
81+
<div></div>
82+
);
83+
assert.equal(
84+
<div>
85+
{Infinity}
3186
{Infinity}
87+
</div>,
88+
<div>InfinityInfinity</div>
89+
);
90+
assert.equal(
91+
<div>
3292
{-Infinity}
33-
{123n}
34-
</>,
35-
<>
36-
<>false</>
37-
<></>
38-
<></>
39-
<>0</>
40-
<>NaN</>
41-
<>true</>
42-
<>Infinity</>
43-
<>-Infinity</>
44-
<>123</>
45-
</>
93+
{-Infinity}
94+
</div>,
95+
<div>-Infinity-Infinity</div>
4696
);
47-
4897
assert.equal(
49-
<div id="truthy" hidden={false} spellcheck={true} translate={undefined}></div>,
50-
'<div id="truthy" spellcheck></div>'
98+
<div>
99+
{[1, 2, 3]}
100+
{[1, 2, 3]}
101+
</div>,
102+
<div>123123</div>
103+
);
104+
105+
// Bigint is the only case where it differs from React.
106+
// where React renders a empty string and we render the whole number.
107+
assert.equal(<div>{123456789123456789n}</div>, <div>123456789123456789</div>);
108+
assert.equal(<>{123456789123456789n}</>, <>123456789123456789</>);
109+
});
110+
111+
test('Rendering objects throws', () => {
112+
assert.throws(
113+
//@ts-expect-error - should warn about invalid child
114+
() => <div>{{}}</div>,
115+
/Objects are not valid as a KitaJSX child/
116+
);
117+
118+
assert.throws(
119+
//@ts-expect-error - should warn about invalid child
120+
() => (
121+
<div>
122+
{{}} {{}}
123+
</div>
124+
),
125+
/Objects are not valid as a KitaJSX child/
51126
);
52127
});
53128

0 commit comments

Comments
 (0)