Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_3
This commit is contained in:
@@ -42,6 +42,8 @@ export interface LaborLibrary {
|
||||
root: HTMLElement;
|
||||
/** 좌측 패널 나무 */
|
||||
tree: HTMLElement;
|
||||
/** 나무 맨 위 더하기 「+」(층 단추 옆) */
|
||||
add: HTMLElement;
|
||||
refresh: () => Promise<void>;
|
||||
/** 메인 칸 「저장 안 한 고침」 손잡이 */
|
||||
bind: (setDirty: (dirty: boolean) => void) => void;
|
||||
@@ -103,9 +105,16 @@ export function buildLaborLibrary(opt: LaborLibraryOptions): LaborLibrary {
|
||||
else editor.showList();
|
||||
},
|
||||
});
|
||||
// 찾기 칸 하나(PLAN 29-6) — 나무와 조합 목록을 같은 글 · 같은 규칙으로 거름
|
||||
tree.search
|
||||
.querySelector("input")
|
||||
?.addEventListener("input", (event) =>
|
||||
editor.setQuery((event.target as HTMLInputElement).value),
|
||||
);
|
||||
return {
|
||||
root: el("div", { className: "m02-labor", children: [tree.search, editor.root] }),
|
||||
tree: tree.root,
|
||||
add: tree.add,
|
||||
refresh: () => tree.refresh(),
|
||||
bind: (fn) => (setDirty = fn),
|
||||
};
|
||||
@@ -120,8 +129,8 @@ export interface LaborSectionOptions {
|
||||
onOpen: () => void;
|
||||
/** 라이브러리 — `reveal` = 메인 칸에 일위대가 화면을 띄움(나무 항목을 누를 때) */
|
||||
make: (reveal: () => Promise<void>) => LaborLibrary;
|
||||
/** 줄 아래 층 단추(PLAN 29-2) */
|
||||
bar?: HTMLElement;
|
||||
/** 층 단추(PLAN 29-2) — `add` = 나무 맨 위 더하기 「+」를 단추 옆에 */
|
||||
bar?: (add: HTMLElement) => HTMLElement;
|
||||
}
|
||||
|
||||
export interface LaborSection {
|
||||
@@ -132,16 +141,11 @@ export interface LaborSection {
|
||||
clear: () => void;
|
||||
}
|
||||
|
||||
/** 컨테이너 몸 = 층 단추 + 나무(같은 이름 줄 단추 없음 · 컨테이너 제목을 펼치면 엶 · PLAN 29-6) */
|
||||
export function buildLaborSection(opt: LaborSectionOptions): LaborSection {
|
||||
const row = el("button", {
|
||||
className: "m02-side__item ui-sidebar-row",
|
||||
text: tc("Library"),
|
||||
attrs: { type: "button", "data-labor-row": "" },
|
||||
});
|
||||
let opened = false;
|
||||
const mark = (on: boolean): void => {
|
||||
opened = on;
|
||||
row.classList.toggle("is-active", on);
|
||||
};
|
||||
|
||||
async function open(): Promise<void> {
|
||||
@@ -156,8 +160,7 @@ export function buildLaborSection(opt: LaborSectionOptions): LaborSection {
|
||||
void library.refresh();
|
||||
const body = el("div", {
|
||||
className: "m02-side__items ui-sidebar-list",
|
||||
children: [row, ...(opt.bar ? [opt.bar] : []), library.tree],
|
||||
children: [...(opt.bar ? [opt.bar(library.add)] : []), library.tree],
|
||||
});
|
||||
row.addEventListener("click", () => void open());
|
||||
return { title: tc("Title"), body, open, clear: () => mark(false) };
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { openLogicPicker } from "./M02_MasterTemplete_Labor_Pick";
|
||||
import { buildPreview } from "./M02_MasterTemplete_Labor_Preview";
|
||||
import { tc } from "./M02_MasterTemplete_Labor_Text";
|
||||
import { matchText } from "./M02_MasterTemplete_TreePanel_Model";
|
||||
import "../M01_MasterData/M01_MasterData_UI_Logic_Style.css";
|
||||
import "../M01_MasterData/M01_MasterData_UI_LogicLab_Style.css";
|
||||
import "./M02_MasterTemplete_Labor.css";
|
||||
@@ -77,6 +78,8 @@ export interface LaborEditor {
|
||||
isDirty: () => boolean;
|
||||
/** 열린 조합 키 — 목록이면 null · 새 조합이면 빈 글 */
|
||||
current: () => string | null;
|
||||
/** 라이브러리 찾기 칸 글 — 목록을 같은 규칙(`matchText`)으로 거름 */
|
||||
setQuery: (text: string) => void;
|
||||
}
|
||||
|
||||
export function createLaborEditor(opt: LaborEditOptions): LaborEditor {
|
||||
@@ -89,11 +92,11 @@ export function createLaborEditor(opt: LaborEditOptions): LaborEditor {
|
||||
let current: { combo: Combo; version: string; saved: string } | null = null;
|
||||
let q = "";
|
||||
const view = el("div", { className: "m01-logic__editor m01-combo m02-labor__edit" });
|
||||
const find = createInputField({ type: "search", placeholder: tc("Search") });
|
||||
find.input.addEventListener("input", () => {
|
||||
q = find.input.value.trim().toLowerCase();
|
||||
// 찾기 칸은 라이브러리 하나(상세 위 · 나무와 같은 글 · PLAN 29-6) — `setQuery` 로 받음
|
||||
const setQuery = (text: string): void => {
|
||||
q = text;
|
||||
if (!current) drawList();
|
||||
});
|
||||
};
|
||||
|
||||
const logicsReady = fetchLibLogics()
|
||||
.then((all) => {
|
||||
@@ -111,7 +114,7 @@ export function createLaborEditor(opt: LaborEditOptions): LaborEditor {
|
||||
nav++;
|
||||
current = null;
|
||||
opt.onDirty(false);
|
||||
const shown = items.filter((x) => !q || `${x.키} ${x.이름}`.toLowerCase().includes(q));
|
||||
const shown = items.filter((x) => matchText([x.키, x.이름, x.구분, x.상세구분, x.단위], q));
|
||||
const heads = [
|
||||
tc("Head_Key"),
|
||||
tc("Head_Name"),
|
||||
@@ -142,7 +145,6 @@ export function createLaborEditor(opt: LaborEditOptions): LaborEditor {
|
||||
el("div", {
|
||||
className: "m01-logic__section-head",
|
||||
children: [
|
||||
find.root,
|
||||
...(edit
|
||||
? [createButton({ label: tc("New"), variant: "filled", onClick: () => startNew([]) })]
|
||||
: [el("span", { className: "m01-logic__muted", text: tc("ReadOnly") })]),
|
||||
@@ -387,5 +389,6 @@ export function createLaborEditor(opt: LaborEditOptions): LaborEditor {
|
||||
showList: drawList,
|
||||
isDirty: dirty,
|
||||
current: () => current?.combo.키 ?? null,
|
||||
setQuery,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,6 +64,28 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 층 단추 옆 「+」 — 나무 맨 위 폴더 · 항목 더하기(PLAN 29-6) */
|
||||
.m02-tree__add {
|
||||
min-width: 1.75rem;
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--radius-buttons);
|
||||
background: none;
|
||||
color: var(--color-text-secondary);
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-tree__add:hover {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.m02-tree__add[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.m02-layers__pick {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -47,8 +47,6 @@ export interface TreePanelOptions {
|
||||
layer: Layer;
|
||||
/** 남의 층 주인(회사 `{C}` · 개인 `{C}/{U}`) — 없으면 자기 층 */
|
||||
owner?: string | null;
|
||||
/** 맨 위 줄 글 — 없으면 층 이름 */
|
||||
label?: string;
|
||||
/** 나무 읽기 · 고치기 길 — 없으면 구조물 조합 나무(`/layers/{층}/tree`) */
|
||||
api?: {
|
||||
fetch: (layer: Layer, owner: string | null) => Promise<TreeView>;
|
||||
@@ -73,6 +71,8 @@ export interface TreePanelHandle {
|
||||
root: HTMLElement;
|
||||
/** 찾기 칸 — 부르는 쪽이 상세 위쪽에 붙임(PLAN 29-2) */
|
||||
search: HTMLElement;
|
||||
/** 「+」 — 맨 위 폴더 · 항목 더하기 · 부르는 쪽이 층 단추 옆에 붙임(읽기만 층 = 숨김 · 29-6) */
|
||||
add: HTMLElement;
|
||||
refresh: () => Promise<void>;
|
||||
/** 표에서 고른 열을 나무에 표시(null = 지움) */
|
||||
select: (colId: string | null) => void;
|
||||
@@ -84,15 +84,6 @@ const why = (error: unknown): string => (error instanceof Error ? error.message
|
||||
const hint = (text: string): HTMLElement => el("p", { className: "m02-tree__hint", text });
|
||||
|
||||
export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
|
||||
const layerLabel =
|
||||
opt.label ??
|
||||
L(
|
||||
opt.layer === "company"
|
||||
? "M02_PickerCompany"
|
||||
: opt.layer === "personal"
|
||||
? "M02_PickerPersonal"
|
||||
: "M02_PickerSystem",
|
||||
);
|
||||
const owner = opt.owner ?? null;
|
||||
const api = opt.api ?? { fetch: fetchTree, edit: editTree };
|
||||
let view: TreeView | null = null;
|
||||
@@ -107,6 +98,16 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
|
||||
const body = el("div", { className: "m02-tree__body", attrs: { tabindex: "-1" } });
|
||||
const root = el("div", { className: "m02-tree", children: [body] });
|
||||
search.root.classList.add("m02-tree__search");
|
||||
// 층 단추 옆 「+」 — 맨 위에 폴더 · 항목 더하기(뿌리 줄 오른쪽 클릭을 갈음)
|
||||
const add = el("button", {
|
||||
className: "m02-tree__add",
|
||||
text: "+",
|
||||
attrs: { type: "button", title: L("M02_TreeAddTop"), "aria-label": L("M02_TreeAddTop") },
|
||||
});
|
||||
add.hidden = !opt.canEdit;
|
||||
add.addEventListener("click", (event) =>
|
||||
openTreeMenu(event, menuOf({ kind: "folder", path: [] })),
|
||||
);
|
||||
|
||||
/* --- 그리기 --- */
|
||||
const menuOf = (target: Target): TreeMenuItem[] => {
|
||||
@@ -158,7 +159,7 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
|
||||
return node(
|
||||
{ kind: "folder", path: folder.path },
|
||||
{
|
||||
label: folder.path.length ? folder.path[folder.path.length - 1] : layerLabel,
|
||||
label: folder.path[folder.path.length - 1] ?? "", // 맨 위(빈 경로)는 그리지 않음
|
||||
count: countItems(folder),
|
||||
folder: true,
|
||||
children: kids,
|
||||
@@ -184,7 +185,8 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
|
||||
const tree = buildFolders(view, text);
|
||||
const none = countItems(tree) === 0 && (searching || !tree.folders.length);
|
||||
body.replaceChildren(
|
||||
...renderTree([folderNode(tree, searching)], make),
|
||||
// 맨 위 뿌리 줄 없음 — 폴더 · 항목부터(PLAN 29-6 · 뿌리 메뉴 = 빈 자리 오른쪽 클릭 · 「+」)
|
||||
...renderTree(folderNode(tree, searching).children ?? [], make),
|
||||
...(none
|
||||
? [
|
||||
hint(
|
||||
@@ -401,5 +403,5 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
|
||||
}
|
||||
});
|
||||
|
||||
return { root, search: search.root, refresh, select };
|
||||
return { root, search: search.root, add, refresh, select };
|
||||
}
|
||||
|
||||
@@ -25,13 +25,18 @@ export const itemIdOf = (item: TreeItem): string => `i|${item.열}`;
|
||||
export const leafName = (item: TreeItem): string => item.경로[item.경로.length - 1] ?? item.이름;
|
||||
export const folderOf = (item: TreeItem): string[] => item.경로.slice(0, -1);
|
||||
|
||||
/** 찾기 — 경로 · 키 · 일위대가 키에 모든 낱말이 들어 있는 항목(빈 글이면 전부) */
|
||||
export function matches(item: TreeItem, text: string): boolean {
|
||||
/** 찾기 규칙 하나(PLAN 29-6) — 띄어쓰기로 나눈 낱말이 모두 들어 있으면 참(AND · 대소문자 무시 · 빈 글 = 참) ·
|
||||
* 구조물 조합 · 일위대가 라이브러리(나무 · 조합 목록)가 함께 씀 */
|
||||
export function matchText(parts: (string | null | undefined)[], text: string): boolean {
|
||||
const words = text.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
||||
const hay = [...item.경로, item.key, item.일위대가 ?? ""].join(" ").toLowerCase();
|
||||
const hay = parts.filter(Boolean).join(" ").toLowerCase();
|
||||
return words.every((word) => hay.includes(word));
|
||||
}
|
||||
|
||||
/** 나무 항목 찾기 — 이름 · 키 · 경로(폴더 · 구분 · 상세구분) · 단위 · 일위대가 키 */
|
||||
export const matches = (item: TreeItem, text: string): boolean =>
|
||||
matchText([...item.경로, item.이름, item.key, item.단위, item.일위대가], text);
|
||||
|
||||
/** 폴더 나무 — 찾는 글이 있으면 맞는 항목과 그 위 폴더만 · 없으면 빈 폴더까지 전부 */
|
||||
export function buildFolders(view: TreeView, text = ""): FolderNode {
|
||||
const searching = text.trim() !== "";
|
||||
|
||||
@@ -28,6 +28,8 @@ export interface LayerBarOptions {
|
||||
company: string | null;
|
||||
/** 다른 층을 누름 — `owner` null = 자기 층 */
|
||||
go: (root: Root, owner: string | null) => void;
|
||||
/** 단추 옆 「+」(고른 층 나무 맨 위 폴더 · 항목 더하기 · PLAN 29-6) */
|
||||
add?: HTMLElement;
|
||||
}
|
||||
|
||||
/** 회사 단추의 주인 — 볼 회사가 자기 회사면 null(자기 층) */
|
||||
@@ -71,7 +73,10 @@ export function createLayerBar(opt: LayerBarOptions): HTMLElement {
|
||||
...(opt.company ? [button("company", companyOwner(opt.company, user))] : []),
|
||||
...(ownPersonal ? [button("personal", null)] : []),
|
||||
];
|
||||
const row = el("div", { className: "m02-layers__row", children: buttons });
|
||||
const row = el("div", {
|
||||
className: "m02-layers__row",
|
||||
children: [...buttons, ...(opt.add ? [opt.add] : [])],
|
||||
});
|
||||
const bar = el("div", { className: "m02-layers", children: [row] });
|
||||
if (isSystem && opt.company) row.append(buildPeople(opt)); // 같은 줄 · 시스템 관리자만
|
||||
return bar;
|
||||
|
||||
@@ -76,12 +76,14 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise<Mounte
|
||||
);
|
||||
};
|
||||
// 층 단추 셋(PLAN 29-2) — 「일위대가 라이브러리」 · 「구조물 조합」 줄 아래 한 벌씩
|
||||
const layerBar = (open: OpenTarget): HTMLElement =>
|
||||
// 단추 옆 「+」 = 그 나무 맨 위 더하기(뿌리 줄을 갈음 · 29-6)
|
||||
const layerBar = (open: OpenTarget, add: HTMLElement): HTMLElement =>
|
||||
createLayerBar({
|
||||
user,
|
||||
active: { root: entry.root, owner },
|
||||
company,
|
||||
go: (next, nextOwner) => void go(next, nextOwner, open),
|
||||
add,
|
||||
});
|
||||
const main = createMain(
|
||||
isAdmin,
|
||||
@@ -114,7 +116,7 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise<Mounte
|
||||
getOpen: main.current,
|
||||
confirmLeave: main.confirmLeave,
|
||||
showView: main.showView,
|
||||
tree: [layerBar("구조물조합"), tree.root],
|
||||
tree: [layerBar("구조물조합", tree.add), tree.root],
|
||||
labor: ({ onOpen }) =>
|
||||
buildLaborSection({
|
||||
show: main.showView,
|
||||
@@ -123,7 +125,7 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise<Mounte
|
||||
onOpen();
|
||||
mark("일위대가");
|
||||
},
|
||||
bar: layerBar("일위대가"),
|
||||
bar: (add) => layerBar("일위대가", add),
|
||||
make: (reveal) => buildLaborLibrary({ scope: { layer, owner }, canEdit: isAdmin, reveal }),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -151,16 +151,26 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
onOpen: () => (matset.clear(), labor?.clear(), paint()),
|
||||
});
|
||||
const labor = opt.labor?.({ onOpen: () => (matset.clear(), options.clear(), paint()) });
|
||||
// 구조물 집계 양식 — 집계표 줄 아래 하위 컨테이너 넷(들여 · 같이 접힘) · 처음부터 펼침(집계표가 열려 있음)
|
||||
/** 컨테이너 제목을 펼치면 그 화면을 엶 — 같은 이름 줄 단추를 갈음(PLAN 29-6) */
|
||||
const openOnExpand = (sec: HTMLElement, run: () => void): HTMLElement => {
|
||||
sec.querySelector(".ui-collapsible__title")?.addEventListener("click", () => {
|
||||
if (sec.classList.contains("is-collapsed")) run();
|
||||
});
|
||||
return sec;
|
||||
};
|
||||
// 구조물 집계 양식 — 층 단추 · 나무 아래 하위 컨테이너 넷(들여 · 같이 접힘) · 처음부터 펼침(집계표가 열려 있음) ·
|
||||
// 같은 이름 줄 단추 없음(제목이 그 이름 · 29-6)
|
||||
const summary = section(
|
||||
L("M02_KindTable"),
|
||||
bodies.get("table")!,
|
||||
...(opt.tree ?? []),
|
||||
el("div", { className: "m02-side__items ui-sidebar-list", children: opt.tree ?? [] }),
|
||||
...COLUMN_KINDS.map((k, i) => section(k.label(), columnBodies[i])),
|
||||
matsetSection,
|
||||
);
|
||||
summary.classList.remove("is-collapsed");
|
||||
summary.classList.add("m02-side__summary");
|
||||
openOnExpand(summary, async () => {
|
||||
if (!isPicked("table", STRUCTURE_TABLE) && (await opt.confirmLeave())) opt.onOpen(null);
|
||||
});
|
||||
// 구조물 옵션 보이기 · 도면 양식은 시스템 층(마스터 템플릿)에만
|
||||
const extras =
|
||||
layer === "system"
|
||||
@@ -172,7 +182,7 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
const listHost = el("div", {
|
||||
className: "m02-side__lists ui-sidebar-panel",
|
||||
children: [
|
||||
...(labor ? [section(labor.title, labor.body)] : []),
|
||||
...(labor ? [openOnExpand(section(labor.title, labor.body), () => void labor.open())] : []),
|
||||
summary,
|
||||
...(extras ? [extras.옵션, extras.도면] : []),
|
||||
],
|
||||
|
||||
@@ -50,5 +50,14 @@ process.stdout.write(
|
||||
leaf: m.leafName(view.items[0]),
|
||||
dir: m.folderOf(view.items[0]),
|
||||
byKey: m.buildFolders(view, "ss000003").items.length + m.buildFolders(view, "SS000003").folders.length,
|
||||
// 찾기 AND(PLAN 29-6) — 낱말 모두 · 대소문자 무시 · 순서 무관 · 빈 글 = 참
|
||||
and: [
|
||||
m.matchText(["돌기슭막이", "메쌓기", "SS000001", "m"], "메쌓기 ss000001"),
|
||||
m.matchText(["돌기슭막이", "메쌓기", "SS000001", "m"], "ss000001 돌기슭"),
|
||||
m.matchText(["돌기슭막이", "메쌓기", "SS000001", "m"], "메쌓기 찰쌓기"),
|
||||
m.matchText(["UC3-000001", "브레인 측구", null, "배수"], ""),
|
||||
m.matchText(["UC3-000001", "브레인 측구", null, "배수"], "측구 배수"),
|
||||
],
|
||||
andTree: m.countItems(m.buildFolders(view, "돌기슭막이 찰쌓기")),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -91,11 +91,14 @@ def test_참조표는_열_머리를_못_고침():
|
||||
|
||||
def test_나무는_좌측_패널_찾기_칸은_상세_위쪽():
|
||||
side = _text(M02 / "M02_MasterTemplete_UI_Side.ts")
|
||||
assert "tree?: HTMLElement[];" in side and "...(opt.tree ?? [])," in side
|
||||
assert "tree?: HTMLElement[];" in side and "children: opt.tree ?? []" in side
|
||||
assert 'info.이름 === STRUCTURE_TABLE ? L("M02_KindTable")' in side # 좌측 줄 이름
|
||||
page = _text(M02 / "M02_MasterTemplete_UI_Page.ts")
|
||||
assert "createTreePanel({" in page and "tree.select(column)" in page
|
||||
assert 'tree: [layerBar("구조물조합"), tree.root],' in page and "search: tree.search," in page
|
||||
assert (
|
||||
'tree: [layerBar("구조물조합", tree.add), tree.root],' in page
|
||||
and "search: tree.search," in page
|
||||
)
|
||||
main = _text(M02 / "M02_MasterTemplete_UI_Main.ts")
|
||||
assert 'at.kind === "table" && at.name === STRUCTURE_TABLE && at.layer !== "project"' in main
|
||||
assert 'isCombo(next) ? L("M02_KindTable")' in main # 상세 제목
|
||||
|
||||
@@ -83,8 +83,8 @@ def test_뿌리를_바꿔도_일위대가_화면_유지_해시도_맞춤():
|
||||
page = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts").read_text("utf-8")
|
||||
assert "go: (next, nextOwner) => void go(next, nextOwner, open)," in page # 두 벌 같은 길
|
||||
assert (
|
||||
'bar: layerBar("일위대가"),' in page
|
||||
and 'tree: [layerBar("구조물조합"), tree.root],' in page
|
||||
'bar: (add) => layerBar("일위대가", add),' in page
|
||||
and 'tree: [layerBar("구조물조합", tree.add), tree.root],' in page
|
||||
)
|
||||
labor = page.split("buildLaborSection({")[1].split("make:")[0]
|
||||
assert 'mark("일위대가")' in labor # 여는 순간 해시 open
|
||||
|
||||
@@ -122,7 +122,8 @@ def test_좌측_줄은_구조물_집계_양식_아래_한_줄_같은_한_곳에
|
||||
)
|
||||
# 차례 = 일위대가 조합(27-2b) → 구조물 조합 → 옵션 → 도면 양식
|
||||
order = [
|
||||
"...(labor ? [section(labor.title, labor.body)] : []),",
|
||||
"...(labor ? [openOnExpand(section(labor.title, labor.body), () => void labor.open())]"
|
||||
" : []),",
|
||||
" summary,",
|
||||
" ...(extras ? [extras.옵션, extras.도면] : []),",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""M02 관리 화면 구조물 라이브러리(PLAN 26-3a) 자료 셈 회귀검증 — 폴더 나무 · 빈 폴더 · 깊이 5 · 찾기 · 폴더 목록.
|
||||
"""M02 구조물 라이브러리(PLAN 26-3a) 자료 셈 회귀검증 — 폴더 나무 · 빈 폴더 · 깊이 5 · 찾기 ·
|
||||
폴더 목록.
|
||||
|
||||
항목 경로 = 폴더 이름 배열 + 마지막 마디가 항목 이름 · 찾는 글이 있으면 맞는 항목과 위 폴더만.
|
||||
Node 헬퍼(`helper_m02_treepanel.cjs`)가 TS 를 트랜스파일해 돌리고, 여기서 값을 판정한다.
|
||||
@@ -58,6 +59,12 @@ def test_키로도_찾음(results):
|
||||
assert results["byKey"] == 1
|
||||
|
||||
|
||||
def test_찾기는_낱말_모두_AND(results):
|
||||
"""PLAN 29-6 — 띄어쓰기로 나눈 낱말을 모두 담은 것만 · 대소문자 무시 · 빈 글 = 전부."""
|
||||
assert results["and"] == [True, True, False, True, True]
|
||||
assert results["andTree"] == 1 # 「돌기슭막이 찰쌓기」 = c2 하나
|
||||
|
||||
|
||||
def test_폴더_목록과_위_길(results):
|
||||
assert ["다섯", "둘", "셋", "넷"] in results["folders"]
|
||||
assert ["빈 폴더"] in results["folders"]
|
||||
|
||||
@@ -104,11 +104,15 @@ export const ui_locales_m2 = {
|
||||
M02_RootReadOnly: ["읽기", "read-only"],
|
||||
M02_RootNone: ["볼 수 있는 층이 없음", "Nothing to show"],
|
||||
M02_PeoplePick: ["사람 고르기", "Pick a person"],
|
||||
M02_TreeSearch: ["항목 찾기", "Find item"],
|
||||
M02_TreeEmptyEdit: [
|
||||
"항목이 없음 — 맨 위 줄을 오른쪽 클릭해 폴더 · 항목을 더함",
|
||||
"No items — right-click the top row to add a folder or item",
|
||||
M02_TreeSearch: [
|
||||
"찾기 — 이름 · 키 · 폴더 · 단위 (낱말 여럿 = 모두 담은 것)",
|
||||
"Find — name, key, folder, unit (all words)",
|
||||
],
|
||||
M02_TreeEmptyEdit: [
|
||||
"항목이 없음 — 빈 자리 오른쪽 클릭 또는 층 단추 옆 「+」 로 폴더 · 항목을 더함",
|
||||
"No items — right-click an empty area or use + beside the layer buttons to add",
|
||||
],
|
||||
M02_TreeAddTop: ["맨 위에 폴더 · 항목 더하기", "Add folder or item at top"],
|
||||
M02_TreeEmpty: ["항목이 없음", "No items"],
|
||||
M02_TreeNoHit: ["찾은 항목이 없음", "No matching item"],
|
||||
M02_TreeAddFolder: ["폴더 더하기", "Add folder"],
|
||||
|
||||
Reference in New Issue
Block a user