fix(site): stabilize date-dependent storybook snapshots (#23657)

_Generated by mux but reviewed by a human_

Several stories computed dates relative to `dayjs()` / `new Date()` at
render time, causing snapshot text to shift daily. I ran into this on my
PRs.

This adds an optional `now` prop to `DateRangePicker`,
`TemplateInsightsControls`, and `CreateTokenForm` so stories can inject
a deterministic clock without global mocking. License stories replace
the misleadingly-named `FIXED_NOW = dayjs().startOf("day")` with
absolute timestamps. All fixed timestamps use noon UTC to avoid timezone
boundary issues.

Affected stories:
- `AgentSettingsPageView`: Usage Date Filter, Usage Date Filter Refetch
Overlay
- `LicenseCard`: Expired/future AI Governance variants, Not Yet Valid
- `LicensesSettingsPage`: Shows Addon Ui For Future License Before Nbf
- `TemplateInsightsControls`: Day
- `CreateTokenPage`: Default
This commit is contained in:
Ethan
2026-03-27 01:21:52 +11:00
committed by GitHub
parent 4d74603045
commit 87aafd4ae2
10 changed files with 118 additions and 60 deletions
@@ -274,6 +274,7 @@ const UsageContent: FC<UsageContentProps> = ({ now }) => {
<DateRangePicker
value={displayDateRange}
onChange={onDateRangeChange}
now={now?.toDate()}
/>
}
/>
@@ -14,6 +14,9 @@ const defaultValue: DateRangeValue = {
const meta: Meta<typeof DateRangePicker> = {
title: "components/DateRangePicker",
component: DateRangePicker,
args: {
now: fixedNow.toDate(),
},
};
export default meta;
@@ -61,7 +64,13 @@ export const Open: Story = {
export const SelectPreset: Story = {
render: function SelectPresetStory() {
const [value, setValue] = useState<DateRangeValue>(defaultValue);
return <DateRangePicker value={value} onChange={setValue} />;
return (
<DateRangePicker
value={value}
onChange={setValue}
now={fixedNow.toDate()}
/>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
@@ -93,7 +102,13 @@ export const SelectCalendarRange: Story = {
startDate: new Date("2025-03-01"),
endDate: new Date("2025-03-15"),
});
return <DateRangePicker value={value} onChange={setValue} />;
return (
<DateRangePicker
value={value}
onChange={setValue}
now={fixedNow.toDate()}
/>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
@@ -119,7 +134,13 @@ export const SelectCalendarRange: Story = {
export const CancelClosesWithoutApplying: Story = {
render: function CancelStory() {
const [value, setValue] = useState<DateRangeValue>(defaultValue);
return <DateRangePicker value={value} onChange={setValue} />;
return (
<DateRangePicker
value={value}
onChange={setValue}
now={fixedNow.toDate()}
/>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
@@ -27,44 +27,60 @@ interface DateRangePreset {
range: () => { from: Date; to: Date };
}
const defaultPresets: DateRangePreset[] = [
{
label: "Today",
range: () => ({ from: new Date(), to: new Date() }),
},
{
label: "Yesterday",
range: () => {
const d = dayjs().subtract(1, "day").toDate();
return { from: d, to: d };
const buildDefaultPresets = (now?: Date): DateRangePreset[] => {
const getCurrentTime = () => dayjs(now ?? new Date());
return [
{
label: "Today",
range: () => {
const currentTime = getCurrentTime();
return { from: currentTime.toDate(), to: currentTime.toDate() };
},
},
},
{
label: "Last 7 days",
range: () => ({
from: dayjs().subtract(6, "day").toDate(),
to: new Date(),
}),
},
{
label: "Last 14 days",
range: () => ({
from: dayjs().subtract(13, "day").toDate(),
to: new Date(),
}),
},
{
label: "Last 30 days",
range: () => ({
from: dayjs().subtract(29, "day").toDate(),
to: new Date(),
}),
},
];
{
label: "Yesterday",
range: () => {
const d = getCurrentTime().subtract(1, "day").toDate();
return { from: d, to: d };
},
},
{
label: "Last 7 days",
range: () => {
const currentTime = getCurrentTime();
return {
from: currentTime.subtract(6, "day").toDate(),
to: currentTime.toDate(),
};
},
},
{
label: "Last 14 days",
range: () => {
const currentTime = getCurrentTime();
return {
from: currentTime.subtract(13, "day").toDate(),
to: currentTime.toDate(),
};
},
},
{
label: "Last 30 days",
range: () => {
const currentTime = getCurrentTime();
return {
from: currentTime.subtract(29, "day").toDate(),
to: currentTime.toDate(),
};
},
},
];
};
interface DateRangePickerProps {
value: DateRangeValue;
onChange: (value: DateRangeValue) => void;
now?: Date;
presets?: DateRangePreset[];
}
@@ -74,10 +90,11 @@ interface DateRangePickerProps {
* rounded up to the next hour (if it falls on today) or to the start of
* the following day.
*/
function toBoundary(from: Date, to: Date): DateRangeValue {
function toBoundary(from: Date, to: Date, now: Date): DateRangeValue {
const currentTime = dayjs(now);
const start = dayjs(from).startOf("day").toDate();
const end = dayjs(to).isSame(dayjs(), "day")
? dayjs().startOf("hour").add(1, "hour").toDate()
const end = dayjs(to).isSame(currentTime, "day")
? currentTime.startOf("hour").add(1, "hour").toDate()
: dayjs(to).startOf("day").add(1, "day").toDate();
return { startDate: start, endDate: end };
}
@@ -100,9 +117,12 @@ function fromBoundary(value: DateRangeValue): DayPickerDateRange {
export const DateRangePicker: FC<DateRangePickerProps> = ({
value,
onChange,
presets = defaultPresets,
now,
presets,
}) => {
const [open, setOpen] = useState(false);
const currentTime = now ?? new Date();
const resolvedPresets = presets ?? buildDefaultPresets(now);
// Internal selection state kept separate from the committed value
// so the user can freely adjust the range before applying. This
@@ -113,7 +133,7 @@ export const DateRangePicker: FC<DateRangePickerProps> = ({
const commit = () => {
if (selection?.from && selection?.to) {
onChange(toBoundary(selection.from, selection.to));
onChange(toBoundary(selection.from, selection.to, now ?? new Date()));
}
setOpen(false);
};
@@ -122,7 +142,7 @@ export const DateRangePicker: FC<DateRangePickerProps> = ({
const { from, to } = preset.range();
setSelection({ from, to });
// Presets are a complete selection — commit immediately.
onChange(toBoundary(from, to));
onChange(toBoundary(from, to, now ?? new Date()));
setOpen(false);
};
@@ -168,7 +188,7 @@ export const DateRangePicker: FC<DateRangePickerProps> = ({
<div className="flex">
{/* Presets sidebar */}
<div className="flex flex-col border-r border-border-default p-2 text-sm">
{presets.map((preset) => (
{resolvedPresets.map((preset) => (
<button
key={preset.label}
type="button"
@@ -223,7 +243,7 @@ export const DateRangePicker: FC<DateRangePickerProps> = ({
selected={selection}
onSelect={handleCalendarSelect}
numberOfMonths={2}
disabled={{ after: new Date() }}
disabled={{ after: currentTime }}
/>
</div>
@@ -33,6 +33,7 @@ interface CreateTokenFormProps {
setFormError: (arg0: unknown) => void;
isCreating: boolean;
creationFailed: boolean;
now?: Date;
}
export const CreateTokenForm: FC<CreateTokenFormProps> = ({
@@ -42,6 +43,7 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
setFormError,
isCreating,
creationFailed,
now,
}) => {
const navigate = useNavigate();
@@ -49,6 +51,7 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
const [lifetimeDays, setLifetimeDays] = useState<number | string>(
determineDefaultLtValue(maxTokenLifetime),
);
const currentTime = dayjs(now ?? new Date());
// biome-ignore lint/correctness/useExhaustiveDependencies: adding form will cause an infinite loop
useEffect(() => {
@@ -86,7 +89,7 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
<>
The token will expire on{" "}
<span data-chromatic="ignore">
{dayjs()
{currentTime
.add(form.values.lifetime, "days")
.utc()
.format("MMMM DD, YYYY")}
@@ -17,4 +17,8 @@ const meta: Meta<typeof CreateTokenPage> = {
export default meta;
type Story = StoryObj<typeof CreateTokenPage>;
export const Default: Story = {};
export const Default: Story = {
args: {
now: new Date("2026-03-12T12:00:00Z"),
},
};
@@ -19,7 +19,11 @@ const initialValues: CreateTokenData = {
lifetime: 30,
};
const CreateTokenPage: FC = () => {
type CreateTokenPageProps = {
now?: Date;
};
const CreateTokenPage: FC<CreateTokenPageProps> = ({ now }) => {
const navigate = useNavigate();
const {
@@ -105,6 +109,7 @@ const CreateTokenPage: FC = () => {
setFormError={setFormError}
isCreating={isCreating}
creationFailed={creationFailed}
now={now}
/>
<ConfirmDialog
@@ -6,9 +6,8 @@ import { expect, fn, within } from "storybook/test";
import { LicenseCard } from "./LicenseCard";
const FIXED_NOW = dayjs().startOf("day");
const YESTERDAY = FIXED_NOW.subtract(1, "day").unix();
const NEXT_WEEK = FIXED_NOW.add(7, "day").unix();
const EXPIRED_DATE = dayjs("2000-01-01T12:00:00Z").unix();
const FUTURE_START_DATE = dayjs("2099-01-01T12:00:00Z").unix();
const meta: Meta<typeof LicenseCard> = {
title: "pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseCard",
@@ -176,7 +175,7 @@ export const ExpiredAIGovernanceOverageShowsExpired: Story = {
...MockLicenseResponse[1],
claims: {
...MockLicenseResponse[1].claims,
license_expires: YESTERDAY,
license_expires: EXPIRED_DATE,
features: {
...MockLicenseResponse[1].claims.features,
ai_governance_user_limit: 1000,
@@ -208,7 +207,7 @@ export const ExpiredAIGovernanceInGracePeriodShowsExceeded: Story = {
...MockLicenseResponse[1],
claims: {
...MockLicenseResponse[1].claims,
license_expires: YESTERDAY,
license_expires: EXPIRED_DATE,
features: {
...MockLicenseResponse[1].claims.features,
ai_governance_user_limit: 1000,
@@ -238,7 +237,7 @@ export const NotYetValid: Story = {
...MockLicenseResponse[1],
claims: {
...MockLicenseResponse[1].claims,
nbf: NEXT_WEEK,
nbf: FUTURE_START_DATE,
},
},
},
@@ -254,7 +253,7 @@ export const FutureAIGovernanceOverageShowsStartsOn: Story = {
...MockLicenseResponse[1],
claims: {
...MockLicenseResponse[1].claims,
nbf: NEXT_WEEK,
nbf: FUTURE_START_DATE,
features: {
...MockLicenseResponse[1].claims.features,
ai_governance_user_limit: 1000,
@@ -286,7 +285,7 @@ export const FutureAIGovernanceUsageShowsNoCurrentSeats: Story = {
...MockLicenseResponse[1],
claims: {
...MockLicenseResponse[1].claims,
nbf: NEXT_WEEK,
nbf: FUTURE_START_DATE,
features: {
...MockLicenseResponse[1].claims.features,
ai_governance_user_limit: 1000,
@@ -23,6 +23,8 @@ const USER_STATUS_COUNTS_QUERY = {
data: { active: [] },
};
const STORY_NOW = dayjs("2099-01-01T12:00:00Z");
const withBaseQueries = ({
entitlements = MockEntitlements,
licenses = MockLicenseResponse,
@@ -89,8 +91,8 @@ const createLicense = ({
addons?: string[];
}) => ({
id,
uploaded_at: String(dayjs().subtract(uploadedDaysAgo, "day").unix()),
expires_at: String(dayjs().add(expiresInDays, "day").unix()),
uploaded_at: String(STORY_NOW.subtract(uploadedDaysAgo, "day").unix()),
expires_at: String(STORY_NOW.add(expiresInDays, "day").unix()),
uuid,
claims: {
trial: false,
@@ -102,8 +104,8 @@ const createLicense = ({
user_limit: userLimit,
},
addons,
license_expires: dayjs().add(licenseExpiresInDays, "day").unix(),
nbf: dayjs().add(nbfOffsetDays, "day").unix(),
license_expires: STORY_NOW.add(licenseExpiresInDays, "day").unix(),
nbf: STORY_NOW.add(nbfOffsetDays, "day").unix(),
},
});
@@ -17,6 +17,7 @@ const defaultArgs: Partial<ComponentProps<typeof TemplateInsightsControls>> = {
startDate: new Date("2025-08-05"),
endDate: new Date("2025-08-07"),
},
now: new Date("2025-08-07T12:00:00Z"),
setDateRange: () => {},
searchParams: new URLSearchParams(),
setSearchParams: () => {},
@@ -142,6 +142,7 @@ interface TemplateInsightsControlsProps {
setDateRange: (value: DateRangeValue) => void;
searchParams: URLSearchParams;
setSearchParams: SetURLSearchParams;
now?: Date;
}
export const TemplateInsightsControls: FC<TemplateInsightsControlsProps> = ({
@@ -150,6 +151,7 @@ export const TemplateInsightsControls: FC<TemplateInsightsControlsProps> = ({
setDateRange,
searchParams,
setSearchParams,
now,
}) => {
return (
<>
@@ -165,7 +167,7 @@ export const TemplateInsightsControls: FC<TemplateInsightsControlsProps> = ({
}}
/>
{interval === "day" ? (
<DailyPicker value={dateRange} onChange={setDateRange} />
<DailyPicker value={dateRange} onChange={setDateRange} now={now} />
) : (
<WeekPicker value={dateRange} onChange={setDateRange} />
)}