feat(ground): 하상 selectable; 설계홍수위 and 세굴 깊이 for 하상
- shape-options: a per-option "confirmed" flag; 하상 is confirmed, 해상 is not. - The 위치 chips store the choice in the pier's draft; 선택 shows it. - 하상: the groundwater inputs give way to 설계홍수위 DHWL (floodLevelEl) and 세굴 깊이 (scourDepthM). The groundwater value is kept (남겨두기). - Checks: G07 DHWL missing, G08 DHWL below the 지반고, G09 scour negative or the scour line below the footing bottom. The groundwater check is skipped for 하상. - Section: the DHWL as a solid line and the 세굴선 dashed. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7b5517e312
commit
26a5aff517
9 files changed
+163
-45
No files matched your search
+3
-1
@@ -23,7 +23,7 @@ export const EDITABLE_FIELDS = [
|
||||
] as const;
|
||||
|
||||
/** Inputs tracked for overrides and pending changes: the numeric cards plus 열별 본수 (an array, compared by value). */
|
||||
export const TRACKED_KEYS = [...EDITABLE_FIELDS.map((f) => f.key), 'pileRows', 'groundPosition', 'groundWaterOn', 'groundWaterEl', 'strataRows'] as const;
|
||||
export const TRACKED_KEYS = [...EDITABLE_FIELDS.map((f) => f.key), 'pileRows', 'groundPosition', 'groundWaterOn', 'groundWaterEl', 'floodLevelEl', 'scourDepthM', 'strataRows'] as const;
|
||||
|
||||
export type EditableKey = (typeof TRACKED_KEYS)[number];
|
||||
|
||||
@@ -47,6 +47,8 @@ export const FIELD_LABELS: Record<EditableKey, string> = {
|
||||
groundPosition: '지반 위치',
|
||||
groundWaterOn: '지하수위 고려',
|
||||
groundWaterEl: '지하수위 EL',
|
||||
floodLevelEl: '설계홍수위 DHWL',
|
||||
scourDepthM: '세굴 깊이',
|
||||
strataRows: '지층 행',
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ export const COPY_ITEMS: Record<CopyItem, ItemRule> = {
|
||||
'coping-length': { label: '코핑 길이 L', keys: ['copingLengthMm'], defaultOn: false, pileItem: false },
|
||||
'pile-length': { label: '말뚝 길이', keys: ['pileLengthMm'], defaultOn: false, pileItem: true },
|
||||
'pile-rows': { label: '열별 본수', keys: ['pileRows'], defaultOn: false, pileItem: true },
|
||||
'ground-position': { label: '지반 위치·지하수위', keys: ['groundPosition', 'groundWaterOn', 'groundWaterEl'], defaultOn: false, pileItem: false },
|
||||
'ground-position': { label: '지반 위치·지하수위', keys: ['groundPosition', 'groundWaterOn', 'groundWaterEl', 'floodLevelEl'], defaultOn: false, pileItem: false },
|
||||
};
|
||||
|
||||
export const DEFAULT_COPY_ITEMS: CopyItem[] = (Object.keys(COPY_ITEMS) as CopyItem[]).filter((k) => COPY_ITEMS[k].defaultOn);
|
||||
|
||||
+20
-2
@@ -16,7 +16,8 @@ export const PILE_EMBED_D = 1.0;
|
||||
/**
|
||||
* 지반·지층 입력 검사 (승인 2026-10-26).
|
||||
* G01: 층 하단 EL은 위 경계보다 낮아야 함. G02: 지하수위는 지반고와 가장 아래 층 사이에 있어야 함.
|
||||
* G03: 층 이름은 지층 목록에 있어야 함. Ground values are inputs only; they do not change the 3D model.
|
||||
* G03: 층 이름은 지층 목록에 있어야 함. 하상: G07 DHWL 없음, G08 DHWL이 지반고보다 낮음, G09 세굴선 확인.
|
||||
* Ground values are inputs only; they do not change the 3D model.
|
||||
*/
|
||||
export function validateGround(input: PierInput, issues: Issue[]): void {
|
||||
const rows = input.strataRows ?? [];
|
||||
@@ -31,7 +32,24 @@ export function validateGround(input: PierInput, issues: Issue[]): void {
|
||||
}
|
||||
upper = row.bottomEl;
|
||||
});
|
||||
if (input.groundWaterOn) {
|
||||
const position = input.groundPosition ?? '육상';
|
||||
if (position === '하상') {
|
||||
// 하상 (승인 2026-10-26): the 설계홍수위 DHWL replaces the groundwater level.
|
||||
const f = input.floodLevelEl;
|
||||
if (f === undefined || !Number.isFinite(f)) {
|
||||
issues.push({ code: 'G07', level: 'W', pier: input.name, message: '하상입니다. 설계홍수위(DHWL) EL을 입력하세요.' });
|
||||
} else if (f < input.groundEl) {
|
||||
issues.push({ code: 'G08', level: 'W', pier: input.name, message: `설계홍수위 ${f.toFixed(3)}이 지반고 ${input.groundEl.toFixed(3)}보다 낮습니다.` });
|
||||
}
|
||||
const scour = input.scourDepthM;
|
||||
if (scour !== undefined) {
|
||||
if (!(scour >= 0)) {
|
||||
issues.push({ code: 'G09', level: 'W', pier: input.name, message: '세굴 깊이는 0 이상이어야 합니다.' });
|
||||
} else if (input.groundEl - scour < input.footingBottomEl) {
|
||||
issues.push({ code: 'G09', level: 'W', pier: input.name, message: `세굴선 EL ${(input.groundEl - scour).toFixed(3)}이 확대기초 하면 ${input.footingBottomEl.toFixed(3)}보다 낮습니다 (기초 노출).` });
|
||||
}
|
||||
}
|
||||
} else if (input.groundWaterOn) {
|
||||
const w = input.groundWaterEl;
|
||||
const lowest = rows.length ? rows[rows.length - 1].bottomEl : undefined;
|
||||
if (w === undefined || !Number.isFinite(w)) {
|
||||
|
||||
@@ -8,6 +8,8 @@ export interface ShapeOption {
|
||||
label: string;
|
||||
/** 굵은 글씨 in the spec: the default that the 1단계 model builds. */
|
||||
isDefault: boolean;
|
||||
/** The rule for this option is confirmed (승인 2026-10-26: 하상), so it can be chosen even if it is not the default. */
|
||||
confirmed?: boolean;
|
||||
}
|
||||
|
||||
export interface ShapeGroup {
|
||||
@@ -17,6 +19,7 @@ export interface ShapeGroup {
|
||||
|
||||
const d = (label: string): ShapeOption => ({ label, isDefault: true });
|
||||
const x = (label: string): ShapeOption => ({ label, isDefault: false });
|
||||
const ok = (label: string): ShapeOption => ({ label, isDefault: false, confirmed: true });
|
||||
|
||||
export const SHAPE_GROUPS: Record<MemberId, ShapeGroup[]> = {
|
||||
bearing: [{ title: '배치', options: [d('분리'), x('일체')] }],
|
||||
@@ -34,14 +37,14 @@ export const SHAPE_GROUPS: Record<MemberId, ShapeGroup[]> = {
|
||||
{ title: '말뚝 종류', options: [d('강관'), x('PHC'), x('복합')] },
|
||||
{ title: '경사말뚝', options: [d('없음'), x('있음')] },
|
||||
],
|
||||
ground: [{ title: '위치', options: [d('육상'), x('하상'), x('해상')] }],
|
||||
ground: [{ title: '위치', options: [d('육상'), ok('하상'), x('해상')] }],
|
||||
};
|
||||
|
||||
export const UNDEFINED_REASON = '규칙 미정 (Q6 확정 전 선택 불가)';
|
||||
|
||||
/** Options currently selectable. Defaults are the only choices today. */
|
||||
/** Options currently selectable: the defaults and the options whose rule is confirmed. */
|
||||
export function isSelectable(option: ShapeOption): boolean {
|
||||
return option.isDefault;
|
||||
return option.isDefault || option.confirmed === true;
|
||||
}
|
||||
|
||||
/** The default summary of a member, e.g. "교좌면 직각 · 하면 직선". */
|
||||
|
||||
@@ -61,6 +61,10 @@ export interface PierInput {
|
||||
groundPosition?: GroundPosition;
|
||||
groundWaterOn?: boolean;
|
||||
groundWaterEl?: number;
|
||||
/** 설계홍수위 DHWL (m), used for 하상 only. Kept when the position changes (승인 2026-10-26). */
|
||||
floodLevelEl?: number;
|
||||
/** 세굴 깊이 (m) below the 지반고, used for 하상 only. */
|
||||
scourDepthM?: number;
|
||||
strataRows?: StrataRow[];
|
||||
}
|
||||
|
||||
|
||||
+57
-30
@@ -834,7 +834,7 @@ function setPileRow(name: string, row: number, delta: number): void {
|
||||
}
|
||||
|
||||
/** 형상 선택칩 (plan.md C5): the default is pressed; every other option is shown and disabled with the reason. */
|
||||
function renderShapeOptions(member: MemberId): void {
|
||||
function renderShapeOptions(member: MemberId, pier?: Pier): void {
|
||||
const box = $<HTMLDivElement>('member-options');
|
||||
box.replaceChildren();
|
||||
SHAPE_GROUPS[member].forEach((group, gi) => {
|
||||
@@ -843,12 +843,15 @@ function renderShapeOptions(member: MemberId): void {
|
||||
for (const option of group.options) {
|
||||
const chip = el('button', option.label, 'chip-opt');
|
||||
chip.type = 'button';
|
||||
chip.setAttribute('aria-pressed', String(option.isDefault));
|
||||
// The ground 위치 is stored in the pier's draft, so its pressed chip is the chosen one (승인 2026-10-26).
|
||||
const chosen = member === 'ground' && pier ? (pier.draft.groundPosition ?? '육상') : undefined;
|
||||
chip.setAttribute('aria-pressed', String(chosen !== undefined ? option.label === chosen : option.isDefault));
|
||||
chip.disabled = !isSelectable(option);
|
||||
if (chip.disabled) chip.title = UNDEFINED_REASON;
|
||||
chip.addEventListener('click', () => {
|
||||
try {
|
||||
choose(member, gi, option.label);
|
||||
if (member === 'ground' && pier) editGround(pier.input.name, 'groundPosition', option.label);
|
||||
} catch (err) {
|
||||
setError((err as Error).message);
|
||||
}
|
||||
@@ -1512,11 +1515,12 @@ function valueText(key: EditableKey, v: unknown): string {
|
||||
}
|
||||
|
||||
/** Sets one 지반·지층 input in the draft (applied to the model by 다시 생성). */
|
||||
function editGround(name: string, key: 'groundPosition' | 'groundWaterOn' | 'groundWaterEl', value: unknown): void {
|
||||
function editGround(name: string, key: 'groundPosition' | 'groundWaterOn' | 'groundWaterEl' | 'floodLevelEl' | 'scourDepthM', value: unknown): void {
|
||||
const p = state.piers.find((q) => q.input.name === name);
|
||||
if (!p) return;
|
||||
if (key === 'groundWaterEl' && value !== undefined && !Number.isFinite(value)) {
|
||||
setError('지하수위 EL은 숫자로 입력하세요. 값을 되돌렸습니다.');
|
||||
const numeric = key === 'groundWaterEl' || key === 'floodLevelEl' || key === 'scourDepthM';
|
||||
if (numeric && value !== undefined && !Number.isFinite(value)) {
|
||||
setError(`${FIELD_LABELS[key]}은(는) 숫자로 입력하세요. 값을 되돌렸습니다.`);
|
||||
renderAll();
|
||||
return;
|
||||
}
|
||||
@@ -1552,29 +1556,50 @@ function renderGroundModel(box: HTMLDivElement, pier: Pier): void {
|
||||
|
||||
// 위치 is the 형상 옵션 of this member (the chips above the card); only the default is selectable until Q6.
|
||||
|
||||
wrap.appendChild(el('p', '지하수위', 'lbl'));
|
||||
const water = el('div', undefined, 'ground-water');
|
||||
water.style.display = 'flex';
|
||||
water.style.flexWrap = 'wrap';
|
||||
water.style.alignItems = 'center';
|
||||
water.style.gap = '8px';
|
||||
const check = el('input');
|
||||
check.type = 'checkbox';
|
||||
check.checked = d.groundWaterOn === true;
|
||||
check.id = 'ground-water-on';
|
||||
check.addEventListener('change', () => editGround(name, 'groundWaterOn', check.checked));
|
||||
const checkLabel = el('label', ' 지하수위 고려');
|
||||
checkLabel.prepend(check);
|
||||
const level = el('input');
|
||||
level.type = 'number';
|
||||
level.step = 'any';
|
||||
level.id = 'ground-water-el';
|
||||
level.placeholder = '지하수위 EL (m)';
|
||||
level.value = d.groundWaterEl === undefined ? '' : String(d.groundWaterEl);
|
||||
level.disabled = !check.checked;
|
||||
level.addEventListener('change', () => editGround(name, 'groundWaterEl', level.value === '' ? undefined : Number(level.value)));
|
||||
water.append(checkLabel, level, el('span', 'm', 'muted small'));
|
||||
wrap.appendChild(water);
|
||||
const position = d.groundPosition ?? '육상';
|
||||
if (position === '하상') {
|
||||
// 하상 (승인 2026-10-26): the 설계홍수위 DHWL and the 세굴 깊이 replace the groundwater; the groundwater value is kept.
|
||||
wrap.appendChild(el('p', '설계홍수위 DHWL · 세굴', 'lbl'));
|
||||
const scourRow = el('div', undefined, 'ground-water');
|
||||
scourRow.style.display = 'flex';
|
||||
scourRow.style.flexWrap = 'wrap';
|
||||
scourRow.style.alignItems = 'center';
|
||||
scourRow.style.gap = '8px';
|
||||
for (const [key, label, value] of [['floodLevelEl', '설계홍수위 EL (m)', d.floodLevelEl], ['scourDepthM', '세굴 깊이 (m)', d.scourDepthM]] as const) {
|
||||
const input = el('input');
|
||||
input.type = 'number';
|
||||
input.step = 'any';
|
||||
input.placeholder = label;
|
||||
input.value = value === undefined ? '' : String(value);
|
||||
input.addEventListener('change', () => editGround(name, key, input.value === '' ? undefined : Number(input.value)));
|
||||
scourRow.append(el('span', label, 'muted small'), input);
|
||||
}
|
||||
wrap.appendChild(scourRow);
|
||||
} else {
|
||||
wrap.appendChild(el('p', '지하수위', 'lbl'));
|
||||
const water = el('div', undefined, 'ground-water');
|
||||
water.style.display = 'flex';
|
||||
water.style.flexWrap = 'wrap';
|
||||
water.style.alignItems = 'center';
|
||||
water.style.gap = '8px';
|
||||
const check = el('input');
|
||||
check.type = 'checkbox';
|
||||
check.checked = d.groundWaterOn === true;
|
||||
check.id = 'ground-water-on';
|
||||
check.addEventListener('change', () => editGround(name, 'groundWaterOn', check.checked));
|
||||
const checkLabel = el('label', ' 지하수위 고려');
|
||||
checkLabel.prepend(check);
|
||||
const level = el('input');
|
||||
level.type = 'number';
|
||||
level.step = 'any';
|
||||
level.id = 'ground-water-el';
|
||||
level.placeholder = '지하수위 EL (m)';
|
||||
level.value = d.groundWaterEl === undefined ? '' : String(d.groundWaterEl);
|
||||
level.disabled = !check.checked;
|
||||
level.addEventListener('change', () => editGround(name, 'groundWaterEl', level.value === '' ? undefined : Number(level.value)));
|
||||
water.append(checkLabel, level, el('span', 'm', 'muted small'));
|
||||
wrap.appendChild(water);
|
||||
}
|
||||
|
||||
// 지층 행 (승인 2026-10-26): one row per layer, from the top down; the EL is the layer's bottom EL.
|
||||
const rows = d.strataRows ?? [];
|
||||
@@ -1868,8 +1893,10 @@ function renderPlan(): void {
|
||||
const member = MEMBERS.find((m) => m.id === state.member)!;
|
||||
$('member-title').textContent = member.label;
|
||||
// Default and current selection are shown separately (spec §5.1). Only defaults are selectable today.
|
||||
$('member-summary').textContent = `기본: ${defaultSummary(member.id)} · 선택: ${defaultSummary(member.id)}`;
|
||||
renderShapeOptions(member.id);
|
||||
// 선택 shows the pier's own 위치 for the ground; the other members still choose only their defaults.
|
||||
const selected = member.id === 'ground' && pier ? (pier.draft.groundPosition ?? '육상') : defaultSummary(member.id);
|
||||
$('member-summary').textContent = `기본: ${defaultSummary(member.id)} · 선택: ${selected}`;
|
||||
renderShapeOptions(member.id, pier);
|
||||
$('member-note').textContent = member.note;
|
||||
if (member.id === 'bearing' && pier?.model) {
|
||||
// Row skew of the bearing blocks (계산값): the block sides follow the bearing row (승인 2026-10-23).
|
||||
|
||||
+19
-6
@@ -20,9 +20,15 @@ export function groundSectionSvg(input: PierInput, model: PierModel): string {
|
||||
const footBottom = Math.min(...model.footings.map((f) => f.bottom));
|
||||
const pileTips = model.piles.map((p) => p.bottom);
|
||||
const lowestRow = rows.length ? rows[rows.length - 1].bottomEl : ground - 1;
|
||||
const water = input.groundWaterOn && input.groundWaterEl !== undefined && Number.isFinite(input.groundWaterEl) ? input.groundWaterEl : undefined;
|
||||
// 하상: the 설계홍수위 (solid) and the 세굴선 replace the groundwater (dashed, 승인 2026-10-26).
|
||||
const river = (input.groundPosition ?? '육상') === '하상';
|
||||
const level = river
|
||||
? (Number.isFinite(input.floodLevelEl) ? input.floodLevelEl : undefined)
|
||||
: (input.groundWaterOn && Number.isFinite(input.groundWaterEl) ? input.groundWaterEl : undefined);
|
||||
const scour = river && Number.isFinite(input.scourDepthM) && (input.scourDepthM as number) >= 0 ? (input.scourDepthM as number) : undefined;
|
||||
const scourEl = scour !== undefined ? ground - scour : undefined;
|
||||
|
||||
const yMin = Math.min(footBottom, lowestRow, ...pileTips, water ?? ground) - 1;
|
||||
const yMin = Math.min(footBottom, lowestRow, ...pileTips, level ?? ground, scourEl ?? ground) - 1;
|
||||
const yMax = Math.max(ground, footTop, input.copingTopEl) + 0.5;
|
||||
const scale = PLOT_H / (yMax - yMin);
|
||||
const y = (el: number) => TOP_PAD + (yMax - el) * scale;
|
||||
@@ -51,10 +57,17 @@ export function groundSectionSvg(input: PierInput, model: PierModel): string {
|
||||
parts.push(`<line x1="0" y1="${y(ground)}" x2="${WIDTH}" y2="${y(ground)}" stroke="#8A6D3B" stroke-width="1.5"></line>`);
|
||||
parts.push(`<text x="8" y="${y(ground) - 6}" font-size="12" fill="#18212B">지반고 ${ground.toFixed(3)}</text>`);
|
||||
|
||||
// Groundwater (only when the input is on and has a value).
|
||||
if (water !== undefined) {
|
||||
parts.push(`<line x1="0" y1="${y(water)}" x2="${WIDTH}" y2="${y(water)}" stroke="#2F6DB5" stroke-dasharray="6 4"></line>`);
|
||||
parts.push(`<text x="8" y="${y(water) - 4}" font-size="12" fill="#123E70">지하수위 ${water.toFixed(3)}</text>`);
|
||||
// Groundwater (육상, when on) as a dashed line; the 설계홍수위 (하상) as a solid line.
|
||||
if (level !== undefined) {
|
||||
const dash = river ? '' : ' stroke-dasharray="6 4"';
|
||||
const label = river ? '설계홍수위 DHWL' : '지하수위';
|
||||
parts.push(`<line x1="0" y1="${y(level)}" x2="${WIDTH}" y2="${y(level)}" stroke="#2F6DB5" stroke-width="${river ? 2 : 1}"${dash}></line>`);
|
||||
parts.push(`<text x="8" y="${y(level) - 4}" font-size="12" fill="#123E70">${label} ${level.toFixed(3)}</text>`);
|
||||
}
|
||||
// 세굴선 (하상): the bed after scour.
|
||||
if (scourEl !== undefined) {
|
||||
parts.push(`<line x1="0" y1="${y(scourEl)}" x2="${WIDTH}" y2="${y(scourEl)}" stroke="#8A6D3B" stroke-dasharray="3 3"></line>`);
|
||||
parts.push(`<text x="8" y="${y(scourEl) + 12}" font-size="12" fill="#5B4A2E">세굴선 ${scourEl.toFixed(3)}</text>`);
|
||||
}
|
||||
|
||||
// Footing, column stub, and piles.
|
||||
|
||||
@@ -171,3 +171,48 @@ describe('N값 입력 (승인 2026-10-26: 글자 입력, 타수/관입량 mm)',
|
||||
expect(() => parseProject(text)).toThrow('지층 행 형식');
|
||||
});
|
||||
});
|
||||
|
||||
describe('하상 (승인 2026-10-26: 설계홍수위 DHWL, 세굴 깊이)', () => {
|
||||
const river = { groundPosition: '하상' as const };
|
||||
|
||||
it('G07: a 하상 without the DHWL', () => {
|
||||
expect(ground(withGround({ ...river }))).toEqual(['G07']);
|
||||
});
|
||||
|
||||
it('G08: a DHWL below the 지반고', () => {
|
||||
expect(ground(withGround({ ...river, floodLevelEl: 28 }))).toEqual(['G08']);
|
||||
});
|
||||
|
||||
it('a DHWL above the 지반고 passes', () => {
|
||||
expect(ground(withGround({ ...river, floodLevelEl: 30 }))).toEqual([]);
|
||||
});
|
||||
|
||||
it('G09: a negative scour depth, or a scour line below the footing bottom', () => {
|
||||
expect(ground(withGround({ ...river, floodLevelEl: 30, scourDepthM: -1 }))).toEqual(['G09']);
|
||||
// 28.931 − 3 = 25.931 is below the footing bottom 26.208; 28.931 − 1 = 27.931 is above it
|
||||
expect(ground(withGround({ ...river, floodLevelEl: 30, scourDepthM: 3 }))).toEqual(['G09']);
|
||||
expect(ground(withGround({ ...river, floodLevelEl: 30, scourDepthM: 1 }))).toEqual([]);
|
||||
});
|
||||
|
||||
it('the groundwater check does not apply to a 하상, so a high groundwater value is ignored there', () => {
|
||||
expect(ground(withGround({ ...river, floodLevelEl: 30, groundWaterOn: true, groundWaterEl: 99 }))).toEqual([]);
|
||||
});
|
||||
|
||||
it('the 설계홍수위 is kept when the position is changed back to 육상 (남겨두기)', () => {
|
||||
const input = withGround({ ...river, floodLevelEl: 30 });
|
||||
const back = { ...input, groundPosition: '육상' as const };
|
||||
expect(back.floodLevelEl).toBe(30);
|
||||
});
|
||||
|
||||
it('the copy item carries the 설계홍수위 with the position', () => {
|
||||
expect(COPY_ITEMS['ground-position'].keys).toContain('floodLevelEl');
|
||||
});
|
||||
|
||||
it('the section draws the 설계홍수위 and the 세굴선, and no groundwater label', () => {
|
||||
const model = buildPier(base);
|
||||
const svg = groundSectionSvg(withGround({ ...river, floodLevelEl: 30, scourDepthM: 1, groundWaterOn: true, groundWaterEl: 25 }), model);
|
||||
expect(svg).toContain('설계홍수위 DHWL 30.000');
|
||||
expect(svg).toContain('세굴선 27.931');
|
||||
expect(svg).not.toContain('지하수위 25');
|
||||
});
|
||||
});
|
||||
@@ -16,11 +16,11 @@ describe('형상 선택지 (spec §5.2, plan C5)', () => {
|
||||
expect(defaultSummary('ground')).toBe('육상');
|
||||
});
|
||||
|
||||
it('only defaults are selectable; every other option is refused with the reason', () => {
|
||||
it('defaults and confirmed options are selectable (하상, 승인 2026-10-26); every other option is refused with the reason', () => {
|
||||
for (const m of MEMBERS) {
|
||||
SHAPE_GROUPS[m].forEach((g, gi) => {
|
||||
for (const o of g.options) {
|
||||
if (o.isDefault) {
|
||||
if (o.isDefault || o.confirmed) {
|
||||
expect(choose(m, gi, o.label)).toBe(o.label);
|
||||
} else {
|
||||
expect(isSelectable(o)).toBe(false);
|
||||
@@ -35,3 +35,9 @@ describe('형상 선택지 (spec §5.2, plan C5)', () => {
|
||||
expect(() => choose('coping', 0, '없는선택')).toThrow('알 수 없는');
|
||||
});
|
||||
});
|
||||
|
||||
it('하상 is the only confirmed ground option; 해상 stays refused until its rule is confirmed', () => {
|
||||
const ground = SHAPE_GROUPS.ground[0].options;
|
||||
expect(ground.find((o) => o.label === '하상')!.confirmed).toBe(true);
|
||||
expect(() => choose('ground', 0, '해상')).toThrow('규칙 미정');
|
||||
});
|
||||
Reference in new issue
Block a user