mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-27 06:47:07 +00:00
fix(web): a date field holding a valid RFC3339 timestamp shows its day instead of "Invalid Date", and the picker opens on it (BUG-3225) (#1560)
This commit is contained in:
@@ -141,3 +141,37 @@ describe('date picker opens from a FOCUSED input (BUG-2858)', () => {
|
||||
expect(document.activeElement).toBe(input);
|
||||
});
|
||||
});
|
||||
|
||||
// BUG-3225: an RFC3339 value, which the server accepts for a date field,
|
||||
// rendered "Invalid Date" because a midnight was appended to the WHOLE string.
|
||||
describe('a date field holding an RFC3339 timestamp (BUG-3225)', () => {
|
||||
const label = (c: HTMLElement) => c.querySelector('.date-label')?.textContent?.trim();
|
||||
const dateInput = (c: HTMLElement) => c.querySelector('input[type="date"]') as HTMLInputElement;
|
||||
|
||||
it('shows the day as written, and preselects it in the picker', () => {
|
||||
const { container } = render(FieldEditor, {
|
||||
props: { field, value: '2026-09-26T23:30:00-05:00', onchange: vi.fn(), itemId: 'item-A' },
|
||||
});
|
||||
expect(label(container)).toBe('Sep 26, 2026');
|
||||
expect(dateInput(container).value).toBe('2026-09-26');
|
||||
});
|
||||
|
||||
it('read-only shows the same day', () => {
|
||||
const { container } = render(FieldEditor, {
|
||||
props: { field, value: '2026-09-26T10:00:00Z', onchange: vi.fn(), itemId: 'item-A', readonly: true },
|
||||
});
|
||||
expect(container.textContent).toContain('Sep 26, 2026');
|
||||
expect(container.textContent).not.toContain('Invalid Date');
|
||||
});
|
||||
|
||||
it('control: a bare date is unchanged', () => {
|
||||
const { container } = render(FieldEditor, { props: { field, value: '2026-09-26', onchange: vi.fn(), itemId: 'item-A' } });
|
||||
expect(label(container)).toBe('Sep 26, 2026');
|
||||
expect(dateInput(container).value).toBe('2026-09-26');
|
||||
});
|
||||
|
||||
it('a string that is not a date shows as itself, not "Invalid Date"', () => {
|
||||
const { container } = render(FieldEditor, { props: { field, value: 'next week', onchange: vi.fn(), itemId: 'item-A' } });
|
||||
expect(label(container)).toBe('next week');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -757,13 +757,27 @@ handlers — onchange is never called.
|
||||
dateTriggerEl?.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* The calendar day a stored date names, as written: `YYYY-MM-DD` itself, or
|
||||
* the day part of an RFC3339 timestamp, which the server also accepts for a
|
||||
* date field (BUG-3225). No timezone conversion: `2026-09-26T23:30:00-05:00`
|
||||
* names the 26th, the day its writer wrote. `null` when the string does not
|
||||
* start with a day.
|
||||
*/
|
||||
function dateDay(dateStr: string): string | null {
|
||||
const m = /^(\d{4}-\d{2}-\d{2})(?:$|T)/.exec(dateStr);
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
try {
|
||||
const d = new Date(dateStr + 'T00:00:00');
|
||||
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
} catch {
|
||||
return dateStr;
|
||||
}
|
||||
// Appending a midnight to the whole string turned every RFC3339 value
|
||||
// into "Invalid Date" (BUG-3225). And `toLocaleDateString` answers
|
||||
// "Invalid Date" rather than throwing, so the old `catch` never ran: an
|
||||
// unparseable string now shows as itself.
|
||||
const day = dateDay(dateStr);
|
||||
const d = day ? new Date(day + 'T00:00:00') : null;
|
||||
if (!d || isNaN(d.getTime())) return dateStr;
|
||||
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
|
||||
// ── Select dropdown state ──────────────────────────────────────────────
|
||||
@@ -1607,7 +1621,7 @@ handlers — onchange is never called.
|
||||
bind:this={dateInputEl}
|
||||
class="date-hidden-input"
|
||||
type="date"
|
||||
value={value ?? ''}
|
||||
value={value ? (dateDay(value) ?? '') : ''}
|
||||
onchange={handleDateInput}
|
||||
onkeydown={handleDateKeydown}
|
||||
onfocus={() => (dateInputFocused = true)}
|
||||
|
||||
Reference in New Issue
Block a user