fix: render Automations pages flush to viewport edges (#225)

This commit is contained in:
Hiep Le
2026-05-09 21:37:57 +07:00
committed by GitHub
parent b2961fd39f
commit 93f9f195b1
4 changed files with 152 additions and 3 deletions
@@ -0,0 +1,105 @@
import { render, screen } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { describe, expect, it, vi } from "vitest";
import { Sidebar } from "#/components/features/sidebar/sidebar";
import {
NavigationProvider,
type NavigationContextValue,
} from "#/context/navigation-context";
vi.mock("#/hooks/query/use-git-user", () => ({
useGitUser: () => ({ data: undefined, isFetching: false }),
}));
vi.mock("#/hooks/query/use-config", () => ({
useConfig: () => ({ data: { feature_flags: {} } }),
}));
vi.mock("#/hooks/query/use-settings", () => ({
useSettings: () => ({
data: { email_verified: true },
error: null,
isError: false,
isFetching: false,
}),
getErrorStatus: () => undefined,
}));
vi.mock("#/components/shared/buttons/styled-tooltip", () => ({
StyledTooltip: ({ children }: { children: unknown }) => children,
}));
vi.mock("#/components/shared/buttons/openhands-logo-button", () => ({
OpenHandsLogoButton: () => <div data-testid="logo-button" />,
}));
vi.mock("#/components/shared/buttons/new-project-button", () => ({
NewProjectButton: () => <div data-testid="new-project-button" />,
}));
vi.mock("#/components/shared/buttons/conversation-panel-button", () => ({
ConversationPanelButton: () => (
<div data-testid="conversation-panel-button" />
),
}));
vi.mock("#/components/shared/buttons/automations-button", () => ({
AutomationsButton: () => <div data-testid="automations-button" />,
}));
vi.mock("#/components/features/sidebar/user-actions", () => ({
UserActions: () => <div data-testid="user-actions" />,
}));
vi.mock("#/components/features/conversation-panel/conversation-panel", () => ({
ConversationPanel: () => null,
}));
vi.mock(
"#/components/features/conversation-panel/conversation-panel-wrapper",
() => ({
ConversationPanelWrapper: () => null,
}),
);
vi.mock("#/components/shared/modals/settings/settings-modal", () => ({
SettingsModal: () => null,
}));
function renderSidebar(currentPath: string) {
const value: NavigationContextValue = {
currentPath,
conversationId: null,
isNavigating: false,
navigate: vi.fn(),
};
return render(
<QueryClientProvider client={new QueryClient()}>
<NavigationProvider value={value}>
<Sidebar />
</NavigationProvider>
</QueryClientProvider>,
);
}
describe("Sidebar", () => {
it.each([["/automations"], ["/automations/abc-123"]])(
"applies the standalone vertical padding on %s so the sidebar has breathing room when the root layout's padding is dropped",
(currentPath) => {
renderSidebar(currentPath);
const sidebar = screen.getByRole("navigation").parentElement;
expect(sidebar?.className).toMatch(/(^|\s)md:pt-6\.5(\s|$)/);
expect(sidebar?.className).toMatch(/(^|\s)md:pb-3(\s|$)/);
},
);
it("does not apply the standalone vertical padding on routes that still use the root layout's padding", () => {
renderSidebar("/settings");
const sidebar = screen.getByRole("navigation").parentElement;
expect(sidebar?.className).not.toMatch(/(^|\s)md:pt-6\.5(\s|$)/);
expect(sidebar?.className).not.toMatch(/(^|\s)md:pb-3(\s|$)/);
});
});
+42 -1
View File
@@ -39,7 +39,9 @@ vi.mock("#/components/features/sidebar/sidebar", () => ({
}));
vi.mock("#/components/features/analytics/analytics-consent-form-modal", () => ({
AnalyticsConsentFormModal: () => <div data-testid="analytics-consent-modal" />,
AnalyticsConsentFormModal: () => (
<div data-testid="analytics-consent-modal" />
),
}));
vi.mock("#/components/features/alerts/alert-banner", () => ({
@@ -61,6 +63,18 @@ const RouterStub = createRoutesStub([
path: "/",
Component: () => <div data-testid="outlet-content" />,
},
{
path: "/automations",
Component: () => <div data-testid="outlet-content" />,
},
{
path: "/automations/:id",
Component: () => <div data-testid="outlet-content" />,
},
{
path: "/settings",
Component: () => <div data-testid="outlet-content" />,
},
],
},
]);
@@ -116,4 +130,31 @@ describe("root layout", () => {
expect(screen.getByTestId("analytics-consent-modal")).toBeInTheDocument();
expect(migrateUserConsentMock).toHaveBeenCalled();
});
it.each([["/automations"], ["/automations/abc-123"]])(
"drops the outer layout padding on %s so the page can render flush to the viewport edges",
(path) => {
render(
<QueryClientProvider client={new QueryClient()}>
<RouterStub initialEntries={[path]} />
</QueryClientProvider>,
);
const layout = screen.getByTestId("root-layout");
expect(layout.className).not.toMatch(/(^|\s)md:p-3(\s|$)/);
expect(layout.className).not.toMatch(/(^|\s)md:pl-0(\s|$)/);
},
);
it("keeps the outer layout padding on routes other than home and automations", () => {
render(
<QueryClientProvider client={new QueryClient()}>
<RouterStub initialEntries={["/settings"]} />
</QueryClientProvider>,
);
const layout = screen.getByTestId("root-layout");
expect(layout.className).toMatch(/(^|\s)md:p-3(\s|$)/);
expect(layout.className).toMatch(/(^|\s)md:pl-0(\s|$)/);
});
});
+2 -1
View File
@@ -67,7 +67,8 @@ export function Sidebar() {
aria-label={t(I18nKey.SIDEBAR$NAVIGATION_LABEL)}
className={cn(
"h-[54px] p-3 md:p-0 md:h-[40px] md:h-auto flex flex-row md:flex-col gap-1 bg-base md:w-[75px] md:min-w-[75px] sm:pt-0 sm:px-2 md:pt-[14px] md:px-0",
currentPath === "/" && "md:pt-6.5 md:pb-3",
(currentPath === "/" || currentPath.startsWith("/automations")) &&
"md:pt-6.5 md:pb-3",
)}
>
<nav className="flex flex-row md:flex-col items-center justify-between w-full h-auto md:w-auto md:h-full">
+3 -1
View File
@@ -97,7 +97,9 @@ export default function MainApp() {
data-testid="root-layout"
className={cn(
"h-screen lg:min-w-5xl flex flex-col md:flex-row bg-base overflow-hidden",
pathname === "/" ? "p-0" : "p-0 md:p-3 md:pl-0",
pathname === "/" || pathname.startsWith("/automations")
? "p-0"
: "p-0 md:p-3 md:pl-0",
)}
>
<title>{appTitle}</title>