From 55e565f3bf6e65ae2d579e9dff38d2a47b8a2cda Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 13 Oct 2026 07:56:53 +0900 Subject: [PATCH] =?UTF-8?q?feat(b09):=20=EC=A2=8C=EC=B8=A1=20=ED=95=98?= =?UTF-8?q?=EB=8B=A8=20=EA=B3=A0=EC=A0=95=20=EC=A4=84=20=E2=80=94=20[?= =?UTF-8?q?=EB=B0=95=EC=8A=A4=EB=B3=B4=EA=B8=B0|=ED=98=B8=ED=91=9C?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0]=20=ED=95=9C=20=EC=84=A4=EC=A0=95(=EB=AC=B6?= =?UTF-8?q?=EC=9D=8C=20=ED=83=AD=20=EC=85=8B=20=C2=B7=20=EC=98=9B=20?= =?UTF-8?q?=ED=83=AD=EB=B3=84=20=EA=B0=92=20=ED=95=98=EB=82=98=EB=A1=9C=20?= =?UTF-8?q?=C2=B7=20=ED=83=AD=20=EC=A4=84=20=ED=86=A0=EA=B8=80=20=EA=B1=B7?= =?UTF-8?q?=EC=9D=8C)=20=C2=B7=20[=ED=95=98=EC=9D=B4=ED=8D=BC=EB=A7=81?= =?UTF-8?q?=ED=81=AC|=ED=8C=9D=EC=97=85]=20=EC=8A=A4=EC=9C=84=EC=B9=98(pre?= =?UTF-8?q?f=20global=20=C2=B7=20=EA=B8=B0=EB=B3=B8=20=ED=8C=9D=EC=97=85)?= =?UTF-8?q?=20=C2=B7=20DRAFT.actions=20=EC=9E=90=EB=A6=AC=20=C2=B7=20[=3F]?= =?UTF-8?q?[=EC=A0=80=EC=9E=A5][=EB=82=B4=EC=97=AD=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94]=20=EA=B7=B8=EB=8C=80=EB=A1=9C=20(303-1=20=C2=B7=2030?= =?UTF-8?q?4-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD --- A00_Common/b_page_state.ts | 4 +- B09_Estimation/B09_Estimation_UI_Dock.ts | 106 +++++++++++++ B09_Estimation/B09_Estimation_UI_Page.css | 21 +++ B09_Estimation/B09_Estimation_UI_Shell.ts | 10 +- B09_Estimation/B09_Estimation_UI_Tabs.ts | 6 +- .../B09_Estimation_UI_Tabs_Groups.ts | 78 +++++----- .../tester/test_157_2_b09_sangeun_views.py | 15 +- resources/tester/test_171_3_b09_group_tabs.py | 29 ++-- resources/tester/test_181_2_b09_box_pick.py | 6 +- resources/tester/test_184_3_b09_bill_menu.py | 2 +- resources/tester/test_185_2_b09_view_bar.py | 16 +- .../tester/test_200_1_b09_box_tool_line.py | 2 +- .../tester/test_202_1_table_hint_bottom.py | 7 +- .../test_248_1_b09_group_jump_unpick.py | 2 +- .../tester/test_264_2_b09_schedule_options.py | 2 +- resources/tester/test_303_b09_row_popup.py | 139 ++++++++++++++++++ resources/tester/test_304_b09_view_setting.py | 103 +++++++++++++ 17 files changed, 460 insertions(+), 88 deletions(-) create mode 100644 B09_Estimation/B09_Estimation_UI_Dock.ts create mode 100644 resources/tester/test_303_b09_row_popup.py create mode 100644 resources/tester/test_304_b09_view_setting.py diff --git a/A00_Common/b_page_state.ts b/A00_Common/b_page_state.ts index 1a3396462..72e2f5a61 100644 --- a/A00_Common/b_page_state.ts +++ b/A00_Common/b_page_state.ts @@ -64,9 +64,11 @@ export const STATE_REGISTRY = { "m02-card-pills": { bucket: "pref", scope: "global" }, /** M01 단가산출 로직 상세 오른쪽 — 열린 패널 하나(flow · source · none) */ "m01-logic-panel": { bucket: "pref", scope: "global" }, - /** B09 묶음 탭(171-3) 보기 `{탭: cards · table}` · 가로 열 수 `{탭: 1~4}` */ + /** B09 묶음 탭(171-3) 보기 `cards · table`(304-1 셋 탭 한 값) · 가로 열 수 `{탭: 1~4}` */ "b09-group-view": { bucket: "pref", scope: "global" }, "b09-group-cols": { bucket: "pref", scope: "global" }, + /** B09 행 누름(303-1) `popup · link` */ + "b09-link-mode": { bucket: "pref", scope: "global" }, /** B05 계획노선 편집 창 — 보던 화면(회전 · 가운데 · 배율). 프로젝트마다 따로 */ "routeedit-view": { bucket: "pref", scope: "project" }, /** B05 계획노선 편집 창 — 시점 · 종점 · 교각점 고정. [저장] 대상이 아니라 draft 에 안 둠 */ diff --git a/B09_Estimation/B09_Estimation_UI_Dock.ts b/B09_Estimation/B09_Estimation_UI_Dock.ts new file mode 100644 index 000000000..c71514586 --- /dev/null +++ b/B09_Estimation/B09_Estimation_UI_Dock.ts @@ -0,0 +1,106 @@ +/* ============================================================================= + * B09_Estimation_UI_Dock.ts + * B09 좌측 하단 고정 줄(PLAN 303-1 · 304-1) — 위에서부터 + * ① [박스보기|호표보기] 묶음 탭 셋 보기(304-1 · 한 값) + * ② [하이퍼링크|팝업] 행 누름(303 · 기본 팝업) + * ③ `DRAFT.actions` 단추(초안 · 비면 줄 없음) + * ④ [?][저장][내역 초기화](도움말 줄 `data-help-row`) + * + * 스위치 값 = pref global(계정 공용 · [저장] 필요 없음) · 바닥 고정 = 공용 `.ui-sidebar-actions` 한 벌 + * ========================================================================== */ + +import { createButton } from "@ui/ui_template_elements"; +import { readState, writeState } from "../A00_Common/b_page_state"; +import { DRAFT } from "./B09_Estimation_UI_Tabs"; +import { + chooseGroupView, + groupView, + viewLabel, + type GroupView, +} from "./B09_Estimation_UI_Tabs_Groups"; + +export type LinkMode = "popup" | "link"; + +const LINK_LABEL: Record = { link: "하이퍼링크", popup: "팝업" }; + +/** 행 누름 길 — 모르는 값 · 못 읽음 = 팝업 */ +export function linkMode(): LinkMode { + try { + return readState("b09-link-mode") === "link" ? "link" : "popup"; + } catch { + return "popup"; + } +} + +function setLinkMode(mode: LinkMode): void { + try { + writeState("b09-link-mode", mode); + } catch { + // 기억 못 하면 기본(팝업)으로 돌아감 + } +} + +/** 단추 둘 스위치 — 켜진 쪽 filled · aria-pressed */ +function switchRow( + name: string, + values: T[], + label: (value: T) => string, + current: () => T, + pick: (value: T) => void, +): HTMLElement { + const row = document.createElement("div"); + row.className = "ui-btn-group b09-dock__row"; + row.dataset.b09Switch = name; + const buttons = values.map((value) => { + const button = createButton({ + label: label(value), + variant: "ghost", + onClick: () => { + if (value === current()) return; + pick(value); + sync(); + }, + }); + button.dataset.value = value; + return button; + }); + const sync = (): void => + buttons.forEach((button, k) => { + const on = values[k] === current(); + button.classList.toggle("ui-btn--filled", on); + button.classList.toggle("ui-btn--ghost", !on); + button.setAttribute("aria-pressed", String(on)); + }); + sync(); + row.append(...buttons); + return row; +} + +/** 하단 고정 줄 — onView = 보기 바뀜(묶음 탭이면 다시 그림) */ +export function createDock(buttons: HTMLElement[], onView: () => void): HTMLElement { + const views: GroupView[] = ["cards", "table"]; + const view = switchRow("view", views, viewLabel, groupView, (value) => { + chooseGroupView(value); + onView(); + }); + const link = switchRow( + "link", + ["link", "popup"], + (mode) => LINK_LABEL[mode], + linkMode, + setLinkMode, + ); + const extra = DRAFT.actions.map((make) => make()); + const draft = document.createElement("div"); + draft.className = "b09-dock__row"; + draft.dataset.b09DraftActions = ""; + draft.append(...extra); + const main = document.createElement("div"); + main.className = "b09-dock__row"; + main.dataset.helpRow = ""; + main.append(...buttons); + const dock = document.createElement("div"); + dock.className = "ui-sidebar-actions b09-dock"; + dock.append(view, link, ...(extra.length ? [draft] : []), main); + return dock; +} diff --git a/B09_Estimation/B09_Estimation_UI_Page.css b/B09_Estimation/B09_Estimation_UI_Page.css index 81137921b..a891f16bc 100644 --- a/B09_Estimation/B09_Estimation_UI_Page.css +++ b/B09_Estimation/B09_Estimation_UI_Page.css @@ -248,3 +248,24 @@ .b09-estimation [data-b09-table="bill"] tbody tr.is-drag-src td { opacity: 0.45; } + +/* 303-1 · 304-1 좌측 하단 고정 줄 — 줄 여럿 세로(보기 · 행 누름 스위치 · 초안 단추 · [?][저장][내역 초기화]) */ +.b09-dock.ui-sidebar-actions { + flex-direction: column; +} + +.b09-dock.ui-sidebar-actions > * { + flex: none; +} + +.b09-dock .b09-dock__row { + display: flex; + gap: var(--btn-group-gap); +} + +.b09-dock .b09-dock__row > .ui-btn:not(.ui-help-btn) { + flex: 1 1 0; + min-width: auto; + padding-inline: var(--spacing-4); + white-space: nowrap; +} diff --git a/B09_Estimation/B09_Estimation_UI_Shell.ts b/B09_Estimation/B09_Estimation_UI_Shell.ts index c84584803..948dc4b12 100644 --- a/B09_Estimation/B09_Estimation_UI_Shell.ts +++ b/B09_Estimation/B09_Estimation_UI_Shell.ts @@ -33,6 +33,7 @@ import { computeTotal } from "./B09_Estimation_Calc_Total"; import { createTabBars } from "./B09_Estimation_UI_TabBar"; import { createCheckPanel } from "./B09_Estimation_UI_Check"; import { tableOptionsOf, tabTableOf } from "./B09_Estimation_UI_Builders"; +import { createDock } from "./B09_Estimation_UI_Dock"; import { DRAFT, rowIndex, @@ -216,9 +217,8 @@ export function renderB09Estimation(root: HTMLElement): void { return made; }; const hint = spec.hint ? lineHint(note(spec.hint)) : null; // 236-1 안내 글 = 한 줄 - // 171-3 묶음 탭(일위대가표 · 중기사용료 · 단가산출근거) = 묶음 카드 기본 · 머리 단추로 표 + // 171-3 묶음 탭(일위대가표 · 중기사용료 · 단가산출근거) = 묶음 카드 기본 · 좌측 하단 스위치로 표(304-1) if (isGroupTab(tab)) { - const redraw = (): void => show(tab); return void panel.replaceChildren( ...renderGroupPanel({ tab, @@ -227,7 +227,6 @@ export function renderB09Estimation(root: HTMLElement): void { focus, goto, makeTable, - redraw, setTools: bars.setTools, }), ); @@ -321,9 +320,8 @@ export function renderB09Estimation(root: HTMLElement): void { // 카드 = 좌측 패널 공용 카드(269 · 「구조물 배치」 양식) const section = createPanelCard("데이터 점검"); section.append(refresh, check.list); - const actions = document.createElement("div"); - actions.className = "ui-sidebar-actions"; - actions.append(save, reset); + // 303-1 · 304-1 하단 고정 줄 — [박스보기|호표보기] · [하이퍼링크|팝업] · 초안 단추 · [?][저장][내역 초기화] + const actions = createDock([save, reset], () => isGroupTab(current) && show(current)); const left = document.createElement("div"); left.className = "ui-sidebar-panel b09-options"; left.append(section, ...DRAFT.panels.map((make) => make(() => user, setUser)), actions); diff --git a/B09_Estimation/B09_Estimation_UI_Tabs.ts b/B09_Estimation/B09_Estimation_UI_Tabs.ts index 97d937b6e..b17177b4b 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs.ts @@ -123,12 +123,14 @@ export type TabBuild = (ctx: TabContext) => TabTable | string; /** 초안 탭(252 · `draft/*_UI.ts` 가 TABS · TAB_GROUPS 와 함께 넣음 · 폴더째 지우면 빈 채로) — * builders = 탭 짜기 · extend = 브라우저 셈 곁(셈 결과에 더함) · - * panels = 왼쪽 패널 카드(264 · 「데이터 점검」 아래 · 지금 사용자 값 · 고침 → 캐시 · 다시 셈) */ + * panels = 왼쪽 패널 카드(264 · 「데이터 점검」 아래 · 지금 사용자 값 · 고침 → 캐시 · 다시 셈) · + * actions = 좌측 하단 고정 줄 단추(303-1 · [저장] 줄 위 한 줄 · 비면 줄 없음) */ export const DRAFT: { builders: Record; extend: Array<(payload: Row, user: Row, sheets: Row) => void>; panels: Array<(user: () => Row, setUser: (next: Row) => void) => HTMLElement>; -} = { builders: {}, extend: [], panels: [] }; + actions: Array<() => HTMLElement>; +} = { builders: {}, extend: [], panels: [], actions: [] }; /** 201-1 호표 묶음 레벨(보기 전용) — 머리 · 합계(제목 줄) = 1 · 그 밖 = 2 · key 열을 들여씀 */ export const blockLevel = (key: string, top: (index: number) => boolean): TabTable["level"] => ({ diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Groups.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Groups.ts index 641818e31..5ffe20bb2 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Groups.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Groups.ts @@ -1,22 +1,22 @@ /* ============================================================================= * B09_Estimation_UI_Tabs_Groups.ts * B09 묶음 탭 셋(PLAN 171-3) — 일위대가표 · 중기사용료 · 단가산출근거 = 공용 묶음 카드(171-2) 기본 · - * [표로 보기] = 공용 표 그대로(맞은편 단추 = [박스보기]). + * 호표보기 = 공용 표 그대로 · 고름 = 좌측 하단 [박스보기|호표보기] 한 벌(304-1 · 셋 탭 함께). * * 카드 = 탭 표 줄(`TabTable.rows`)을 호표(줄 키 앞머리)마다 묶음 — 값 · 풍선 · 누름 이동은 표와 같음 * 머리 줄 = 묶음 첫 줄 · 합계 줄 = 키 끝 `|합계` · `|총계` · 그 밖 = 몸 줄(표 줄 번호 그대로) * 목록표에서 온 호표(181-2) = 박스 붉은 외곽선 · 표 보기 = 그 호표 줄 구간 전체 붉은 외곽선 * 248-1 빨강 하나(공용 고름 강조 안 붙임 · 스크롤만) · 줄 · 카드 누름 · 방향키 · Esc = 빨강 풂 · * 누른 줄만 고름(is-focus) · Esc = 고름도 풂 - * 보기 · 가로 열 수 기억 = `b_page_state`(`b09-group-view` · `b09-group-cols` · 탭마다) · 못 읽으면 카드 · 2 - * 보기 단추 · 가로 열 수(202-1) = 페이지 보조 탭 줄 오른쪽(`setTools`) · 안내 글 = 표 · 카드 판 맨 아래 + * 보기 기억 = `b_page_state`(`b09-group-view` · 한 값 · 옛 탭별 값은 첫 읽기에 하나로) · 가로 열 수 = + * `b09-group-cols`(탭마다) · 못 읽으면 카드 · 2 + * 가로 열 수(202-1) = 페이지 보조 탭 줄 오른쪽(`setTools`) · 안내 글 = 표 · 카드 판 맨 아래 * 안내 글 길면 한 줄 줄임표 + 공용 호버로 전문(200-1) * 278-2 단가산출근거 박스 = 머리 줄(`|머리n`)을 운반 단가 창 ③ 칸 꼴로(`headSteps` 한 벌 · 몸 위 칸) · * 출처 = 그 칸 맨 위 · 호표 줄 · 총계 = 그대로 · 묶음 자료(`tabGroups`) · 표는 안 바뀜 * ========================================================================== */ import type { DataRow, DataTable } from "@ui/ui_template_data_table"; -import { createButton } from "@ui/ui_template_elements"; import { attachHoverInfo } from "@ui/ui_template_hover_info"; import { clampColumns, @@ -38,10 +38,8 @@ export const GROUP_TABS: TabId[] = ["dg", "machine_blocks", "sg"]; export const isGroupTab = (tab: TabId): boolean => GROUP_TABS.includes(tab); -/** 단추 글 = 바꿀 보기 이름 */ -export const viewLabel = (view: GroupView): string => (view === "cards" ? "표로 보기" : "박스보기"); - -export const flipView = (view: GroupView): GroupView => (view === "cards" ? "table" : "cards"); +/** 단추 글 = 그 보기 이름(304-1 좌측 스위치) */ +export const viewLabel = (view: GroupView): string => (view === "cards" ? "박스보기" : "호표보기"); type Saved = Record | null; @@ -53,19 +51,39 @@ const saved = (name: "b09-group-view" | "b09-group-cols"): Saved => { } }; -/** 고른 보기 — 모르는 값 · 못 읽음 = 카드 */ -export const readGroupView = (tab: TabId, kept: Saved = saved("b09-group-view")): GroupView => - kept?.[tab] === "table" ? "table" : "cards"; +/** 고른 보기(셋 탭 한 값) — 옛 탭별 값 = dg · 다른 탭 차례 · 모르는 값 · 못 읽음 = 카드 */ +export function readGroupView(kept: unknown = saved("b09-group-view")): GroupView { + const old = kept && typeof kept === "object" ? (kept as Record) : null; + const value = old ? (old.dg ?? old.machine_blocks ?? old.sg) : kept; + return value === "table" ? "table" : "cards"; +} + +/** 이 화면 안 보기(기억이 안 돼도 고른 보기 유지) */ +let chosen: GroupView | null = null; + +/** 지금 보기 — 첫 읽기에 옛 탭별 값을 한 값으로 옮겨 적음 */ +export function groupView(): GroupView { + if (chosen) return chosen; + const kept = saved("b09-group-view"); + chosen = readGroupView(kept); + if (kept && typeof kept === "object") chooseGroupView(chosen); + return chosen; +} + +export function chooseGroupView(view: GroupView): void { + chosen = view; + try { + writeState("b09-group-view", view); + } catch { + // 기억 못 해도 이 화면 안에서는 고른 값 그대로 + } +} /** 고른 가로 열 수 — 1~4 · 모르는 값 = 2 */ export const readGroupCols = (tab: TabId, kept: Saved = saved("b09-group-cols")): number => clampColumns(kept?.[tab]); -export function rememberGroup( - name: "b09-group-view" | "b09-group-cols", - tab: TabId, - value: unknown, -): void { +export function rememberGroup(name: "b09-group-cols", tab: TabId, value: unknown): void { try { writeState(name, { ...(saved(name) ?? {}), [tab]: value }); } catch { @@ -244,33 +262,18 @@ export function unpickOnRelease(root: HTMLElement): void { listenKeys(); } -/** 이 화면 안 보기(기억이 안 돼도 바꾼 보기 유지) */ -const chosen = new Map(); - -/** 묶음 탭 — 카드 = 카드 판 + 안내 글 · 표 = 표 + 안내 글 · 보기 단추 · 열 수 = `setTools`(보조 탭 줄 오른쪽) */ +/** 묶음 탭 — 카드 = 카드 판 + 안내 글 · 표 = 표 + 안내 글 · 열 수 = `setTools`(보조 탭 줄 오른쪽) */ export function renderGroupPanel(o: { tab: TabId; spec: TabTable; hint: HTMLElement | null; focus?: string; - goto: (link: Link | null) => void; + goto: (link: Link | null, anchor?: Element | null) => void; makeTable: (rowClass?: (index: number) => string | null) => DataTable; - redraw: () => void; setTools: (tools: HTMLElement[]) => void; }): HTMLElement[] { - const view = chosen.get(o.tab) ?? readGroupView(o.tab); + const view = groupView(); const lead = lineHint(o.hint); - const toggle = createButton({ - label: viewLabel(view), - variant: "ghost", - size: "sm", - onClick: () => { - chosen.set(o.tab, flipView(view)); - rememberGroup("b09-group-view", o.tab, flipView(view)); - o.redraw(); - }, - }); - toggle.dataset.b09GroupView = view; const at = o.focus === undefined ? -1 : rowIndex(o.spec.rows, o.focus); if (view === "cards") { const first = o.spec.columns[0]?.key ?? ""; @@ -281,19 +284,18 @@ export function renderGroupPanel(o: { : tabGroups(o.tab, o.spec), columns: readGroupCols(o.tab), onColumns: (n) => rememberGroup("b09-group-cols", o.tab, n), - onPick: (i) => o.goto(o.spec.link?.(i, first) ?? null), + onPick: (i) => + o.goto(o.spec.link?.(i, first) ?? null, cards.querySelector(`[data-at="${i}"]`)), focus: at, money: numberFormat(o.spec, "sum"), qty: numberFormat(o.spec, "qty"), - actions: [toggle], - placeTools: o.setTools, // 202-1 가로 열 수 · 보기 단추 = 보조 탭 줄 오른쪽 + placeTools: o.setTools, // 202-1 가로 열 수 = 보조 탭 줄 오른쪽 }); cards.dataset.b09Table = o.tab; live = { root: cards, unpick: (esc) => unpickCards(cards, esc) }; listenKeys(); return [cards, ...(lead ? [lead] : [])]; } - o.setTools([toggle]); let range = pickRange(o.spec.rows, o.focus); let chose = -1; // 누른 줄(다시 그려도 유지) const table = o.makeTable((i) => (i === chose ? "is-focus" : pickClass(range, i))); diff --git a/resources/tester/test_157_2_b09_sangeun_views.py b/resources/tester/test_157_2_b09_sangeun_views.py index b8cbb03d4..068632436 100644 --- a/resources/tester/test_157_2_b09_sangeun_views.py +++ b/resources/tester/test_157_2_b09_sangeun_views.py @@ -62,20 +62,19 @@ def test_157_2_두_보기_같은_값(): def test_157_2_단추_바꿈(): - out = _node( - "\nconsole.log(JSON.stringify([viewLabel('table'), viewLabel('cards')," - " flipView('table'), flipView(flipView('table'))]));" - ) - assert out == ["박스보기", "표로 보기", "cards", "table"] + out = _node("\nconsole.log(JSON.stringify([viewLabel('table'), viewLabel('cards')]));") + assert out == ["호표보기", "박스보기"] # 304-1 단추 글 = 그 보기 이름 def test_157_2_기억_실패_때_카드(): out = _node( - "\nconst a = [readGroupView('sg', null), readGroupView('sg', { sg: 'x' })," - " readGroupView('sg', { dg: 'table' }), readGroupView('sg', { sg: 'table' })];" + "\nconst a = [readGroupView(null), readGroupView({ sg: 'x' }), readGroupView({ dg: 'table' })," + " readGroupView({ sg: 'table' }), readGroupView('table'), readGroupView('x')," + " readGroupView({ dg: 'cards', sg: 'table' })];" "\nconsole.log(JSON.stringify(a));" ) - assert out == ["cards", "cards", "cards", "table"] + # 304-1 한 값 · 옛 탭별 값 = dg 먼저 · 없으면 다른 탭 · 모르는 값 = 카드 + assert out == ["cards", "cards", "table", "table", "table", "cards", "cards"] def test_157_2_화면_잇기(): diff --git a/resources/tester/test_171_3_b09_group_tabs.py b/resources/tester/test_171_3_b09_group_tabs.py index 139aab8d6..36668ade1 100644 --- a/resources/tester/test_171_3_b09_group_tabs.py +++ b/resources/tester/test_171_3_b09_group_tabs.py @@ -118,27 +118,27 @@ def test_단가산출근거_카드(): assert out["links"] == [None, {"tab": "labor", "key": "LB2"}] -def test_보기_기억_탭마다(): +def test_보기_한_값_열_수_탭마다(): out = run( [COMMON, TABS, GROUPS], - "\nconst a = [readGroupView('dg'), readGroupCols('dg')];" - "\nrememberGroup('b09-group-view', 'dg', 'table');" + "\nconst a = [readGroupView(), readGroupCols('dg')];" + "\nkept.set('b09-group-view', JSON.stringify({ machine_blocks: 'table' }));" # 옛 탭별 값 "\nrememberGroup('b09-group-cols', 'sg', 3);" - "\nconst b = [readGroupView('dg'), readGroupView('sg'), readGroupView('machine_blocks')," + "\nconst b = [groupView(), readGroupView(), readGroupView()," " readGroupCols('sg'), readGroupCols('dg'), readGroupCols('dg', { dg: 9 })];" "\nconsole.log(JSON.stringify({ a, b, kept: Object.fromEntries(kept)," " tabs: GROUP_TABS, other: ['bill', 'sum_material', 'sum_labor', 'sum_expense'," " 'sum_machine', 'dg_list'].map(isGroupTab) }));", ) assert out["a"] == ["cards", 2] # 기본 = 카드 · 가로 2 - assert out["b"] == ["table", "cards", "cards", 3, 2, 2] - assert json.loads(out["kept"]["b09-group-view"]) == {"dg": "table"} + assert out["b"] == ["table", "table", "table", 3, 2, 2] # 304-1 셋 탭 한 값 + assert json.loads(out["kept"]["b09-group-view"]) == "table" # 첫 읽기에 옛 값 → 한 값 assert json.loads(out["kept"]["b09-group-cols"]) == {"sg": 3} assert out["tabs"] == ["dg", "machine_blocks", "sg"] assert out["other"] == [False] * 6 # 집계표 넷 · 설계내역서 · 목록표 = 대상 밖 -def test_화면_보기_바꿈_누름_이동(): +def test_화면_보기_고름_누름_이동(): out = run( [COMMON, TABS, f"{B09}Labor.ts", GROUPS], f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});" @@ -147,26 +147,25 @@ def test_화면_보기_바꿈_누름_이동(): " redraw: () => redraws++, setTools: (t) => calls.push(t), makeTable: () => ({ root: document.createElement('table')," " focusRow: (i) => focused.push(i) }) };" "\nconst [view] = renderGroupPanel(o);" - "\nconst btn = calls[0].find((e) => e.dataset.b09GroupView);" - "\nconst label = btn.textContent;" + "\nconst label = calls.flat().filter((e) => e.dataset.b09GroupView).length;" "\nconst lit = view.querySelector('[data-at=\"3\"]').classList.contains('is-focus');" "\nconst box = view.querySelector('.ui-group-card').classList.contains('is-pick');" "\nview.querySelector('[data-at=\"2\"]').click();" "\nview.querySelector('[data-at=\"0\"]').click();" - "\nbtn.click();" + "\nconst before = calls.length; chooseGroupView('table');" "\nconst [view2] = renderGroupPanel(o);" "\nconsole.log(JSON.stringify({ label, table: view.dataset.b09Table, gone, lit, box, redraws," - " label2: calls[1].find((e) => e.dataset.b09GroupView).textContent, tag2: view2.tagName, focused," + " tools2: calls.length - before, tag2: view2.tagName, focused," " kept: Object.fromEntries(kept) }));", RAW, ) - assert out["label"] == "표로 보기" and out["table"] == "dg" # 기본 = 카드 + assert out["label"] == 0 and out["table"] == "dg" # 기본 = 카드 · 304-1 탭 줄 보기 단추 없음 assert out["gone"] == [{"tab": "labor", "key": "L00005"}, None] # 줄 누름 = 표와 같은 이동 assert out["lit"] is False # 248-1 이동해 온 줄 = 빨강 하나(줄 강조 안 붙임) assert out["box"] is True # 그 호표 박스 = 붉은 외곽선(181-2) - assert out["redraws"] == 1 - assert out["label2"] == "박스보기" and out["tag2"] == "TABLE" and out["focused"] == [3] - assert json.loads(out["kept"]["b09-group-view"]) == {"dg": "table"} + assert out["redraws"] == 0 + assert out["tools2"] == 0 and out["tag2"] == "TABLE" and out["focused"] == [3] + assert json.loads(out["kept"]["b09-group-view"]) == "table" def test_잇기(): diff --git a/resources/tester/test_181_2_b09_box_pick.py b/resources/tester/test_181_2_b09_box_pick.py index 0233c6458..211dd7009 100644 --- a/resources/tester/test_181_2_b09_box_pick.py +++ b/resources/tester/test_181_2_b09_box_pick.py @@ -1,6 +1,6 @@ """181-2 박스보기 — 이름 · 접기 없음 · 목록표에서 고른 호표 = 박스 붉은 외곽선 · 표 보기 = 줄 구간 전체. -- 단추 글 「박스보기」 ↔ 「표로 보기」 · 요약(접기) 보기 지움 +- 단추 글 「박스보기」 · 「호표보기」(304-1 좌측 스위치) · 요약(접기) 보기 지움 - 표 보기 = 고른 호표 줄 구간(키 `|` 앞머리 같은 줄) 모두 `is-pick` · 첫 줄 `is-pick-top` · 끝 줄 `is-pick-end` - 붉은 색 = `--color-danger`(B06 횡단 카드 고름 꼴) """ @@ -52,7 +52,7 @@ def test_표_보기_줄_class_넘김(): "\nconst o = { tab: 'dg', spec, hint: null, focus: 'B|1', goto: () => {}, redraw: () => {}, setTools: () => {}," " makeTable: (e) => { extra = e; return { root: document.createElement('table')," " focusRow: () => {} }; } };" - "\nchosen.set('dg', 'table');" + "\nchooseGroupView('table');" "\nrenderGroupPanel(o);" "\nconsole.log(JSON.stringify(spec.rows.map((_, i) => extra(i))));", ) @@ -63,7 +63,7 @@ def test_표_보기_줄_class_넘김(): def test_글_접기_없음_CSS_잇기(): groups = (ROOT / GROUPS).read_text("utf-8") - assert '"표로 보기" : "박스보기"' in groups and "호표별 보기" not in groups + assert '"박스보기" : "호표보기"' in groups and "호표별 보기" not in groups # 304-1 글 common = (ROOT / COMMON).read_text("utf-8") for gone in ("isSummary", "GROUP_SUMMARY_OVER", "is-closed", "모두 펼침", "모두 접기"): assert gone not in common diff --git a/resources/tester/test_184_3_b09_bill_menu.py b/resources/tester/test_184_3_b09_bill_menu.py index b3daf1fba..8fbb62580 100644 --- a/resources/tester/test_184_3_b09_bill_menu.py +++ b/resources/tester/test_184_3_b09_bill_menu.py @@ -286,4 +286,4 @@ def test_내역_초기화_배선(): assert "showConfirmDialog(" in block and "if (!ok) return;" in block assert "const { bill: _bill, ...rest } = user;" in block assert 'run("/estimation", "PUT"' in block and "{ user: rest }" in block - assert "actions.append(save, reset);" in src + assert "createDock([save, reset]," in src # 303-1 하단 고정 줄 맨 아래 줄 diff --git a/resources/tester/test_185_2_b09_view_bar.py b/resources/tester/test_185_2_b09_view_bar.py index 4aebd6501..e18aa71c4 100644 --- a/resources/tester/test_185_2_b09_view_bar.py +++ b/resources/tester/test_185_2_b09_view_bar.py @@ -1,6 +1,6 @@ -"""185-2 B09 묶음 탭 보기 바꿈 단추 글 한 줄 · 가로 열 수 단추와 같은 줄. +"""185-2 B09 묶음 탭 가로 열 수 단추 한 줄 · 보기 고름 = 좌측 하단(304-1 · 탭 줄에 없음). -- 보기 바꿈 단추 · 가로 열 수 = 도구 묶음(`setTools` · 202-1 로 페이지 보조 탭 줄 오른쪽) · 카드 판 머리 띠 = 묶음 수만 +- 가로 열 수 = 도구 묶음(`setTools` · 202-1 로 페이지 보조 탭 줄 오른쪽) · 카드 판 머리 띠 = 묶음 수만 - 단추 · 라벨 = `white-space: nowrap` · 도구 묶음 = 줄바꿈 없음(`flex-wrap: nowrap`) """ @@ -31,7 +31,7 @@ def _panel(view: str, hint: str): return run( [COMMON, TABS, f"{B09}Labor.ts", GROUPS], f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});" - f"\nchosen.set('dg', '{view}');" + f"\nchooseGroupView('{view}');" "\nconst tools = [];" f"\nconst o = {{ tab: 'dg', spec, hint: {hint}, goto: () => {{}}, redraw: () => {{}}," " setTools: (t) => tools.push(...t)," @@ -39,7 +39,7 @@ def _panel(view: str, hint: str): "\nconst out = renderGroupPanel(o);" "\nconst btn = tools.find((e) => e.dataset.b09GroupView);" "\nconst bar = out[0].all('.ui-group-cards__bar')[0] ?? null;" - "\nconsole.log(JSON.stringify({ n: out.length, first: out[0].className, label: btn.textContent," + "\nconsole.log(JSON.stringify({ n: out.length, first: out[0].className, label: btn ? btn.textContent : null, nTools: tools.length," " cols: tools.flatMap((e) => e.all('[data-group-cols]')).map((b) => b.dataset.groupCols)," " barBtn: bar ? bar.all('[data-b09-group-view]').length : null," " barCols: bar ? bar.all('[data-group-cols]').length : null }));", @@ -49,7 +49,7 @@ def _panel(view: str, hint: str): def test_카드_보기_단추가_열수_단추와_같은_도구_묶음에(): out = _panel("cards", "null") - assert out["label"] == "표로 보기" + assert out["label"] is None # 304-1 보기 단추 = 좌측 하단(탭 줄에 없음) assert out["cols"] == ["1", "2", "3", "4"] # 같은 묶음에 열 수 단추 넷 assert out["barBtn"] == 0 and out["barCols"] == 0 # 카드 판 머리 띠에는 없음 assert out["n"] == 1 # 안내 글 없으면 카드 판 하나뿐 @@ -63,8 +63,8 @@ def test_카드_보기_안내_글은_카드_판_아래에(): def test_표_보기_단추는_도구_묶음에(): out = _panel("table", "null") - assert out["label"] == "박스보기" and out["n"] == 1 - assert out["cols"] == [] # 표 보기 = 열 수 단추 없음 + assert out["label"] is None and out["n"] == 1 + assert out["cols"] == [] and out["nTools"] == 0 # 표 보기 = 도구 없음 def test_CSS_한_줄_nowrap(): @@ -81,4 +81,4 @@ def test_CSS_한_줄_nowrap(): page, ".b09-estimation__tools .ui-btn,\n.b09-estimation__tools .ui-group-cards__label" ) assert "white-space: nowrap" in tools - assert "actions: [toggle]" in (ROOT / GROUPS).read_text("utf-8") + assert "toggle" not in (ROOT / GROUPS).read_text("utf-8") # 304-1 탭 줄 토글 걷음 diff --git a/resources/tester/test_200_1_b09_box_tool_line.py b/resources/tester/test_200_1_b09_box_tool_line.py index e2ac75c0f..203d08e9b 100644 --- a/resources/tester/test_200_1_b09_box_tool_line.py +++ b/resources/tester/test_200_1_b09_box_tool_line.py @@ -36,7 +36,7 @@ def _panel(view: str, hint: str): return run( [COMMON, TABS, f"{B09}Labor.ts", GROUPS], f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});" - f"\nchosen.set('dg', '{view}');" + f"\nchooseGroupView('{view}');" f"\nconst hint = {hint};" "\nconst o = { tab: 'dg', spec, hint, goto: () => {}, redraw: () => {}, setTools: () => {}," " makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }) };" diff --git a/resources/tester/test_202_1_table_hint_bottom.py b/resources/tester/test_202_1_table_hint_bottom.py index 35bacaafc..8cbe24bb4 100644 --- a/resources/tester/test_202_1_table_hint_bottom.py +++ b/resources/tester/test_202_1_table_hint_bottom.py @@ -80,7 +80,7 @@ def test_묶음_탭_단추가_보조_줄에_안내는_아래에(): "\nconst hint = document.createElement('p');" "\nconst res = {};" "\nfor (const view of ['cards', 'table']) {" - "\n chosen.set('dg', view);" + "\n chooseGroupView(view);" "\n bars.draw('dg'); bars.setTools([]);" "\n const panel = renderGroupPanel({ tab: 'dg', spec, hint, goto: () => {}, redraw: () => {}," " makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} })," @@ -95,8 +95,9 @@ def test_묶음_탭_단추가_보조_줄에_안내는_아래에(): "\nconsole.log(JSON.stringify(res));", RAW, ) - assert out["cards"]["inRow"] == ["표로 보기"] and out["cards"]["cols"] == 4 - assert out["table"]["inRow"] == ["박스보기"] and out["table"]["cols"] == 0 + # 304-1 보기 고름 = 좌측 하단 스위치(보조 줄에 없음) · 가로 열 수 = 보조 줄 그대로 + assert out["cards"]["inRow"] == [] and out["cards"]["cols"] == 4 + assert out["table"]["inRow"] == [] and out["table"]["cols"] == 0 for view in ("cards", "table"): assert out[view]["inPanel"] == 0 # 판 안에는 단추 없음 assert out[view]["hintLast"] and out[view]["n"] == 2 # 판 + 안내 줄(맨 아래) diff --git a/resources/tester/test_248_1_b09_group_jump_unpick.py b/resources/tester/test_248_1_b09_group_jump_unpick.py index 0ebb03c45..5c555a3b9 100644 --- a/resources/tester/test_248_1_b09_group_jump_unpick.py +++ b/resources/tester/test_248_1_b09_group_jump_unpick.py @@ -60,7 +60,7 @@ def test_표_빨강_하나_누름_키_풂(): " return { root, focusRow: (i, mark) => scrolled.push([i, mark]) }; };" "\nconst o = { tab: 'dg', spec, hint: null, focus: 'B|1', goto: () => {}, redraw: () => {}," " setTools: () => {}, makeTable };" - "\nchosen.set('dg', 'table');" + "\nchooseGroupView('table');" "\nconst marks = () => [root.children.map((tr) => tr.className || null)," " spec.rows.map((_, i) => extra(i))];" "\nconst out = {};" diff --git a/resources/tester/test_264_2_b09_schedule_options.py b/resources/tester/test_264_2_b09_schedule_options.py index 2266968f5..93163e72f 100644 --- a/resources/tester/test_264_2_b09_schedule_options.py +++ b/resources/tester/test_264_2_b09_schedule_options.py @@ -205,6 +205,6 @@ def test_패널_훅_draft_없으면_빈칸(): tabs = (ROOT / "B09_Estimation/B09_Estimation_UI_Tabs.ts").read_text("utf-8") shell = (ROOT / "B09_Estimation/B09_Estimation_UI_Shell.ts").read_text("utf-8") ui = (ROOT / "B09_Estimation/draft/B09_Draft_Schedule_UI.ts").read_text("utf-8") - assert "{ builders: {}, extend: [], panels: [] }" in tabs # draft 없으면 카드 없음 + assert "{ builders: {}, extend: [], panels: [], actions: [] }" in tabs # draft 없으면 카드 없음 assert "...DRAFT.panels.map((make) => make(() => user, setUser))" in shell assert "DRAFT.panels.push(" in ui diff --git a/resources/tester/test_303_b09_row_popup.py b/resources/tester/test_303_b09_row_popup.py new file mode 100644 index 000000000..43be21b02 --- /dev/null +++ b/resources/tester/test_303_b09_row_popup.py @@ -0,0 +1,139 @@ +"""303 B09 행 고름 = 팝업 · 하이퍼링크 스위치 · 좌측 하단 줄. + +303-1 좌측 하단 고정 줄 — [박스보기|호표보기] · [하이퍼링크|팝업] · `DRAFT.actions`(비면 줄 없음) · + [?][저장][내역 초기화](도움말 줄) · 스위치 = pref global · 기본 = 팝업 +303-2 표 짜기 = `…_UI_Builders`(화면 · 팝업 · 인쇄 같은 옵션) +""" + +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from resources.tester.helper_b09_group_cards import COMMON, GROUPS, TABS, run # noqa: E402 + +B09 = ROOT / "B09_Estimation" +DOCK = "B09_Estimation/B09_Estimation_UI_Dock.ts" +BUILDERS = "B09_Estimation/B09_Estimation_UI_Builders.ts" + +DOCK_JS = ( + "\nconst save = document.createElement('button'); save.textContent = '저장';" + "\nconst reset = document.createElement('button'); reset.textContent = '내역 초기화';" + "\nconst shape = (dock) => dock.children.map((r) => ({ sw: r.dataset.b09Switch ?? null," + " draft: 'b09DraftActions' in r.dataset, help: 'helpRow' in r.dataset," + " text: r.children.map((b) => b.textContent) }));" +) + + +def test_하단_줄_차례_초안_단추_없으면_줄_없음(): + out = run( + [COMMON, TABS, GROUPS, DOCK], + DOCK_JS + "\nconst a = createDock([save, reset], () => {});" + "\nDRAFT.actions.push(() => { const b = document.createElement('button');" + " b.textContent = '인쇄 미리보기'; return b; });" + "\nconst b = createDock([save, reset], () => {});" + "\nconsole.log(JSON.stringify({ cls: a.className, a: shape(a), b: shape(b) }));", + ) + assert out["cls"] == "ui-sidebar-actions b09-dock" # 바닥 고정 = 공용 액션 줄 한 벌 + view = {"sw": "view", "draft": False, "help": False, "text": ["박스보기", "호표보기"]} + link = {"sw": "link", "draft": False, "help": False, "text": ["하이퍼링크", "팝업"]} + main = {"sw": None, "draft": False, "help": True, "text": ["저장", "내역 초기화"]} + assert out["a"] == [view, link, main] # 초안 단추 없음 = 줄 없음 + draft = {"sw": None, "draft": True, "help": False, "text": ["인쇄 미리보기"]} + assert out["b"] == [view, link, draft, main] # 초안 단추 = [저장] 줄 위 + + +def test_스위치_기본_팝업_누름_pref(): + out = run( + [COMMON, TABS, GROUPS, DOCK], + DOCK_JS + "\nconst dock = createDock([save, reset], () => {});" + "\nconst row = dock.children[1];" + "\nconst pressed = () => row.children.map((b) => b.attrs['aria-pressed']);" + "\nconst a = [linkMode(), pressed()];" + "\nrow.children[0].click();" + "\nconst b = [linkMode(), pressed(), kept.get('b09-link-mode')];" + "\nrow.children[1].click();" + "\nconst c = [linkMode(), pressed(), kept.get('b09-link-mode')];" + "\nkept.set('b09-link-mode', '\"x\"');" + "\nconsole.log(JSON.stringify({ a, b, c, odd: linkMode() }));", + ) + assert out["a"] == ["popup", ["false", "true"]] # 기본 = 팝업 + assert out["b"] == ["link", ["true", "false"], '"link"'] + assert out["c"] == ["popup", ["false", "true"], '"popup"'] + assert out["odd"] == "popup" # 모르는 값 = 팝업 + + +def test_DRAFT_actions_자리(): + tabs = (B09 / "B09_Estimation_UI_Tabs.ts").read_text("utf-8") + assert "actions: Array<() => HTMLElement>;" in tabs + assert "{ builders: {}, extend: [], panels: [], actions: [] }" in tabs + state = (ROOT / "A00_Common/b_page_state.ts").read_text("utf-8") + assert '"b09-link-mode": { bucket: "pref", scope: "global" },' in state + css = (B09 / "B09_Estimation_UI_Page.css").read_text("utf-8") + m = re.search(r"\.b09-dock\.ui-sidebar-actions\s*\{([^}]*)\}", css) + assert m and "flex-direction: column" in m.group(1) # 줄 여럿 세로 + + +# 표 짜기 — 탭 짜기 18 개 대신 가짜(줄 하나) · 옵션 = 표 그대로 넘김 +FAKE_BUILDS = "".join( + f"\nconst {name} = () => ({{ columns: [], rows: [{{ key: '{name}' }}] }});" + for name in re.findall( + r":\s*(build\w+),", + (ROOT / BUILDERS).read_text("utf-8"), + ) +) + + +def test_Builders_화면_같은_옵션(): + out = run( + [TABS, BUILDERS], + "\nDRAFT.builders.draft_x = () => '안내';" + "\nconst spec = { columns: [{ key: 'a', label: 'A' }], rows: [{ key: 'r' }], frozen: 2," + " editable: () => true, onEdit: () => {}, rowClass: () => 'x', cellClass: () => 'y'," + " level: { key: 'a', of: () => 1 }, mergeSame: ['a'], link: () => null, menu: () => [] };" + "\nconst o = tableOptionsOf(spec, 'bill');" + "\nconst plain = tableOptionsOf({ columns: [], rows: [], editOnDouble: false });" + "\nconsole.log(JSON.stringify({ keys: Object.keys(o).sort(), caption: o.caption," + " same: ['columns', 'rows', 'editable', 'onEdit', 'rowClass', 'cellClass', 'level', 'mergeSame']" + ".every((k) => o[k] === spec[k]), frozen: o.frozen, total: o.total, empty: o.empty," + " dbl: [o.editOnDouble, plain.editOnDouble], key: [o.keyEdit, plain.keyEdit]," + " plainCaption: plain.caption ?? null, plainFrozen: plain.frozen," + " bill: tabTableOf('bill', {}).rows[0].key, draft: tabTableOf('draft_x', {})," + " none: tabTableOf('nope', {}) }));", + FAKE_BUILDS, + ) + assert out["keys"] == sorted( + [ + "columns", + "rows", + "total", + "frozen", + "empty", + "caption", + "editable", + "editOnDouble", + "keyEdit", + "onEdit", + "rowClass", + "cellClass", + "level", + "mergeSame", + ] + ) # 누름 · 메뉴 · 풍선 없음(그리는 쪽 몫) + assert out["caption"] == "설계내역서" and out["same"] is True + assert out["frozen"] == 2 and out["total"] is None and out["empty"] == "" + assert out["dbl"] == [True, False] # 243-1 기본 = 더블클릭 + assert out["key"] == [False, True] # 설계내역서(메뉴) Enter · F2 = 레벨 키 몫 + assert out["plainCaption"] is None and out["plainFrozen"] == 0 + assert out["bill"] == "buildBill" and out["draft"] == "안내" and out["none"] is None + + +def test_화면이_Builders_옵션을_씀(): + shell = (B09 / "B09_Estimation_UI_Shell.ts").read_text("utf-8") + assert "...tableOptionsOf(spec, tab)," in shell + assert "const spec = tabTableOf(tab, ctx);" in shell + assert "BUILDERS" not in shell diff --git a/resources/tester/test_304_b09_view_setting.py b/resources/tester/test_304_b09_view_setting.py new file mode 100644 index 000000000..5f29ce13b --- /dev/null +++ b/resources/tester/test_304_b09_view_setting.py @@ -0,0 +1,103 @@ +"""304-1 B09 박스보기 · 호표보기 = 좌측 하단 한 설정(묶음 탭 셋 함께). + +- 한 값(`b09-group-view` · pref global)이 dg · machine_blocks · sg 셋에 함께 · 기본 = 박스(카드) +- 옛 탭별 값(`{탭: 보기}`) = 첫 읽기에 한 값으로(dg · 없으면 다른 탭 · 없으면 기본) 옮겨 적음 +- 보조 탭 줄 보기 토글 걷음 · 가로 열 수 단추(박스 보기)는 그대로 +- 좌측 하단 스위치 [박스보기|호표보기] 누름 = 값 바꿈 · 다시 그림 부름 +""" + +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from resources.tester.helper_b09_group_cards import COMMON, GROUPS, RAW, TABS, run # noqa: E402 + +LABOR = "B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts" +DOCK = "B09_Estimation/B09_Estimation_UI_Dock.ts" +DG = {"id": "DG1", "no": 1, "name": "찰쌓기", "unit": "m2", "lines": [], "sum": 0} +SPEC = ( + "\nconst spec = buildDaegaBlocks({ sheets: { daega_blocks: [" + + json.dumps(DG, ensure_ascii=False) + + "] } });" + "\nconst tools = [];" + "\nconst draw = (tab) => renderGroupPanel({ tab, spec, hint: null, goto: () => {}," + " setTools: (t) => tools.push(...t)," + " makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }) })[0];" + "\nconst kinds = () => ['dg', 'machine_blocks', 'sg'].map((t) => draw(t).tagName);" +) + + +def test_한_설정_셋_탭_함께(): + out = run( + [COMMON, TABS, LABOR, GROUPS], + SPEC + "\nconst a = kinds();" + "\nchooseGroupView('table');" + "\nconst b = kinds();" + "\nconsole.log(JSON.stringify({ a, b, kept: Object.fromEntries(kept)," + " toggles: tools.filter((e) => e.dataset.b09GroupView).length," + " cols: tools.flatMap((e) => e.all('[data-group-cols]')).length }));", + RAW, + ) + assert out["a"] == ["DIV"] * 3 # 기본 = 박스 + assert out["b"] == ["TABLE"] * 3 # 한 값이 셋 탭 함께 + assert json.loads(out["kept"]["b09-group-view"]) == "table" + assert out["toggles"] == 0 # 탭 줄 보기 토글 없음 + assert out["cols"] == 4 * 3 # 박스 보기 가로 열 수 단추 = 탭 줄 그대로 + + +def test_옛_탭별_값_첫_읽기에_한_값으로(): + cases = [ + ({"dg": "table", "sg": "cards"}, "table"), + ({"dg": "cards", "sg": "table"}, "cards"), # dg 먼저 + ({"sg": "table"}, "table"), # dg 없으면 다른 탭 + ({"machine_blocks": "table", "sg": "cards"}, "table"), + ({}, "cards"), # 없으면 기본 + ] + for old, want in cases: + out = run( + [COMMON, TABS, LABOR, GROUPS], + "\nkept.set('b09-group-view', " + + json.dumps(json.dumps(old)) + + ");" + + SPEC + + "\nconst got = kinds();" + "\nconsole.log(JSON.stringify({ got, kept: Object.fromEntries(kept) }));", + RAW, + ) + assert out["got"] == ["TABLE" if want == "table" else "DIV"] * 3, old + assert json.loads(out["kept"]["b09-group-view"]) == want, old # 옮겨 적음 + + +def test_좌측_스위치_누름(): + out = run( + [COMMON, TABS, GROUPS, DOCK], + "\nlet redraws = 0;" + "\nconst dock = createDock([document.createElement('button')], () => redraws++);" + "\nconst row = dock.children[0];" + "\nconst pressed = () => row.children.map((b) => b.attrs['aria-pressed']);" + "\nconst labels = row.children.map((b) => b.textContent);" + "\nconst a = pressed();" + "\nrow.children[0].click();" # 이미 박스 = 그대로 + "\nconst b = [redraws, pressed()];" + "\nrow.children[1].click();" + "\nconsole.log(JSON.stringify({ name: row.dataset.b09Switch, labels, a, b, c: [redraws, pressed()]," + " view: groupView(), kept: Object.fromEntries(kept) }));", + ) + assert out["name"] == "view" and out["labels"] == ["박스보기", "호표보기"] + assert out["a"] == ["true", "false"] # 기본 = 박스보기 + assert out["b"] == [0, ["true", "false"]] + assert out["c"] == [1, ["false", "true"]] and out["view"] == "table" + assert json.loads(out["kept"]["b09-group-view"]) == "table" # [저장] 없이 pref + + +def test_잇기(): + groups = (ROOT / GROUPS).read_text("utf-8") + assert "toggle" not in groups and "redraw" not in groups and "createButton" not in groups + shell = (ROOT / "B09_Estimation/B09_Estimation_UI_Shell.ts").read_text("utf-8") + assert "createDock([save, reset], () => isGroupTab(current) && show(current))" in shell + state = (ROOT / "A00_Common/b_page_state.ts").read_text("utf-8") + assert '"b09-group-view": { bucket: "pref", scope: "global" },' in state