Skip to content

Commit d61b304

Browse files
added extra tests for math, svg, select edge case and styles,
1 parent 2c2e173 commit d61b304

File tree

9 files changed

+943
-118
lines changed

9 files changed

+943
-118
lines changed

src/components/Test.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ const Test = () => {
2020
console.log("cleanup");
2121
};
2222
});
23-
23+
setTimeout(() => {
24+
showTextSignal.update((prev) => !prev);
25+
}, 2000);
2426
const h1ref = createRef();
2527
// console.log("hello");
2628
return (
@@ -30,7 +32,7 @@ const Test = () => {
3032
<>{() => textSignal.value}</>
3133
</h1>
3234

33-
<svg
35+
{/* <svg
3436
width="100"
3537
height="100"
3638
>
@@ -42,8 +44,13 @@ const Test = () => {
4244
stroke-width="4"
4345
fill="yellow"
4446
/>
45-
</svg>
47+
</svg> */}
4648

49+
{/* <select value="B">
50+
<option value="A">A</option>
51+
<option value="B">B</option>
52+
<option value="C">C</option>
53+
</select> */}
4754
{/* {() =>
4855
showTextSignal.value ? (
4956
<LazyFC2
@@ -57,6 +64,19 @@ const Test = () => {
5764
/>
5865
)
5966
} */}
67+
{() =>
68+
showTextSignal.value ? (
69+
<LazyFC2
70+
fallback={<h2>Loading...</h2>}
71+
errorFallback={(error) => <h2>{error.message}</h2>}
72+
/>
73+
) : (
74+
<LazyFC1
75+
fallback={"Loading..."}
76+
errorFallback={(error) => <h2>{error.message}</h2>}
77+
/>
78+
)
79+
}
6080
<button onClick={() => showTextSignal.update((prev) => !prev)}>
6181
Toggle
6282
</button>

src/lib.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { isPlainObject } from "./utils/general";
2+
export const IS_NON_DIMENSIONAL =
3+
/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
24

35
export function styleObjectToString(
46
style: Record<string, string | number>
@@ -9,10 +11,10 @@ export function styleObjectToString(
911
const value = style[key];
1012
const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase(); // CamelCase to kebab-case
1113

12-
if (typeof value === "number") {
13-
newStyles.push(`${cssKey}: ${value}px;`); // Convert numbers to strings with px suffix
14-
} else {
14+
if (typeof value != "number" || IS_NON_DIMENSIONAL.test(cssKey)) {
1515
newStyles.push(`${cssKey}: ${value};`); // Convert numbers to strings with px suffix
16+
} else {
17+
newStyles.push(`${cssKey}: ${value}px;`); // Convert numbers to strings with px suffix
1618
}
1719
}
1820
return newStyles.join(" ");

src/rendering/constants.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,47 @@ export const SVG_TAGS = new Set([
6060
"use",
6161
"view",
6262
]);
63+
export const MATH_TAGS = new Set([
64+
"math",
65+
"maction",
66+
"maligngroup",
67+
"malignmark",
68+
"menclose",
69+
"merror",
70+
"mfenced",
71+
"mfrac",
72+
"mglyph",
73+
"mi",
74+
"mlabeledtr",
75+
"mlongdiv",
76+
"mmultiscripts",
77+
"mn",
78+
"mo",
79+
"mover",
80+
"mpadded",
81+
"mphantom",
82+
"mroot",
83+
"mrow",
84+
"ms",
85+
"mscarries",
86+
"mscarry",
87+
"msgroup",
88+
"msline",
89+
"mspace",
90+
"msqrt",
91+
"msrow",
92+
"mstack",
93+
"mstyle",
94+
"msub",
95+
"msup",
96+
"msubsup",
97+
"mtable",
98+
"mtd",
99+
"mtext",
100+
"mtr",
101+
"munder",
102+
"munderover",
103+
]);
63104

64105
export const MATH_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
65106

src/rendering/createElements.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import {
1010
RenderFunction,
1111
} from "../types";
1212
import { isPrimitive } from "../utils/general";
13-
import { MATH_NAMESPACE, SVG_NAMESPACE, SVG_TAGS } from "./constants";
13+
import {
14+
MATH_NAMESPACE,
15+
MATH_TAGS,
16+
SVG_NAMESPACE,
17+
SVG_TAGS,
18+
} from "./constants";
1419
import { setAttribute, setReactiveAttribute } from "./props";
1520
export const FRAGMENT_SYMBOL = Symbol("FRAGMENT");
1621

@@ -122,7 +127,7 @@ export function createNode(element: Fiber): HTMLElement | Text {
122127
let namespace: string | null = null;
123128

124129
if (SVG_TAGS.has(element.type as string)) namespace = SVG_NAMESPACE;
125-
else if (element.type == "math") namespace = MATH_NAMESPACE;
130+
else if (MATH_TAGS.has(element.type as string)) namespace = MATH_NAMESPACE;
126131

127132
const dom =
128133
element.type === "TEXT_CHILD"

src/rendering/props.ts

Lines changed: 9 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -2,114 +2,6 @@ import { isValidStyle, preprocessStyle, styleObjectToString } from "../lib";
22
import { setReactiveAttributes } from "../signals/batch";
33
import { reactiveAttribute } from "../signals/signal";
44
import { SVG_NAMESPACE } from "./constants";
5-
// export function setProperty(dom, name, value, oldValue, namespace) {
6-
// o: if (name == "style") {
7-
// if (typeof value == "string") {
8-
// dom.style.cssText = value;
9-
// } else {
10-
// if (typeof oldValue == "string") {
11-
// dom.style.cssText = oldValue = "";
12-
// }
13-
14-
// if (oldValue) {
15-
// for (name in oldValue) {
16-
// if (!(value && name in value)) {
17-
// setStyle(dom.style, name, "");
18-
// }
19-
// }
20-
// }
21-
22-
// if (value) {
23-
// for (name in value) {
24-
// if (!oldValue || value[name] !== oldValue[name]) {
25-
// setStyle(dom.style, name, value[name]);
26-
// }
27-
// }
28-
// }
29-
// }
30-
// }
31-
// // Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
32-
// else if (name[0] == "o" && name[1] == "n") {
33-
// useCapture = name != (name = name.replace(CAPTURE_REGEX, "$1"));
34-
35-
// // Infer correct casing for DOM built-in events:
36-
// if (
37-
// name.toLowerCase() in dom ||
38-
// name == "onFocusOut" ||
39-
// name == "onFocusIn"
40-
// )
41-
// name = name.toLowerCase().slice(2);
42-
// else name = name.slice(2);
43-
44-
// if (!dom._listeners) dom._listeners = {};
45-
// dom._listeners[name + useCapture] = value;
46-
47-
// if (value) {
48-
// if (!oldValue) {
49-
// value._attached = eventClock;
50-
// dom.addEventListener(
51-
// name,
52-
// useCapture ? eventProxyCapture : eventProxy,
53-
// useCapture
54-
// );
55-
// } else {
56-
// value._attached = oldValue._attached;
57-
// }
58-
// } else {
59-
// dom.removeEventListener(
60-
// name,
61-
// useCapture ? eventProxyCapture : eventProxy,
62-
// useCapture
63-
// );
64-
// }
65-
// } else {
66-
// if (namespace == SVG_NAMESPACE) {
67-
// // Normalize incorrect prop usage for SVG:
68-
// // - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed)
69-
// // - className --> class
70-
// name = name.replace(/xlink(H|:h)/, "h").replace(/sName$/, "s");
71-
// } else if (
72-
// name != "width" &&
73-
// name != "height" &&
74-
// name != "href" &&
75-
// name != "list" &&
76-
// name != "form" &&
77-
// // Default value in browsers is `-1` and an empty string is
78-
// // cast to `0` instead
79-
// name != "tabIndex" &&
80-
// name != "download" &&
81-
// name != "rowSpan" &&
82-
// name != "colSpan" &&
83-
// name != "role" &&
84-
// name != "popover" &&
85-
// name in dom
86-
// ) {
87-
// try {
88-
// dom[name] = value == NULL ? "" : value;
89-
// // labelled break is 1b smaller here than a return statement (sorry)
90-
// break o;
91-
// } catch (e) {}
92-
// }
93-
94-
// // aria- and data- attributes have no boolean representation.
95-
// // A `false` value is different from the attribute not being
96-
// // present, so we can't remove it. For non-boolean aria
97-
// // attributes we could treat false as a removal, but the
98-
// // amount of exceptions would cost too many bytes. On top of
99-
// // that other frameworks generally stringify `false`.
100-
101-
// if (typeof value == "function") {
102-
// // never serialize functions as attribute values
103-
// } else if (value != null && (value !== false || name[4] == "-")) {
104-
// dom.setAttribute(
105-
// name,
106-
// name == "popover" && value == true ? "" : value
107-
// );
108-
// } else {
109-
// dom.removeAttribute(name);
110-
// }
111-
// }
112-
// }
1135

1146
export function setStyle(
1157
style: Record<string, string | number> | string,
@@ -159,7 +51,6 @@ export function setAttribute(
15951
setStyle(value, dom);
16052
return;
16153
}
162-
16354
if (name[0] === "o" && name[1] === "n" && typeof value === "function") {
16455
const useCapture = name != (name = name.replace(CAPTURE_REGEX, "$1"));
16556

@@ -198,7 +89,15 @@ export function setAttribute(
19889
) {
19990
try {
20091
// Set the property directly on the DOM element.
201-
dom[name] = value == null ? "" : value;
92+
if (name === "value" && dom.tagName === "SELECT") {
93+
setTimeout(() => {
94+
dom[name] = value == null ? "" : value;
95+
});
96+
} else {
97+
dom[name] = value == null ? "" : value;
98+
}
99+
// console.log(dom[name], dom);
100+
202101
// We simply return after setting the property.
203102
return;
204103
} catch (e) {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { vi, describe, it, expect } from "vitest";
2+
import * as rendering from "../../rendering/render";
3+
import { createSignal } from "../../signals/signal";
4+
5+
vi.stubGlobal("requestIdleCallback", (cb) => {
6+
queueMicrotask(() => cb({ timeRemaining: () => 2 }));
7+
});
8+
9+
// @ts-expect-error
10+
const createFiber = rendering.createFiber;
11+
// @ts-expect-error
12+
const commitFiber = rendering.commitFiber;
13+
14+
describe("mathml", () => {
15+
it("should render with the correct namespace URI", () => {
16+
const fiber = (
17+
<div>
18+
<math />
19+
</div>
20+
);
21+
createFiber(fiber);
22+
commitFiber(fiber);
23+
let namespace = fiber.dom.querySelector("math").namespaceURI;
24+
25+
expect(namespace).to.equal("http://www.w3.org/1998/Math/MathML");
26+
});
27+
28+
it("should render children with the correct namespace URI", () => {
29+
const fiber = (
30+
<div>
31+
<math>
32+
<mrow />
33+
</math>
34+
</div>
35+
);
36+
createFiber(fiber);
37+
commitFiber(fiber);
38+
let namespace = fiber.dom.querySelector("mrow").namespaceURI;
39+
40+
expect(namespace).to.equal("http://www.w3.org/1998/Math/MathML");
41+
});
42+
43+
it("should inherit correct namespace URI from parent", () => {
44+
const fiber = (
45+
<div>
46+
<math>
47+
<mrow />
48+
</math>
49+
</div>
50+
);
51+
52+
createFiber(fiber);
53+
commitFiber(fiber);
54+
55+
let namespace = fiber.dom.querySelector("mrow").namespaceURI;
56+
expect(namespace).to.equal("http://www.w3.org/1998/Math/MathML");
57+
});
58+
59+
it("should inherit correct namespace URI from parent upon updating", async () => {
60+
const show = createSignal<boolean>(true);
61+
62+
const App = () => {
63+
return <>{() => (show.value ? <mi>1</mi> : <mo>2</mo>)}</>;
64+
};
65+
66+
const fiber = (
67+
<div>
68+
<math>
69+
<App />
70+
</math>
71+
</div>
72+
);
73+
createFiber(fiber);
74+
commitFiber(fiber);
75+
76+
let namespace = fiber.dom.querySelector("mi").namespaceURI;
77+
expect(namespace).to.equal("http://www.w3.org/1998/Math/MathML");
78+
79+
show.update((prev) => !prev);
80+
81+
await Promise.resolve();
82+
namespace = null;
83+
84+
namespace = fiber.dom.querySelector("mo").namespaceURI;
85+
expect(namespace).to.equal("http://www.w3.org/1998/Math/MathML");
86+
});
87+
88+
it("should transition from DOM to MathML and back", () => {
89+
const fiber = (
90+
<div>
91+
<math>
92+
<msup>
93+
<mi>c</mi>
94+
<mn>2</mn>
95+
</msup>
96+
</math>
97+
</div>
98+
);
99+
createFiber(fiber);
100+
commitFiber(fiber);
101+
102+
expect(fiber.dom).to.be.an("HTMLDivElement");
103+
const mathFiber = fiber.props.children[0];
104+
expect(mathFiber.type).toEqual("math");
105+
expect(mathFiber.dom.namespaceURI).toEqual(
106+
"http://www.w3.org/1998/Math/MathML"
107+
);
108+
expect(mathFiber.dom).toBeInstanceOf(Element);
109+
});
110+
});

0 commit comments

Comments
 (0)