mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-08-17 16:34:44 -05:00
Fix blank Query Tool screen caused by malformed runtime locale (#10066)
The Query History panel formats entry dates/times with Date.prototype.toLocaleDateString()/toLocaleTimeString(). On runtimes whose default locale (derived from the OS/environment) is malformed, these throw "RangeError: Incorrect locale information provided". Because getDateFormatted()/getTimeFormatted() are called during render (via getGroups/getGroupHeader/getDatePrefix), the uncaught exception unmounts the whole SQL editor React tree, leaving the user with a blank white screen and losing any unsaved query work. Guard both helpers and fall back to a moment-based format (moment uses its own locale data and does not depend on the broken Intl default) so the editor keeps working instead of crashing. Closes #7596
This commit is contained in:
@@ -42,6 +42,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 #9091 <https://github.com/pgadmin-org/pgadmin4/issues/9091>`_ - Fix the Query Tool re-prompting for an unsaved password in a loop and rejecting the re-entered password, by caching the entered password on the server manager when the primary connection is already established.
|
||||
| `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.
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
function getTimeFormatted(time) {
|
||||
return time.toLocaleTimeString();
|
||||
export function getTimeFormatted(time) {
|
||||
try {
|
||||
return time.toLocaleTimeString();
|
||||
} catch {
|
||||
return moment(time).format('LTS');
|
||||
}
|
||||
}
|
||||
|
||||
class QueryHistoryUtils {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user