Skip to content

Commit

Permalink
fix: primitives rendering to equal react output (#190)
Browse files Browse the repository at this point in the history
* fix: do not convert boolean to string

* feat: match react output

---------

Co-authored-by: Arthur Fiorette <[email protected]>
Co-authored-by: Arthur Fiorette <[email protected]>
  • Loading branch information
3 people authored Jul 23, 2024
1 parent 88ea4f4 commit a9adfd4
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 98 deletions.
5 changes: 5 additions & 0 deletions .changeset/witty-timers-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@kitajs/html': patch
---

Fixed primitives rendering to equal react output
35 changes: 24 additions & 11 deletions packages/html/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,10 +386,13 @@ function contentsToString(contents, escape) {
switch (typeof content) {
case 'string':
case 'number':
case 'boolean':
// Bigint is the only case where it differs from React.
// where React renders a empty string and we render the whole number.
case 'bigint':
result += content;
continue;
case 'boolean':
continue;
}

if (!content) {
Expand All @@ -402,11 +405,15 @@ function contentsToString(contents, escape) {
continue;
}

// @ts-ignore - Type instantiation is excessively deep and possibly infinite.
return Promise.all(contents.slice(index)).then(function resolveContents(resolved) {
resolved.unshift(result);
return contentsToString(resolved, escape);
});
if (typeof content.then === 'function') {
// @ts-ignore - Type instantiation is excessively deep and possibly infinite.
return Promise.all(contents.slice(index)).then(function resolveContents(resolved) {
resolved.unshift(result);
return contentsToString(resolved, escape);
});
}

throw new Error('Objects are not valid as a KitaJSX child');
}

// escapeHtml is faster with longer strings, that's
Expand All @@ -427,11 +434,13 @@ function contentToString(content, safe) {
switch (typeof content) {
case 'string':
return safe ? escapeHtml(content) : content;

case 'number':
case 'boolean':
// Bigint is the only case where it differs from React.
// where React renders a empty string and we render the whole number.
case 'bigint':
return content.toString();
case 'boolean':
return '';
}

if (!content) {
Expand All @@ -442,9 +451,13 @@ function contentToString(content, safe) {
return contentsToString(content, safe);
}

return content.then(function resolveContent(resolved) {
return contentToString(resolved, safe);
});
if (typeof content.then === 'function') {
return content.then(function resolveContent(resolved) {
return contentToString(resolved, safe);
});
}

throw new Error('Objects are not valid as a KitaJSX child');
}

/**
Expand Down
138 changes: 69 additions & 69 deletions packages/html/jsx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -855,73 +855,73 @@ declare namespace JSX {

// All the WAI-ARIA 1.1 role attribute values from https://www.w3.org/TR/wai-aria-1.1/#role_definitions
type AriaRole =
| "alert"
| "alertdialog"
| "application"
| "article"
| "banner"
| "button"
| "cell"
| "checkbox"
| "columnheader"
| "combobox"
| "complementary"
| "contentinfo"
| "definition"
| "dialog"
| "directory"
| "document"
| "feed"
| "figure"
| "form"
| "grid"
| "gridcell"
| "group"
| "heading"
| "img"
| "link"
| "list"
| "listbox"
| "listitem"
| "log"
| "main"
| "marquee"
| "math"
| "menu"
| "menubar"
| "menuitem"
| "menuitemcheckbox"
| "menuitemradio"
| "navigation"
| "none"
| "note"
| "option"
| "presentation"
| "progressbar"
| "radio"
| "radiogroup"
| "region"
| "row"
| "rowgroup"
| "rowheader"
| "scrollbar"
| "search"
| "searchbox"
| "separator"
| "slider"
| "spinbutton"
| "status"
| "switch"
| "tab"
| "table"
| "tablist"
| "tabpanel"
| "term"
| "textbox"
| "timer"
| "toolbar"
| "tooltip"
| "tree"
| "treegrid"
| "treeitem"
| 'alert'
| 'alertdialog'
| 'application'
| 'article'
| 'banner'
| 'button'
| 'cell'
| 'checkbox'
| 'columnheader'
| 'combobox'
| 'complementary'
| 'contentinfo'
| 'definition'
| 'dialog'
| 'directory'
| 'document'
| 'feed'
| 'figure'
| 'form'
| 'grid'
| 'gridcell'
| 'group'
| 'heading'
| 'img'
| 'link'
| 'list'
| 'listbox'
| 'listitem'
| 'log'
| 'main'
| 'marquee'
| 'math'
| 'menu'
| 'menubar'
| 'menuitem'
| 'menuitemcheckbox'
| 'menuitemradio'
| 'navigation'
| 'none'
| 'note'
| 'option'
| 'presentation'
| 'progressbar'
| 'radio'
| 'radiogroup'
| 'region'
| 'row'
| 'rowgroup'
| 'rowheader'
| 'scrollbar'
| 'search'
| 'searchbox'
| 'separator'
| 'slider'
| 'spinbutton'
| 'status'
| 'switch'
| 'tab'
| 'table'
| 'tablist'
| 'tabpanel'
| 'term'
| 'textbox'
| 'timer'
| 'toolbar'
| 'tooltip'
| 'tree'
| 'treegrid'
| 'treeitem'
| (string & {});
9 changes: 9 additions & 0 deletions packages/html/test/attributes.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ describe('Attributes', () => {
assert.equal('<form novalidate></form>', <form novalidate></form>);
});

test('Undefined', () => {
assert.equal(
'<div></div>',
<div hidden={undefined} translate={undefined}>
{undefined}
</div>
);
});

test('Dates & Objects', () => {
const date = new Date();
assert.equal(<del datetime={date} />, `<del datetime="${date.toISOString()}"></del>`);
Expand Down
111 changes: 93 additions & 18 deletions packages/html/test/misc.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,110 @@ describe('Miscellaneous', () => {
assert.equal(<div hx-target="find "></div>, '<div hx-target="find "></div>');
});

test('Falsy values', () => {
test('Primitive values renders exactly like React', () => {
assert.equal(<div>{false}</div>, <div></div>);
assert.equal(<div>{null}</div>, <div></div>);
assert.equal(<div>{undefined}</div>, <div></div>);
assert.equal(<div>{0}</div>, <div>0</div>);
assert.equal(<div>{432}</div>, <div>432</div>);
assert.equal(<div>{NaN}</div>, <div>NaN</div>);
assert.equal(<div>{true}</div>, <div></div>);
assert.equal(<div>{Infinity}</div>, <div>Infinity</div>);
assert.equal(<div>{-Infinity}</div>, <div>-Infinity</div>);
assert.equal(<div>{[1, 2, 3]}</div>, <div>123</div>);

assert.equal(
<>
<div>
{false}
{false}
</div>,
<div></div>
);
assert.equal(
<div>
{null}
{null}
</div>,
<div></div>
);
assert.equal(
<div>
{undefined}
{undefined}
</div>,
<div></div>
);
assert.equal(
<div>
{0}
{0}
</div>,
<div>00</div>
);
assert.equal(
<div>
{432}
{432}
</div>,
<div>432432</div>
);
assert.equal(
<div>
{NaN}
{NaN}
</div>,
<div>NaNNaN</div>
);
assert.equal(
<div>
{true}
{true}
</div>,
<div></div>
);
assert.equal(
<div>
{Infinity}
{Infinity}
</div>,
<div>InfinityInfinity</div>
);
assert.equal(
<div>
{-Infinity}
{123n}
</>,
<>
<>false</>
<></>
<></>
<>0</>
<>NaN</>
<>true</>
<>Infinity</>
<>-Infinity</>
<>123</>
</>
{-Infinity}
</div>,
<div>-Infinity-Infinity</div>
);

assert.equal(
<div id="truthy" hidden={false} spellcheck={true} translate={undefined}></div>,
'<div id="truthy" spellcheck></div>'
<div>
{[1, 2, 3]}
{[1, 2, 3]}
</div>,
<div>123123</div>
);

// Bigint is the only case where it differs from React.
// where React renders a empty string and we render the whole number.
assert.equal(<div>{123456789123456789n}</div>, <div>123456789123456789</div>);
assert.equal(<>{123456789123456789n}</>, <>123456789123456789</>);
});

test('Rendering objects throws', () => {
assert.throws(
//@ts-expect-error - should warn about invalid child
() => <div>{{}}</div>,
/Objects are not valid as a KitaJSX child/
);

assert.throws(
//@ts-expect-error - should warn about invalid child
() => (
<div>
{{}} {{}}
</div>
),
/Objects are not valid as a KitaJSX child/
);
});

Expand Down

0 comments on commit a9adfd4

Please sign in to comment.