fix(18): enable first-run timezone save when not explicitly set (WR-01)
- Derive isExplicit from timezoneQuery.data?.isExplicitlySet - Apply the input===stored no-op guard only when isExplicit is true - Keep pending and empty-input guards unconditional - Add unit tests (AdminPage.timezone.test.ts) verifying first-run Save is enabled when isExplicitlySet:false and input matches stored fallback value
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* AdminPage timezone Save-button disabled logic — unit tests (WR-01 fix verification)
|
||||
*
|
||||
* Tests the `timezoneSaveDisabled` derivation inline:
|
||||
*
|
||||
* const timezoneSaveDisabled =
|
||||
* timezoneMutation.isPending ||
|
||||
* effectiveTimezoneInput === '' ||
|
||||
* (isExplicit && effectiveTimezoneInput === storedTimezone);
|
||||
*
|
||||
* The pre-fix bug: Save was always disabled when input === stored value,
|
||||
* regardless of isExplicitlySet. On first run (isExplicitlySet: false) this meant
|
||||
* the admin could never confirm/save the displayed system-default zone.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
/**
|
||||
* Pure function extracted from AdminPage that computes whether Save should be disabled.
|
||||
* This mirrors the logic that must be present post-fix in AdminPage.tsx.
|
||||
*/
|
||||
function computeTimezoneSaveDisabled({
|
||||
isPending,
|
||||
effectiveTimezoneInput,
|
||||
storedTimezone,
|
||||
isExplicit,
|
||||
}: {
|
||||
isPending: boolean;
|
||||
effectiveTimezoneInput: string;
|
||||
storedTimezone: string;
|
||||
isExplicit: boolean;
|
||||
}): boolean {
|
||||
return (
|
||||
isPending ||
|
||||
effectiveTimezoneInput === '' ||
|
||||
(isExplicit && effectiveTimezoneInput === storedTimezone)
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pre-fix behavior: the bug that WR-01 identified
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('timezoneSaveDisabled — WR-01 first-run (isExplicitlySet: false)', () => {
|
||||
it('Save is ENABLED when not explicitly set, even if input matches stored value (WR-01)', () => {
|
||||
// First run: GET returns { timezone: 'UTC', isExplicitlySet: false }
|
||||
// Input pre-fills to 'UTC'. This must enable Save so admin can confirm.
|
||||
const disabled = computeTimezoneSaveDisabled({
|
||||
isPending: false,
|
||||
effectiveTimezoneInput: 'UTC',
|
||||
storedTimezone: 'UTC',
|
||||
isExplicit: false,
|
||||
});
|
||||
expect(disabled).toBe(false); // WR-01: was `true` before fix (bug)
|
||||
});
|
||||
|
||||
it('Save is ENABLED when not explicitly set with a real zone matching stored', () => {
|
||||
const disabled = computeTimezoneSaveDisabled({
|
||||
isPending: false,
|
||||
effectiveTimezoneInput: 'America/Chicago',
|
||||
storedTimezone: 'America/Chicago',
|
||||
isExplicit: false,
|
||||
});
|
||||
expect(disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('Save is DISABLED when pending (regardless of isExplicit)', () => {
|
||||
const disabled = computeTimezoneSaveDisabled({
|
||||
isPending: true,
|
||||
effectiveTimezoneInput: 'UTC',
|
||||
storedTimezone: 'UTC',
|
||||
isExplicit: false,
|
||||
});
|
||||
expect(disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('Save is DISABLED when input is empty', () => {
|
||||
const disabled = computeTimezoneSaveDisabled({
|
||||
isPending: false,
|
||||
effectiveTimezoneInput: '',
|
||||
storedTimezone: 'UTC',
|
||||
isExplicit: false,
|
||||
});
|
||||
expect(disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Already-explicit cases: no-op re-saves should stay disabled
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('timezoneSaveDisabled — already explicitly set (isExplicitlySet: true)', () => {
|
||||
it('Save is DISABLED when input matches stored and timezone is explicit (no-op guard)', () => {
|
||||
// Admin previously saved 'America/Chicago'. Input still shows 'America/Chicago'.
|
||||
// No change → Save stays disabled (two-tap UX).
|
||||
const disabled = computeTimezoneSaveDisabled({
|
||||
isPending: false,
|
||||
effectiveTimezoneInput: 'America/Chicago',
|
||||
storedTimezone: 'America/Chicago',
|
||||
isExplicit: true,
|
||||
});
|
||||
expect(disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('Save is ENABLED when input differs from stored (user changed value)', () => {
|
||||
const disabled = computeTimezoneSaveDisabled({
|
||||
isPending: false,
|
||||
effectiveTimezoneInput: 'Europe/Paris',
|
||||
storedTimezone: 'America/Chicago',
|
||||
isExplicit: true,
|
||||
});
|
||||
expect(disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('Save is DISABLED when pending even if value differs', () => {
|
||||
const disabled = computeTimezoneSaveDisabled({
|
||||
isPending: true,
|
||||
effectiveTimezoneInput: 'Europe/Paris',
|
||||
storedTimezone: 'America/Chicago',
|
||||
isExplicit: true,
|
||||
});
|
||||
expect(disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -111,11 +111,21 @@ export function AdminPage() {
|
||||
const storedTimezone = timezoneQuery.data?.timezone ?? '';
|
||||
const effectiveTimezoneInput = timezoneInput ?? storedTimezone;
|
||||
|
||||
// Save is disabled when pending, or when input matches what's stored
|
||||
// WR-01: derive isExplicit so we only apply the no-op guard when the timezone
|
||||
// has ALREADY been explicitly saved. On first run (isExplicitlySet: false) the
|
||||
// admin must be able to confirm/save the displayed system-default — even if the
|
||||
// input value already matches the fallback string. Keep pending and empty-input
|
||||
// guards unconditional.
|
||||
const isExplicit = timezoneQuery.data?.isExplicitlySet ?? false;
|
||||
|
||||
// Save is disabled when:
|
||||
// - mutation is in-flight (pending), OR
|
||||
// - input is empty, OR
|
||||
// - the timezone IS already explicitly set AND the input is unchanged (no-op)
|
||||
const timezoneSaveDisabled =
|
||||
timezoneMutation.isPending ||
|
||||
effectiveTimezoneInput === '' ||
|
||||
effectiveTimezoneInput === storedTimezone;
|
||||
(isExplicit && effectiveTimezoneInput === storedTimezone);
|
||||
|
||||
// IANA zones list for the datalist (Intl.supportedValuesOf may not be present in all runtimes)
|
||||
const ianaZones: string[] =
|
||||
|
||||
Reference in New Issue
Block a user