import { expect } from "@std/expect";
import { describe, it } from "@std/testing/bdd";
import { Identity } from "@commonfabric/identity";
import { Runtime } from "@commonfabric/runner";
import { StorageManager } from "@commonfabric/runner/storage/cache.deno";
import { renderInProcess } from "../src/in-process.ts";
import { MockDoc } from "../src/mock-doc.ts";
describe("in-process-attributes", () => {
it("reflects successive property values on the same rendered element", async () => {
const signer = await Identity.fromPassphrase("in-process attributes");
const runtime = new Runtime({
apiUrl: new URL(import.meta.url),
storageManager: StorageManager.emulate({ as: signer }),
});
const mock = new MockDoc('
');
const container = mock.document.getElementById("root")!;
let render: ReturnType | undefined;
try {
const tx = runtime.edit();
const vdom = runtime.getCell(
signer.did(),
"attributes",
undefined,
tx,
);
vdom.set({
type: "vnode",
name: "span",
props: { "aria-label": "green vote", style: "color: green" },
children: ["V"],
});
await tx.commit().settled;
render = renderInProcess(container, vdom, {
document: mock.document,
setProp: mock.renderOptions.setProp,
});
await runtime.idle();
render.flush();
const element = container.firstChild;
expect(element).toBeDefined();
expect(element).not.toBeNull();
expect(container.innerHTML).toBe(
'V',
);
for (const color of ["yellow", "green"]) {
const update = runtime.edit();
vdom.withTx(update).key("props").set({
"aria-label": `${color} vote`,
style: `color: ${color}`,
});
await update.commit().settled;
await runtime.idle();
render.flush();
expect(container.firstChild).toBe(element);
expect(container.innerHTML).toBe(
`V`,
);
}
} finally {
render?.cancel();
await runtime.dispose();
}
});
it("renders the same `style` attribute whichever order a style object lists its keys in", async () => {
// The reconciler turns a style object into the attribute's text. A
// runtime holds the object's keys in the order its writer listed them,
// and holds the same object loaded from the store in UTF-8 order.
const signer = await Identity.fromPassphrase("in-process style order");
const runtime = new Runtime({
apiUrl: new URL(import.meta.url),
storageManager: StorageManager.emulate({ as: signer }),
});
const mock = new MockDoc('');
const container = mock.document.getElementById("root")!;
let render: ReturnType | undefined;
try {
const tx = runtime.edit();
const vdom = runtime.getCell(
signer.did(),
"style-order",
undefined,
tx,
);
vdom.set({
type: "vnode",
name: "div",
props: {},
children: [
{
type: "vnode",
name: "span",
props: {
style: { display: "none", gap: "0.25rem", alignItems: "center" },
},
children: [],
},
{
type: "vnode",
name: "span",
props: {
style: { alignItems: "center", display: "none", gap: "0.25rem" },
},
children: [],
},
],
});
await tx.commit().settled;
render = renderInProcess(container, vdom, {
document: mock.document,
setProp: mock.renderOptions.setProp,
});
await runtime.idle();
render.flush();
const span = '';
expect(container.innerHTML).toBe(`${span}${span}
`);
} finally {
render?.cancel();
await runtime.dispose();
}
});
it("updates and removes ARIA attributes through the default property setter", async () => {
const signer = await Identity.fromPassphrase("in-process ARIA attributes");
const runtime = new Runtime({
apiUrl: new URL(import.meta.url),
storageManager: StorageManager.emulate({ as: signer }),
});
const mock = new MockDoc('');
const container = mock.document.getElementById("root")!;
const elementDescriptor = Object.getOwnPropertyDescriptor(
globalThis,
"Element",
);
// A supplied document may create nodes outside the ambient DOM realm.
// Its elements must still receive attributes through the default setter.
Object.defineProperty(globalThis, "Element", {
configurable: true,
value: class AmbientElement {},
});
let render: ReturnType | undefined;
try {
const tx = runtime.edit();
const vdom = runtime.getCell(
signer.did(),
"aria",
undefined,
tx,
);
vdom.set({
type: "vnode",
name: "button",
props: { "aria-label": "Edit name", "aria-expanded": false },
children: ["Edit"],
});
await tx.commit().settled;
render = renderInProcess(container, vdom, { document: mock.document });
await runtime.idle();
render.flush();
const element = container.firstChild as Element;
expect(element.getAttribute("aria-label")).toBe("Edit name");
expect(element.getAttribute("aria-expanded")).toBe("false");
expect(Object.hasOwn(element, "aria-label")).toBe(false);
const updates = [
{ "aria-label": "Save name", "aria-expanded": true },
{ "aria-label": null, "aria-expanded": false },
{ "aria-label": "Edit again", "aria-expanded": undefined },
{},
];
for (const props of updates) {
const update = runtime.edit();
vdom.withTx(update).key("props").set(props);
await update.commit().settled;
await runtime.idle();
render.flush();
expect(container.firstChild).toBe(element);
for (const key of ["aria-label", "aria-expanded"] as const) {
const value = props[key];
if (value == null) {
expect(element.hasAttribute(key)).toBe(false);
} else {
expect(element.getAttribute(key)).toBe(String(value));
}
}
}
} finally {
if (elementDescriptor) {
Object.defineProperty(globalThis, "Element", elementDescriptor);
} else {
Reflect.deleteProperty(globalThis, "Element");
}
try {
render?.cancel();
} finally {
await runtime.dispose();
}
}
});
});