Skip to content
Merged
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 @@ -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.
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');
}
}

function getTimeFormatted(time) {
return time.toLocaleTimeString();
export function getTimeFormatted(time) {
try {
return time.toLocaleTimeString();
} catch {
return moment(time).format('LTS');
}
}

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