import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { test } from "vitest"; import { compileScript, compileTemplate, parse } from "vue/compiler-sfc"; const contentAreaPath = "apps/desktop/src/components/layout/ContentArea.vue"; const appPath = "apps/desktop/src/App.vue"; const dataGridPath = "apps/desktop/src/components/grid/DataGrid.vue"; const viewSwitcherPath = "apps/desktop/src/components/layout/QueryResultViewSwitcher.vue"; const toolbarActionsPath = "apps/desktop/src/components/layout/QueryResultToolbarActions.vue"; function source(path: string): string { return readFileSync(path, "utf8"); } function assertSfcCompiles(path: string): void { const { descriptor, errors } = parse(source(path), { filename: path }); assert.deepEqual(errors, [], `${path} should parse without SFC errors`); assert.ok(descriptor.scriptSetup, `${path} should have a script setup block`); compileScript(descriptor, { id: path }); if (descriptor.template) { const result = compileTemplate({ id: path, filename: path, source: descriptor.template.content }); assert.deepEqual(result.errors, [], `${path} template should compile`); } } test("query result toolbar SFCs compile", () => { for (const path of [contentAreaPath, dataGridPath, viewSwitcherPath, toolbarActionsPath]) assertSfcCompiles(path); }); test("ContentArea keeps DISTINCT inserts separate from result-row deletes", () => { const contentArea = source(contentAreaPath); assert.match(contentArea, /:allow-insert-rows="activeTab\.queryAnalysis\?\.allowInsert \?\? activeTab\.queryAnalysis\?\.allowInsertDelete !== false"/); assert.match(contentArea, /:allow-delete-rows="activeTab\.queryAnalysis\?\.allowInsertDelete !== false"/); }); test("query result toolbar reuses the production icon contract", () => { const contentArea = source(contentAreaPath); const viewSwitcher = source(viewSwitcherPath); const toolbarActions = source(toolbarActionsPath); assert.match(contentArea, / { const contentArea = source(contentAreaPath); const runScrollerStart = contentArea.indexOf('ref="resultTabsScrollerRef"'); const listSelectorStart = contentArea.indexOf('
resultRuns\.value\.length > 0 && resultRunDisplayMode\.value === "tabs"\)/); assert.match(contentArea, /showResultRunSelector = computed\(\(\) => resultRuns\.value\.length > 0 && resultRunDisplayMode\.value === "list"\)/); assert.match(contentArea, /ref="resultTabsScrollerRef"[\s\S]*v-for="\(run, runIndex\) in resultRuns"/); assert.match(contentArea, /role="tablist" :aria-label="t\('tabs\.resultRuns'\)"/); assert.match(contentArea, /data-result-run-tab/); assert.match(contentArea, /@keydown="onResultRunTabKeydown\(\$event, runIndex\)"/); assert.match(contentArea, /@click\.stop\.prevent="removeResultRun\(run\.id\)"/); assert.match(contentArea, /const canCloseQueryResult = computed\([\s\S]*!props\.activeTab\.activeResultRunId/); assert.match(contentArea, /v-if="canCloseQueryResult"[\s\S]*@click="closeCurrentQueryResult"/); assert.match(contentArea, /queryStore\.closeQueryResult\(props\.activeTab\.id\)/); assert.match(contentArea, /t\('tabs\.closeResult'\)/); assert.match(contentArea, /[\s\S]*v-for="run in resultRuns"/); assert.match(contentArea, /setResultRunDisplayMode\('list'\)/); assert.match(contentArea, /setResultRunDisplayMode\('tabs'\)/); assert.ok(runScrollerStart >= 0); assert.ok(listSelectorStart > runScrollerStart); assert.ok(fixedResultSetStart > listSelectorStart); assert.doesNotMatch(contentArea.slice(runScrollerStart, listSelectorStart), /visibleResultItems/); assert.match(contentArea, /resultAutoSave \? 'bg-primary\/10 text-primary[\s\S]*: 'text-muted-foreground hover:bg-accent hover:text-foreground'/); assert.doesNotMatch(contentArea, /queryResultAutoRefresh|QUERY_RESULT_AUTO_REFRESH|nextResultToolbarLayout/); assert.equal((contentArea.match(/ { const contentArea = source(contentAreaPath); const resultRunWatcherStart = contentArea.indexOf("function resultRunIdsWereAppended"); const resultRunWatcherEnd = contentArea.indexOf("const summaryItems", resultRunWatcherStart); const resultRunWatcher = contentArea.slice(resultRunWatcherStart, resultRunWatcherEnd); assert.ok(resultRunWatcherStart >= 0); assert.match(resultRunWatcher, /current\.length > previous\.length/); assert.match(resultRunWatcher, /activeRunId: props\.activeTab\.activeResultRunId/); assert.match(resultRunWatcher, /activeRunChanged && !resultRunIdsWereAppended\(previous\.runIds, current\.runIds\)/); assert.match(resultRunWatcher, /updateResultTabsAfterRender/); assert.match(resultRunWatcher, /revealActiveResultRunAfterRender/); assert.match(contentArea, /function focusResultRunByIndex[\s\S]*scrollIntoView/); assert.match(contentArea, /async function removeResultRun[\s\S]*scrollIntoView/); }); test("the close-tab shortcut clears query results before closing the tab", () => { const app = source(appPath); const closeShortcutStart = app.indexOf("if (isCloseTabShortcut(e, shortcuts))"); const closeShortcutEnd = app.indexOf("if (isSaveShortcut", closeShortcutStart); const closeShortcut = app.slice(closeShortcutStart, closeShortcutEnd); assert.ok(closeShortcutStart >= 0); assert.ok(closeShortcut.indexOf("await queryStore.clearQueryResults(queryStore.activeTabId)") < closeShortcut.indexOf("queryStore.closeTab(queryStore.activeTabId)")); }); test("ContentArea keeps MySQL standard explain results available in the shared toolbar", () => { const contentArea = source(contentAreaPath); assert.match(contentArea, /canShowExplainOutput = computed\([\s\S]*explainTableResult[\s\S]*explainTableError/); assert.match(contentArea, /:table-result="activeTab\.explainTableResult"/); assert.match(contentArea, /:table-error="activeTab\.explainTableError"/); }); test("DataGrid exposes persistent result toolbar slots", () => { const dataGrid = source(dataGridPath); assert.match(dataGrid, /slots\["result-toolbar-leading"\]/); assert.match(dataGrid, /slots\["result-toolbar-actions"\]/); assert.match(dataGrid, / { const dataGrid = source(dataGridPath); assert.match(dataGrid, /preferredDataGridPageSize\(settingsStore\.editorSettings, pageSizePreference\.value, props\.pageLimit\)/); assert.match(dataGrid, /if \(pageSizePreference\.value !== "results"\) return;[\s\S]*pageSize\.value = normalizeResultPageSize\(value, pageSize\.value\)/); assert.match(dataGrid, /dataGridPageSizeSettingsPatch\(pageSizePreference\.value, normalizedSize\)/); assert.match(dataGrid, /const resetToFirstPage = hasPendingConditionInputs\(\);/); assert.match(dataGrid, /emit\("reload", props\.sql, searchText\.value, currentWhereInput\(\), currentOrderBy\(\), pageSize\.value, resetToFirstPage \? 0 : \(currentPage\.value - 1\) \* pageSize\.value, "refresh"\)/); }); test("standalone result views use the same compact toolbar breakpoint", () => { const contentArea = source(contentAreaPath); const dataGrid = source(dataGridPath); assert.match(contentArea, /ref="standaloneResultToolbarRef"/); assert.match(contentArea, /isDataGridToolbarCompact\(standaloneResultToolbarWidth\.value, standaloneResultToolbarViewportWidth\.value\)/); assert.equal((contentArea.match(/:compact="standaloneResultToolbarCompact"/g) ?? []).length, 2); assert.match(dataGrid, /isDataGridToolbarCompact\(dataGridTopbarWidth\.value, dataGridViewportWidth\.value, DATA_GRID_CONDITION_TOOLBAR_MIN_WIDTH\)/); }); test("embedded and standalone result toolbars share the same fixed height", () => { const contentArea = source(contentAreaPath); const dataGrid = source(dataGridPath); const standaloneClasses = contentArea.match(/ref="standaloneResultToolbarRef" class="([^"]+)"/)?.[1].split(/\s+/) ?? []; const embeddedClasses = dataGrid.match(/ref="dataGridTopbarRef"[^>]+class="([^"]+)"/)?.[1].split(/\s+/) ?? []; assert.ok(standaloneClasses.includes("h-8")); assert.ok(embeddedClasses.includes("h-8")); assert.ok(standaloneClasses.includes("items-center")); assert.ok(embeddedClasses.includes("items-center")); assert.ok(!standaloneClasses.includes("h-7")); assert.ok(!embeddedClasses.includes("h-7")); assert.ok(!standaloneClasses.includes("min-h-7")); assert.ok(!embeddedClasses.includes("min-h-7")); }); test("embedded result toolbar cannot scroll vertically", () => { const dataGrid = source(dataGridPath); const scrollClasses = dataGrid.match(/class="data-grid-topbar-scroll ([^"]+)"/)?.[1].split(/\s+/) ?? []; assert.ok(scrollClasses.includes("overflow-clip")); assert.ok(!scrollClasses.some((className) => className.startsWith("overflow-x-"))); assert.ok(!scrollClasses.some((className) => className.startsWith("overflow-y-"))); }); test("DataGrid marks toolbar refresh separately from current-result reloads", () => { const dataGrid = source(dataGridPath); assert.match(dataGrid, /emit\("reload", props\.sql,[^;]+"refresh"\);/); assert.match(dataGrid, /function onToolbarRollback\(\)[\s\S]*?emit\("reload", props\.sql,[^;]+\);/); }); test("Elasticsearch JSON refresh preserves multi-result query groups", () => { const contentArea = source(contentAreaPath); assert.match(contentArea, /if \(activeElasticsearchJsonResponse\.value\) \{[\s\S]*?emit\("reload", activeResultSql\.value, undefined, undefined, undefined, undefined, undefined, "refresh"\);/); });