Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/en_US/release_notes_9_16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Bug fixes
*********

| `Issue #6308 <https://github.com/pgadmin-org/pgadmin4/issues/6308>`_ - Fix the infinite loading spinner after an idle database connection is silently dropped, by detecting stale connections and offering a reconnect dialog.
| `Issue #7596 <https://github.com/pgadmin-org/pgadmin4/issues/7596>`_ - Fix the Query Tool turning into a blank white screen when the runtime has a malformed default locale, by guarding the Query History date/time formatting against the resulting RangeError.
| `Issue #9595 <https://github.com/pgadmin-org/pgadmin4/issues/9595>`_ - Fix missing ALTER ... SET DEFAULT statements for inherited columns in the generated table SQL/EDIT script.
| `Issue #9677 <https://github.com/pgadmin-org/pgadmin4/issues/9677>`_ - Fix the Unlogged table toggle in table properties not generating any ALTER TABLE ... SET LOGGED/UNLOGGED statement.
| `Issue #9828 <https://github.com/pgadmin-org/pgadmin4/issues/9828>`_ - Fix tool calls failing against OpenAI-compatible providers that emit empty/null name, arguments, or id fields in streaming continuation deltas.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,27 @@ export const QuerySources = {
},
};

function getDateFormatted(date) {
return date.toLocaleDateString();
// On some runtimes the default locale (derived from the OS/environment) is
// malformed, which makes Date.prototype.toLocaleDateString/toLocaleTimeString
// throw "RangeError: Incorrect locale information provided". As these are
// called while rendering the Query History panel, an uncaught throw unmounts
// the whole SQL editor and the user sees a blank white screen, losing any
// unsaved work. Fall back to a moment-based format (moment uses its own
// locale data and does not depend on the broken Intl default). See #7596.
export function getDateFormatted(date) {
try {
return date.toLocaleDateString();
} catch {
return moment(date).format('L');
}
}
Comment on lines +134 to 140

function getTimeFormatted(time) {
return time.toLocaleTimeString();
export function getTimeFormatted(time) {
try {
return time.toLocaleTimeString();
} catch {
return moment(time).format('LTS');
}
}
Comment on lines +142 to 148

class QueryHistoryUtils {
Expand Down
85 changes: 85 additions & 0 deletions web/regression/javascript/sqleditor/QueryHistory.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////

// Mock url_for
jest.mock('sources/url_for', () => ({
__esModule: true,
default: jest.fn((endpoint) => `/mock/${endpoint}`),
}));

// Mock the QueryToolComponent to avoid importing all its dependencies
jest.mock('../../../pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx', () => {
const React = require('react');
return {
QueryToolContext: React.createContext(null),
QueryToolConnectionContext: React.createContext(null),
QueryToolEventsContext: React.createContext(null),
};
});

// Mock CodeMirror
jest.mock('../../../pgadmin/static/js/components/ReactCodeMirror', () => ({
__esModule: true,
default: ({ value }) => value,
}));

import { getDateFormatted, getTimeFormatted } from '../../../pgadmin/tools/sqleditor/static/js/components/sections/QueryHistory.jsx';

describe('QueryHistory date/time formatting', () => {
it('formats a date using the native locale formatter', () => {
const date = new Date(2025, 0, 15);
expect(getDateFormatted(date)).toBe(date.toLocaleDateString());
});

it('formats a time using the native locale formatter', () => {
const time = new Date(2025, 0, 15, 10, 30, 45);
expect(getTimeFormatted(time)).toBe(time.toLocaleTimeString());
});

// Regression test for #7596: a malformed default locale makes
// toLocaleDateString/toLocaleTimeString throw "RangeError: Incorrect
// locale information provided". The helpers must not propagate the throw
// (which would white-screen the SQL editor) and must return a usable
// string instead.
describe('when the runtime locale is broken', () => {
let dateSpy, timeSpy;

beforeEach(() => {
dateSpy = jest.spyOn(Date.prototype, 'toLocaleDateString')
.mockImplementation(() => {
throw new RangeError('Incorrect locale information provided');
});
timeSpy = jest.spyOn(Date.prototype, 'toLocaleTimeString')
.mockImplementation(() => {
throw new RangeError('Incorrect locale information provided');
});
});

afterEach(() => {
dateSpy.mockRestore();
timeSpy.mockRestore();
});

it('does not throw and returns a non-empty date string', () => {
const date = new Date(2025, 0, 15);
let result;
expect(() => { result = getDateFormatted(date); }).not.toThrow();
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
});

it('does not throw and returns a non-empty time string', () => {
const time = new Date(2025, 0, 15, 10, 30, 45);
let result;
expect(() => { result = getTimeFormatted(time); }).not.toThrow();
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
});
});
});
Loading