feat(manager): wildcard.max_ref_depth setting (1-32, default 8)
This commit is contained in:
@@ -42,4 +42,51 @@ describe("Settings.vue", () => {
|
||||
expect(lightChip.attributes("data-active")).toBe("true");
|
||||
expect(darkChip.attributes("data-active")).toBe("false");
|
||||
});
|
||||
|
||||
it("renders Wildcard card with max_ref_depth field", () => {
|
||||
const wrap = mount(Settings);
|
||||
const text = wrap.text();
|
||||
expect(text).toContain("Wildcard");
|
||||
expect(text).toContain("Ref recursion limit");
|
||||
expect(wrap.find('[data-test="settings-wildcard-max-ref-depth"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("max_ref_depth field has correct type and default value", async () => {
|
||||
const wrap = mount(Settings);
|
||||
const store = useUiStore();
|
||||
const input = wrap.get<HTMLInputElement>('[data-test="settings-wildcard-max-ref-depth"]');
|
||||
expect(input.attributes("type")).toBe("number");
|
||||
expect(Number(input.element.value)).toBe(store.maxRefDepth);
|
||||
});
|
||||
|
||||
it("changing max_ref_depth updates store and localStorage", async () => {
|
||||
const wrap = mount(Settings);
|
||||
const store = useUiStore();
|
||||
const input = wrap.get('[data-test="settings-wildcard-max-ref-depth"]');
|
||||
await input.setValue("16");
|
||||
await input.trigger("blur");
|
||||
expect(store.maxRefDepth).toBe(16);
|
||||
expect(localStorage.getItem("wp-wildcard-max-ref-depth")).toBe("16");
|
||||
});
|
||||
|
||||
it("max_ref_depth clamps to valid range (1-32)", async () => {
|
||||
const wrap = mount(Settings);
|
||||
const store = useUiStore();
|
||||
const input = wrap.get('[data-test="settings-wildcard-max-ref-depth"]');
|
||||
|
||||
// Test clamping to min
|
||||
await input.setValue("0");
|
||||
await input.trigger("blur");
|
||||
expect(store.maxRefDepth).toBe(1);
|
||||
|
||||
// Test clamping to max
|
||||
await input.setValue("100");
|
||||
await input.trigger("blur");
|
||||
expect(store.maxRefDepth).toBe(32);
|
||||
|
||||
// Test valid value in range
|
||||
await input.setValue("15");
|
||||
await input.trigger("blur");
|
||||
expect(store.maxRefDepth).toBe(15);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,9 @@ interface Props {
|
||||
addon?: string;
|
||||
id?: string;
|
||||
ariaLabel?: string;
|
||||
min?: number | string;
|
||||
max?: number | string;
|
||||
step?: number | string;
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: "text",
|
||||
@@ -55,6 +58,9 @@ function onInput(e: Event) {
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:aria-label="ariaLabel"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
@input="onInput"
|
||||
@blur="(e) => emit('blur', e)"
|
||||
@focus="(e) => emit('focus', e)"
|
||||
@@ -70,6 +76,9 @@ function onInput(e: Event) {
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:aria-label="ariaLabel"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
@input="onInput"
|
||||
@blur="(e) => emit('blur', e)"
|
||||
@focus="(e) => emit('focus', e)"
|
||||
|
||||
@@ -5,7 +5,11 @@ import { computed, ref, watch } from "vue";
|
||||
export type ThemeMode = "dark" | "light" | "auto";
|
||||
|
||||
const STORAGE_KEY = "wp-theme-mode";
|
||||
const STORAGE_KEY_MAX_REF_DEPTH = "wp-wildcard-max-ref-depth";
|
||||
const FLASH_SUPPRESS_MS = 120;
|
||||
const DEFAULT_MAX_REF_DEPTH = 8;
|
||||
const MIN_MAX_REF_DEPTH = 1;
|
||||
const MAX_MAX_REF_DEPTH = 32;
|
||||
|
||||
function readStoredTheme(): ThemeMode {
|
||||
try {
|
||||
@@ -17,6 +21,21 @@ function readStoredTheme(): ThemeMode {
|
||||
return "dark";
|
||||
}
|
||||
|
||||
function readStoredMaxRefDepth(): number {
|
||||
try {
|
||||
const v = localStorage.getItem(STORAGE_KEY_MAX_REF_DEPTH);
|
||||
if (v !== null) {
|
||||
const n = parseInt(v, 10);
|
||||
if (!isNaN(n) && n >= MIN_MAX_REF_DEPTH && n <= MAX_MAX_REF_DEPTH) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* localStorage unavailable */
|
||||
}
|
||||
return DEFAULT_MAX_REF_DEPTH;
|
||||
}
|
||||
|
||||
function systemPrefersDark(): boolean {
|
||||
return typeof window !== "undefined"
|
||||
&& typeof window.matchMedia === "function"
|
||||
@@ -26,6 +45,7 @@ function systemPrefersDark(): boolean {
|
||||
export const useUiStore = defineStore("ui", () => {
|
||||
const themeMode = ref<ThemeMode>(readStoredTheme());
|
||||
const sidebarCollapsed = ref(false);
|
||||
const maxRefDepth = ref<number>(readStoredMaxRefDepth());
|
||||
|
||||
/** Resolved theme — `"auto"` collapsed to dark/light via OS preference. */
|
||||
const resolvedTheme = computed<"dark" | "light">(() =>
|
||||
@@ -48,6 +68,12 @@ export const useUiStore = defineStore("ui", () => {
|
||||
try { localStorage.setItem(STORAGE_KEY, mode); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function setMaxRefDepth(depth: number) {
|
||||
const clamped = Math.max(MIN_MAX_REF_DEPTH, Math.min(MAX_MAX_REF_DEPTH, Math.floor(depth)));
|
||||
maxRefDepth.value = clamped;
|
||||
try { localStorage.setItem(STORAGE_KEY_MAX_REF_DEPTH, String(clamped)); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
/** Cycle dark → light → auto → dark. */
|
||||
function cycleTheme() {
|
||||
const next: ThemeMode =
|
||||
@@ -78,8 +104,10 @@ export const useUiStore = defineStore("ui", () => {
|
||||
themeMode,
|
||||
resolvedTheme,
|
||||
sidebarCollapsed,
|
||||
maxRefDepth,
|
||||
cycleTheme,
|
||||
setThemeMode,
|
||||
setMaxRefDepth,
|
||||
initializeTheme,
|
||||
toggleSidebar,
|
||||
};
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
*/
|
||||
import Button from "../components/ui/Button.vue";
|
||||
import Card from "../components/ui/Card.vue";
|
||||
import Field from "../components/ui/Field.vue";
|
||||
import Icon from "../components/ui/Icon.vue";
|
||||
import Input from "../components/ui/Input.vue";
|
||||
import { useUiStore, type ThemeMode } from "../stores/uiStore";
|
||||
|
||||
const uiStore = useUiStore();
|
||||
@@ -101,6 +103,32 @@ function resetPreferences() {
|
||||
@click="resetPreferences"
|
||||
>Reset preferences</Button>
|
||||
</Card>
|
||||
|
||||
<Card title="Wildcard">
|
||||
<Field
|
||||
label="Ref recursion limit"
|
||||
hint="How deep nested @{uuid} references can resolve. Range: 1–32. Default: 8."
|
||||
for="wildcard-max-ref-depth"
|
||||
>
|
||||
<Input
|
||||
id="wildcard-max-ref-depth"
|
||||
v-model.number="uiStore.maxRefDepth"
|
||||
type="number"
|
||||
data-test="settings-wildcard-max-ref-depth"
|
||||
:aria-label="'Wildcard ref recursion limit'"
|
||||
:min="1"
|
||||
:max="32"
|
||||
:step="1"
|
||||
@blur="(e) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const val = parseInt(target.value, 10);
|
||||
if (!isNaN(val)) {
|
||||
uiStore.setMaxRefDepth(val);
|
||||
}
|
||||
}"
|
||||
/>
|
||||
</Field>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user