feat(B06): 보라 선 치수 두 줄 · 사면 이동 패널(151-1 · 151-3)
- 치수 글 두 줄(사면길이 · 수평 / 높이 · 구배 · 이동) · 좁은 카드는 면적표 아래 높이로 - [◀ ▶] 지움 → 공용 조정창 꼴 이동 패널(◀ ↺ ▶ · 방향키 ←→ · 0.1 m · ✕ = 고름 풂) - 시험 — test_137_design_pick · test_139_6_line_shift(패널 · 방향키 · ↺ · 숨김) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jonfdxn1Xit9bdkNJpE4G
This commit is contained in:
@@ -4,15 +4,22 @@
|
||||
*
|
||||
* 그려진 보라 선(`b06-chart__design-cross`)을 화면 좌표에서 m 로 되돌려 같은 방향 토막을
|
||||
* 직선 조각 하나로 합치고, 벽 · 본체 폴리곤에 가린 부분을 뺀 **보이는 조각**마다 투명 판정
|
||||
* 선을 구조물 판정면 아래에 깐다. 고르면 그 조각을 강조하고 [+ − ⤢] 왼쪽에 치수를 적는다.
|
||||
* 선을 구조물 판정면 아래에 깐다. 고르면 그 조각을 강조하고 [+ − ⤢] 왼쪽에 치수를 두 줄로 적는다.
|
||||
* 구역 판정(`crossZoneAt` · 도로 · 절토 · 성토)은 설계선 쉬프트(139-6)도 쓴다.
|
||||
* 고름 상태는 페이지에 한 칸(측점 + 조각 번호) — 카드를 다시 그려도 되살아난다.
|
||||
* 고른 조각의 쪽 노견을 [◀ ▶] 0.1 m 로 넓히고 좁힘(139-6 쉬프트 · 노폭 안쪽 금지 = 0 에서 멈춤).
|
||||
* 고른 조각 쪽 사면을 노견째 이동 패널(◀ ↺ ▶ · 방향키 · 0.1 m)로 옮김(139-6 쉬프트 · 151-3 ·
|
||||
* 노폭 안쪽 금지 = 0 에서 멈춤).
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CrossDesign } from "./B06_Section_Api_Fetch";
|
||||
import { clipSegment, type XY } from "../A00_Common/b_polygon_clip";
|
||||
import { sideRole } from "@util/common_util_cross_design_geometry";
|
||||
import {
|
||||
bindArrowKeys,
|
||||
buildPanelShell,
|
||||
dpadButton,
|
||||
makeRow,
|
||||
} from "./B06_Section_UI_Cross_Panel_Base";
|
||||
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
const DESIGN_CLASS = "b06-chart__design-cross";
|
||||
@@ -205,12 +212,13 @@ export function pieceMeasure(
|
||||
return { lengthM: Math.hypot(horizontalM, heightM), horizontalM, heightM, grade };
|
||||
}
|
||||
|
||||
/** 두 줄(151-1) — 1줄 = 길이 · 수평 · 2줄 = 높이 · 구배(줄바꿈 글자로 가름). */
|
||||
export function pieceLabel(design: ZoneDesign, piece: DesignPiece): string {
|
||||
const { zone } = pieceZone(design, piece);
|
||||
const m = pieceMeasure(piece, zone);
|
||||
return (
|
||||
`${zone === "road" ? "길이" : "사면길이"} ${m.lengthM.toFixed(2)}m · ` +
|
||||
`수평 ${m.horizontalM.toFixed(2)}m · 높이 ${m.heightM.toFixed(2)}m · ${m.grade}`
|
||||
`수평 ${m.horizontalM.toFixed(2)}m\n높이 ${m.heightM.toFixed(2)}m · ${m.grade}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -239,7 +247,7 @@ const pointsOf = (element: Element): XY[] => {
|
||||
.filter((p) => p.length === 2 && p.every(Number.isFinite));
|
||||
};
|
||||
|
||||
/** 설계선 쉬프트(139-6) — 쪽마다 노견 확장(m · ≥ 0). */
|
||||
/** 설계선 쉬프트(139-6) — 쪽마다 사면 이동(m · ≥ 0 · 노견째 · 151-2). */
|
||||
export interface ShoulderShift {
|
||||
valueOf: (side: CrossSide) => number;
|
||||
set: (side: CrossSide, valueM: number) => void;
|
||||
@@ -247,7 +255,7 @@ export interface ShoulderShift {
|
||||
/** 쉬프트 걸음(m). */
|
||||
export const SHIFT_STEP_M = 0.1;
|
||||
|
||||
/** 화면 ◀(−1) · ▶(+1) 한 걸음 → 그 쪽 노견 확장 새 값(0.1 m 눈금 · 0 아래 금지).
|
||||
/** 화면 ◀(−1) · ▶(+1) 한 걸음 → 그 쪽 사면 이동 새 값(0.1 m 눈금 · 0 아래 금지).
|
||||
* `kx` = 화면 x / offset 기울기(+offset = 좌 · 화면 왼쪽이면 kx < 0). */
|
||||
export function shiftedExtend(now: number, side: CrossSide, screenDir: -1 | 1, kx: number): number {
|
||||
const offsetDelta = screenDir * Math.sign(kx) * SHIFT_STEP_M;
|
||||
@@ -259,6 +267,8 @@ export interface DesignLinePick {
|
||||
pieces: DesignPiece[];
|
||||
/** 치수 칸 — 카드가 [+ − ⤢] 앞에 붙임 · 고름 없으면 숨김. */
|
||||
label: HTMLElement;
|
||||
/** 사면 이동 패널(151-3) — 카드가 그림 칸에 붙임 · `shift` 없거나 고름 없으면 숨김. */
|
||||
panel: HTMLElement;
|
||||
setActive: (index: number | null) => void;
|
||||
}
|
||||
|
||||
@@ -272,7 +282,7 @@ export function attachDesignLinePick(input: {
|
||||
x: (offset: number) => number;
|
||||
toDisplayY: (elevation: number) => number;
|
||||
onPick: (index: number) => void;
|
||||
/** 있으면 고른 조각 쪽 노견을 [◀ ▶] 로 옮김(139-6). */
|
||||
/** 있으면 고른 조각 쪽 사면을 이동 패널로 옮김(139-6 · 151-3). */
|
||||
shift?: ShoulderShift;
|
||||
}): DesignLinePick {
|
||||
const { layer, design, x, toDisplayY } = input;
|
||||
@@ -319,40 +329,62 @@ export function attachDesignLinePick(input: {
|
||||
const label = document.createElement("span");
|
||||
label.className = "b06-cross-card__line-dims";
|
||||
label.hidden = true;
|
||||
const text = document.createElement("span");
|
||||
label.append(text);
|
||||
let side: CrossSide | null = null;
|
||||
let active: number | null = null;
|
||||
const { shift } = input;
|
||||
if (shift) {
|
||||
const button = (glyph: string, dir: -1 | 1, tip: string): HTMLButtonElement => {
|
||||
const el = document.createElement("button");
|
||||
el.type = "button";
|
||||
el.className = "b06-cross-card__zoom-btn b06-cross-card__line-shift";
|
||||
el.textContent = glyph;
|
||||
el.title = tip;
|
||||
el.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
if (side) shift.set(side, shiftedExtend(shift.valueOf(side), side, dir, kx));
|
||||
});
|
||||
return el;
|
||||
};
|
||||
label.append(
|
||||
button("◀", -1, "고른 선 쪽 노견을 화면 왼쪽으로 0.1 m(노폭 안쪽은 못 감)"),
|
||||
button("▶", 1, "고른 선 쪽 노견을 화면 오른쪽으로 0.1 m(노폭 안쪽은 못 감)"),
|
||||
);
|
||||
}
|
||||
|
||||
// 사면 이동 패널(151-3) — B06 공용 조정창 꼴(머리 · 「이동」 줄 십자 · 방향키 ←→).
|
||||
// ✕ = 같은 조각 다시 고름(카드가 풂) · ↺ = 그 쪽 0.
|
||||
const shell = buildPanelShell(() => {
|
||||
if (active !== null) input.onPick(active);
|
||||
});
|
||||
const readout = document.createElement("div");
|
||||
readout.className = "b06-structure-panel__value";
|
||||
const move = makeRow("이동");
|
||||
move.controls.classList.add("b06-structure-panel__buttons");
|
||||
const step = (dir: -1 | 1) => () => {
|
||||
if (side && shift) shift.set(side, shiftedExtend(shift.valueOf(side), side, dir, kx));
|
||||
};
|
||||
move.controls.append(
|
||||
dpadButton(
|
||||
"◀",
|
||||
"left",
|
||||
"고른 사면을 화면 왼쪽으로 0.1 m(노견째 · 노폭 안쪽은 못 감)",
|
||||
step(-1),
|
||||
),
|
||||
dpadButton("↺", "reset", "이 쪽 이동 0", () => {
|
||||
if (side && shift) shift.set(side, 0);
|
||||
}),
|
||||
dpadButton(
|
||||
"▶",
|
||||
"right",
|
||||
"고른 사면을 화면 오른쪽으로 0.1 m(노견째 · 노폭 안쪽은 못 감)",
|
||||
step(1),
|
||||
),
|
||||
);
|
||||
shell.root.append(readout, move.row);
|
||||
const arrows = bindArrowKeys(shell.root, () => [move.controls]);
|
||||
|
||||
return {
|
||||
pieces,
|
||||
label,
|
||||
panel: shell.root,
|
||||
setActive: (index) => {
|
||||
const piece = index === null ? undefined : pieces[index];
|
||||
hits.forEach((hit, i) => hit.classList.toggle("is-active", !!piece && i === index));
|
||||
label.hidden = !piece;
|
||||
active = piece ? index : null;
|
||||
side = piece ? pieceZone(design, piece).side : null;
|
||||
const extend = side && shift ? shift.valueOf(side) : 0;
|
||||
text.textContent = piece
|
||||
? pieceLabel(design, piece) + (extend > 0 ? ` · 노견 +${extend.toFixed(1)}m` : "")
|
||||
label.textContent = piece
|
||||
? pieceLabel(design, piece) + (extend > 0 ? ` · 이동 +${extend.toFixed(1)}m` : "")
|
||||
: "";
|
||||
const open = !!(piece && shift);
|
||||
shell.root.classList.toggle("is-hidden", !open);
|
||||
shell.title.textContent = side === "left" ? "사면 이동 (좌)" : "사면 이동 (우)";
|
||||
readout.textContent = `이동 ${extend.toFixed(1)}m`;
|
||||
arrows.detach();
|
||||
if (open) arrows.attach();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -662,6 +662,7 @@ export function createCrossSectionCard(
|
||||
const zoomBar = buildZoomControls(zoomPan, widthActions);
|
||||
if (linePick) zoomBar.prepend(linePick.label); // 치수 = [+ − ⤢] 왼쪽(137-2)
|
||||
chartWrap.append(svg, readout.root, zoomBar, panel.root);
|
||||
if (linePick) chartWrap.append(linePick.panel); // 사면 이동 패널(151-3)
|
||||
for (const panel of bodies.panels) chartWrap.append(panel.root);
|
||||
if (activeArea) {
|
||||
setBandActive(activeArea);
|
||||
|
||||
@@ -223,14 +223,39 @@
|
||||
background: color-mix(in srgb, var(--color-surface-raised) 88%, transparent);
|
||||
}
|
||||
|
||||
/* 고른 보라 선 치수(137-2) — [+ − ⤢] 왼쪽 같은 줄. */
|
||||
/* 고른 보라 선 치수(137-2) — [+ − ⤢] 왼쪽 · 두 줄(151-1 · 길이 · 수평 / 높이 · 구배). */
|
||||
.b06-cross-card__line-dims {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--spacing-4);
|
||||
align-self: center;
|
||||
padding: 1px var(--spacing-4);
|
||||
color: var(--color-royal-amethyst);
|
||||
font-size: 0.75rem;
|
||||
white-space: nowrap;
|
||||
line-height: 1.25;
|
||||
text-align: right;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* 좁은 카드(3열 등)는 막대 옆에 두면 왼쪽 면적표를 덮음 — 표 아래 높이로 내려 오른쪽에 붙임(151-1). */
|
||||
.b06-cross-card__chart-wrap {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
@container (max-width: 620px) {
|
||||
.b06-cross-card__zoom {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.b06-cross-card__line-dims {
|
||||
position: absolute;
|
||||
top: calc(var(--spacing-8) + var(--b06-areas-height, 3.6rem));
|
||||
right: -1px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-inputs);
|
||||
background: color-mix(in srgb, var(--color-surface-raised) 88%, transparent);
|
||||
}
|
||||
|
||||
.b06-cross-card__line-dims + .b06-cross-card__zoom-btn {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
.b06-cross-card__line-dims[hidden] {
|
||||
|
||||
@@ -112,6 +112,8 @@ class FakeNode {
|
||||
globalThis.document = {
|
||||
createElement: (tag) => new FakeNode(tag),
|
||||
createElementNS: (_ns, tag) => new FakeNode(tag),
|
||||
addEventListener: () => {}, // 조정창 공용 부품(사면 이동 패널 · 151-3)이 불러올 때 닮
|
||||
removeEventListener: () => {},
|
||||
};
|
||||
|
||||
function loadTs(file) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* 139-6 고른 설계선 [◀ ▶] 노견 확장 헬퍼 — `_Cross_Design_Pick.attachDesignLinePick` 을 그 자리에서
|
||||
* 트랜스파일해 흉내 DOM 으로 돌림. 짝 = `test_139_6_line_shift.py`.
|
||||
/* 139-6 고른 설계선 사면 이동 헬퍼 — `_Cross_Design_Pick.attachDesignLinePick` 을 그 자리에서
|
||||
* 트랜스파일해 흉내 DOM 으로 돌림 · 151-3 이동 패널(◀ ↺ ▶ · 방향키 ←→). 짝 = `test_139_6_line_shift.py`.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
@@ -15,6 +15,7 @@ class Node {
|
||||
this.attrs = {};
|
||||
this.listeners = {};
|
||||
this.hidden = false;
|
||||
this.isConnected = true;
|
||||
this._text = "";
|
||||
this._cls = new Set();
|
||||
const self = this;
|
||||
@@ -24,6 +25,16 @@ class Node {
|
||||
contains: (c) => self._cls.has(c),
|
||||
};
|
||||
}
|
||||
get descendants() {
|
||||
return this.children.flatMap((c) => [c, ...c.descendants]);
|
||||
}
|
||||
/** `.이름` 한 개만 — 패널 공용 부품이 슬롯 단추를 찾는 꼴. */
|
||||
querySelector(sel) {
|
||||
return this.descendants.find((n) => n._cls.has(sel.slice(1))) ?? null;
|
||||
}
|
||||
getClientRects() {
|
||||
return this._cls.has("is-hidden") ? [] : [{}];
|
||||
}
|
||||
set className(v) {
|
||||
this._cls = new Set(String(v).split(/\s+/).filter(Boolean));
|
||||
}
|
||||
@@ -56,10 +67,19 @@ class Node {
|
||||
(this.listeners.click ?? []).forEach((fn) => fn({ stopPropagation: () => undefined }));
|
||||
}
|
||||
}
|
||||
const docListeners = {};
|
||||
globalThis.document = {
|
||||
createElement: (t) => new Node(t),
|
||||
createElementNS: (_n, t) => new Node(t),
|
||||
addEventListener: (t, fn) => (docListeners[t] ??= []).push(fn),
|
||||
removeEventListener: (t, fn) => {
|
||||
docListeners[t] = (docListeners[t] ?? []).filter((f) => f !== fn);
|
||||
},
|
||||
};
|
||||
const key = (name) =>
|
||||
[...(docListeners.keydown ?? [])].forEach((fn) =>
|
||||
fn({ key: name, target: null, preventDefault: () => undefined }),
|
||||
);
|
||||
|
||||
function loadTs(file) {
|
||||
const full = path.resolve(file);
|
||||
@@ -115,37 +135,65 @@ layer.append(
|
||||
]),
|
||||
);
|
||||
const values = { left: 0, right: 0 };
|
||||
const picks = [];
|
||||
const handle = pick.attachDesignLinePick({
|
||||
layer,
|
||||
design,
|
||||
x,
|
||||
toDisplayY: y,
|
||||
onPick: () => undefined,
|
||||
onPick: (i) => picks.push(i),
|
||||
shift: {
|
||||
valueOf: (side) => values[side],
|
||||
set: (side, v) => (values[side] = v),
|
||||
},
|
||||
});
|
||||
const [left, right] = handle.label.children.filter((c) => c.tagName === "button");
|
||||
const panel = handle.panel;
|
||||
const slot = (name) => panel.querySelector(`.b06-structure-panel__btn--${name}`);
|
||||
const close = panel.querySelector(".b06-structure-panel__close");
|
||||
const pieceAt = (offset) =>
|
||||
handle.pieces.findIndex(
|
||||
(p) =>
|
||||
Math.min(p.from.offset, p.to.offset) <= offset &&
|
||||
offset <= Math.max(p.from.offset, p.to.offset),
|
||||
);
|
||||
const hiddenAtStart = panel.classList.contains("is-hidden");
|
||||
const log = [];
|
||||
handle.setActive(pieceAt(4)); // 좌 사면
|
||||
left.click(); // 화면 왼쪽 = 좌측 바깥 → +0.1
|
||||
left.click();
|
||||
const titleLeft = panel.descendants.find((n) =>
|
||||
n._cls.has("b06-structure-panel__title"),
|
||||
).textContent;
|
||||
slot("left").click(); // 화면 왼쪽 = 좌측 바깥 → +0.1
|
||||
key("ArrowLeft"); // 방향키 = 십자
|
||||
log.push({ ...values });
|
||||
right.click(); // 안쪽 → 0.1
|
||||
right.click();
|
||||
right.click(); // 0 아래 금지
|
||||
key("ArrowRight"); // 안쪽 → 0.1
|
||||
slot("right").click();
|
||||
key("ArrowRight"); // 0 아래 금지
|
||||
log.push({ ...values });
|
||||
handle.setActive(pieceAt(-4)); // 우 사면
|
||||
left.click(); // 화면 왼쪽 = 우측 안쪽 → 0 에서 멈춤
|
||||
right.click(); // 우측 바깥 → +0.1
|
||||
key("ArrowLeft"); // 화면 왼쪽 = 우측 안쪽 → 0 에서 멈춤
|
||||
slot("right").click(); // 우측 바깥 → +0.1
|
||||
log.push({ ...values });
|
||||
handle.setActive(pieceAt(-4));
|
||||
const text = handle.label.textContent;
|
||||
process.stdout.write(JSON.stringify({ log, text, buttons: [left.textContent, right.textContent] }));
|
||||
const readout = panel.descendants.find((n) => n._cls.has("b06-structure-panel__value")).textContent;
|
||||
slot("reset").click(); // ↺ = 그 쪽 0
|
||||
const afterReset = { ...values };
|
||||
close.click(); // ✕ = 같은 조각 다시 고름(카드가 풂)
|
||||
handle.setActive(null);
|
||||
key("ArrowRight"); // 숨은 패널은 방향키 안 먹음
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
log,
|
||||
text,
|
||||
readout,
|
||||
titleLeft,
|
||||
afterReset,
|
||||
afterHidden: { ...values },
|
||||
picks,
|
||||
pieceRight: pieceAt(-4),
|
||||
hiddenAtStart,
|
||||
hiddenAfter: panel.classList.contains("is-hidden"),
|
||||
labelButtons: handle.label.children.length,
|
||||
slots: ["left", "reset", "right"].map((n) => slot(n).textContent),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -57,8 +57,9 @@ def test_치수_사면길이_수평_높이_구배(out) -> None:
|
||||
assert abs(fill["lengthM"] - 13**0.5) < 1e-9
|
||||
assert out["measure"]["road"]["grade"] == "2.0%" # 노면 = %
|
||||
assert out["measure"]["flatSlope"] == "0.0%"
|
||||
assert out["label"]["fill"] == "사면길이 3.61m · 수평 3.00m · 높이 2.00m · 1:1.50"
|
||||
assert out["label"]["road"] == "길이 2.50m · 수평 2.50m · 높이 0.05m · 2.0%"
|
||||
# 두 줄(151-1) — 1줄 = 길이 · 수평 · 2줄 = 높이 · 구배
|
||||
assert out["label"]["fill"] == "사면길이 3.61m · 수평 3.00m\n높이 2.00m · 1:1.50"
|
||||
assert out["label"]["road"] == "길이 2.50m · 수평 2.50m\n높이 0.05m · 2.0%"
|
||||
|
||||
|
||||
def test_구역_도로_절토_성토(out) -> None:
|
||||
@@ -100,10 +101,21 @@ def test_카드_배선_하나만() -> None:
|
||||
assert view.count("dropLine();") >= 4 # 본체 · 벽 · 카드 풂 · 면적
|
||||
assert "setLineActive(pickedLineAt(section.station_id));" in view # 다시 그려도 되살림
|
||||
assert "zoomBar.prepend(linePick.label);" in view # 치수 = [+ − ⤢] 왼쪽
|
||||
assert "chartWrap.append(linePick.panel);" in view # 사면 이동 패널(151-3)
|
||||
css = _src("B06_Section/B06_Section_UI_Style_Cross_Areas.css")
|
||||
assert ".b06-chart__design-hit.is-active {" in css
|
||||
|
||||
|
||||
def test_치수_두_줄_좁은_카드는_표_아래로() -> None:
|
||||
"""151-1 — 줄바꿈 글자를 살리고 오른쪽 맞춤 · 좁은 카드(3열 등)는 표 높이 아래로 내려 표를 안 덮음."""
|
||||
css = _src("B06_Section/B06_Section_UI_Style_Cross_Controls.css")
|
||||
dims = css.split(".b06-cross-card__line-dims {", 1)[1].split("}", 1)[0]
|
||||
assert "white-space: pre-line;" in dims and "text-align: right;" in dims
|
||||
assert "container-type: inline-size;" in css
|
||||
narrow = css.split("@container (max-width: 620px) {", 1)[1]
|
||||
assert "top: calc(var(--spacing-8) + var(--b06-areas-height, 3.6rem));" in narrow
|
||||
|
||||
|
||||
def test_폴리곤_자르기는_공용_한_벌() -> None:
|
||||
b07 = _src("B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts")
|
||||
assert "function clipSegment(" not in b07 and "function pointInPolygon(" not in b07
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""139-6 고른 설계선 [◀ ▶] — 고른 조각 쪽 노견 확장 0.1 m 걸음 · 노폭 안쪽 금지(0 에서 멈춤).
|
||||
"""139-6 · 151-3 고른 설계선 사면 이동 패널 — 고른 조각 쪽만 0.1 m 걸음 · 노폭 안쪽 금지(0 에서 멈춤).
|
||||
|
||||
화면 TS(`_Cross_Design_Pick`)를 흉내 DOM 으로 돌린 값(`helper_139_6_line_shift.cjs`).
|
||||
"""
|
||||
@@ -26,16 +26,27 @@ def out() -> dict:
|
||||
return json.loads(done.stdout)
|
||||
|
||||
|
||||
def test_화면_방향대로_그_쪽_노견만(out: dict) -> None:
|
||||
def test_화면_방향대로_그_쪽만_단추와_방향키(out: dict) -> None:
|
||||
first, back, right = out["log"]
|
||||
assert first == {"left": 0.2, "right": 0} # 좌 사면 고름 · ◀ 두 번 = 좌 바깥 0.2
|
||||
assert back == {"left": 0, "right": 0} # ▶ 세 번 = 0 에서 멈춤(노폭 안쪽 금지)
|
||||
assert right == {"left": 0, "right": 0.1} # 우 사면 · ◀ = 안쪽 0 · ▶ = 바깥 0.1
|
||||
assert first == {"left": 0.2, "right": 0} # 좌 사면 · ◀ 단추 + ← 키 = 좌 바깥 0.2
|
||||
assert back == {"left": 0, "right": 0} # → 키 · ▶ · → 키 = 0 에서 멈춤(노폭 안쪽 금지)
|
||||
assert right == {"left": 0, "right": 0.1} # 우 사면 · ← = 안쪽 0 · ▶ = 바깥 0.1
|
||||
|
||||
|
||||
def test_치수_줄에_노견_확장_표기(out: dict) -> None:
|
||||
assert "노견 +0.1m" in out["text"]
|
||||
assert out["buttons"] == ["◀", "▶"]
|
||||
def test_치수_줄과_패널에_이동_표기(out: dict) -> None:
|
||||
assert "이동 +0.1m" in out["text"] and "노견 +" not in out["text"]
|
||||
assert out["readout"] == "이동 0.1m"
|
||||
assert out["titleLeft"] == "사면 이동 (좌)"
|
||||
|
||||
|
||||
def test_단추는_패널로_옮김_재설정_닫기(out: dict) -> None:
|
||||
"""151-3 — 치수 칸 [◀ ▶] 지움 · 패널 ◀ ↺ ▶ · ↺ = 그 쪽 0 · ✕ = 고름 풂 · 숨으면 방향키 안 먹음."""
|
||||
assert out["labelButtons"] == 0
|
||||
assert out["slots"] == ["◀", "↺", "▶"]
|
||||
assert out["afterReset"] == {"left": 0, "right": 0}
|
||||
assert out["picks"] == [out["pieceRight"]]
|
||||
assert out["hiddenAtStart"] is True and out["hiddenAfter"] is True
|
||||
assert out["afterHidden"] == {"left": 0, "right": 0}
|
||||
|
||||
|
||||
def test_페이지가_같은_창구로_세션_재계산() -> None:
|
||||
|
||||
Reference in New Issue
Block a user