Phase 18: Auto timezone detection and ability to change timezone #21

Merged
luckberg merged 38 commits from gsd/phase-18-auto-timezone-detection-and-ability-to-change-timezone into main 2026-06-15 09:55:53 -04:00
2 changed files with 17 additions and 2 deletions
Showing only changes of commit ddeb87cdba - Show all commits
+7
View File
@@ -109,6 +109,13 @@ export default async function globalSetup(): Promise<void> {
await conn.execute('TRUNCATE TABLE calendar_events'); await conn.execute('TRUNCATE TABLE calendar_events');
await conn.execute('SET FOREIGN_KEY_CHECKS=1'); await conn.execute('SET FOREIGN_KEY_CHECKS=1');
// Phase 18: clear any stored household timezone so the timezone spec always
// starts from the first-run (isExplicitlySet:false) state. app_config is NOT
// truncated above (it can hold other non-test config), so delete only this key.
// Without this, a prior run's saved value leaks across runs and makes the
// first-run / persist-across-reload timezone assertions non-deterministic.
await conn.execute("DELETE FROM app_config WHERE `key` = 'household_timezone'");
// Seed the dev-bypass admin user row for id=1 (D-01 dev note, Phase 10). // Seed the dev-bypass admin user row for id=1 (D-01 dev note, Phase 10).
// DEV_USER (id=1) is injected by devBypass.ts WITHOUT a DB upsert, so the users table // DEV_USER (id=1) is injected by devBypass.ts WITHOUT a DB upsert, so the users table
// has no row for id=1 by default. requireAdmin (Plan 02) does a DB lookup and would 403 // has no row for id=1 by default. requireAdmin (Plan 02) does a DB lookup and would 403
+10 -2
View File
@@ -30,11 +30,19 @@ test.describe('Admin Timezone section — 18-04 round-trip', () => {
expect(val.length, 'Input should have a non-empty timezone').toBeGreaterThan(0); expect(val.length, 'Input should have a non-empty timezone').toBeGreaterThan(0);
}); });
test('Save button is disabled when timezone is unchanged', async ({ page }) => { test('Save is enabled on first run when timezone is not yet explicit (WR-01)', async ({ page }) => {
// On first run the GET returns isExplicitlySet:false with the detected zone
// pre-filled. Saving that value to make the choice explicit is a meaningful
// action, so Save must be ENABLED even though the input matches the displayed
// default. (The disabled-when-unchanged behaviour for an already-explicit
// value is covered by the "Save persists" test below, which re-disables Save
// after a successful save.)
const tzSection = page.getByRole('region', { name: 'Timezone' }); const tzSection = page.getByRole('region', { name: 'Timezone' });
// Confirm we are in the first-run (system-default) state for this assertion.
await expect(tzSection.getByText('Using system default')).toBeVisible();
const saveBtn = tzSection.getByRole('button', { name: /Save|Saving/ }); const saveBtn = tzSection.getByRole('button', { name: /Save|Saving/ });
await expect(saveBtn).toBeVisible(); await expect(saveBtn).toBeVisible();
await expect(saveBtn).toBeDisabled(); await expect(saveBtn).toBeEnabled();
}); });
test('Changing the input enables Save', async ({ page }) => { test('Changing the input enables Save', async ({ page }) => {