Merge remote-tracking branch 'origin/dev' into sub_laptop_5
This commit is contained in:
@@ -595,7 +595,7 @@ def pipe_summary(
|
|||||||
notes = []
|
notes = []
|
||||||
missing = sheet.unlinked | {key for key, found in others.items() if not found}
|
missing = sheet.unlinked | {key for key, found in others.items() if not found}
|
||||||
if missing:
|
if missing:
|
||||||
notes.append(f"조합 사본 없는 시설 {len(missing)}곳 — 「미연결」 줄을 눌러 조합 연결")
|
notes.append(f"조합 사본 없는 시설 {len(missing)}곳 — 노랑 램프 줄을 눌러 조합 연결")
|
||||||
if no_length:
|
if no_length:
|
||||||
notes.append(f"관 연장 없음 {no_length}곳 — B06 횡단 [저장] 뒤 채워짐")
|
notes.append(f"관 연장 없음 {no_length}곳 — B06 횡단 [저장] 뒤 채워짐")
|
||||||
if no_basin:
|
if no_basin:
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
* 줄 = 구조물 · 배관이 놓인 측점(같은 측점은 한 줄 · 오름차순)
|
* 줄 = 구조물 · 배관이 놓인 측점(같은 측점은 한 줄 · 오름차순)
|
||||||
* 열 = 저장된 구조물 조합(본체 `summary_item` + 그룹 `group_items`) — 같은 층 · 주인 · 키는 한 열
|
* 열 = 저장된 구조물 조합(본체 `summary_item` + 그룹 `group_items`) — 같은 층 · 주인 · 키는 한 열
|
||||||
* 조합 = 프로젝트 안 사본이 있는 것만(`linkedOnly` · 서버 `linked_snapshots` · PLAN 143-1)
|
* 조합 = 프로젝트 안 사본이 있는 것만(`linkedOnly` · 서버 `linked_snapshots` · PLAN 143-1)
|
||||||
* 미연결 시설 = 서버 `unlinked`(셈한 칸 기준) — 그 측점도 줄(값 빈칸 · 「미연결」 · PLAN 154-1)
|
* 미연결 시설 = 서버 `unlinked`(셈한 칸 기준) — 그 측점도 줄(값 빈칸 · 노랑 램프 · PLAN 154-1 · 198-1)
|
||||||
* 「미지정」 열 글 · 같은 조합 열 칸 = 빈 조합 칸(`unassignedText` · `unassignedCombos` · 160-2)
|
* 램프 호버 글 = 빈 조합 칸(`unassignedText` · 160-2)
|
||||||
*
|
*
|
||||||
* 읽기만 — B05/B06 저장 자료(structures.json · pipe_points.json)를 고치지 않는다.
|
* 읽기만 — B05/B06 저장 자료(structures.json · pipe_points.json)를 고치지 않는다.
|
||||||
* 런타임 import 는 `common_util_summary_item` 하나 — 시험이 두 파일을 이어 붙여 돌림.
|
* 런타임 import 는 `common_util_summary_item` 하나 — 시험이 두 파일을 이어 붙여 돌림.
|
||||||
@@ -114,7 +114,7 @@ export interface ComboColumn {
|
|||||||
export const comboId = (item: SummaryItem): string =>
|
export const comboId = (item: SummaryItem): string =>
|
||||||
`${item.layer}|${item.owner ?? ""}|${item.key}`;
|
`${item.layer}|${item.owner ?? ""}|${item.key}`;
|
||||||
|
|
||||||
/** 측점 「미지정」 칸 글 — 시설마다 「이름: 빈 칸 · 빈 칸」(칸 이름 = 그룹 · 본체 = `body` · 160-2). */
|
/** 측점 램프 호버 글 —시설마다 「이름: 빈 칸 · 빈 칸」(칸 이름 = 그룹 · 본체 = `body` · 160-2). */
|
||||||
export const unassignedText = (facilities: readonly UnlinkedFacility[], body: string): string =>
|
export const unassignedText = (facilities: readonly UnlinkedFacility[], body: string): string =>
|
||||||
facilities
|
facilities
|
||||||
.map((f) => {
|
.map((f) => {
|
||||||
@@ -123,17 +123,6 @@ export const unassignedText = (facilities: readonly UnlinkedFacility[], body: st
|
|||||||
})
|
})
|
||||||
.join(" / ");
|
.join(" / ");
|
||||||
|
|
||||||
/** 빈 칸에 고른 조합 id — 같은 조합 열이 있으면 그 칸에 「미지정」(160-2). */
|
|
||||||
export function unassignedCombos(facilities: readonly UnlinkedFacility[]): Set<string> {
|
|
||||||
const ids = new Set<string>();
|
|
||||||
for (const f of facilities)
|
|
||||||
for (const slot of f.slots) {
|
|
||||||
const item = slot.linked ? null : readSummaryItem(slot.item);
|
|
||||||
if (item) ids.add(comboId(item));
|
|
||||||
}
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stationOf(thing: PlacedItem): number | null {
|
function stationOf(thing: PlacedItem): number | null {
|
||||||
const value = thing.chainage_m ?? thing.start_m;
|
const value = thing.chainage_m ?? thing.start_m;
|
||||||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
||||||
|
|||||||
@@ -44,6 +44,18 @@
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 구조물 집계표 미연결 램프(198-1) — 측점 칸 왼쪽 경고 노랑 점 하나(177-1 미연결 노랑 토큰) · 연결 줄은 없음 */
|
||||||
|
[data-b08-summary="table"] td.b08-summary__lamp::before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
margin-right: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-warning);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
/* 좌측 패널 옵션(122) — 연암 · 경암 두 칸 나란히 · 설명 한 줄 */
|
/* 좌측 패널 옵션(122) — 연암 · 경암 두 칸 나란히 · 설명 한 줄 */
|
||||||
.b08-options__row {
|
.b08-options__row {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
* 구조물 = GET …/route/structures · 배관 = GET …/drainage/pipe-points (읽기만)
|
* 구조물 = GET …/route/structures · 배관 = GET …/drainage/pipe-points (읽기만)
|
||||||
* 머리 두 줄 = 조합 구분(경로 첫 칸) · 조합 이름(나머지 경로) — 사본 manifest 경로(집계 응답 `linked`)
|
* 머리 두 줄 = 조합 구분(경로 첫 칸) · 조합 이름(나머지 경로) — 사본 manifest 경로(집계 응답 `linked`)
|
||||||
* 조합 · 측점 = 프로젝트 안 사본이 있는 것만(`linkedOnly` · PLAN 143-1) — 미연결 시설은 그 측점
|
* 조합 · 측점 = 프로젝트 안 사본이 있는 것만(`linkedOnly` · PLAN 143-1) — 미연결 시설은 그 측점
|
||||||
* 줄만 「· 미연결」(값 빈칸 · 서버 `unlinked` · PLAN 154-1) · 측점 옆 「미지정」 열 = 그 측점 빈 조합 칸
|
* 줄만 측점 칸 왼쪽 노랑 램프(값 빈칸 · 서버 `unlinked` · PLAN 154-1 · 198-1)
|
||||||
* · 같은 조합 열이 있으면 그 칸에도 「미지정」(160-2 · 화면만)
|
* · 램프 호버(공용 `ui_template_hover_info`) = 그 측점 빈 조합 칸(160-2 · 화면만)
|
||||||
* 횡단배수 열 · 칸 = GET …/quantity/summary(서버 단독 · PLAN 98 · 120) — 머리 구조물 · 형식 · 규격
|
* 횡단배수 열 · 칸 = GET …/quantity/summary(서버 단독 · PLAN 98 · 120) — 머리 구조물 · 형식 · 규격
|
||||||
* 3단 + 단위 줄 · 「관 공」 맨 윗 머리 = 관에 이은 조합 경로(있으면) · 서버가 펼친 종류(`types`)는
|
* 3단 + 단위 줄 · 「관 공」 맨 윗 머리 = 관에 이은 조합 경로(있으면) · 서버가 펼친 종류(`types`)는
|
||||||
* 조합 열에서 뺌 · 다른 구조물 열은 조합 그대로
|
* 조합 열에서 뺌 · 다른 구조물 열은 조합 그대로
|
||||||
@@ -24,6 +24,7 @@ import { fetchStructures } from "../B05_Profile/B05_Profile_Api_Structures";
|
|||||||
import { fetchLatestRoute, readRouteLatestCache } from "../B05_Profile/B05_Profile_Api_Fetch";
|
import { fetchLatestRoute, readRouteLatestCache } from "../B05_Profile/B05_Profile_Api_Fetch";
|
||||||
import { fetchDetailPipePoints } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
import { fetchDetailPipePoints } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||||
import { createDataTable, type DataColumn, type DataRow } from "@ui/ui_template_data_table";
|
import { createDataTable, type DataColumn, type DataRow } from "@ui/ui_template_data_table";
|
||||||
|
import { attachHoverInfo } from "@ui/ui_template_hover_info";
|
||||||
import { pipeStructureId } from "@ui/ui_template_item_picker_project";
|
import { pipeStructureId } from "@ui/ui_template_item_picker_project";
|
||||||
import { stationLabel } from "../common_util/common_util_station";
|
import { stationLabel } from "../common_util/common_util_station";
|
||||||
import { summaryTooltip, type SummaryItem } from "../common_util/common_util_summary_item";
|
import { summaryTooltip, type SummaryItem } from "../common_util/common_util_summary_item";
|
||||||
@@ -39,7 +40,6 @@ import {
|
|||||||
type LinkedSnapshot,
|
type LinkedSnapshot,
|
||||||
type PlacedItem,
|
type PlacedItem,
|
||||||
type UnlinkedFacility,
|
type UnlinkedFacility,
|
||||||
unassignedCombos,
|
|
||||||
unassignedText,
|
unassignedText,
|
||||||
unlinkedAt,
|
unlinkedAt,
|
||||||
widthOf,
|
widthOf,
|
||||||
@@ -98,6 +98,9 @@ async function fetchPipeSummary(projectId: string): Promise<PipeSummary> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PIPE_TOP = "관 공";
|
const PIPE_TOP = "관 공";
|
||||||
|
/** 미연결 램프 칸 class(`B08_Quantity_UI_Page.css` · 198-1) · 램프 자리 폭(12px 글 기준) */
|
||||||
|
const LAMP_CLASS = "b08-summary__lamp";
|
||||||
|
const LAMP_W = 14;
|
||||||
const stationKey = (station: number): number => Math.round(station * 100);
|
const stationKey = (station: number): number => Math.round(station * 100);
|
||||||
/** 열 소수 자리 — 칸 · 계 값이 쓰는 만큼(최대 2). */
|
/** 열 소수 자리 — 칸 · 계 값이 쓰는 만큼(최대 2). */
|
||||||
const placesOf = (values: number[]): number =>
|
const placesOf = (values: number[]): number =>
|
||||||
@@ -150,29 +153,14 @@ function buildTable(
|
|||||||
? { station: L("B08_Quantity_Summary_Total"), ...pipe.total }
|
? { station: L("B08_Quantity_Summary_Total"), ...pipe.total }
|
||||||
: null;
|
: null;
|
||||||
const labels = stations.map((station) => stationLabel(station, interval));
|
const labels = stations.map((station) => stationLabel(station, interval));
|
||||||
// 미연결 시설 측점 = 「· 미연결」(값 빈칸 · 누르면 바로 연결 · 154)
|
|
||||||
const shown = stations.map((station, i) =>
|
|
||||||
unlinkedAt(unlinked, station).length
|
|
||||||
? `${labels[i]} · ${L("B08_Quantity_Summary_Unlinked")}`
|
|
||||||
: labels[i],
|
|
||||||
);
|
|
||||||
const stationHead = L("B08_Quantity_Summary_Station");
|
const stationHead = L("B08_Quantity_Summary_Station");
|
||||||
// 160-2 — 빈 조합 칸 이름 · 같은 조합 열 칸에도 「미지정」(미연결 시설이 있을 때만 열)
|
// 198-1 — 미연결 시설 측점 = 측점 칸 왼쪽 노랑 램프(값 빈칸 · 누르면 바로 연결 · 154) · 호버 = 빈 조합 칸(160-2)
|
||||||
const missingHead = L("B08_Quantity_Summary_Unassigned");
|
const missing = stations.map((station) =>
|
||||||
const here = stations.map((station) => unlinkedAt(unlinked, station));
|
unassignedText(unlinkedAt(unlinked, station), L("B08_Quantity_Link_Body")),
|
||||||
const missing = here.map((at) => unassignedText(at, L("B08_Quantity_Link_Body")));
|
);
|
||||||
const hasMissing = missing.some(Boolean);
|
|
||||||
const comboIds = new Set(columns.map((column) => column.id));
|
|
||||||
const markOf = (at: readonly UnlinkedFacility[]): Record<string, string> =>
|
|
||||||
Object.fromEntries(
|
|
||||||
[...unassignedCombos(at)].filter((id) => comboIds.has(id)).map((id) => [id, missingHead]),
|
|
||||||
);
|
|
||||||
const table = createDataTable({
|
const table = createDataTable({
|
||||||
columns: [
|
columns: [
|
||||||
{ key: "station", label: stationHead, width: widthOf(stationHead, ...shown) },
|
{ key: "station", label: stationHead, width: widthOf(stationHead, ...labels) + LAMP_W },
|
||||||
...(hasMissing
|
|
||||||
? [{ key: "unassigned", label: missingHead, width: widthOf(missingHead, ...missing) }]
|
|
||||||
: []),
|
|
||||||
...columns.map((column) => ({
|
...columns.map((column) => ({
|
||||||
key: column.id,
|
key: column.id,
|
||||||
label: column.name,
|
label: column.name,
|
||||||
@@ -185,19 +173,27 @@ function buildTable(
|
|||||||
],
|
],
|
||||||
// 조합 열 칸은 아직 비움 — 사용자가 채울 자리라 「-」 대신 빈칸 · 배관 칸 = 서버 값
|
// 조합 열 칸은 아직 비움 — 사용자가 채울 자리라 「-」 대신 빈칸 · 배관 칸 = 서버 값
|
||||||
rows: stations.map((station, i) => ({
|
rows: stations.map((station, i) => ({
|
||||||
station: shown[i],
|
station: labels[i],
|
||||||
...(hasMissing ? { unassigned: missing[i] } : {}),
|
|
||||||
...markOf(here[i]),
|
|
||||||
...values.get(stationKey(station)),
|
...values.get(stationKey(station)),
|
||||||
})),
|
})),
|
||||||
total,
|
total,
|
||||||
onRowClick: (i) => onRowClick(stations[i], labels[i]),
|
onRowClick: (i) => onRowClick(stations[i], labels[i]),
|
||||||
frozen: hasMissing ? 2 : 1,
|
cellClass: (i, key) => (key === "station" && missing[i] ? LAMP_CLASS : null),
|
||||||
|
frozen: 1,
|
||||||
empty: "",
|
empty: "",
|
||||||
caption: L("B08_Quantity_Tab_StructureSummary"),
|
caption: L("B08_Quantity_Tab_StructureSummary"),
|
||||||
fillViewport: true,
|
fillViewport: true,
|
||||||
});
|
});
|
||||||
table.root.dataset.b08Summary = "table";
|
table.root.dataset.b08Summary = "table";
|
||||||
|
// 줄을 다시 그려도(보이는 줄만) 떠 있는 root 하나에 걸어 둠 — 줄 번호 = 줄 `data-i`
|
||||||
|
attachHoverInfo(
|
||||||
|
table.root,
|
||||||
|
(cell) => {
|
||||||
|
const why = missing[Number(cell.closest("tr")?.dataset.i)];
|
||||||
|
return why && `${L("B08_Quantity_Summary_LampTip")} — ${why}`;
|
||||||
|
},
|
||||||
|
`td.${LAMP_CLASS}`,
|
||||||
|
);
|
||||||
return table.root;
|
return table.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ def test_사본_없는_시설_알림_응답에_사본(root):
|
|||||||
out = _run(root)
|
out = _run(root)
|
||||||
# 100(유출 흙막이) · 200 · 300 · 400 · 500(기본 날개벽 잇기) · 600 · 개거 700 = 7곳
|
# 100(유출 흙막이) · 200 · 300 · 400 · 500(기본 날개벽 잇기) · 600 · 개거 700 = 7곳
|
||||||
assert not any("wing" in c["id"] for c in out["columns"])
|
assert not any("wing" in c["id"] for c in out["columns"])
|
||||||
assert "조합 사본 없는 시설 7곳 — 「미연결」 줄을 눌러 조합 연결" in out["notes"]
|
assert "조합 사본 없는 시설 7곳 — 노랑 램프 줄을 눌러 조합 연결" in out["notes"]
|
||||||
assert not any("관 연장 없음" in n for n in out["notes"])
|
assert not any("관 연장 없음" in n for n in out["notes"])
|
||||||
assert set(out["linked"]) == {"pipe@100.0", "pipe@500.0__gford"}
|
assert set(out["linked"]) == {"pipe@100.0", "pipe@500.0__gford"}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ def test_화면_조합_이름_구조물도는_사본에서():
|
|||||||
ui = (ROOT / "B08_Quantity/B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8")
|
ui = (ROOT / "B08_Quantity/B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8")
|
||||||
assert "loadPickerRows" not in ui # 층 목록 GET 안 씀
|
assert "loadPickerRows" not in ui # 층 목록 GET 안 씀
|
||||||
assert "linkedOnly(" in ui
|
assert "linkedOnly(" in ui
|
||||||
assert "unlinkedAt(" in ui and "B08_Quantity_Summary_Unlinked" in ui # 154-1 미연결 줄
|
assert "unlinkedAt(" in ui and "B08_Quantity_Summary_LampTip" in ui # 154-1 · 198-1 미연결 램프
|
||||||
drawing = (ROOT / "B08_Quantity/B08_Quantity_UI_Drawing.ts").read_text(encoding="utf-8")
|
drawing = (ROOT / "B08_Quantity/B08_Quantity_UI_Drawing.ts").read_text(encoding="utf-8")
|
||||||
assert "readTemplate" not in drawing # 층 폴백 없음
|
assert "readTemplate" not in drawing # 층 폴백 없음
|
||||||
assert "B08_Quantity_Sheets_NoCopy" in drawing
|
assert "B08_Quantity_Sheets_NoCopy" in drawing
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""160-2 B08 구조물 집계표 「미지정」 — 측점 옆 열 글 · 같은 조합 열 칸 표기(화면만).
|
"""160-2 B08 구조물 집계표 빈 조합 칸 글(198-1 부터 미연결 램프 호버 글 · 「미지정」 열 · 칸 표기는 지움).
|
||||||
|
|
||||||
`B08_Quantity_Summary_Data.ts` 를 `common_util_summary_item.ts` 와 이어 붙여 node 로 돌림(61-1 시험과 같은 꼴).
|
`B08_Quantity_Summary_Data.ts` 를 `common_util_summary_item.ts` 와 이어 붙여 node 로 돌림(61-1 시험과 같은 꼴).
|
||||||
"""
|
"""
|
||||||
@@ -47,31 +47,23 @@ FACILITIES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_미지정_글과_조합_칸():
|
def test_빈_조합_칸_글():
|
||||||
out = _run(
|
out = _run(
|
||||||
f"\nconst f = {json.dumps(FACILITIES, ensure_ascii=False)};"
|
f"\nconst f = {json.dumps(FACILITIES, ensure_ascii=False)};"
|
||||||
"\nconst at = (s) => unlinkedAt(f, s);"
|
"\nconst at = (s) => unlinkedAt(f, s);"
|
||||||
"\nconsole.log(JSON.stringify({"
|
"\nconsole.log(JSON.stringify({"
|
||||||
" t100: unassignedText(at(100), '본체'), t160: unassignedText(at(160), '본체'),"
|
" t100: unassignedText(at(100), '본체'), t160: unassignedText(at(160), '본체'),"
|
||||||
" t0: unassignedText(at(0), '본체'),"
|
" t0: unassignedText(at(0), '본체') }));"
|
||||||
" c100: [...unassignedCombos(at(100))], c160: [...unassignedCombos(at(160))] }));"
|
|
||||||
)
|
)
|
||||||
assert out["t100"] == "배관: 본체 · 관 / 세월교: 유출 구조"
|
assert out["t100"] == "배관: 본체 · 관 / 세월교: 유출 구조"
|
||||||
assert out["t160"] == "BOX" and out["t0"] == ""
|
assert out["t160"] == "BOX" and out["t0"] == ""
|
||||||
assert out["c100"] == ["system||SS2"] # 연결된 칸 · 고른 조합 없는 칸은 뺌
|
|
||||||
assert out["c160"] == []
|
|
||||||
|
|
||||||
|
|
||||||
def test_화면_연결():
|
def test_화면_연결():
|
||||||
summary = (ROOT / "B08_Quantity/B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8")
|
summary = (ROOT / "B08_Quantity/B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8")
|
||||||
assert (
|
assert (
|
||||||
'unassignedText(at, L("B08_Quantity_Link_Body"))' in summary
|
'unassignedText(unlinkedAt(unlinked, station), L("B08_Quantity_Link_Body"))' in summary
|
||||||
) # 칸 이름 = 모달과 같은 규칙
|
) # 칸 이름 = 모달과 같은 규칙
|
||||||
assert (
|
assert 'key: "unassigned"' not in summary and "unassignedCombos" not in summary # 198-1
|
||||||
'key: "unassigned", label: missingHead' in summary
|
|
||||||
and "frozen: hasMissing ? 2 : 1" in summary
|
|
||||||
)
|
|
||||||
assert "...markOf(here[i])," in summary and "filter((id) => comboIds.has(id))" in summary
|
|
||||||
assert '${L("B08_Quantity_Summary_Unlinked")}' in summary # 「· 미연결」 측점 표기 그대로
|
|
||||||
locale = (ROOT / "ui_template/ui_template_locale_b4.ts").read_text(encoding="utf-8")
|
locale = (ROOT / "ui_template/ui_template_locale_b4.ts").read_text(encoding="utf-8")
|
||||||
assert 'B08_Quantity_Summary_Unassigned: ["미지정", "Unassigned"]' in locale
|
assert "B08_Quantity_Summary_Unassigned" not in locale
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
"""198-1 B08 구조물 집계표 — 미연결 = 측점 칸 왼쪽 램프 하나 · 「미지정」 열 지움.
|
||||||
|
|
||||||
|
- 칸 class: 화면 소스의 `cellClass` 를 떼어 공용 표 `renderDataTableHtml` 로 그림(Node) — 미연결 줄 측점 칸만 램프
|
||||||
|
- 호버 글: 화면 소스의 호버 함수를 떼어 가짜 칸으로 부름 — 「구조물 조합 미연결 — 빈 칸 이름」 · 연결 줄은 없음
|
||||||
|
- CSS: 램프 = 경고 노랑 토큰(177-1) · 밝음 · 어두움 둘 다
|
||||||
|
- 줄 값 = 측점 + 서버 값뿐(「미지정」 · 「· 미연결」 글 없음 → 수량 · 합계 그대로)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
TS = ROOT / "config" / "node_modules" / "typescript"
|
||||||
|
UI = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
PRELUDE = f"""
|
||||||
|
const fs = require("fs");
|
||||||
|
const ts = require({json.dumps(str(TS))});
|
||||||
|
const js = (code) => ts.transpileModule(code, {{
|
||||||
|
compilerOptions: {{ module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }},
|
||||||
|
}}).outputText;
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _node(script: str):
|
||||||
|
out = subprocess.run(
|
||||||
|
["node", "-e", script], capture_output=True, text=True, encoding="utf-8", timeout=60
|
||||||
|
)
|
||||||
|
assert out.returncode == 0, out.stderr
|
||||||
|
return json.loads(out.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def _lamp_class() -> str:
|
||||||
|
m = re.search(r'const LAMP_CLASS = "([^"]+)";', UI)
|
||||||
|
assert m
|
||||||
|
return m.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_미연결_줄_측점_칸만_램프():
|
||||||
|
cell = re.search(r"cellClass: (\(i, key\) => .*?),\n", UI)
|
||||||
|
assert cell
|
||||||
|
table = json.dumps(str(ROOT / "ui_template" / "ui_template_data_table.ts"))
|
||||||
|
got = _node(
|
||||||
|
PRELUDE
|
||||||
|
+ f"""
|
||||||
|
const box = {{ exports: {{}} }};
|
||||||
|
new Function("exports", "module", "require", js(fs.readFileSync({table}, "utf8")))(
|
||||||
|
box.exports, box, (p) => (p.endsWith(".css") ? {{}} : require(p)));
|
||||||
|
const LAMP_CLASS = {json.dumps(_lamp_class())};
|
||||||
|
const missing = ["", "BOX", ""];
|
||||||
|
const cellClass = {cell.group(1)};
|
||||||
|
const html = box.exports.renderDataTableHtml({{
|
||||||
|
columns: [{{ key: "station", label: "측점" }}, {{ key: "c1", label: "조합" }}],
|
||||||
|
rows: [{{ station: "0+000" }}, {{ station: "0+020" }}, {{ station: "0+040", c1: 1 }}],
|
||||||
|
frozen: 1, empty: "", cellClass }});
|
||||||
|
const rows = html.split("<tbody>")[1].split("</tr>");
|
||||||
|
console.log(JSON.stringify({{
|
||||||
|
count: html.split(LAMP_CLASS).length - 1,
|
||||||
|
row1: rows[1],
|
||||||
|
row0: rows[0],
|
||||||
|
}}));
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assert got["count"] == 1
|
||||||
|
assert f'<td class="is-frozen is-edge {_lamp_class()}"' in got["row1"] # 측점 칸(첫 칸)
|
||||||
|
assert _lamp_class() not in got["row0"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_호버_글_까닭_한_줄():
|
||||||
|
hover = re.search(
|
||||||
|
r"attachHoverInfo\(\n\s*table\.root,\n\s*(\(cell\) => \{.*?\n\s*\}),\n", UI, re.S
|
||||||
|
)
|
||||||
|
assert hover
|
||||||
|
got = _node(
|
||||||
|
PRELUDE
|
||||||
|
+ f"""
|
||||||
|
const L = (k) => ({{ B08_Quantity_Summary_LampTip: "구조물 조합 미연결" }})[k];
|
||||||
|
const missing = ["", "배관: 본체 · 관 / 세월교: 유출 구조"];
|
||||||
|
const text = eval(js("(" + {json.dumps(hover.group(1))} + ")"));
|
||||||
|
const cell = (i) => ({{ closest: () => ({{ dataset: {{ i: String(i) }} }}) }});
|
||||||
|
console.log(JSON.stringify([text(cell(1)), text(cell(0)) || null]));
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assert got == ["구조물 조합 미연결 — 배관: 본체 · 관 / 세월교: 유출 구조", None]
|
||||||
|
assert 'import { attachHoverInfo } from "@ui/ui_template_hover_info";' in UI
|
||||||
|
assert "`td.${LAMP_CLASS}`," in UI # 램프 칸에만
|
||||||
|
locale = (ROOT / "ui_template" / "ui_template_locale_b4.ts").read_text(encoding="utf-8")
|
||||||
|
assert 'B08_Quantity_Summary_LampTip: ["구조물 조합 미연결",' in locale
|
||||||
|
|
||||||
|
|
||||||
|
def test_미지정_열_없음_줄_값_그대로():
|
||||||
|
assert 'key: "unassigned"' not in UI and "B08_Quantity_Summary_Unassigned" not in UI
|
||||||
|
assert 'L("B08_Quantity_Summary_Unlinked")' not in UI # 측점 「· 미연결」 글 지움
|
||||||
|
assert "frozen: 1," in UI
|
||||||
|
rows = re.search(r"rows: stations\.map\(\(station, i\) => \(\{(.*?)\}\)\),", UI, re.S)
|
||||||
|
assert rows and [s.strip() for s in rows.group(1).strip().split("\n")] == [
|
||||||
|
"station: labels[i],",
|
||||||
|
"...values.get(stationKey(station)),",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_램프_경고_노랑_토큰():
|
||||||
|
css = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Page.css").read_text(encoding="utf-8")
|
||||||
|
m = re.search(
|
||||||
|
r'\[data-b08-summary="table"\] td\.' + re.escape(_lamp_class()) + r"::before\s*\{([^}]*)\}",
|
||||||
|
css,
|
||||||
|
)
|
||||||
|
assert m
|
||||||
|
assert re.search(r"background:\s*var\(--color-warning\)", m.group(1))
|
||||||
|
assert "border-radius: 50%" in m.group(1)
|
||||||
|
theme = (ROOT / "ui_template" / "ui_template_theme.css").read_text(encoding="utf-8")
|
||||||
|
assert len(re.findall(r"--color-warning:\s*#", theme)) >= 2 # 밝음 · 어두움
|
||||||
@@ -96,5 +96,5 @@ def test_집계표는_공용_표():
|
|||||||
# PLAN 70-2 — 공용 많은 자료 표 · 측점 열 고정 · 칸은 빈칸(「—」 아님)
|
# PLAN 70-2 — 공용 많은 자료 표 · 측점 열 고정 · 칸은 빈칸(「—」 아님)
|
||||||
ui = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8")
|
ui = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8")
|
||||||
assert "createDataTable(" in ui and "@ui/ui_template_data_table" in ui
|
assert "createDataTable(" in ui and "@ui/ui_template_data_table" in ui
|
||||||
assert "frozen: hasMissing ? 2 : 1" in ui and 'empty: ""' in ui and "onRowClick" in ui
|
assert "frozen: 1," in ui and 'empty: ""' in ui and "onRowClick" in ui
|
||||||
assert 'document.createElement("table")' not in ui
|
assert 'document.createElement("table")' not in ui
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const ui_locales_b4 = {
|
|||||||
],
|
],
|
||||||
B08_Quantity_Summary_Total: ["계", "Total"],
|
B08_Quantity_Summary_Total: ["계", "Total"],
|
||||||
B08_Quantity_Summary_Unlinked: ["미연결", "Unlinked"],
|
B08_Quantity_Summary_Unlinked: ["미연결", "Unlinked"],
|
||||||
B08_Quantity_Summary_Unassigned: ["미지정", "Unassigned"],
|
B08_Quantity_Summary_LampTip: ["구조물 조합 미연결", "Structure combination unlinked"],
|
||||||
B08_Quantity_Link_Title: ["조합 연결", "Link combinations"],
|
B08_Quantity_Link_Title: ["조합 연결", "Link combinations"],
|
||||||
B08_Quantity_Link_Body: ["본체", "Body"],
|
B08_Quantity_Link_Body: ["본체", "Body"],
|
||||||
B08_Quantity_Link_Linked: ["연결됨", "Linked"],
|
B08_Quantity_Link_Linked: ["연결됨", "Linked"],
|
||||||
|
|||||||
Reference in New Issue
Block a user