1 parent
9c90edabe3
commit
4eabeb6a15
2 files changed
+143
-22
No files matched your search
@@ -4613,22 +4613,22 @@ onUnmounted(() => {
|
||||
<div class="settings-appearance-group">
|
||||
<Label>{{ t("settings.tabLayout") }}</Label>
|
||||
<div class="settings-appearance-choice-grid">
|
||||
<Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editTabLayout === 'scroll' ? 'dbx-choice-selected' : ''" @click="setTabLayout('scroll')">
|
||||
<Button type="button" variant="outline" class="settings-choice-card h-auto min-w-0 justify-start overflow-hidden whitespace-normal border p-3" :class="editTabLayout === 'scroll' ? 'dbx-choice-selected' : ''" @click="setTabLayout('scroll')">
|
||||
<div class="w-full min-w-0 text-left">
|
||||
<div class="text-sm font-medium">
|
||||
{{ t("settings.tabLayoutScroll") }}
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
<div class="break-words whitespace-normal text-xs text-muted-foreground">
|
||||
{{ t("settings.tabLayoutScrollDescription") }}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
<Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editTabLayout === 'wrap' ? 'dbx-choice-selected' : ''" @click="setTabLayout('wrap')">
|
||||
<Button type="button" variant="outline" class="settings-choice-card h-auto min-w-0 justify-start overflow-hidden whitespace-normal border p-3" :class="editTabLayout === 'wrap' ? 'dbx-choice-selected' : ''" @click="setTabLayout('wrap')">
|
||||
<div class="w-full min-w-0 text-left">
|
||||
<div class="text-sm font-medium">
|
||||
{{ t("settings.tabLayoutWrap") }}
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
<div class="break-words whitespace-normal text-xs text-muted-foreground">
|
||||
{{ t("settings.tabLayoutWrapDescription") }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -4801,10 +4801,10 @@ onUnmounted(() => {
|
||||
</HelpTooltip>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
||||
<Button type="button" variant="outline" class="h-auto items-start justify-start border p-3" :class="editDataTabReuseMode === 'always-new' ? 'dbx-choice-selected' : ''" @click="editDataTabReuseMode = 'always-new'">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-sm font-medium">{{ t("settings.dataTabReuseAlwaysNew") }}</div>
|
||||
<Button type="button" variant="outline" class="settings-choice-card h-auto min-w-0 items-start justify-start overflow-hidden whitespace-normal border p-3" :class="editDataTabReuseMode === 'always-new' ? 'dbx-choice-selected' : ''" @click="editDataTabReuseMode = 'always-new'">
|
||||
<div class="w-full min-w-0 text-left">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<div class="min-w-0 break-words text-sm font-medium">{{ t("settings.dataTabReuseAlwaysNew") }}</div>
|
||||
<Tooltip :open="dataTabReuseModeHelp === 'always-new'">
|
||||
<TooltipTrigger as-child>
|
||||
<span class="inline-flex shrink-0 cursor-help text-muted-foreground hover:text-foreground" @click.stop @pointerdown.stop @mouseenter="dataTabReuseModeHelp = 'always-new'" @mouseleave="dataTabReuseModeHelp = null">
|
||||
@@ -4818,10 +4818,10 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
<Button type="button" variant="outline" class="h-auto items-start justify-start border p-3" :class="editDataTabReuseMode === 'same-table' ? 'dbx-choice-selected' : ''" @click="editDataTabReuseMode = 'same-table'">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-sm font-medium">{{ t("settings.dataTabReuseSameTable") }}</div>
|
||||
<Button type="button" variant="outline" class="settings-choice-card h-auto min-w-0 items-start justify-start overflow-hidden whitespace-normal border p-3" :class="editDataTabReuseMode === 'same-table' ? 'dbx-choice-selected' : ''" @click="editDataTabReuseMode = 'same-table'">
|
||||
<div class="w-full min-w-0 text-left">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<div class="min-w-0 break-words text-sm font-medium">{{ t("settings.dataTabReuseSameTable") }}</div>
|
||||
<Tooltip :open="dataTabReuseModeHelp === 'same-table'">
|
||||
<TooltipTrigger as-child>
|
||||
<span class="inline-flex shrink-0 cursor-help text-muted-foreground hover:text-foreground" @click.stop @pointerdown.stop @mouseenter="dataTabReuseModeHelp = 'same-table'" @mouseleave="dataTabReuseModeHelp = null">
|
||||
@@ -4835,10 +4835,10 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
<Button type="button" variant="outline" class="h-auto items-start justify-start border p-3" :class="editDataTabReuseMode === 'active-tab' ? 'dbx-choice-selected' : ''" @click="editDataTabReuseMode = 'active-tab'">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-sm font-medium">{{ t("settings.dataTabReuseActiveTab") }}</div>
|
||||
<Button type="button" variant="outline" class="settings-choice-card h-auto min-w-0 items-start justify-start overflow-hidden whitespace-normal border p-3" :class="editDataTabReuseMode === 'active-tab' ? 'dbx-choice-selected' : ''" @click="editDataTabReuseMode = 'active-tab'">
|
||||
<div class="w-full min-w-0 text-left">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<div class="min-w-0 break-words text-sm font-medium">{{ t("settings.dataTabReuseActiveTab") }}</div>
|
||||
<Tooltip :open="dataTabReuseModeHelp === 'active-tab'">
|
||||
<TooltipTrigger as-child>
|
||||
<span class="inline-flex shrink-0 cursor-help text-muted-foreground hover:text-foreground" @click.stop @pointerdown.stop @mouseenter="dataTabReuseModeHelp = 'active-tab'" @mouseleave="dataTabReuseModeHelp = null">
|
||||
@@ -4914,18 +4914,18 @@ onUnmounted(() => {
|
||||
</HelpTooltip>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editRoutineSourceOpenMode === 'query-tab' ? 'dbx-choice-selected' : ''" @click="setRoutineSourceOpenMode('query-tab')">
|
||||
<div class="text-left">
|
||||
<Button type="button" variant="outline" class="settings-choice-card h-auto min-w-0 justify-start overflow-hidden whitespace-normal border p-3" :class="editRoutineSourceOpenMode === 'query-tab' ? 'dbx-choice-selected' : ''" @click="setRoutineSourceOpenMode('query-tab')">
|
||||
<div class="w-full min-w-0 text-left">
|
||||
<div class="text-sm font-medium">{{ t("settings.routineSourceOpenModeQueryTab") }}</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
<div class="break-words whitespace-normal text-xs text-muted-foreground">
|
||||
{{ t("settings.routineSourceOpenModeQueryTabDescription") }}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
<Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editRoutineSourceOpenMode === 'dialog' ? 'dbx-choice-selected' : ''" @click="setRoutineSourceOpenMode('dialog')">
|
||||
<div class="text-left">
|
||||
<Button type="button" variant="outline" class="settings-choice-card h-auto min-w-0 justify-start overflow-hidden whitespace-normal border p-3" :class="editRoutineSourceOpenMode === 'dialog' ? 'dbx-choice-selected' : ''" @click="setRoutineSourceOpenMode('dialog')">
|
||||
<div class="w-full min-w-0 text-left">
|
||||
<div class="text-sm font-medium">{{ t("settings.routineSourceOpenModeDialog") }}</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
<div class="break-words whitespace-normal text-xs text-muted-foreground">
|
||||
{{ t("settings.routineSourceOpenModeDialogDescription") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { cn } from "@/lib/common/utils";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../EditorSettingsDialog.vue", import.meta.url), "utf8");
|
||||
const buttonSource = readFileSync(new URL("../../ui/button/index.ts", import.meta.url), "utf8");
|
||||
const templateSource = dialogSource.slice(dialogSource.indexOf("<template>"));
|
||||
const buttonBaseClass = /cva\(\s*"([^"]+)"/.exec(buttonSource)?.[1];
|
||||
if (!buttonBaseClass) throw new Error("Missing shared Button base class contract");
|
||||
|
||||
const affectedChoiceKeys = ["tabLayoutScroll", "tabLayoutWrap", "dataTabReuseAlwaysNew", "dataTabReuseSameTable", "dataTabReuseActiveTab", "routineSourceOpenModeQueryTab", "routineSourceOpenModeDialog"] as const;
|
||||
|
||||
function sourceIndexForKey(key: string): number {
|
||||
const index = templateSource.indexOf(`t("settings.${key}")`);
|
||||
if (index < 0) throw new Error(`Missing settings key: ${key}`);
|
||||
return index;
|
||||
}
|
||||
|
||||
function buttonBlockForKey(key: string): string {
|
||||
const keyIndex = sourceIndexForKey(key);
|
||||
const start = templateSource.lastIndexOf("<Button", keyIndex);
|
||||
const end = templateSource.indexOf("</Button>", keyIndex);
|
||||
if (start < 0 || end < 0) throw new Error(`Missing Button for settings key: ${key}`);
|
||||
return templateSource.slice(start, end + "</Button>".length);
|
||||
}
|
||||
|
||||
function openingTag(source: string, element: string): string {
|
||||
const start = source.indexOf(`<${element}`);
|
||||
const end = tagEnd(source, start);
|
||||
if (start < 0 || end < 0) throw new Error(`Missing <${element}> opening tag`);
|
||||
return source.slice(start, end + 1);
|
||||
}
|
||||
|
||||
function elementTagForKey(key: string, element = "div"): string {
|
||||
const keyIndex = sourceIndexForKey(key);
|
||||
const start = templateSource.lastIndexOf(`<${element}`, keyIndex);
|
||||
const end = tagEnd(templateSource, start);
|
||||
if (start < 0 || end < 0) throw new Error(`Missing <${element}> for settings key: ${key}`);
|
||||
return templateSource.slice(start, end + 1);
|
||||
}
|
||||
|
||||
function elementTagForKeyInButton(buttonKey: string, key: string, element = "div"): string {
|
||||
const block = buttonBlockForKey(buttonKey);
|
||||
const keyIndex = block.indexOf(`t("settings.${key}")`);
|
||||
const start = block.lastIndexOf(`<${element}`, keyIndex);
|
||||
const end = tagEnd(block, start);
|
||||
if (keyIndex < 0 || start < 0 || end < 0) throw new Error(`Missing <${element}> for settings key ${key} in ${buttonKey}`);
|
||||
return block.slice(start, end + 1);
|
||||
}
|
||||
|
||||
function tagEnd(source: string, start: number): number {
|
||||
let quote: '"' | "'" | null = null;
|
||||
for (let index = start; index < source.length; index += 1) {
|
||||
const char = source[index];
|
||||
if (quote) {
|
||||
if (char === quote && source[index - 1] !== "\\") quote = null;
|
||||
} else if (char === '"' || char === "'") {
|
||||
quote = char;
|
||||
} else if (char === ">") {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function classNameFromTag(tag: string): string {
|
||||
const match = /\sclass="([^"]+)"/.exec(tag);
|
||||
if (!match) throw new Error(`Missing static class attribute in: ${tag}`);
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function expectClassTokens(className: string, tokens: readonly string[]) {
|
||||
const classes = new Set(className.split(/\s+/));
|
||||
for (const token of tokens) expect(classes.has(token), `${className} should include ${token}`).toBe(true);
|
||||
}
|
||||
|
||||
describe("EditorSettingsDialog choice card containment", () => {
|
||||
it("overrides the shared Button nowrap contract on two- and three-column cards", () => {
|
||||
for (const key of affectedChoiceKeys) {
|
||||
const cardClass = classNameFromTag(openingTag(buttonBlockForKey(key), "Button"));
|
||||
expectClassTokens(cardClass, ["settings-choice-card", "min-w-0", "whitespace-normal", "overflow-hidden"]);
|
||||
|
||||
const mergedClass = cn(buttonBaseClass, cardClass);
|
||||
expectClassTokens(mergedClass, ["min-w-0", "whitespace-normal", "overflow-hidden"]);
|
||||
expect(mergedClass.split(/\s+/)).not.toContain("whitespace-nowrap");
|
||||
}
|
||||
});
|
||||
|
||||
it("lets long two-column descriptions wrap inside a shrinkable text container", () => {
|
||||
for (const key of ["tabLayoutScroll", "tabLayoutWrap", "routineSourceOpenModeQueryTab", "routineSourceOpenModeDialog"] as const) {
|
||||
const block = buttonBlockForKey(key);
|
||||
expect(block).toMatch(/<div class="[^"]*\bmin-w-0\b[^"]*\btext-left\b[^"]*">/);
|
||||
}
|
||||
|
||||
for (const [buttonKey, descriptionKey] of [
|
||||
["tabLayoutScroll", "tabLayoutScrollDescription"],
|
||||
["tabLayoutWrap", "tabLayoutWrapDescription"],
|
||||
["routineSourceOpenModeQueryTab", "routineSourceOpenModeQueryTabDescription"],
|
||||
["routineSourceOpenModeDialog", "routineSourceOpenModeDialogDescription"],
|
||||
] as const) {
|
||||
expectClassTokens(classNameFromTag(elementTagForKeyInButton(buttonKey, descriptionKey)), ["whitespace-normal", "break-words"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps three-column titles shrinkable without allowing help icons to collapse", () => {
|
||||
for (const key of ["dataTabReuseAlwaysNew", "dataTabReuseSameTable", "dataTabReuseActiveTab"] as const) {
|
||||
const block = buttonBlockForKey(key);
|
||||
expect(block).toMatch(/<div class="[^"]*\bmin-w-0\b[^"]*\btext-left\b[^"]*">/);
|
||||
expect(block).toMatch(/<div class="[^"]*\bflex\b[^"]*\bmin-w-0\b[^"]*\bitems-center\b[^"]*">/);
|
||||
expectClassTokens(classNameFromTag(elementTagForKey(key)), ["min-w-0", "break-words"]);
|
||||
expect(block).toMatch(/<span class="[^"]*\bshrink-0\b[^"]*cursor-help[^"]*"[^>]*>/);
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves intentional single-line truncation for cards with tooltips", () => {
|
||||
for (const key of ["appLayoutSeparatedDescription", "appLayoutClassicDescription", "iconThemeDefaultDescription"] as const) {
|
||||
expectClassTokens(classNameFromTag(elementTagForKey(key)), ["truncate"]);
|
||||
}
|
||||
expect(dialogSource).toContain('class="text-xs text-muted-foreground truncate">\n {{ iconThemeBlackDescriptionText }}');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user