Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: primitives rendering to equal react output #190

Merged
merged 5 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading