mirror of
https://github.com/svm0n/datadeck.git
synced 2026-07-22 08:31:46 +00:00
feat(config): surface auto-detected roles in the ⚙ Config modal
The per-column Role dropdown only ever showed an explicit fileCfg override — a column literally named 'Status' (or Category/Notes/Image/ Anki-front by their own name lists) already drives getStatusCol() etc. at render time by name, but the modal showed '— no special role —' for it with nothing to indicate that. This was the exact confusion behind a user question: 'what corresponds to the checkmark in the file?' — the Status role does, but for a conventionally-named column there was no visual confirmation it was already active. - Extracted the Category/Status/Notes column name-alias lists (already duplicated between main.ts and inline-view.ts) into shared constants in src/utils.ts, and split ankiFrontCol's fallback chain out into an exported autoAnkiFrontCol() in src/view/anki.ts. Both were already pure name/position lookups; this just makes them independently callable without an explicit override applied. - FileConfigModal now takes an AutoDetectedRoles snapshot (computed once in toolbar.ts's openColumns(), the same lists/functions the views themselves use) and roleOf() shows the resolved role plus an 'auto (by name)' badge whenever nothing is explicitly configured for that slot — exactly mirroring the fact that explicit overrides always win outright over name detection at runtime (never blended). Tests: two new FileConfigModal cases — a name-matched column shows its role with the auto badge and no saved config, and an explicit override on a different column suppresses the badge everywhere for that role, matching what getStatusCol()/etc. actually resolve. Co-authored-by: SVM0N <SVM0N@users.noreply.github.com>
This commit is contained in:
parent
53ca235bc2
commit
e260a4ed00
9 changed files with 182 additions and 85 deletions
40
main.js
40
main.js
File diff suppressed because one or more lines are too long
33
main.ts
33
main.ts
|
|
@ -19,7 +19,7 @@ import type { Chart as ChartType } from "chart.js";
|
|||
|
||||
// Import from src modules
|
||||
import { CSVRow, ViewMode, FileConfig, CardViewSettings, DEFAULT_SETTINGS, CARD_VIEW_TYPE } from "./src/types";
|
||||
import { sanitizeFilename, tagify, titleCase, formatRatingForDisplay, showSelectPicker, parseCSV, migrateFileConfigKey, sortRowsByColumn, isMultiValueColName, IMAGE_COL_ALIASES, looksBoolean, looksCategorical } from "./src/utils";
|
||||
import { sanitizeFilename, tagify, titleCase, formatRatingForDisplay, showSelectPicker, parseCSV, migrateFileConfigKey, sortRowsByColumn, isMultiValueColName, IMAGE_COL_ALIASES, CATEGORY_COL_ALIASES, STATUS_COL_ALIASES, NOTES_COL_ALIASES, looksBoolean, looksCategorical } from "./src/utils";
|
||||
import { isDateCol } from "./src/field-types";
|
||||
import { AddEntryModal, NoteExpanderModal, FileConfigModal, SearchModal, PromptModal } from "./src/modals";
|
||||
import { renderTravel } from "./src/travel-view";
|
||||
|
|
@ -233,15 +233,7 @@ export class CardView extends FileView {
|
|||
// 1. Per-file override
|
||||
if (this.fileCfg.notesColumn) return this.fileCfg.notesColumn;
|
||||
// 2. Fallback chain
|
||||
return this.resolveCol([
|
||||
"Notes","notes","Note","note",
|
||||
"Summary","summary",
|
||||
"Review","review",
|
||||
"Quote","quote","Quotes","quotes",
|
||||
"Comment","comment","Comments","comments",
|
||||
"Description","description",
|
||||
"Annotation","annotation",
|
||||
]);
|
||||
return this.resolveCol(NOTES_COL_ALIASES);
|
||||
}
|
||||
|
||||
isNotesCol(h: string): boolean {
|
||||
|
|
@ -283,31 +275,14 @@ export class CardView extends FileView {
|
|||
if (this.fileCfg.statusColumn) {
|
||||
return this.headers.find(h => h.toLowerCase() === this.fileCfg.statusColumn!.toLowerCase()) ?? null;
|
||||
}
|
||||
return this.resolveCol([
|
||||
"Status","status",
|
||||
"State","state",
|
||||
"Progress","progress",
|
||||
"Stage","stage",
|
||||
"Read","read",
|
||||
"Watched","watched","Seen","seen",
|
||||
"Done","done","Completed","completed",
|
||||
]);
|
||||
return this.resolveCol(STATUS_COL_ALIASES);
|
||||
}
|
||||
|
||||
getCategoryCol(): string | null {
|
||||
if (this.fileCfg.categoryColumn) {
|
||||
return this.headers.find(h => h.toLowerCase() === this.fileCfg.categoryColumn!.toLowerCase()) ?? null;
|
||||
}
|
||||
return this.resolveCol([
|
||||
"Category","category",
|
||||
"Categories","categories",
|
||||
"Genre","genre","Genres","genres",
|
||||
"Type","type","Types","types",
|
||||
"Tag","tag","Tags","tags",
|
||||
"Topic","topic","Topics","topics",
|
||||
"Subject","subject",
|
||||
"Section","section",
|
||||
]);
|
||||
return this.resolveCol(CATEGORY_COL_ALIASES);
|
||||
}
|
||||
|
||||
titleKey(): string | undefined {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import {
|
|||
import Papa from "papaparse";
|
||||
import type { CardView } from "../main";
|
||||
import { CSVRow, ViewMode, FileConfig, CardViewSettings } from "./types";
|
||||
import { parseCSV, resolvePath, sanitizeFilename, showSelectPicker, IMAGE_COL_ALIASES, looksBoolean, looksCategorical, isMultiValueColName } from "./utils";
|
||||
import { parseCSV, resolvePath, sanitizeFilename, showSelectPicker, IMAGE_COL_ALIASES, CATEGORY_COL_ALIASES, STATUS_COL_ALIASES, NOTES_COL_ALIASES, looksBoolean, looksCategorical, isMultiValueColName } from "./utils";
|
||||
import { isDateCol } from "./field-types";
|
||||
import { AddEntryModal, NoteExpanderModal } from "./modals";
|
||||
import { renderTable } from "./view/table";
|
||||
|
|
@ -257,11 +257,7 @@ export class InlineCardHost extends MarkdownRenderChild {
|
|||
}
|
||||
getNotesCol(): string | null {
|
||||
if (this.fileCfg.notesColumn) return this.fileCfg.notesColumn;
|
||||
return this.resolveCol([
|
||||
"Notes", "notes", "Note", "note", "Summary", "summary", "Review", "review",
|
||||
"Quote", "quote", "Quotes", "quotes", "Comment", "comment", "Comments", "comments",
|
||||
"Description", "description", "Annotation", "annotation",
|
||||
]);
|
||||
return this.resolveCol(NOTES_COL_ALIASES);
|
||||
}
|
||||
isNotesCol(h: string): boolean {
|
||||
const notesCol = this.getNotesCol();
|
||||
|
|
@ -284,20 +280,13 @@ export class InlineCardHost extends MarkdownRenderChild {
|
|||
if (this.fileCfg.statusColumn) {
|
||||
return this.headers.find(h => h.toLowerCase() === this.fileCfg.statusColumn!.toLowerCase()) ?? null;
|
||||
}
|
||||
return this.resolveCol([
|
||||
"Status", "status", "State", "state", "Progress", "progress", "Stage", "stage",
|
||||
"Read", "read", "Watched", "watched", "Seen", "seen", "Done", "done", "Completed", "completed",
|
||||
]);
|
||||
return this.resolveCol(STATUS_COL_ALIASES);
|
||||
}
|
||||
getCategoryCol(): string | null {
|
||||
if (this.fileCfg.categoryColumn) {
|
||||
return this.headers.find(h => h.toLowerCase() === this.fileCfg.categoryColumn!.toLowerCase()) ?? null;
|
||||
}
|
||||
return this.resolveCol([
|
||||
"Category", "category", "Categories", "categories", "Genre", "genre", "Genres", "genres",
|
||||
"Type", "type", "Types", "types", "Tag", "tag", "Tags", "tags",
|
||||
"Topic", "topic", "Topics", "topics", "Subject", "subject", "Section", "section",
|
||||
]);
|
||||
return this.resolveCol(CATEGORY_COL_ALIASES);
|
||||
}
|
||||
titleKey(): string | undefined {
|
||||
return this.resolveCol(["Title", "title", "Name", "name"]) ?? undefined;
|
||||
|
|
|
|||
|
|
@ -648,12 +648,26 @@ export class SearchModal extends Modal {
|
|||
// ─── File Config Modal ────────────────────────────────────────────────────────
|
||||
// Per-file column mapping — which column is the kanban group, notes, status
|
||||
|
||||
// What each of the 5 exclusive per-column roles would resolve to with no
|
||||
// explicit fileCfg override — i.e. what getCategoryCol/getStatusCol/
|
||||
// getNotesCol/getImageCol/ankiFrontCol already do by name/position at
|
||||
// render time. Lets the Config modal show "(auto)" on whichever column is
|
||||
// already fulfilling a role, instead of looking like no column does.
|
||||
export interface AutoDetectedRoles {
|
||||
category: string | null;
|
||||
status: string | null;
|
||||
notes: string | null;
|
||||
image: string | null;
|
||||
anki: string | null;
|
||||
}
|
||||
|
||||
export class FileConfigModal extends Modal {
|
||||
headers: string[];
|
||||
filePath: string;
|
||||
current: FileConfig;
|
||||
autoDetectedHabits: string[];
|
||||
autoDetectedCategorical: string[];
|
||||
autoDetectedRoles: AutoDetectedRoles;
|
||||
availableModes: { id: ViewMode; label: string }[];
|
||||
onSave: (cfg: FileConfig) => void;
|
||||
// Column-structure ops (add/remove the CSV column itself, not its role).
|
||||
|
|
@ -666,7 +680,7 @@ export class FileConfigModal extends Modal {
|
|||
|
||||
constructor(
|
||||
app: App, headers: string[], filePath: string, current: FileConfig, autoDetectedHabits: string[],
|
||||
autoDetectedCategorical: string[],
|
||||
autoDetectedCategorical: string[], autoDetectedRoles: AutoDetectedRoles,
|
||||
availableModes: { id: ViewMode; label: string }[], onSave: (cfg: FileConfig) => void,
|
||||
getHeaders: () => string[], getFileCfg: () => FileConfig,
|
||||
onAddColumn: (name: string) => string | null, onRemoveColumn: (header: string) => void,
|
||||
|
|
@ -681,6 +695,7 @@ export class FileConfigModal extends Modal {
|
|||
};
|
||||
this.autoDetectedHabits = autoDetectedHabits;
|
||||
this.autoDetectedCategorical = autoDetectedCategorical;
|
||||
this.autoDetectedRoles = autoDetectedRoles;
|
||||
this.availableModes = availableModes;
|
||||
this.onSave = onSave;
|
||||
this.getHeaders = getHeaders;
|
||||
|
|
@ -730,7 +745,7 @@ export class FileConfigModal extends Modal {
|
|||
colSection.createEl("label", { text: "Columns", cls: "csv-modal-label" });
|
||||
colSection.createEl("p", {
|
||||
cls: "csv-modal-hint",
|
||||
text: "Role is exclusive — picking it for one column clears it from any other. The three toggles can be combined freely.",
|
||||
text: "Role is exclusive — picking it for one column clears it from any other. \"auto (by name)\" means it's already playing that role because of its column name, with nothing saved here yet. The three toggles can be combined freely.",
|
||||
});
|
||||
|
||||
type Role = "category" | "status" | "notes" | "image" | "anki" | "";
|
||||
|
|
@ -742,13 +757,28 @@ export class FileConfigModal extends Modal {
|
|||
{ value: "image", label: "Image (card / kanban thumbnail)" },
|
||||
{ value: "anki", label: "Anki card front" },
|
||||
];
|
||||
const roleOf = (h: string): Role => {
|
||||
if (this.current.categoryColumn === h) return "category";
|
||||
if (this.current.statusColumn === h) return "status";
|
||||
if (this.current.notesColumn === h) return "notes";
|
||||
if (this.current.imageColumn === h) return "image";
|
||||
if (this.current.ankiFrontCol === h) return "anki";
|
||||
return "";
|
||||
// Explicit fileCfg fields always win outright over name-based detection —
|
||||
// mirrors getCategoryCol/getStatusCol/getNotesCol/getImageCol/ankiFrontCol,
|
||||
// which never blend an auto guess in once a role has an override, even if
|
||||
// that override points at a column that no longer looks like a great fit.
|
||||
// So `auto: true` only ever appears when the role's field is fully unset.
|
||||
const roleOf = (h: string): { role: Role; auto: boolean } => {
|
||||
if (this.current.categoryColumn !== undefined) { if (this.current.categoryColumn === h) return { role: "category", auto: false }; }
|
||||
else if (this.autoDetectedRoles.category === h) return { role: "category", auto: true };
|
||||
|
||||
if (this.current.statusColumn !== undefined) { if (this.current.statusColumn === h) return { role: "status", auto: false }; }
|
||||
else if (this.autoDetectedRoles.status === h) return { role: "status", auto: true };
|
||||
|
||||
if (this.current.notesColumn !== undefined) { if (this.current.notesColumn === h) return { role: "notes", auto: false }; }
|
||||
else if (this.autoDetectedRoles.notes === h) return { role: "notes", auto: true };
|
||||
|
||||
if (this.current.imageColumn !== undefined) { if (this.current.imageColumn === h) return { role: "image", auto: false }; }
|
||||
else if (this.autoDetectedRoles.image === h) return { role: "image", auto: true };
|
||||
|
||||
if (this.current.ankiFrontCol !== undefined) { if (this.current.ankiFrontCol === h) return { role: "anki", auto: false }; }
|
||||
else if (this.autoDetectedRoles.anki === h) return { role: "anki", auto: true };
|
||||
|
||||
return { role: "", auto: false };
|
||||
};
|
||||
// Only clears the role(s) *this* column currently holds — reassigning a
|
||||
// role to a different column evicts its previous holder for free, since
|
||||
|
|
@ -813,11 +843,20 @@ export class FileConfigModal extends Modal {
|
|||
const controls = row.createDiv({ cls: "csv-modal-colcfg-controls" });
|
||||
|
||||
const roleSel = controls.createEl("select", { cls: "csv-modal-select csv-modal-colcfg-role" });
|
||||
const currentRole = roleOf(h);
|
||||
// roleOf already reflects an auto-detected role in `role` (not just
|
||||
// `auto`) — e.g. a column literally named "Status" shows "Status"
|
||||
// selected here with no config saved at all, matching what
|
||||
// getStatusCol() already resolves by name at render time. The badge
|
||||
// is just there to distinguish "picked by name" from "picked by you".
|
||||
const { role: currentRole, auto: isAutoRole } = roleOf(h);
|
||||
ROLE_OPTIONS.forEach(o => {
|
||||
const opt = roleSel.createEl("option", { text: o.label, value: o.value });
|
||||
if (o.value === currentRole) opt.selected = true;
|
||||
});
|
||||
if (isAutoRole) {
|
||||
roleSel.addClass("auto-detected");
|
||||
controls.createSpan({ cls: "csv-modal-colcfg-auto-badge", text: "auto (by name)" });
|
||||
}
|
||||
roleSel.addEventListener("change", () => {
|
||||
setRole(h, roleSel.value as Role);
|
||||
renderRows();
|
||||
|
|
|
|||
33
src/utils.ts
33
src/utils.ts
|
|
@ -6,6 +6,39 @@ import { CSVRow } from "./types";
|
|||
// thumbnails. Matched case-insensitively against headers (see getImageCol).
|
||||
export const IMAGE_COL_ALIASES = ["Image", "image", "Cover", "cover", "Poster", "poster", "Thumbnail", "thumbnail", "Thumb", "thumb", "Photo", "photo", "Picture", "picture", "Img", "img"];
|
||||
|
||||
// Column-name aliases for the three other name-detected roles (see
|
||||
// getCategoryCol/getStatusCol/getNotesCol). Shared between main.ts,
|
||||
// inline-view.ts, and the ⚙ Config modal's auto-detected role indicator, so
|
||||
// the modal's "this is auto-detected" badge always agrees with what the
|
||||
// views actually resolve at render time.
|
||||
export const CATEGORY_COL_ALIASES = [
|
||||
"Category","category","Categories","categories",
|
||||
"Genre","genre","Genres","genres",
|
||||
"Type","type","Types","types",
|
||||
"Tag","tag","Tags","tags",
|
||||
"Topic","topic","Topics","topics",
|
||||
"Subject","subject",
|
||||
"Section","section",
|
||||
];
|
||||
export const STATUS_COL_ALIASES = [
|
||||
"Status","status",
|
||||
"State","state",
|
||||
"Progress","progress",
|
||||
"Stage","stage",
|
||||
"Read","read",
|
||||
"Watched","watched","Seen","seen",
|
||||
"Done","done","Completed","completed",
|
||||
];
|
||||
export const NOTES_COL_ALIASES = [
|
||||
"Notes","notes","Note","note",
|
||||
"Summary","summary",
|
||||
"Review","review",
|
||||
"Quote","quote","Quotes","quotes",
|
||||
"Comment","comment","Comments","comments",
|
||||
"Description","description",
|
||||
"Annotation","annotation",
|
||||
];
|
||||
|
||||
/**
|
||||
* Resolve a cell value into an <img> src, or null if it isn't resolvable.
|
||||
* Accepts: http(s)/data URLs (used as-is), `![[wikilink]]` / `[[wikilink]]`,
|
||||
|
|
|
|||
|
|
@ -38,20 +38,29 @@ async function ankiInvoke(action: string, params: Record<string, unknown>): Prom
|
|||
}
|
||||
|
||||
/**
|
||||
* Resolve the column used as the Anki card front. Honours the per-file
|
||||
* `ankiFrontCol`; otherwise falls back to the title/primary field, then to a
|
||||
* content-bearing column (so a quotes file fronts on Quote, not its first
|
||||
* column Author), then to the first column.
|
||||
* What ankiFrontCol would resolve to with no per-file override — the
|
||||
* title/primary field, then a content-bearing column (so a quotes file
|
||||
* fronts on Quote, not its first column Author), then the first column.
|
||||
* Exposed separately so the ⚙ Config modal can show which column is
|
||||
* *already* the front by name/position, without an explicit `ankiFrontCol`.
|
||||
*/
|
||||
export function ankiFrontCol(view: CardView): string | null {
|
||||
const configured = view.fileCfg.ankiFrontCol;
|
||||
if (configured && view.headers.includes(configured)) return configured;
|
||||
export function autoAnkiFrontCol(view: CardView): string | null {
|
||||
return view.titleKey()
|
||||
?? view.resolveCol(["Quote", "Headline", "Phrase", "Term", "Word", "Question", "Front", "Name", "Title"])
|
||||
?? view.headers[0]
|
||||
?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the column used as the Anki card front. Honours the per-file
|
||||
* `ankiFrontCol`; otherwise falls back to autoAnkiFrontCol.
|
||||
*/
|
||||
export function ankiFrontCol(view: CardView): string | null {
|
||||
const configured = view.fileCfg.ankiFrontCol;
|
||||
if (configured && view.headers.includes(configured)) return configured;
|
||||
return autoAnkiFrontCol(view);
|
||||
}
|
||||
|
||||
// HTML-escape a cell so quotes/dictionary entries with <, >, & render as text
|
||||
// in Anki rather than as broken markup.
|
||||
function esc(s: string): string {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@
|
|||
import { Menu, Notice } from "obsidian";
|
||||
import type { CardView } from "../../main";
|
||||
import { ViewMode } from "../types";
|
||||
import { FileConfigModal } from "../modals";
|
||||
import { FileConfigModal, AutoDetectedRoles } from "../modals";
|
||||
import { generateMobileFiles } from "./mobile";
|
||||
import { syncToAnki } from "./anki";
|
||||
import { syncToAnki, autoAnkiFrontCol } from "./anki";
|
||||
import { hasStatsColumns } from "./stats";
|
||||
import { hasTaskColumns } from "./tasks";
|
||||
import { effectiveGroupCol } from "./kanban";
|
||||
import { CATEGORY_COL_ALIASES, STATUS_COL_ALIASES, NOTES_COL_ALIASES, IMAGE_COL_ALIASES } from "../utils";
|
||||
|
||||
declare const __BUILD_TIME__: string;
|
||||
|
||||
|
|
@ -197,9 +198,20 @@ export function renderToolbar(view: CardView, root: HTMLElement): void {
|
|||
// Handlers are defined once and reused by both surfaces so there's a
|
||||
// single place to maintain behaviour.
|
||||
const openColumns = () => {
|
||||
// What each exclusive role would resolve to with no fileCfg override,
|
||||
// purely by column name/position — same lists/logic getCategoryCol etc.
|
||||
// already use at render time — so the modal's "auto (by name)" badge
|
||||
// always agrees with what's actually driving the view right now.
|
||||
const autoDetectedRoles: AutoDetectedRoles = {
|
||||
category: view.resolveCol(CATEGORY_COL_ALIASES),
|
||||
status: view.resolveCol(STATUS_COL_ALIASES),
|
||||
notes: view.resolveCol(NOTES_COL_ALIASES),
|
||||
image: view.resolveCol(IMAGE_COL_ALIASES),
|
||||
anki: autoAnkiFrontCol(view),
|
||||
};
|
||||
new FileConfigModal(
|
||||
view.app, view.headers, view.file?.path ?? "", view.fileCfg, view.autoDetectBooleanColumns(),
|
||||
view.autoDetectCategoricalColumns(), availableModes(view),
|
||||
view.autoDetectCategoricalColumns(), autoDetectedRoles, availableModes(view),
|
||||
(cfg) => {
|
||||
view.saveFileCfg(cfg);
|
||||
if (cfg.defaultMode) view.mode = cfg.defaultMode;
|
||||
|
|
|
|||
12
styles.css
12
styles.css
|
|
@ -2032,6 +2032,18 @@ td .csv-select-chip {
|
|||
width: auto;
|
||||
}
|
||||
|
||||
.csv-modal-colcfg-role.auto-detected {
|
||||
border-color: rgba(52, 168, 83, 0.4);
|
||||
background: rgba(52, 168, 83, 0.08);
|
||||
}
|
||||
|
||||
.csv-modal-colcfg-auto-badge {
|
||||
font-size: 11px;
|
||||
font-style: italic;
|
||||
color: var(--color-green, #34a853);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.csv-modal-colcfg-toggle {
|
||||
padding: 4px 8px;
|
||||
flex: 0 0 auto;
|
||||
|
|
|
|||
|
|
@ -828,9 +828,10 @@ await test("note-expander: an explicit isCategoricalCol override wins over auto-
|
|||
const { FileConfigModal } = await load("./src/modals.ts");
|
||||
|
||||
function openFileConfigModal(headers, current, autoDetectedCategorical, overrides = {}) {
|
||||
const autoDetectedRoles = { category: null, status: null, notes: null, image: null, anki: null, ...overrides.autoDetectedRoles };
|
||||
const modal = new FileConfigModal(
|
||||
new StubApp(), headers, "test.csv", current, overrides.autoDetectedHabits ?? [],
|
||||
autoDetectedCategorical, overrides.availableModes ?? [],
|
||||
autoDetectedCategorical, autoDetectedRoles, overrides.availableModes ?? [],
|
||||
overrides.onSave ?? (() => {}),
|
||||
overrides.getHeaders ?? (() => headers),
|
||||
overrides.getFileCfg ?? (() => current),
|
||||
|
|
@ -921,6 +922,33 @@ await test("FileConfigModal: role/toggle edits don't touch disk — only add/rem
|
|||
assert(getFileCfgCalls === 0, "editing roles/toggles is a pending, in-memory draft until Save — it must not refetch from disk");
|
||||
});
|
||||
|
||||
await test("FileConfigModal: a name-detected column shows its role with an auto badge, no config needed", async () => {
|
||||
// Mirrors a real project dash file: nothing in fileCfg, but "Status" already
|
||||
// resolves by name at render time (getStatusCol) — the row should show
|
||||
// that, not "— no special role —", which is what prompted this feature.
|
||||
const headers = ["Title", "Type", "Project", "Status"];
|
||||
const modal = openFileConfigModal(headers, {}, [], { autoDetectedRoles: { status: "Status" } });
|
||||
const sel = roleSelectFor(modal, "Status");
|
||||
assert(sel.value === "status", "Status column shows the Status role even with nothing saved");
|
||||
assert(sel.hasClass("auto-detected"), "auto-detected role select gets the auto-detected styling");
|
||||
assert(colConfigRowFor(modal, "Status").textContent.includes("auto (by name)"), "row surfaces the auto badge text");
|
||||
|
||||
const typeSel = roleSelectFor(modal, "Type");
|
||||
assert(typeSel.value === "", "a column with no matching alias and no config shows no role");
|
||||
assert(!typeSel.hasClass("auto-detected"), "no badge when there's nothing auto-detected for this column");
|
||||
});
|
||||
|
||||
await test("FileConfigModal: an explicit override elsewhere suppresses the auto badge, matching runtime resolution", async () => {
|
||||
// getStatusCol() never blends a name-based guess back in once statusColumn
|
||||
// is set — even to a different column — so neither the auto pick nor the
|
||||
// explicit pick should show "auto" once an override exists for the role.
|
||||
const headers = ["Title", "Status", "Progress"];
|
||||
const modal = openFileConfigModal(headers, { statusColumn: "Progress" }, [], { autoDetectedRoles: { status: "Status" } });
|
||||
assert(roleSelectFor(modal, "Progress").value === "status", "the explicit override holds the Status role");
|
||||
assert(!roleSelectFor(modal, "Progress").hasClass("auto-detected"), "explicit picks aren't flagged as auto");
|
||||
assert(roleSelectFor(modal, "Status").value === "", "the name-matched column no longer shows the role once something else is configured");
|
||||
});
|
||||
|
||||
// ── Multi-select picker ──────────────────────────────────────────────────────
|
||||
const { showSelectPicker, isMultiValueColName } = await load("./src/utils.ts");
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue