Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion client/src/protoFleet/api/generated/alerts/v1/alerts_pb.ts

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions client/src/protoFleet/components/AppLayout/AppLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useHasPermission } from "@/protoFleet/store";
const mockUseWindowDimensions = vi.fn();
const mockUseReactiveLocalStorage = vi.fn();
const mockUseCurtailmentPillData = vi.fn();
const mockUseActiveAlertsPillData = vi.fn();
const mockUseSchedulePillData = vi.fn();
const mockUseUpdateIndicator = vi.fn();

Expand Down Expand Up @@ -43,6 +44,10 @@ vi.mock("@/protoFleet/components/PageHeader/useCurtailmentPillData", () => ({
useCurtailmentPillData: () => mockUseCurtailmentPillData(),
}));

vi.mock("@/protoFleet/components/PageHeader/useActiveAlertsPillData", () => ({
useActiveAlertsPillData: (options: { enabled?: boolean }) => mockUseActiveAlertsPillData(options),
}));

vi.mock("@/protoFleet/features/updates/useUpdateIndicator", () => ({
useUpdateIndicator: (options: { enabled?: boolean }) => mockUseUpdateIndicator(options),
}));
Expand Down Expand Up @@ -97,12 +102,15 @@ const activeCurtailmentEvent: CurtailmentPillEvent = {

describe("AppLayout", () => {
beforeEach(() => {
// Without this, a `toHaveBeenCalledWith` assertion matches any earlier test's render instead of its own.
vi.clearAllMocks();
mockUseWindowDimensions.mockReturnValue({
width: 375,
isPhone: true,
});
mockUseReactiveLocalStorage.mockReturnValue([false, vi.fn()]);
mockUseCurtailmentPillData.mockReturnValue({ activeEvent: null });
mockUseActiveAlertsPillData.mockReturnValue({ groups: [], error: null, hasMore: false, hasVisiblePill: false });
mockUseSchedulePillData.mockReturnValue(createSchedulePillData());
mockUseUpdateIndicator.mockReturnValue(null);
vi.mocked(useHasPermission).mockReturnValue(true);
Expand Down Expand Up @@ -250,6 +258,30 @@ describe("AppLayout", () => {
expect(screen.getByText("Body content").parentElement).toHaveClass("phone:top-[calc(theme(spacing.1)*12+120px)]");
});

it("uses the four-widget phone content offset when a firing alert makes five widgets visible", () => {
mockUseReactiveLocalStorage.mockReturnValue([true, vi.fn()]);
mockUseActiveAlertsPillData.mockReturnValue({
groups: [{ key: "miner|offline" }],
error: null,
hasMore: false,
hasVisiblePill: true,
});
mockUseCurtailmentPillData.mockReturnValue({ activeEvent: activeCurtailmentEvent });
mockUseSchedulePillData.mockReturnValue(createSchedulePillData({ pillSchedule: createPillSchedule() }));
mockUseUpdateIndicator.mockReturnValue({ version: "v1.3.0", onClick: vi.fn() });

render(
<MemoryRouter>
<AppLayout>
<div>Body content</div>
</AppLayout>
</MemoryRouter>,
);

// The alerts pill takes the inline slot, leaving four stacked pills that the three-row ladder would clip.
expect(screen.getByText("Body content").parentElement).toHaveClass("phone:top-[calc(theme(spacing.1)*12+160px)]");
});

it("disables update polling when the route hides the shell header", () => {
render(
<MemoryRouter>
Expand All @@ -262,6 +294,39 @@ describe("AppLayout", () => {
expect(mockUseUpdateIndicator).toHaveBeenCalledWith({ enabled: false });
});

it("disables active-alert polling when the route hides the shell header", () => {
render(
<MemoryRouter>
<AppLayout hideShellHeader>
<div>Body content</div>
</AppLayout>
</MemoryRouter>,
);

expect(mockUseActiveAlertsPillData).toHaveBeenCalledWith({ enabled: false });
});

it("offsets the phone content for a firing alert so the pill has a row", () => {
mockUseActiveAlertsPillData.mockReturnValue({
groups: [{ key: "miner|offline" }],
error: null,
hasMore: false,
hasVisiblePill: true,
});
mockUseSchedulePillData.mockReturnValue(createSchedulePillData({ pillSchedule: createPillSchedule() }));

render(
<MemoryRouter>
<AppLayout>
<div>Body content</div>
</AppLayout>
</MemoryRouter>,
);

// The alerts pill takes the inline slot, pushing the schedule pill into a row of its own.
expect(screen.getByText("Body content").parentElement).toHaveClass("phone:top-[calc(theme(spacing.1)*12+40px)]");
});

it("keeps the base phone content offset when the only curtailment widget fits inline", () => {
mockUseCurtailmentPillData.mockReturnValue({ activeEvent: activeCurtailmentEvent });

Expand Down
5 changes: 5 additions & 0 deletions client/src/protoFleet/components/AppLayout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
shouldInlineFirstPhoneHeaderWidget,
shouldStackPhoneHeaderWidgets,
} from "@/protoFleet/components/PageHeader/headerWidgetLayout";
import { useActiveAlertsPillData } from "@/protoFleet/components/PageHeader/useActiveAlertsPillData";
import { useCurtailmentPillData } from "@/protoFleet/components/PageHeader/useCurtailmentPillData";
import { useSchedulePillData } from "@/protoFleet/components/PageHeader/useSchedulePillData";
import { primaryNavItems } from "@/protoFleet/config/navItems";
Expand All @@ -38,12 +39,15 @@ const AppLayoutContent = ({ children, hideShellHeader = false }: Props) => {
// Release discovery is an enhancement of the normal Fleet header. Focused
// detail routes deliberately skip the poll along with the hidden header.
const updatePill = useUpdateIndicator({ enabled: !hideShellHeader });
// Same deal for the active-alert poll: no header, no pill to feed.
const activeAlertsPillData = useActiveAlertsPillData({ enabled: !hideShellHeader });
const hasDismissedSetup = Boolean(dismissedSetup);
const canReadCurtailment = useHasPermission("curtailment:read");
const hasVisibleCurtailmentPill = activeCurtailmentEvent !== null && canReadCurtailment;
const hasVisibleUpdatePill = updatePill !== null;
const headerWidgetCount = getVisibleHeaderWidgetCount({
hasDismissedSetup,
hasVisibleAlertsPill: activeAlertsPillData.hasVisiblePill,
hasVisibleUpdatePill,
hasVisibleCurtailmentPill,
hasVisibleSchedules: schedulePillData.hasVisibleSchedules,
Expand Down Expand Up @@ -96,6 +100,7 @@ const AppLayoutContent = ({ children, hideShellHeader = false }: Props) => {
className={`fixed top-0 right-0 bottom-[calc(100vh-theme(spacing.1)*12)] left-0 z-40 laptop:bottom-[calc(100vh-theme(spacing.1)*15)] laptop:left-16 desktop:left-50 ${bgClass}`}
>
<PageHeader
activeAlertsPillData={activeAlertsPillData}
activeCurtailmentEvent={activeCurtailmentEvent}
isMenuOpen={isMenuOpen}
openMenu={() => setIsMenuOpen(true)}
Expand Down
Loading
Loading