web: fix flash of unstructured content, add tests for it (#11013)

* web: fix Flash of Unstructured Content while SearchSelect is loading from the backend

Provide an alternative, readonly, disabled, unindexed input object with the text "Loading...", to be
replaced with the _real_ input element after the content is loaded.

This provides the correct appearance and spacing so the content doesn't jiggle about between the
start of loading and the SearchSelect element being finalized.  It was visually distracting and
unappealing.

* web: comment on state management in API layer, move file to point to correct component under test.

* web: test for flash of unstructured content

- Add a unit test to ensure the "Loading..." element is displayed correctly before data arrives
- Demo how to mock a `fetchObjects()` call in testing. Very cool.
- Make distinguishing rule sets for code, tests, and scripts in nightmare mode
- In SearchSelect, Move the `styles()` declaration to the top of the class for consistency.

- To test for the FLOUC issue in SearchSelect.

This is both an exercise in mocking @beryju's `fetchObjects()` protocol, and shows how we can unit
test generic components that render API objects.
This commit is contained in:
Ken Sternberg
2024-08-22 02:17:30 -07:00
committed by GitHub
parent 40b93e9b10
commit 85eb104966
8 changed files with 430 additions and 235 deletions

View File

@ -0,0 +1,104 @@
import { $, browser } from "@wdio/globals";
import { slug } from "github-slugger";
import { Key } from "webdriverio";
import { html, render } from "lit";
import "../ak-search-select-view.js";
import { sampleData } from "../stories/sampleData.js";
import { AkSearchSelectViewDriver } from "./ak-search-select-view.comp.js";
const longGoodForYouPairs = {
grouped: false,
options: sampleData.map(({ produce }) => [slug(produce), produce]),
};
describe("Search select: Test Input Field", () => {
let select: AkSearchSelectViewDriver;
beforeEach(async () => {
await render(
html`<ak-search-select-view .options=${longGoodForYouPairs}> </ak-search-select-view>`,
document.body,
);
// @ts-ignore
select = await AkSearchSelectViewDriver.build(await $("ak-search-select-view"));
});
it("should open the menu when the input is clicked", async () => {
expect(await select.open).toBe(false);
expect(await select.menuIsVisible()).toBe(false);
await select.clickInput();
expect(await select.open).toBe(true);
// expect(await select.menuIsVisible()).toBe(true);
});
it("should not open the menu when the input is focused", async () => {
expect(await select.open).toBe(false);
await select.focusOnInput();
expect(await select.open).toBe(false);
expect(await select.menuIsVisible()).toBe(false);
});
it("should close the menu when the input is clicked a second time", async () => {
expect(await select.open).toBe(false);
expect(await select.menuIsVisible()).toBe(false);
await select.clickInput();
expect(await select.menuIsVisible()).toBe(true);
expect(await select.open).toBe(true);
await select.clickInput();
expect(await select.open).toBe(false);
expect(await select.open).toBe(false);
});
it("should open the menu from a focused but closed input when a search is begun", async () => {
expect(await select.open).toBe(false);
await select.focusOnInput();
expect(await select.open).toBe(false);
expect(await select.menuIsVisible()).toBe(false);
await browser.keys("A");
expect(await select.open).toBe(true);
expect(await select.menuIsVisible()).toBe(true);
});
it("should update the list as the user types", async () => {
await select.focusOnInput();
await browser.keys("Ap");
expect(await select.menuIsVisible()).toBe(true);
const elements = Array.from(await select.listElements());
expect(elements.length).toBe(2);
});
it("set the value when a match is close", async () => {
await select.focusOnInput();
await browser.keys("Ap");
expect(await select.menuIsVisible()).toBe(true);
const elements = Array.from(await select.listElements());
expect(elements.length).toBe(2);
await browser.keys(Key.Tab);
expect(await (await select.input()).getValue()).toBe("Apples");
});
it("should close the menu when the user clicks away", async () => {
document.body.insertAdjacentHTML(
"afterbegin",
'<input id="a-separate-component" type="text" />',
);
const input = await browser.$("#a-separate-component");
await select.clickInput();
expect(await select.open).toBe(true);
await input.click();
expect(await select.open).toBe(false);
});
afterEach(async () => {
await document.body.querySelector("#a-separate-component")?.remove();
await document.body.querySelector("ak-search-select-view")?.remove();
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
if (document.body["_$litPart$"]) {
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
delete document.body["_$litPart$"];
}
});
});