fix(ui): honor select search limits for large regional lists
This commit is contained in:
1 parent
b1b84eb41c
commit
9e0907ffb2
5 files changed
+84
-4
No files matched your search
@@ -688,7 +688,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@wrnexus/ui",
|
"name": "@wrnexus/ui",
|
||||||
"version": "0.8.59",
|
"version": "0.8.60",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import { mkdirSync, mkdtempSync, writeFileSync, readFileSync, rmSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join, basename } from "node:path";
|
||||||
|
import { buildRouter } from "@wrnexus/router";
|
||||||
|
import { renderComponent } from "../../test/src/index.ts";
|
||||||
|
import { createHandlers } from "../src/runtime.ts";
|
||||||
|
|
||||||
|
test("large timezone select responds through the complete page renderer", async () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), "wrn-large-select-"));
|
||||||
|
try {
|
||||||
|
mkdirSync(join(root, "pages"));
|
||||||
|
mkdirSync(join(root, "components"));
|
||||||
|
writeFileSync(join(root, "pages/index.ts"), "");
|
||||||
|
for (const name of ["AdvancedSelect", "SelectStyles", "Shell"])
|
||||||
|
writeFileSync(
|
||||||
|
join(root, `components/${name}.wrn`),
|
||||||
|
`component ${name} { view { <div></div> } }`,
|
||||||
|
);
|
||||||
|
const select = await renderComponent(
|
||||||
|
readFileSync(new URL("../../ui/components/AdvancedSelect.wrn", import.meta.url), "utf8"),
|
||||||
|
{
|
||||||
|
name: "timezone",
|
||||||
|
value: "UTC",
|
||||||
|
searchResultLimit: 40,
|
||||||
|
options: Intl.supportedValuesOf("timeZone").map((value) => ({ value, label: value })),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const handlers = createHandlers({
|
||||||
|
mode: "development",
|
||||||
|
hmr: false,
|
||||||
|
router: buildRouter(root),
|
||||||
|
loadModule: async (file) =>
|
||||||
|
basename(file) === "AdvancedSelect.wrn"
|
||||||
|
? { render: () => select }
|
||||||
|
: basename(file) === "SelectStyles.wrn"
|
||||||
|
? { render: () => "" }
|
||||||
|
: basename(file) === "Shell.wrn"
|
||||||
|
? { render: () => "<main><slot></slot></main>" }
|
||||||
|
: {
|
||||||
|
default: () =>
|
||||||
|
'<div data-component="Shell">' +
|
||||||
|
'<div data-component="AdvancedSelect"></div>'.repeat(4) +
|
||||||
|
"</div>",
|
||||||
|
},
|
||||||
|
getMiddleware: async () => [],
|
||||||
|
assets: { serve: async () => null },
|
||||||
|
});
|
||||||
|
const response = await handlers.fetch(new Request("http://example.test/"), {
|
||||||
|
upgrade: () => false,
|
||||||
|
});
|
||||||
|
expect(response?.status).toBe(200);
|
||||||
|
const html = await response!.text();
|
||||||
|
expect(html).toContain("Asia/Kolkata");
|
||||||
|
// Four full lists previously exceeded 11 MB, including hydration metadata.
|
||||||
|
expect(html.length).toBeLessThan(6_000_000);
|
||||||
|
} finally {
|
||||||
|
rmSync(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}, 15000);
|
||||||
@@ -375,7 +375,7 @@ component AdvancedSelect {
|
|||||||
{#if visibleOptions(group.options || []).length}
|
{#if visibleOptions(group.options || []).length}
|
||||||
<div class="wrn-next__option-group" role="group" aria-label="{group.label}">
|
<div class="wrn-next__option-group" role="group" aria-label="{group.label}">
|
||||||
<div class="wrn-next__group-label">{group.label}</div>
|
<div class="wrn-next__group-label">{group.label}</div>
|
||||||
{#each group.options || [] as option}
|
{#each visibleOptions(group.options || []) as option}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="wrn-next__select-option {isSelected(option) ? 'wrn-next__select-option--selected' : ''} {optionIndex(option) === activeIndex ? 'wrn-next__select-option--active' : ''}"
|
class="wrn-next__select-option {isSelected(option) ? 'wrn-next__select-option--selected' : ''} {optionIndex(option) === activeIndex ? 'wrn-next__select-option--active' : ''}"
|
||||||
@@ -402,7 +402,7 @@ component AdvancedSelect {
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#each options as option}
|
{#each visibleOptions(options) as option}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="wrn-next__select-option {isSelected(option) ? 'wrn-next__select-option--selected' : ''} {optionIndex(option) === activeIndex ? 'wrn-next__select-option--active' : ''}"
|
class="wrn-next__select-option {isSelected(option) ? 'wrn-next__select-option--selected' : ''} {optionIndex(option) === activeIndex ? 'wrn-next__select-option--active' : ''}"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ui",
|
"name": "@wrnexus/ui",
|
||||||
"version": "0.8.59",
|
"version": "0.8.60",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1890,6 +1890,26 @@ test("input number increments, decrements, applies steps, and respects limits",
|
|||||||
expect(decrement().disabled).toBe(true);
|
expect(decrement().disabled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("advanced select limits rendered options but searches the full dataset and retains saved values", async () => {
|
||||||
|
const source = readFileSync(uiComponentPath("AdvancedSelect"), "utf8");
|
||||||
|
const dom = mountHtml(
|
||||||
|
await renderComponent(source, {
|
||||||
|
name: "region",
|
||||||
|
value: "99",
|
||||||
|
searchResultLimit: 4,
|
||||||
|
options: Array.from({ length: 100 }, (_, i) => ({ value: String(i), label: "Region " + i })),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(dom.querySelector(".wrn-next__select-value")?.textContent).toContain("Region 99");
|
||||||
|
expect(dom.querySelectorAll(".wrn-next__select-option")).toHaveLength(4);
|
||||||
|
(dom.querySelector(".wrn-next__select-trigger") as HTMLButtonElement).click();
|
||||||
|
const search = dom.querySelector('input[type="search"]') as HTMLInputElement;
|
||||||
|
search.value = "Region 99";
|
||||||
|
search.dispatchEvent(new (dom.window as any).Event("input", { bubbles: true }));
|
||||||
|
expect(dom.querySelectorAll(".wrn-next__select-option")).toHaveLength(1);
|
||||||
|
expect(dom.querySelector(".wrn-next__select-option")?.textContent).toContain("Region 99");
|
||||||
|
});
|
||||||
|
|
||||||
test("advanced select opens, filters, and selects without reactive handler errors", async () => {
|
test("advanced select opens, filters, and selects without reactive handler errors", async () => {
|
||||||
const source = readFileSync(uiComponentPath("AdvancedSelect"), "utf8");
|
const source = readFileSync(uiComponentPath("AdvancedSelect"), "utf8");
|
||||||
const html = await renderComponent(source, {
|
const html = await renderComponent(source, {
|
||||||
|
|||||||
Reference in new issue
Block a user