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
11 changes: 6 additions & 5 deletions src/ui/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { generatePKCE, randomState } from '../core/pkce.js';
import * as oauthCfg from '../net/oauth-config.js';
import * as oauth from '../net/oauth.js';
import * as ch from '../net/ch-client.js';
import { mountEditor, insertAtCursor, insertTopLine, replaceEditor } from './editor.js';
import { mountEditor, insertAtCursor, replaceEditor } from './editor.js';
import { renderTabs, selectTab, newTab, closeTab, loadIntoNewTab } from './tabs.js';
import { renderSchema } from './schema.js';
import { renderResults } from './results.js';
Expand Down Expand Up @@ -317,8 +317,9 @@ export function createApp(env = {}) {
}

// Fetch the DDL for `target` (e.g. 'db.table' or 'DATABASE db') with
// SHOW CREATE, pretty-print it through formatQuery(), and drop it in as a top
// line. Two round-trips by design; if formatting fails the raw DDL is used.
// SHOW CREATE, pretty-print it through formatQuery(), and drop it into the
// editor (replacing its content — undo restores the prior query). Two
// round-trips by design; if formatting fails the raw DDL is used.
async function insertCreate(target) {
await ensureConfig();
if (!(await getToken())) { chCtx.onSignedOut(); return; }
Expand All @@ -331,7 +332,7 @@ export function createApp(env = {}) {
const fmt = await ch.queryJson(chCtx, 'SELECT formatQuery(' + sqlString(stmt) + ') AS q FORMAT JSON');
out = (fmt.data && fmt.data[0] && fmt.data[0].q) || stmt;
} catch { /* formatting is best-effort — fall back to the raw DDL */ }
insertTopLine(app, out);
replaceEditor(app, out);
} catch (e) {
flashToast('SHOW CREATE failed: ' + String((e && e.message) || e), { document: doc });
}
Expand Down Expand Up @@ -539,7 +540,7 @@ export function createApp(env = {}) {
insertCreate,
openShortcuts: () => openShortcuts(app),
insertAtCursor: (text) => insertAtCursor(app, text),
insertTopLine: (text) => insertTopLine(app, text),
replaceEditor: (text) => replaceEditor(app, text),
loadColumns,
rerenderTabs: () => renderTabs(app),
rerenderResults: () => renderResults(app),
Expand Down
9 changes: 0 additions & 9 deletions src/ui/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,6 @@ export function insertAtCursor(app, text) {
applyEdit(ta, text);
}

/** Prepend `text` as a new first line (does not replace existing content). */
export function insertTopLine(app, text) {
const ta = app.dom.editorTextarea;
if (!ta) return;
ta.focus();
ta.selectionStart = ta.selectionEnd = 0;
applyEdit(ta, text + (ta.value ? '\n' : ''));
}

/** Replace the whole editor content with `text` (undoable). */
export function replaceEditor(app, text) {
const ta = app.dom.editorTextarea;
Expand Down
2 changes: 1 addition & 1 deletion src/ui/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export function renderSchema(app) {
if (state.expandedTables.has(key) && tb.columns == null) app.actions.loadColumns(db.db, tb.name, tb);
else renderSchema(app);
},
ondblclick: (e) => { e.stopPropagation(); app.actions.insertTopLine('SELECT * FROM ' + key + ' LIMIT 100'); },
ondblclick: (e) => { e.stopPropagation(); app.actions.replaceEditor('SELECT * FROM ' + key + ' LIMIT 100'); },
},
...treeRow(Icon.table(), tb.name, formatRows(tb.total_rows), { expanded: isOpen, iconColor: 'var(--accent)' }),
));
Expand Down
2 changes: 1 addition & 1 deletion tests/helpers/fake-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function makeApp(over = {}) {
insertCreate: vi.fn(),
openShortcuts: vi.fn(),
insertAtCursor: vi.fn(),
insertTopLine: vi.fn(),
replaceEditor: vi.fn(),
loadColumns: vi.fn(),
rerenderTabs: vi.fn(),
rerenderResults: vi.fn(),
Expand Down
1 change: 1 addition & 0 deletions tests/unit/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ describe('exhaustive controller coverage', () => {
app.state.tabs.push({ id: 'tx', name: 'X', sql: '', dirty: false, result: null, savedId: null });
app.actions.selectTab('tx');
app.actions.insertAtCursor('zz');
app.actions.replaceEditor('SELECT 9');
app.actions.loadIntoNewTab('n', 'SELECT 2');
app.actions.rerenderTabs();
app.actions.rerenderResults();
Expand Down
21 changes: 5 additions & 16 deletions tests/unit/editor.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect, vi } from 'vitest';
import { renderHighlightInto, mountEditor, insertAtCursor, insertTopLine, replaceEditor, IDENT_MIME } from '../../src/ui/editor.js';
import { renderHighlightInto, mountEditor, insertAtCursor, replaceEditor, IDENT_MIME } from '../../src/ui/editor.js';
import { makeApp } from '../helpers/fake-app.js';

describe('renderHighlightInto', () => {
Expand Down Expand Up @@ -128,32 +128,21 @@ describe('insertAtCursor', () => {
});
});

describe('insertTopLine / replaceEditor', () => {
describe('replaceEditor', () => {
function mounted(sql = '') {
const app = makeApp();
app.activeTab().sql = sql;
mountEditor(app, document.createElement('div'));
return { app, ta: app.dom.editorTextarea };
}
it('insertTopLine prepends a new first line above existing content', () => {
const { app, ta } = mounted('SELECT 1');
insertTopLine(app, 'SHOW CREATE db.t');
expect(ta.value).toBe('SHOW CREATE db.t\nSELECT 1');
expect(app.activeTab().sql).toBe('SHOW CREATE db.t\nSELECT 1');
});
it('insertTopLine on an empty editor adds no trailing newline', () => {
const { ta, app } = mounted('');
insertTopLine(app, 'SELECT 1');
expect(ta.value).toBe('SELECT 1');
});
it('replaceEditor swaps the whole content', () => {
it('swaps the whole content', () => {
const { ta, app } = mounted('select 1');
replaceEditor(app, 'SELECT\n 1');
expect(ta.value).toBe('SELECT\n 1');
expect(app.activeTab().sql).toBe('SELECT\n 1');
});
it('insertTopLine / replaceEditor no-op without a textarea', () => {
it('no-ops without a textarea', () => {
const app = makeApp();
expect(() => insertTopLine(app, 'x')).not.toThrow();
expect(() => replaceEditor(app, 'x')).not.toThrow();
});
});
4 changes: 2 additions & 2 deletions tests/unit/schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ describe('renderSchema tree', () => {
click(ordersRow); // collapse
expect(app.state.expandedTables.has('db1.orders')).toBe(false);
});
it('double-clicking a table inserts a SELECT * as a top line', () => {
it('double-clicking a table replaces the editor with a SELECT *', () => {
const app = withSchema();
renderSchema(app);
const ordersRow = rows(app).find((r) => r.querySelector('.label').textContent === 'orders');
dblclick(ordersRow);
expect(app.actions.insertTopLine).toHaveBeenCalledWith('SELECT * FROM db1.orders LIMIT 100');
expect(app.actions.replaceEditor).toHaveBeenCalledWith('SELECT * FROM db1.orders LIMIT 100');
});
it('shift-clicking a table inserts its formatted DDL without expanding', () => {
const app = withSchema();
Expand Down
Loading