mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-19 04:59:43 +08:00
- 新增标签展示元素配置,支持单行、双行布局和元素排序 - 在设置面板提供标签展示入口并持久化用户配置 - 标签右键菜单增加标签设置入口并优化悬浮信息展示 - 关闭外部 SQL 文件标签前检测未保存草稿并支持保存后关闭
357 lines
11 KiB
TypeScript
357 lines
11 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import type { SavedConnection, TabData } from '../types';
|
|
import {
|
|
applyTabDisplaySettingsPatch,
|
|
buildTabDisplayModel,
|
|
buildTabDisplayTitle,
|
|
resolveTabDisplayElementOrder,
|
|
resolveConnectionHostSummary,
|
|
sanitizeTabDisplaySettings,
|
|
switchTabDisplayLayout,
|
|
stripSchemaFromTabObjectLabel,
|
|
} from './tabDisplay';
|
|
|
|
const redisConnection: SavedConnection = {
|
|
id: 'redis-1',
|
|
name: '订单缓存',
|
|
config: {
|
|
type: 'redis',
|
|
host: '10.10.0.12',
|
|
port: 6379,
|
|
user: '',
|
|
database: '',
|
|
hosts: ['10.10.0.13:6379', '10.10.0.14:6379'],
|
|
},
|
|
};
|
|
|
|
describe('tabDisplay', () => {
|
|
it('builds compact host summary for multi-host redis connections', () => {
|
|
expect(resolveConnectionHostSummary(redisConnection.config)).toBe('10.10.0.12 +2');
|
|
});
|
|
|
|
it('adds connection and host identity to redis key tabs', () => {
|
|
const redisKeysTab: TabData = {
|
|
id: 'redis-keys-redis-1-db0',
|
|
title: 'db0',
|
|
type: 'redis-keys',
|
|
connectionId: 'redis-1',
|
|
redisDB: 0,
|
|
};
|
|
|
|
expect(buildTabDisplayTitle(redisKeysTab, redisConnection)).toBe('[订单缓存 | 10.10.0.12 +2] db0');
|
|
});
|
|
|
|
it('normalizes redis command and monitor tabs to db-scoped labels', () => {
|
|
const commandTab: TabData = {
|
|
id: 'cmd-1',
|
|
title: '命令 - db1',
|
|
type: 'redis-command',
|
|
connectionId: 'redis-1',
|
|
redisDB: 1,
|
|
};
|
|
const monitorTab: TabData = {
|
|
id: 'monitor-1',
|
|
title: '监控: 订单缓存',
|
|
type: 'redis-monitor',
|
|
connectionId: 'redis-1',
|
|
redisDB: 1,
|
|
};
|
|
|
|
expect(buildTabDisplayTitle(commandTab, redisConnection)).toBe('[订单缓存 | 10.10.0.12 +2] 命令 - db1');
|
|
expect(buildTabDisplayTitle(monitorTab, redisConnection)).toBe('[订单缓存 | 10.10.0.12 +2] 监控 - db1');
|
|
});
|
|
|
|
it('keeps table tabs on the existing prefix strategy', () => {
|
|
const tableTab: TabData = {
|
|
id: 'table-1',
|
|
title: 'orders',
|
|
type: 'table',
|
|
connectionId: 'redis-1',
|
|
dbName: 'app',
|
|
tableName: 'orders',
|
|
};
|
|
|
|
expect(buildTabDisplayTitle(tableTab, redisConnection)).toBe('[订单缓存] orders');
|
|
});
|
|
|
|
it('hides schema prefixes from schema-qualified table tab labels', () => {
|
|
const connection: SavedConnection = {
|
|
id: 'kingbase-1',
|
|
name: 'Kingbase DEV',
|
|
config: {
|
|
type: 'kingbase',
|
|
host: '127.0.0.1',
|
|
port: 54321,
|
|
user: 'SYSTEM',
|
|
database: 'appdb',
|
|
},
|
|
};
|
|
const tableTab: TabData = {
|
|
id: 'kingbase-1-appdb-table-ldf_server.andon_events',
|
|
title: 'ldf_server.andon_events',
|
|
type: 'table',
|
|
connectionId: 'kingbase-1',
|
|
dbName: 'appdb',
|
|
tableName: 'ldf_server.andon_events',
|
|
};
|
|
|
|
expect(buildTabDisplayTitle(tableTab, connection)).toBe('[DEV] andon_events');
|
|
});
|
|
|
|
it('hides schema prefixes from design and definition tab labels', () => {
|
|
const designTab: TabData = {
|
|
id: 'design-1',
|
|
title: '表结构 (public.orders)',
|
|
type: 'design',
|
|
connectionId: 'pg-1',
|
|
dbName: 'app',
|
|
tableName: 'public.orders',
|
|
readOnly: true,
|
|
};
|
|
const viewTab: TabData = {
|
|
id: 'view-1',
|
|
title: '视图: reporting.active_users',
|
|
type: 'view-def',
|
|
connectionId: 'pg-1',
|
|
dbName: 'app',
|
|
viewName: 'reporting.active_users',
|
|
};
|
|
const triggerTab: TabData = {
|
|
id: 'trigger-1',
|
|
title: '触发器: audit.users_bi',
|
|
type: 'trigger',
|
|
connectionId: 'pg-1',
|
|
dbName: 'app',
|
|
triggerName: 'audit.users_bi',
|
|
};
|
|
const routineTab: TabData = {
|
|
id: 'routine-1',
|
|
title: '存储过程: reporting.refresh_stats',
|
|
type: 'routine-def',
|
|
connectionId: 'pg-1',
|
|
dbName: 'app',
|
|
routineName: 'reporting.refresh_stats',
|
|
routineType: 'PROCEDURE',
|
|
};
|
|
|
|
expect(buildTabDisplayTitle(designTab)).toBe('表结构 (orders)');
|
|
expect(buildTabDisplayTitle(viewTab)).toBe('视图: active_users');
|
|
expect(buildTabDisplayTitle(triggerTab)).toBe('触发器: users_bi');
|
|
expect(buildTabDisplayTitle(routineTab)).toBe('存储过程: refresh_stats');
|
|
});
|
|
|
|
it('keeps quoted dots inside object names when hiding schema prefixes', () => {
|
|
expect(stripSchemaFromTabObjectLabel('"sales.schema"."order.items"')).toBe('order.items');
|
|
expect(stripSchemaFromTabObjectLabel('\\"ldf_server\\".\\"andon_events\\"')).toBe('andon_events');
|
|
expect(stripSchemaFromTabObjectLabel('[dbo].[order.items]')).toBe('order.items');
|
|
});
|
|
|
|
it('builds configurable single-line tab labels from ordered elements', () => {
|
|
const connection: SavedConnection = {
|
|
id: 'kingbase-1',
|
|
name: 'Kingbase DEV',
|
|
config: {
|
|
type: 'kingbase',
|
|
host: '192.168.10.8',
|
|
port: 54321,
|
|
user: 'SYSTEM',
|
|
database: 'appdb',
|
|
},
|
|
};
|
|
const tableTab: TabData = {
|
|
id: 'kingbase-1-appdb-table-ldf_server.andon_events',
|
|
title: 'ldf_server.andon_events',
|
|
type: 'table',
|
|
connectionId: 'kingbase-1',
|
|
dbName: 'appdb',
|
|
tableName: 'ldf_server.andon_events',
|
|
};
|
|
|
|
expect(buildTabDisplayTitle(tableTab, connection, {
|
|
layout: 'single',
|
|
primaryElements: ['object', 'schema', 'host'],
|
|
secondaryElements: [],
|
|
})).toBe('andon_events SCHEMA:ldf_server 192.168.10.8');
|
|
});
|
|
|
|
it('builds the default configurable model with connection, type and compact object name', () => {
|
|
const connection: SavedConnection = {
|
|
id: 'kingbase-1',
|
|
name: 'Kingbase DEV',
|
|
config: {
|
|
type: 'kingbase',
|
|
host: '192.168.10.8',
|
|
port: 54321,
|
|
user: 'SYSTEM',
|
|
database: 'appdb',
|
|
},
|
|
};
|
|
const tableTab: TabData = {
|
|
id: 'kingbase-1-appdb-table-ldf_server.andon_events',
|
|
title: 'ldf_server.andon_events',
|
|
type: 'table',
|
|
connectionId: 'kingbase-1',
|
|
dbName: 'appdb',
|
|
tableName: 'ldf_server.andon_events',
|
|
};
|
|
|
|
expect(buildTabDisplayModel(tableTab, connection).fullTitle).toBe('[DEV] TABLE andon_events');
|
|
});
|
|
|
|
it('keeps query tab labels compact when the title is raw SQL', () => {
|
|
const connection: SavedConnection = {
|
|
id: 'mysql-1',
|
|
name: '开发240',
|
|
config: {
|
|
type: 'mysql',
|
|
host: '192.168.1.240',
|
|
port: 3306,
|
|
user: 'root',
|
|
database: 'front_end_sys',
|
|
},
|
|
};
|
|
const queryTab: TabData = {
|
|
id: 'query-1',
|
|
title: 'select * from fs_org_auth_application where application_id is not null;',
|
|
type: 'query',
|
|
connectionId: 'mysql-1',
|
|
dbName: 'front_end_sys',
|
|
query: 'select * from fs_org_auth_application where application_id is not null;',
|
|
};
|
|
|
|
const model = buildTabDisplayModel(queryTab, connection);
|
|
|
|
expect(model.primaryText).toBe('[开发240] SQL 新建查询');
|
|
expect(model.fullTitle).not.toContain('fs_org_auth_application');
|
|
expect(model.fullTitle).not.toContain('select *');
|
|
});
|
|
|
|
it('uses SQL file names as compact query tab object labels', () => {
|
|
const queryTab: TabData = {
|
|
id: 'query-file-1',
|
|
title: 'select * from very_long_table_name;',
|
|
type: 'query',
|
|
connectionId: 'mysql-1',
|
|
filePath: '/Users/me/sql/monthly-report.sql',
|
|
query: 'select * from very_long_table_name;',
|
|
};
|
|
|
|
const model = buildTabDisplayModel(queryTab);
|
|
|
|
expect(model.primaryText).toBe('SQL monthly-report.sql');
|
|
});
|
|
|
|
it('builds configurable double-line tab display models', () => {
|
|
const connection: SavedConnection = {
|
|
id: 'pg-1',
|
|
name: 'Postgres PROD',
|
|
config: {
|
|
type: 'postgres',
|
|
host: '10.0.0.9',
|
|
port: 5432,
|
|
user: 'postgres',
|
|
database: 'analytics',
|
|
},
|
|
};
|
|
const tableTab: TabData = {
|
|
id: 'pg-1-analytics-table-reporting.events',
|
|
title: 'reporting.events',
|
|
type: 'table',
|
|
connectionId: 'pg-1',
|
|
dbName: 'analytics',
|
|
tableName: 'reporting.events',
|
|
};
|
|
|
|
const model = buildTabDisplayModel(tableTab, connection, {
|
|
layout: 'double',
|
|
primaryElements: ['kind', 'object'],
|
|
secondaryElements: ['connection', 'database', 'schema', 'host'],
|
|
});
|
|
|
|
expect(model.layout).toBe('double');
|
|
expect(model.primaryText).toBe('TABLE events');
|
|
expect(model.secondaryText).toBe('[PROD]·analytics·SCHEMA:reporting·10.0.0.9');
|
|
expect(model.fullTitle).toBe('TABLE events · [PROD]·analytics·SCHEMA:reporting·10.0.0.9');
|
|
});
|
|
|
|
it('sanitizes tab display settings with fallback defaults', () => {
|
|
expect(sanitizeTabDisplaySettings({
|
|
layout: 'invalid' as never,
|
|
primaryElements: ['schema', 'schema', 'bad' as never],
|
|
secondaryElements: ['object', 'schema', 'host'],
|
|
})).toEqual({
|
|
layout: 'single',
|
|
primaryElements: ['schema'],
|
|
secondaryElements: ['object', 'host'],
|
|
});
|
|
|
|
expect(sanitizeTabDisplaySettings({
|
|
layout: 'double',
|
|
primaryElements: ['bad' as never],
|
|
secondaryElements: [],
|
|
})).toEqual({
|
|
layout: 'double',
|
|
primaryElements: ['connection', 'kind', 'object'],
|
|
secondaryElements: [],
|
|
});
|
|
|
|
expect(sanitizeTabDisplaySettings({
|
|
layout: 'single',
|
|
primaryElements: ['object'],
|
|
secondaryElements: [],
|
|
single: {
|
|
primaryElements: ['object', 'object', 'host'],
|
|
secondaryElements: ['bad' as never],
|
|
},
|
|
double: {
|
|
primaryElements: ['kind', 'object'],
|
|
secondaryElements: ['connection', 'kind', 'schema'],
|
|
},
|
|
})).toEqual({
|
|
layout: 'single',
|
|
primaryElements: ['object'],
|
|
secondaryElements: [],
|
|
single: {
|
|
primaryElements: ['object', 'host'],
|
|
secondaryElements: [],
|
|
},
|
|
double: {
|
|
primaryElements: ['kind', 'object'],
|
|
secondaryElements: ['connection', 'schema'],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('resolves visible tab display elements before hidden elements', () => {
|
|
expect(resolveTabDisplayElementOrder({
|
|
layout: 'double',
|
|
primaryElements: ['object', 'kind'],
|
|
secondaryElements: ['host'],
|
|
})).toEqual(['object', 'kind', 'host', 'connection', 'database', 'schema']);
|
|
});
|
|
|
|
it('keeps separate single-line and double-line settings when switching layouts', () => {
|
|
const doubleConfigured = sanitizeTabDisplaySettings({
|
|
layout: 'double',
|
|
primaryElements: ['kind', 'object'],
|
|
secondaryElements: ['connection', 'database'],
|
|
});
|
|
|
|
const singleLayout = switchTabDisplayLayout(doubleConfigured, 'single');
|
|
const singleConfigured = applyTabDisplaySettingsPatch(singleLayout, {
|
|
primaryElements: ['object', 'host'],
|
|
secondaryElements: [],
|
|
});
|
|
const restoredDouble = switchTabDisplayLayout(singleConfigured, 'double');
|
|
const restoredSingle = switchTabDisplayLayout(restoredDouble, 'single');
|
|
|
|
expect(restoredDouble.layout).toBe('double');
|
|
expect(restoredDouble.primaryElements).toEqual(['kind', 'object']);
|
|
expect(restoredDouble.secondaryElements).toEqual(['connection', 'database']);
|
|
expect(restoredSingle.layout).toBe('single');
|
|
expect(restoredSingle.primaryElements).toEqual(['object', 'host']);
|
|
expect(restoredSingle.secondaryElements).toEqual([]);
|
|
});
|
|
});
|