Estado do disco em 14/10/2026, sem filtrar. Inclui as copias .sync-conflict-* do Syncthing de 07/10 e os 15 arquivos que o Syncthing apagou; todo esse conteudo ja existia em outros commits. So havia fora do Git: AGENTS.md, CLAUDE.md e a alteracao em data/project-health.json.
This commit is contained in:
1 parent
b2f3899dc2
commit
dfcadc71fa
77 files changed
+6944
-2419
No files matched your search
@@ -0,0 +1,221 @@
|
||||
import { act, render, screen, waitFor } from "@testing-library/react";
|
||||
import { getFeaturedProjects } from "@/lib/portfolio-data";
|
||||
import NotebookStory, { canUseNotebook3D } from "./NotebookStory";
|
||||
|
||||
const mockNotebookStage = jest.fn(({ isVisible }: { isVisible: boolean }) => (
|
||||
<div data-is-visible={String(isVisible)} data-testid="notebook-stage" />
|
||||
));
|
||||
|
||||
jest.mock("next/dynamic", () => () => {
|
||||
return function DynamicNotebookStage(props: { isVisible: boolean }) {
|
||||
return mockNotebookStage(props);
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock("gsap", () => ({
|
||||
gsap: {
|
||||
registerPlugin: jest.fn(),
|
||||
timeline: jest.fn(() => ({ to: jest.fn().mockReturnThis() })),
|
||||
context: jest.fn((callback: () => void) => {
|
||||
callback();
|
||||
return { revert: jest.fn() };
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("gsap/ScrollTrigger", () => ({
|
||||
ScrollTrigger: { getAll: jest.fn(() => []) },
|
||||
}));
|
||||
const projects = getFeaturedProjects(null);
|
||||
|
||||
let intersectionCallback: IntersectionObserverCallback | undefined;
|
||||
const mockObserve = jest.fn();
|
||||
const mockDisconnect = jest.fn();
|
||||
|
||||
function setDocumentHidden(hidden: boolean) {
|
||||
Object.defineProperty(document, "hidden", {
|
||||
configurable: true,
|
||||
value: hidden,
|
||||
});
|
||||
}
|
||||
|
||||
function setSectionIntersects(isIntersecting: boolean) {
|
||||
const entry = { isIntersecting } as IntersectionObserverEntry;
|
||||
act(() => intersectionCallback?.([entry], {} as IntersectionObserver));
|
||||
}
|
||||
|
||||
function mockMatchMedia({
|
||||
reduced = false,
|
||||
coarse = false,
|
||||
hover = true,
|
||||
}: {
|
||||
reduced?: boolean;
|
||||
coarse?: boolean;
|
||||
hover?: boolean;
|
||||
} = {}) {
|
||||
const matcher = jest.isMockFunction(window.matchMedia)
|
||||
? window.matchMedia
|
||||
: jest.spyOn(window, "matchMedia");
|
||||
matcher.mockImplementation((query: string) => ({
|
||||
matches:
|
||||
(query === "(prefers-reduced-motion: reduce)" && reduced) ||
|
||||
(query === "(pointer: coarse)" && coarse) ||
|
||||
(query === "(hover: hover)" && hover),
|
||||
media: query,
|
||||
onchange: null,
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
}));
|
||||
}
|
||||
|
||||
describe("NotebookStory", () => {
|
||||
beforeEach(() => {
|
||||
mockMatchMedia();
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
configurable: true,
|
||||
value: 1280,
|
||||
});
|
||||
setDocumentHidden(false);
|
||||
intersectionCallback = undefined;
|
||||
mockObserve.mockReset();
|
||||
mockDisconnect.mockReset();
|
||||
class MockIntersectionObserver {
|
||||
readonly root = null;
|
||||
readonly rootMargin = "";
|
||||
readonly thresholds = [];
|
||||
|
||||
constructor(callback: IntersectionObserverCallback) {
|
||||
intersectionCallback = callback;
|
||||
}
|
||||
|
||||
disconnect = mockDisconnect;
|
||||
observe = mockObserve;
|
||||
takeRecords = () => [];
|
||||
unobserve = jest.fn();
|
||||
}
|
||||
global.IntersectionObserver =
|
||||
MockIntersectionObserver as unknown as typeof IntersectionObserver;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("renders the static fallback when reduced motion is enabled", async () => {
|
||||
mockMatchMedia({ reduced: true });
|
||||
|
||||
render(<NotebookStory projects={projects} />);
|
||||
|
||||
expect(screen.getByTestId("notebook-fallback")).toBeInTheDocument();
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByTestId("notebook-stage")).not.toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not initialize the 3D stage on coarse pointers", async () => {
|
||||
mockMatchMedia({ coarse: true });
|
||||
|
||||
render(<NotebookStory projects={projects} />);
|
||||
|
||||
expect(screen.getByTestId("notebook-fallback")).toBeInTheDocument();
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByTestId("notebook-stage")).not.toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
it("initializes the 3D stage only on a desktop hover-capable viewport", async () => {
|
||||
render(<NotebookStory projects={projects} />);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("notebook-stage")).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it("stays paused when the tab returns while the section is offscreen", async () => {
|
||||
render(<NotebookStory projects={projects} />);
|
||||
|
||||
const stage = await screen.findByTestId("notebook-stage");
|
||||
expect(stage).toHaveAttribute("data-is-visible", "false");
|
||||
|
||||
setSectionIntersects(true);
|
||||
await waitFor(() =>
|
||||
expect(stage).toHaveAttribute("data-is-visible", "true"),
|
||||
);
|
||||
|
||||
setDocumentHidden(true);
|
||||
act(() => document.dispatchEvent(new Event("visibilitychange")));
|
||||
await waitFor(() =>
|
||||
expect(stage).toHaveAttribute("data-is-visible", "false"),
|
||||
);
|
||||
|
||||
setSectionIntersects(false);
|
||||
setDocumentHidden(false);
|
||||
act(() => document.dispatchEvent(new Event("visibilitychange")));
|
||||
expect(stage).toHaveAttribute("data-is-visible", "false");
|
||||
});
|
||||
it("returns to the static fallback when the viewport loses desktop eligibility", async () => {
|
||||
render(<NotebookStory projects={projects} />);
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("notebook-stage")).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
configurable: true,
|
||||
value: 800,
|
||||
});
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByTestId("notebook-stage")).not.toBeInTheDocument(),
|
||||
);
|
||||
expect(screen.getByTestId("notebook-fallback")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the static fallback when no mapped projects are available", async () => {
|
||||
mockMatchMedia();
|
||||
|
||||
render(<NotebookStory projects={[]} />);
|
||||
|
||||
expect(screen.getByTestId("notebook-fallback")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByLabelText("Projetos em produção no notebook"),
|
||||
).toBeInTheDocument();
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByTestId("notebook-stage")).not.toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it("requires desktop hover input without reduced motion", () => {
|
||||
const matchMedia = (query: string) =>
|
||||
({
|
||||
matches: query === "(hover: hover)",
|
||||
}) as MediaQueryList;
|
||||
|
||||
expect(canUseNotebook3D({ innerWidth: 1280, matchMedia })).toBe(true);
|
||||
expect(canUseNotebook3D({ innerWidth: 1024, matchMedia })).toBe(false);
|
||||
expect(
|
||||
canUseNotebook3D({
|
||||
innerWidth: 1280,
|
||||
matchMedia: (query) =>
|
||||
({
|
||||
matches:
|
||||
query === "(hover: hover)" ||
|
||||
query === "(prefers-reduced-motion: reduce)",
|
||||
}) as MediaQueryList,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("passes only mapped projects into the fallback browser", () => {
|
||||
mockMatchMedia({ reduced: true });
|
||||
|
||||
render(<NotebookStory projects={projects} />);
|
||||
|
||||
expect(screen.getByRole("tab", { name: /NutriScan/i })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("tab", { name: /Twins Lab/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user