muddle/tests/prediction.test.ts
2026-10-23 08:53:07 -04:00

75 lines
5.1 KiB
TypeScript

import { expect, test } from "bun:test";
import { predictLayout } from "../adapters/prediction.ts";
test("prediction measures desktop and mobile geometry and restores closed disclosure", async () => {
const html = '<!doctype html><html><head><style>.panel { width: 400px; height: 180px; } @media(max-width:48rem) { .panel { width: 200px; height: 240px; } }</style></head><body><details><summary>Menu</summary><div class="panel" data-muddle-anchor="panel">Content</div></details></body></html>';
const result = await predictLayout(html, { selectors: [".panel"], strict: true });
expect(result.predictions.map((prediction) => prediction.width)).toEqual([400, 200]);
expect(result.predictions.map((prediction) => prediction.height)).toEqual([180, 240]);
expect(result.html).not.toContain("<details open");
expect(result.html).toContain('data-muddle-predicted="predict-0"');
expect(result.html).toContain("--muddle-predicted-desktop-width: 400px");
expect(result.html).toContain("--muddle-predicted-mobile-width: 200px");
expect(result.html).toContain("--muddle-relate-panel-desktop-width: 400px");
expect(result.html).toContain("--muddle-relate-panel-mobile-width: 200px");
expect(result.html).toContain("@media (max-width: 48rem)");
});
test("prediction resolves a target that is sized from an ahead-of-time relation", async () => {
const html = `<!doctype html><html><head><style>
.source { position: absolute; left: 100px; top: 20px; width: 240px; height: 32px; }
.target { position: absolute; left: var(--muddle-relate-source-x); top: 80px; width: var(--muddle-relate-source-width); height: 24px; }
@media (max-width: 48rem) {
.source { left: 24px; width: 160px; }
}
</style></head><body>
<div class="source" data-muddle-anchor="source"></div>
<div class="target"></div>
</body></html>`;
const result = await predictLayout(html, { selectors: [".target"], strict: true });
const target = result.predictions.filter((prediction) => prediction.selector === '[data-muddle-predicted="predict-0"]');
expect(target.map((prediction) => prediction.width)).toEqual([240, 160]);
expect(target.map((prediction) => prediction.x)).toEqual([100, 24]);
expect(result.html).toContain("--muddle-predicted-desktop-width: 240px");
expect(result.html).toContain("--muddle-predicted-mobile-width: 160px");
});
test("prediction settles chained relations before writing the static page", async () => {
const html = `<!doctype html><html><head><style>
.source { position: absolute; left: 72px; top: 20px; width: 216px; height: 24px; }
.middle { position: absolute; left: var(--muddle-relate-source-x); top: 60px; width: var(--muddle-relate-source-width); height: 24px; }
.target { position: absolute; left: var(--muddle-relate-middle-x); top: 100px; width: var(--muddle-relate-middle-width); height: 24px; }
</style></head><body>
<div class="source" data-muddle-anchor="source"></div>
<div class="middle" data-muddle-anchor="middle"></div>
<div class="target"></div>
</body></html>`;
const result = await predictLayout(html, { selectors: [".target"], strict: true });
const target = result.predictions.filter((prediction) => prediction.selector === '[data-muddle-predicted="predict-0"]');
expect(target.map((prediction) => prediction.width)).toEqual([216, 216]);
expect(target.map((prediction) => prediction.x)).toEqual([72, 72]);
});
test("prediction follows the page's widest mobile breakpoint", async () => {
const html = '<!doctype html><html><head><style>.panel { width: 500px; height: 24px; } @media (max-width: 40rem) { .panel { width: 280px; } }</style></head><body><div class="panel" data-muddle-anchor="panel"></div></body></html>';
const result = await predictLayout(html, { selectors: [".panel"], strict: true });
expect(result.predictions.map((prediction) => prediction.width)).toEqual([500, 280]);
expect(result.html).toContain("@media (max-width: 40rem)");
});
test("prediction emits each selected breakpointAcc relation layer", async () => {
const html = '<!doctype html><html><head><style>.panel { width: 600px; height: 24px; } @media (max-width: 64rem) { .panel { width: 420px; } } @media (max-width: 48rem) { .panel { width: 300px; } }</style></head><body><div class="panel" data-muddle-anchor="panel"></div></body></html>';
const result = await predictLayout(html, { selectors: [".panel"], breakpointAcc: ["LG", "MD"], strict: true });
expect(result.html).toContain("--muddle-relate-panel-lg-width: 420px");
expect(result.html).toContain("--muddle-relate-panel-md-width: 300px");
expect(result.html).toContain("@media (max-width: 64rem)");
expect(result.html).toContain("@media (max-width: 48rem)");
});
test("prediction discovers dotted anchor groups and emits reflective metadata", async () => {
const html = '<!doctype html><html><head><style>.item { width: 200px; }</style></head><body><a class="item" data-muddle-anchor="docs.intro"></a><a class="item" data-muddle-anchor="docs.api"></a></body></html>';
const result = await predictLayout(html, { strict: true });
expect(result.groups).toEqual([{ name: "docs", anchors: ["docs.intro", "docs.api"] }]);
expect(result.html).toContain('data-muddle-group="docs"');
expect(result.html).toContain('--muddle-reflect-docs-count: 2');
});