Align the notebook browser with the physical LCD, refresh real project captures and navigation, simplify service content, and stabilize hero hydration.
This commit is contained in:
1 parent
e2b20bc699
commit
8b12e51bc5
35 files changed
+2359
-1299
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { getFeaturedProjects } from "@/lib/portfolio-data";
|
||||
import NotebookBrowser, { type NotebookBrowserProps } from "./NotebookBrowser";
|
||||
@@ -24,6 +24,15 @@ function renderBrowser(overrides: Partial<NotebookBrowserProps> = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
function imageFor(container: HTMLElement, index: number) {
|
||||
const source = new URL(projects[index].screenshotSrc, document.baseURI).href;
|
||||
const image = Array.from(container.querySelectorAll("img")).find(
|
||||
(candidate) => candidate.src === source,
|
||||
);
|
||||
if (!image) throw new Error(`Missing screenshot for ${projects[index].id}`);
|
||||
return image;
|
||||
}
|
||||
|
||||
describe("NotebookBrowser", () => {
|
||||
it("renders the active project as an accessible tabpanel", () => {
|
||||
renderBrowser({ activeIndex: 1 });
|
||||
@@ -42,12 +51,12 @@ describe("NotebookBrowser", () => {
|
||||
"Projeto ativo: SC Plus Aquicultura",
|
||||
);
|
||||
});
|
||||
it("preloads the next screenshot and keeps a short outgoing crossfade", () => {
|
||||
it("keeps decoded content visible and ignores a superseded decode", async () => {
|
||||
const view = renderBrowser();
|
||||
|
||||
expect(
|
||||
view.container.querySelector(".notebook-screenshot-preload"),
|
||||
).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
fireEvent.load(imageFor(view.container, 0));
|
||||
});
|
||||
expect(screen.getByRole("img")).toHaveAccessibleName(/NutriScan/i);
|
||||
|
||||
view.rerender(
|
||||
<NotebookBrowser
|
||||
@@ -57,8 +66,91 @@ describe("NotebookBrowser", () => {
|
||||
onSelectTab={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
let finishDecode!: () => void;
|
||||
imageFor(view.container, 1).decode = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(undefined)
|
||||
.mockImplementation(
|
||||
() =>
|
||||
new Promise<void>((resolve) => {
|
||||
finishDecode = resolve;
|
||||
}),
|
||||
);
|
||||
await act(async () => {
|
||||
fireEvent.load(imageFor(view.container, 1));
|
||||
});
|
||||
expect(screen.getByRole("img")).toHaveAccessibleName(/NutriScan/i);
|
||||
|
||||
view.rerender(
|
||||
<NotebookBrowser
|
||||
projects={projects}
|
||||
activeIndex={2}
|
||||
frame={frame}
|
||||
onSelectTab={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
await act(async () => {
|
||||
fireEvent.load(imageFor(view.container, 2));
|
||||
});
|
||||
expect(screen.getByRole("img")).toHaveAccessibleName(/SEV Moda Íntima/i);
|
||||
await act(async () => {
|
||||
finishDecode();
|
||||
});
|
||||
expect(screen.getByRole("img")).toHaveAccessibleName(/SEV Moda Íntima/i);
|
||||
});
|
||||
|
||||
it("keeps the last capture with a truthful notice when the requested image fails", async () => {
|
||||
const view = renderBrowser();
|
||||
await act(async () => {
|
||||
fireEvent.load(imageFor(view.container, 0));
|
||||
});
|
||||
view.rerender(
|
||||
<NotebookBrowser
|
||||
projects={projects}
|
||||
activeIndex={1}
|
||||
frame={frame}
|
||||
onSelectTab={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
fireEvent.error(imageFor(view.container, 1));
|
||||
expect(screen.getByRole("img")).toHaveAccessibleName(/NutriScan/i);
|
||||
expect(
|
||||
view.container.querySelector(".notebook-screenshot-outgoing"),
|
||||
screen.getByText(/não foi possível carregar.*SC Plus/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole("tabpanel")).toHaveAccessibleName(/SC Plus/i);
|
||||
});
|
||||
|
||||
it("can reverse to an already-loaded outgoing image without another load event", async () => {
|
||||
const view = renderBrowser();
|
||||
const first = imageFor(view.container, 0);
|
||||
Object.defineProperties(first, {
|
||||
complete: { value: true },
|
||||
naturalWidth: { value: 1280 },
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.load(first);
|
||||
});
|
||||
view.rerender(
|
||||
<NotebookBrowser
|
||||
projects={projects}
|
||||
activeIndex={1}
|
||||
frame={frame}
|
||||
onSelectTab={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
await act(async () => {
|
||||
fireEvent.load(imageFor(view.container, 1));
|
||||
});
|
||||
view.rerender(
|
||||
<NotebookBrowser
|
||||
projects={projects}
|
||||
activeIndex={0}
|
||||
frame={frame}
|
||||
onSelectTab={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
await screen.findByRole("img", { name: /NutriScan/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
it("moves between tabs with arrow keys and Home/End", async () => {
|
||||
@@ -172,35 +264,11 @@ describe("NotebookBrowser", () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the animated cursor decorative and never embeds an iframe", () => {
|
||||
const { container } = renderBrowser({
|
||||
frame: { ...frame, cursorClicking: true },
|
||||
it("uses the selected tab to label the panel and decoded screenshot", async () => {
|
||||
const view = renderBrowser({ activeIndex: 2 });
|
||||
await act(async () => {
|
||||
fireEvent.load(imageFor(view.container, 2));
|
||||
});
|
||||
const cursor = container.querySelector(".notebook-cursor");
|
||||
|
||||
expect(cursor).toHaveAttribute("aria-hidden", "true");
|
||||
expect(cursor).toHaveStyle({ left: "40%", top: "60%" });
|
||||
expect(cursor?.closest(".notebook-browser")).toBe(
|
||||
container.querySelector(".notebook-browser"),
|
||||
);
|
||||
expect(container.querySelector("iframe")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("marks only the selected tab as pressed by the choreographed cursor", () => {
|
||||
const { container } = renderBrowser({
|
||||
activeIndex: 1,
|
||||
frame: { ...frame, cursorClicking: true },
|
||||
});
|
||||
const tabs = container.querySelectorAll(".notebook-tab");
|
||||
|
||||
expect(tabs[1]).toHaveAttribute("data-cursor-clicking", "true");
|
||||
expect(tabs[0]).toHaveAttribute("data-cursor-clicking", "false");
|
||||
expect(tabs[2]).toHaveAttribute("data-cursor-clicking", "false");
|
||||
expect(tabs[3]).toHaveAttribute("data-cursor-clicking", "false");
|
||||
});
|
||||
|
||||
it("uses the selected tab to label the panel and screenshot", () => {
|
||||
renderBrowser({ activeIndex: 2 });
|
||||
|
||||
const tab = screen.getAllByRole("tab")[2];
|
||||
const panel = screen.getByRole("tabpanel");
|
||||
@@ -209,6 +277,9 @@ describe("NotebookBrowser", () => {
|
||||
expect(tab).toHaveAttribute("aria-controls", panel.id);
|
||||
expect(panel).toHaveAttribute("aria-labelledby", tab.id);
|
||||
expect(image).toHaveAccessibleName(/SEV Moda Íntima/i);
|
||||
expect(image).toHaveAttribute("src", "/assets/prints/print-sev.png");
|
||||
expect(image).toHaveProperty(
|
||||
"src",
|
||||
new URL(projects[2].screenshotSrc, document.baseURI).href,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user