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
41 changes: 41 additions & 0 deletions apps/loopover-ui/src/routes/app.index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";

import { TooltipProvider } from "@/components/ui/tooltip";
import { SparkStat } from "./app.index";

// #6984: SparkStat's loading branch hand-rolled its own animate-pulse divs instead of the shared
// Skeleton primitive every other loading placeholder in this app already uses.
describe("SparkStat loading state (#6984)", () => {
it("renders Skeleton placeholders (not the raw hand-rolled divs) while loading", () => {
const { container } = render(
<SparkStat
label="Open PRs"
value="4"
values={[1, 2, 3, 4]}
live
statusLabel="live"
loading
/>,
);

expect(screen.getByRole("status", { name: "Loading Open PRs" })).toBeTruthy();
// Skeleton renders animate-pulse blocks; the label/value/sparkline placeholders are 3 in total.
expect(container.querySelectorAll(".animate-pulse").length).toBe(3);
// The real label/value text never renders while loading.
expect(screen.queryByText("Open PRs")).toBeNull();
expect(screen.queryByText("4")).toBeNull();
});

it("renders the real label and value once data is available (not loading)", () => {
render(
<TooltipProvider>
<SparkStat label="Open PRs" value="4" values={[1, 2, 3, 4]} live statusLabel="live" />
</TooltipProvider>,
);

expect(screen.getByText("Open PRs")).toBeTruthy();
expect(screen.getByText("4")).toBeTruthy();
expect(screen.queryByRole("status", { name: "Loading Open PRs" })).toBeNull();
});
});
9 changes: 5 additions & 4 deletions apps/loopover-ui/src/routes/app.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
X,
} from "lucide-react";

import { Skeleton } from "@/components/ui/skeleton";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";

import { StatusPill } from "@/components/site/control-primitives";
Expand Down Expand Up @@ -494,7 +495,7 @@ function OnboardingChecklist() {
);
}

function SparkStat({
export function SparkStat({
label,
value,
hint,
Expand All @@ -518,10 +519,10 @@ function SparkStat({
aria-label={`Loading ${label}`}
className="rounded-token border-hairline bg-card p-4"
>
<div className="h-3 w-24 animate-pulse rounded bg-muted/40 motion-reduce:animate-none" />
<Skeleton className="h-3 w-24" />
<div className="mt-3 flex items-end justify-between gap-3">
<div className="h-7 w-16 animate-pulse rounded bg-muted/40 motion-reduce:animate-none" />
<div className="h-10 w-24 animate-pulse rounded bg-muted/30 motion-reduce:animate-none" />
<Skeleton className="h-7 w-16" />
<Skeleton className="h-10 w-24" />
</div>
</div>
);
Expand Down
Loading