feat(b08): 거리이정표 도구 왼쪽 아래 · 거래처 탭 = 길찾기 지도 + 그림 변경 · 초기화 (250-6)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
This commit is contained in:
@@ -36,7 +36,7 @@ CHILD = __file__ # 자식으로 돌릴 스크립트
|
||||
NO_BROWSER = "서버에서 브라우저(Edge · Chrome)를 못 띄움"
|
||||
CHANNELS = ("msedge", "chrome") # 시스템 브라우저 — 번들 chromium 은 안 받아 둠
|
||||
LINE_COLOR = "#E60046" # 화면 지도(`…_UI_Milestone_KakaoMap`) 길 선과 같은 색
|
||||
TAIL = " — 그림 올리기를 쓰세요."
|
||||
TAIL = " — [그림 변경]으로 넣으세요."
|
||||
|
||||
_lock = threading.Lock() # ponytail: 서버 전체 한 줄 — 브라우저 한 번에 하나(2~3초)
|
||||
|
||||
|
||||
@@ -56,10 +56,10 @@
|
||||
}
|
||||
|
||||
/* 244-2 다이어그램 — 상자 · 선 · km 글(테마 변수) · 스크롤 없음(244-8 · 그림 = 틀 안 맞춤) */
|
||||
/* 153 · 155 [자동 정렬](끌어 놓은 자리 있을 때만) · [요소 추가] — 다이어그램 왼쪽 위(오른쪽 위 = 진행단계 판 자리) */
|
||||
/* 153 · 155 · 157 [자동 정렬](끌어 놓은 자리 있을 때만) · [요소 추가] — 다이어그램 왼쪽 아래 · 단추 = 메인 탭 크기 */
|
||||
.b08-milestone__tools {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
@@ -248,3 +248,23 @@
|
||||
stroke-width: 3;
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
/* 157 경로 탭 머리 줄 — 거래처 이름 · 주소(왼) + [그림 변경] · [초기화](오른) · 글자 = 메인 탭 크기 */
|
||||
.b08-milestone__route-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-16);
|
||||
}
|
||||
|
||||
.b08-milestone__route-title {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: var(--spacing-8) var(--spacing-16);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.b08-milestone__route-addr {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@@ -267,7 +267,6 @@ export function createDiagram(
|
||||
// 153 [자동 정렬] — 끌어 놓은 자리 모두 지우고 방위 자동 자리로
|
||||
const arrange = createButton({
|
||||
label: "자동 정렬",
|
||||
size: "sm",
|
||||
variant: "ghost",
|
||||
onClick: () => {
|
||||
const fresh = liveMilestone(projectId);
|
||||
@@ -281,7 +280,6 @@ export function createDiagram(
|
||||
// 155 [요소 추가] — 기준점 · 거래처 등록(공사지 · 기준점 [+] 없앰) · 등록 뒤 연결 점으로 선 이음
|
||||
const addElem = createButton({
|
||||
label: "요소 추가",
|
||||
size: "sm",
|
||||
variant: "ghost",
|
||||
onClick: () => openPlaceModal(projectId, { add: true }, addRoute),
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import { el } from "@ui/ui_template_elements";
|
||||
import { request } from "./B08_Quantity_UI_Hydro";
|
||||
|
||||
export const NO_MAP =
|
||||
"카카오 JavaScript 키 · 도메인 등록 필요 — 지도 대신 [그림 올리기]로 넣으세요";
|
||||
"카카오 JavaScript 키 · 도메인 등록 필요 — 지도 대신 [그림 변경]으로 넣으세요";
|
||||
const SDK_FAIL = "KakaoSdkFail";
|
||||
const SDK = "https://dapi.kakao.com/v2/maps/sdk.js";
|
||||
const LINE_COLOR = "#E60046"; // 테마 무관 또렷한 길 선
|
||||
@@ -74,7 +74,7 @@ export function loadKakaoMaps(projectId: string): Promise<KakaoMaps> {
|
||||
fail(
|
||||
Object.assign(
|
||||
new Error(
|
||||
`카카오 JavaScript 키 도메인에 지금 주소(${location.origin}) 등록 필요 — 지도 대신 [그림 올리기]로 넣으세요`,
|
||||
`카카오 JavaScript 키 도메인에 지금 주소(${location.origin}) 등록 필요 — 지도 대신 [그림 변경]으로 넣으세요`,
|
||||
),
|
||||
{ name: SDK_FAIL },
|
||||
),
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/* =============================================================================
|
||||
* B08_Quantity_UI_Milestone_Route.ts
|
||||
* 거리이정표 경로 하위 탭(PLAN 244-1 · 244-4) — 길찾기 지도 결과 그림 하나만(구간 · 표 줄은 항목 정보 모달).
|
||||
* 거리이정표 경로 하위 탭(PLAN 244-1 · 244-4 · 157) — 머리 줄(거래처 이름 · 주소 + [그림 변경] · [초기화]) +
|
||||
* 길찾기 지도(연결선 모달과 같은 카카오 지도 · 표지 끌어 소재지 옮김) · 사용자 그림을 올리면 그 그림 · [초기화] = 지도로
|
||||
*
|
||||
* 그림 = [등록] · [길찾기] 때 서버 자동 캡처(244-11 · 카카오 지도 + 길 선 + 표지) · [그림 올리기] = 사용자
|
||||
* 그림 우선 · [자동 그림으로 되돌리기] = 사용자 그림만 지움 · [그림 지우기] = 둘 다 · 즉시 서버
|
||||
* 자동 그림 실패 = 까닭 한 줄(이 탭 안내 줄) · 사진 올리기
|
||||
* 길찾기 · 카카오 지도는 연결선 모달 몫(156 · 이 탭은 그림만 · 새 창 길찾기 · 지도 칸 없앰)
|
||||
* [그림 변경] = 사용자 그림 올림(이 탭 = 그 그림) · [초기화] = 사용자 그림만 지움(길찾기 지도로 · 즉시 서버)
|
||||
* 서버 자동 그림(244-11 · 선 이을 때 · 서류용)은 그대로 만듦 · 이 탭에는 안 보임(157)
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
@@ -15,14 +14,18 @@ import {
|
||||
} from "@ui/ui_template_context_menu";
|
||||
import { createButton, el, showToast } from "@ui/ui_template_elements";
|
||||
import type { Dest, MilestoneData, MilestoneRoute } from "./B08_Quantity_Milestone_Calc";
|
||||
import { editMap, type MapPin } from "./B08_Quantity_UI_Milestone_KakaoMap";
|
||||
import { reverseAddress } from "./B08_Quantity_UI_Milestone_Place";
|
||||
import {
|
||||
autoImageNote,
|
||||
canRevertImage,
|
||||
deleteImage,
|
||||
findSegmentKm,
|
||||
hasUserImage,
|
||||
imageBlobUrl,
|
||||
liveMilestone,
|
||||
putMilestone,
|
||||
routeImageKey,
|
||||
segmentPath,
|
||||
siteInfo,
|
||||
uploadImage,
|
||||
} from "./B08_Quantity_UI_Milestone_State";
|
||||
|
||||
@@ -66,94 +69,130 @@ export interface MilestoneView {
|
||||
}
|
||||
|
||||
export function routeView(projectId: string, routeId: string): MilestoneView {
|
||||
const title = el("h4");
|
||||
const HINT =
|
||||
"경로 그림 = 연결선 길찾기 때 서버가 카카오 지도에 길 선을 그려 자동으로 만듦 · [그림 올리기] = 내 그림 우선";
|
||||
const hint = el("p", { className: "b08-options__hint", text: HINT });
|
||||
hint.dataset.b08MilestoneRouteHint = "";
|
||||
// 157 머리 줄 = 거래처 이름 · 주소(왼) + [그림 변경] · [초기화](오른) · 글자 = 메인 탭 크기
|
||||
const name = el("strong", { className: "b08-milestone__route-name" });
|
||||
const address = el("span", { className: "b08-milestone__route-addr" });
|
||||
|
||||
// 그림 — 경로 하나 = 그림 하나
|
||||
// 몸 = 사용자 그림 있으면 그 그림 · 없으면 길찾기 지도(연결선 모달과 같은 지도 · 표지 끌어 소재지 옮김)
|
||||
const img = el("img", { className: "b08-milestone__image", attrs: { alt: "경로 그림" } });
|
||||
const noImage = el("p", { className: "ui-general-block__empty", text: "길찾기 그림 없음" });
|
||||
let shown: string | null | undefined;
|
||||
const loadImage = (force = false): void => {
|
||||
const name = routeImageKey(routeId);
|
||||
if (!force && name === shown) return;
|
||||
shown = name;
|
||||
img.hidden = !name;
|
||||
noImage.hidden = !!name;
|
||||
const loadImage = (): void => {
|
||||
const key = hasUserImage(routeId) ? routeImageKey(routeId) : null;
|
||||
img.hidden = !key;
|
||||
if (key === shown) return;
|
||||
shown = key;
|
||||
if (img.src) URL.revokeObjectURL(img.src);
|
||||
img.removeAttribute("src");
|
||||
if (name)
|
||||
if (key)
|
||||
imageBlobUrl(projectId, routeId).then(
|
||||
(url) => (img.src = url),
|
||||
(error: unknown) => showToast(`그림을 못 읽었습니다 — ${String(error)}`, "error"),
|
||||
);
|
||||
};
|
||||
const put = (picked: File): Promise<void> =>
|
||||
uploadImage(projectId, routeId, picked).then(
|
||||
() => loadImage(true),
|
||||
(error: unknown) => showToast(`그림을 올리지 못했습니다 — ${String(error)}`, "error"),
|
||||
);
|
||||
const file = el("input", { attrs: { type: "file", accept: "image/*" } });
|
||||
file.hidden = true;
|
||||
file.addEventListener("change", () => {
|
||||
const picked = file.files?.[0];
|
||||
file.value = "";
|
||||
if (picked) void put(picked);
|
||||
if (picked)
|
||||
void uploadImage(projectId, routeId, picked).then(
|
||||
() => refresh(),
|
||||
(error: unknown) => showToast(`그림을 올리지 못했습니다 — ${String(error)}`, "error"),
|
||||
);
|
||||
});
|
||||
|
||||
const upload = createButton({
|
||||
label: "그림 올리기",
|
||||
size: "sm",
|
||||
const change = createButton({
|
||||
label: "그림 변경",
|
||||
variant: "ghost",
|
||||
onClick: () => file.click(),
|
||||
});
|
||||
const revert = createButton({
|
||||
label: "자동 그림으로 되돌리기",
|
||||
size: "sm",
|
||||
change.dataset.b08MilestoneImageChange = "";
|
||||
// 초기화 = 사용자 그림만 지우고 길찾기 지도로 되돌림
|
||||
const reset = createButton({
|
||||
label: "초기화",
|
||||
variant: "ghost",
|
||||
onClick: () =>
|
||||
void deleteImage(projectId, routeId, true).catch((error: unknown) =>
|
||||
showToast(`자동 그림으로 되돌리지 못했습니다 — ${String(error)}`, "error"),
|
||||
void deleteImage(projectId, routeId, true).then(
|
||||
() => refresh(),
|
||||
(error: unknown) => showToast(`초기화하지 못했습니다 — ${String(error)}`, "error"),
|
||||
),
|
||||
});
|
||||
revert.dataset.b08MilestoneRevert = "";
|
||||
const removeImage = createButton({
|
||||
label: "그림 지우기",
|
||||
size: "sm",
|
||||
variant: "ghost",
|
||||
onClick: () =>
|
||||
void deleteImage(projectId, routeId).catch((error: unknown) =>
|
||||
showToast(`그림을 지우지 못했습니다 — ${String(error)}`, "error"),
|
||||
),
|
||||
reset.dataset.b08MilestoneRevert = "";
|
||||
|
||||
const start = (): [number, number] | null => siteInfo()?.start_lonlat ?? null;
|
||||
const pins = (): MapPin[] => {
|
||||
const d = liveMilestone(projectId);
|
||||
const r = d.routes.find((x) => x.id === routeId);
|
||||
const out: MapPin[] = [];
|
||||
const add = (key: string, label: string, lon?: number | null, lat?: number | null): void => {
|
||||
if (lon != null && lat != null)
|
||||
out.push({ key, name: label, lon, lat, movable: key !== "start" });
|
||||
};
|
||||
if (!r) return out;
|
||||
add(r.id, r.name || "거래처", r.lon, r.lat);
|
||||
if (r.linked === false) return out;
|
||||
if (d.base.enabled && r.via_base) add("base", d.base.name || "기준점", d.base.lon, d.base.lat);
|
||||
add("start", "시점", start()?.[0], start()?.[1]);
|
||||
return out;
|
||||
};
|
||||
const lines = (): Array<Array<[number, number]>> => {
|
||||
const d = liveMilestone(projectId);
|
||||
const r = d.routes.find((x) => x.id === routeId);
|
||||
const via = !!r && r.linked !== false && d.base.enabled && r.via_base;
|
||||
return [segmentPath(routeId), ...(via ? [segmentPath("base")] : [])].filter(
|
||||
(p) => p.length >= 2,
|
||||
);
|
||||
};
|
||||
const map = editMap(projectId, {
|
||||
hint: "",
|
||||
onMove: (key, lon, lat) =>
|
||||
void reverseAddress(projectId, lon, lat).then((addr) => {
|
||||
const fresh = liveMilestone(projectId);
|
||||
const target = key === "base" ? fresh.base : fresh.routes.find((x) => x.id === key);
|
||||
if (!target) return;
|
||||
Object.assign(target, { lon, lat, address: addr });
|
||||
putMilestone(projectId, fresh);
|
||||
void findSegmentKm(projectId, key === "base" ? "base" : routeId).then(() => drawMap());
|
||||
}),
|
||||
});
|
||||
const drawMap = (): void => map.set(pins(), lines());
|
||||
let asked = false; // 길 선이 아직 없으면 열 때 한 번 길찾기(그림 · 알림 없음)
|
||||
const ensurePath = (): void => {
|
||||
if (asked || segmentPath(routeId).length) return;
|
||||
asked = true;
|
||||
void findSegmentKm(projectId, routeId, false).then(() => drawMap());
|
||||
};
|
||||
|
||||
const root = el("section", {
|
||||
className: "b08-milestone__route ui-card",
|
||||
attrs: { "data-b08-milestone-route": routeId },
|
||||
children: [
|
||||
title,
|
||||
el("div", {
|
||||
className: "b08-milestone__bar",
|
||||
children: [upload, revert, removeImage, file],
|
||||
className: "b08-milestone__route-head",
|
||||
children: [
|
||||
el("div", { className: "b08-milestone__route-title", children: [name, address] }),
|
||||
el("div", { className: "b08-milestone__bar", children: [change, reset, file] }),
|
||||
],
|
||||
}),
|
||||
hint,
|
||||
noImage,
|
||||
img,
|
||||
map.root,
|
||||
],
|
||||
});
|
||||
|
||||
const refresh = (): void => {
|
||||
function refresh(): void {
|
||||
const d = liveMilestone(projectId);
|
||||
const r = d.routes.find((x) => x.id === routeId);
|
||||
if (!r) return;
|
||||
const dest = DEST_TEXT[r.rows[0]?.dest ?? "start"];
|
||||
title.textContent = `${r.name || "거래처"} → ${dest}${r.address ? ` · ${r.address}` : ""}`;
|
||||
name.textContent = `${r.name || "거래처"} → ${DEST_TEXT[r.rows[0]?.dest ?? "start"]}`;
|
||||
address.textContent = r.address || "";
|
||||
loadImage();
|
||||
revert.hidden = !canRevertImage(routeId);
|
||||
hint.textContent = autoImageNote(routeId) ?? HINT;
|
||||
};
|
||||
const user = hasUserImage(routeId);
|
||||
reset.hidden = !user;
|
||||
map.root.hidden = user;
|
||||
if (!user) {
|
||||
drawMap();
|
||||
ensurePath();
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
return { root, refresh };
|
||||
}
|
||||
|
||||
@@ -81,11 +81,10 @@ export function siteAddress(projectId: string, midM: number | null): SiteAddress
|
||||
}
|
||||
export const routeImages = (): Record<string, string> => saved?.images ?? {};
|
||||
|
||||
/** 보이는 그림이 사용자 그림이고 자동 그림도 있음 = 「자동 그림으로 되돌리기」 */
|
||||
export const canRevertImage = (routeId: string): boolean => {
|
||||
/** 사용자가 올린 그림이 있음(157 · 자동 그림과 다른 이름) — 경로 탭 = 그 그림 · 없으면 길찾기 지도 */
|
||||
export const hasUserImage = (routeId: string): boolean => {
|
||||
const name = saved?.images[routeId];
|
||||
const auto = saved?.auto_images?.[routeId];
|
||||
return !!name && !!auto && name !== auto;
|
||||
return !!name && name !== saved?.auto_images?.[routeId];
|
||||
};
|
||||
|
||||
/** 경로 그림 다시 읽을 표(이름 + 자동 그림 새로 만든 횟수 — 이름이 같아도 새 그림) */
|
||||
@@ -95,10 +94,6 @@ export const routeImageKey = (routeId: string): string | null => {
|
||||
return name ? `${name}#${imageRev.get(routeId) ?? 0}` : null;
|
||||
};
|
||||
|
||||
/** 경로 그림 자동 상태 한 줄(만드는 중 · 실패 까닭) — 없으면 null */
|
||||
const autoNotes = new Map<string, string>();
|
||||
export const autoImageNote = (routeId: string): string | null => autoNotes.get(routeId) ?? null;
|
||||
|
||||
export const hasMilestoneDraft = (projectId: string): boolean =>
|
||||
readState<MilestoneData>("b08-milestone", projectId) !== null;
|
||||
|
||||
@@ -189,8 +184,6 @@ export async function autoRouteImage(
|
||||
0,
|
||||
2,
|
||||
);
|
||||
autoNotes.set(routeId, "경로 그림 만드는 중…(서버 지도 캡처)");
|
||||
changes.dispatchEvent(new Event("change"));
|
||||
let reason: string | null;
|
||||
try {
|
||||
const res = await request<{ name: string | null; reason: string | null }>(
|
||||
@@ -205,12 +198,9 @@ export async function autoRouteImage(
|
||||
imageRev.set(routeId, (imageRev.get(routeId) ?? 0) + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
reason = `경로 그림 자동 캡처 요청 실패(${String(error)}) — 그림 올리기를 쓰세요.`;
|
||||
reason = `경로 그림 자동 캡처 요청 실패(${String(error)}) — [그림 변경]으로 넣으세요.`;
|
||||
}
|
||||
if (reason) {
|
||||
autoNotes.set(routeId, reason);
|
||||
showToast(reason, "warning");
|
||||
} else autoNotes.delete(routeId);
|
||||
if (reason) showToast(reason, "warning");
|
||||
changes.dispatchEvent(new Event("change"));
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,6 @@ def test_SDK_401_도메인_등록_안내():
|
||||
got = _view(NO_MEDIA + sdk_401) # window.kakao 없음 → script 넣음 → 401 = onerror
|
||||
assert len(got["injected"]) == 1 and "sdk.js?appkey=fake-js-key" in got["injected"][0]
|
||||
assert got["hint"] == (
|
||||
f"카카오 JavaScript 키 도메인에 지금 주소({ORIGIN}) 등록 필요 — 지도 대신 [그림 올리기]로 넣으세요"
|
||||
f"카카오 JavaScript 키 도메인에 지금 주소({ORIGIN}) 등록 필요 — 지도 대신 [그림 변경]으로 넣으세요"
|
||||
)
|
||||
assert got["boxHidden"]
|
||||
|
||||
@@ -201,7 +201,7 @@ def test_사용자_그림_우선_되돌리기(tmp_path, monkeypatch):
|
||||
|
||||
|
||||
def test_실패_까닭_한_줄(tmp_path, monkeypatch):
|
||||
tail = " — 그림 올리기를 쓰세요."
|
||||
tail = " — [그림 변경]으로 넣으세요."
|
||||
cases = {
|
||||
"no_browser": "경로 그림 자동 캡처 실패 — 서버에서 브라우저(Edge · Chrome)를 못 띄움",
|
||||
"timeout": "경로 그림 자동 캡처 실패 — 지도 그림이 20초 안에 안 끝남",
|
||||
@@ -269,14 +269,11 @@ def test_화면_브라우저_캡처_없음_길찾기_뒤_자동():
|
||||
assert "JSON.stringify({ lines, marks })" in state and "/image/auto?route=" in state
|
||||
assert "user_only=true" in state and "showToast(reason," in state
|
||||
route = read("B08_Quantity_UI_Milestone_Route.ts")
|
||||
assert (
|
||||
'label: "자동 그림으로 되돌리기"' in route
|
||||
and "deleteImage(projectId, routeId, true)" in route
|
||||
)
|
||||
assert "revert.hidden = !canRevertImage(routeId);" in route
|
||||
assert "hint.textContent = autoImageNote(routeId) ?? HINT;" in route
|
||||
# 157 [초기화] = 사용자 그림만 지우고 길찾기 지도로 · 쓸데없는 안내 줄 없음
|
||||
assert 'label: "초기화"' in route and "deleteImage(projectId, routeId, true)" in route
|
||||
assert "reset.hidden = !user;" in route and "HINT" not in route
|
||||
kmap = read("B08_Quantity_UI_Milestone_KakaoMap.ts")
|
||||
assert "등록 필요 — 지도 대신 [그림 올리기]로 넣으세요" in kmap # SDK 도메인 안내 한 줄은 남김
|
||||
assert "등록 필요 — 지도 대신 [그림 변경]으로 넣으세요" in kmap # SDK 도메인 안내 한 줄은 남김
|
||||
big = ("Milestone_Capture.py", "Router_Milestone.py", "UI_Milestone_State.ts")
|
||||
for name in (f"B08_Quantity_{n}" for n in big):
|
||||
assert len(read(name).splitlines()) <= 700
|
||||
|
||||
@@ -169,7 +169,7 @@ def test_화면_모달_팬줌_그림탭():
|
||||
"createDataTable" not in route and "구간" not in route.split("export function routeView")[1]
|
||||
)
|
||||
assert "getDisplayMedia" not in route and "uploadImage(projectId, routeId, picked)" in route
|
||||
assert 'label: "그림 올리기"' in route
|
||||
assert 'label: "그림 변경"' in route # 157 그림 올리기 → 그림 변경
|
||||
page = _read("B08_Quantity/B08_Quantity_UI_Milestone.ts")
|
||||
assert 'label: "+ 추가"' not in page and 'label: "삭제"' not in page
|
||||
for rel in ("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts",
|
||||
|
||||
@@ -177,7 +177,7 @@ def test_키_없음_안내_한_줄():
|
||||
assert got["asked"] == ["/projects/p1/quantity/milestone/map-key"]
|
||||
assert got["injected"] == [] # SDK 안 부름
|
||||
assert got["hint"] == (
|
||||
"카카오 JavaScript 키 · 도메인 등록 필요 — 지도 대신 [그림 올리기]로 넣으세요"
|
||||
"카카오 JavaScript 키 · 도메인 등록 필요 — 지도 대신 [그림 변경]으로 넣으세요"
|
||||
)
|
||||
assert got["boxHidden"]
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ def test_자식_프로세스로_PNG_생김(tmp_path, monkeypatch):
|
||||
|
||||
def test_자식_시간_넘김_죽음_까닭(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv(cap.KAKAO_JS, KEY)
|
||||
tail = " — 그림 올리기를 쓰세요."
|
||||
tail = " — [그림 변경]으로 넣으세요."
|
||||
head = "경로 그림 자동 캡처 실패 — "
|
||||
hang = tmp_path / "hang.py"
|
||||
hang.write_text("import time\ntime.sleep(30)\n", encoding="utf-8")
|
||||
|
||||
@@ -146,3 +146,23 @@ def test_155_요소_추가_안_이은_거래처_수기_입력():
|
||||
assert "if (owner?.manual) return" in state # 수기 입력 = 카카오 길찾기 안 함
|
||||
layout = _read("B08_Quantity/B08_Quantity_Milestone_Layout.ts")
|
||||
assert "if (!route || route.linked === false) return null;" in layout
|
||||
|
||||
|
||||
def test_157_도구_왼쪽_아래_경로_탭_지도_그림_변경_초기화():
|
||||
css = _read("B08_Quantity/B08_Quantity_UI_Milestone.css")
|
||||
tools = css[css.index(".b08-milestone__tools {") :]
|
||||
assert "bottom: 8px;" in tools[: tools.index("}")] and "left: 8px;" in tools[: tools.index("}")]
|
||||
ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts")
|
||||
assert (
|
||||
'label: "요소 추가",\n size: "sm"' not in ui
|
||||
and 'label: "자동 정렬",\n size: "sm"' not in ui
|
||||
)
|
||||
route = _read("B08_Quantity/B08_Quantity_UI_Milestone_Route.ts")
|
||||
assert 'label: "그림 변경"' in route and 'label: "초기화"' in route
|
||||
assert "그림 지우기" not in route and "자동 그림으로 되돌리기" not in route.split("*/", 1)[1]
|
||||
assert (
|
||||
"map.root.hidden = user;" in route and "editMap(projectId" in route
|
||||
) # 그림 없으면 길찾기 지도
|
||||
assert ".b08-milestone__route-head {" in css and "justify-content: space-between;" in css
|
||||
state = _read("B08_Quantity/B08_Quantity_UI_Milestone_State.ts")
|
||||
assert "export const hasUserImage" in state and "canRevertImage" not in state
|
||||
|
||||
Reference in New Issue
Block a user