Merge remote-tracking branch 'origin/dev' into sub_laptop_8
This commit is contained in:
@@ -216,6 +216,34 @@ ok("0.00", fmt(frac(BigInt(150), 100n), "0.00") === "1.50", fmt(frac(150n, 100n)
|
||||
ok("#,##0", fmt(frac(1234567n), "#,##0") === "1,234,567", fmt(frac(1234567n), "#,##0"));
|
||||
ok("천 나눔 끝쉼표", fmt(frac(1234000n), "#,##0,") === "1,234", fmt(frac(1234000n), "#,##0,"));
|
||||
ok("퍼센트", fmt(frac(50n, 100n), "0%") === "50%", fmt(frac(50n, 100n), "0%"));
|
||||
|
||||
// 실무 구조도 xlsx 세 벌 실측 코드(2026-09-27 대조) — 회귀 방지.
|
||||
ok(
|
||||
"_x 폭띄움 끝쉼표",
|
||||
fmt(frac(3n), "0.00_ ") === "3.00 ",
|
||||
JSON.stringify(fmt(frac(3n), "0.00_ ")),
|
||||
);
|
||||
ok(
|
||||
"_x 글에는 안 먹음(순수 수 형식)",
|
||||
fmt("외부벽체", "0.00_ ") === "외부벽체",
|
||||
fmt("외부벽체", "0.00_ "),
|
||||
);
|
||||
ok(
|
||||
"General 단위접미(실무 버릇)",
|
||||
fmt(frac(4n), 'General"열"') === "4열",
|
||||
fmt(frac(4n), 'General"열"'),
|
||||
);
|
||||
ok(
|
||||
"회계형(끝 구역 글)",
|
||||
fmt(frac(1044n, 1000n), '_-* #,##0.00_-;\\-* #,##0.00_-;_-* "-"_-;_-@_-') === " 1.04 ",
|
||||
fmt(frac(1044n, 1000n), '_-* #,##0.00_-;\\-* #,##0.00_-;_-* "-"_-;_-@_-'),
|
||||
);
|
||||
ok(
|
||||
"통화 태그([$기호-로캘])",
|
||||
fmt(frac(2n), '_-[$€-2]* #,##0.00_-;-[$€-2]* #,##0.00_-;_-[$€-2]* "-"??_-') === " €2.00 ",
|
||||
fmt(frac(2n), '_-[$€-2]* #,##0.00_-;-[$€-2]* #,##0.00_-;_-[$€-2]* "-"??_-'),
|
||||
);
|
||||
ok("따옴표 접두·접미", fmt(frac(800n), '"φ"###') === "φ800", fmt(frac(800n), '"φ"###'));
|
||||
ok(
|
||||
"음수 회계형",
|
||||
fmt(frac(-150n, 100n), "0.00_);[Red](0.00)") === "(1.50)",
|
||||
@@ -278,6 +306,15 @@ function bookWith(cells: Record<string, Cell>): Workbook {
|
||||
JSON.stringify(out),
|
||||
);
|
||||
}
|
||||
{
|
||||
const book = bookWith({ B1: { 식: "A1*2" } });
|
||||
const out = fillCells(book, "s1", { r0: 0, c0: 1, r1: 0, c1: 1 }, { r0: 0, c0: 1, r1: 2, c1: 1 });
|
||||
ok(
|
||||
"채우기 식 상대 이동(A moveFormula)",
|
||||
out.B2?.식 === "A2*2" && out.B3?.식 === "A3*2",
|
||||
JSON.stringify(out),
|
||||
);
|
||||
}
|
||||
|
||||
// ── 요약 ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>C 격자 시험 틀</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
#app {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./spreadsheet_grid_test.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,161 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_grid_test.ts
|
||||
* C(격자) 화면 시험 틀 — 병합 · 틀고정 · 숨김 · 폭 · hair 선 · 균등 분할 · 선택 영역 가운데 ·
|
||||
* [Red] 음수 서식 · 1만 행 스크롤 때 DOM 칸 수를 ORCA 로 확인. D · 엔진이 없으니 이 파일이
|
||||
* `SpreadsheetContext` 를 최소로 흉내 낸다(엔진 값은 안 씀 — 식 칸이 없음).
|
||||
* ========================================================================== */
|
||||
|
||||
import { createGrid } from "../../../A00_Common/spreadsheet/spreadsheet_grid";
|
||||
import type {
|
||||
CalcEngine,
|
||||
Command,
|
||||
History,
|
||||
Sheet,
|
||||
Workbook,
|
||||
} from "../../../A00_Common/spreadsheet/spreadsheet_types";
|
||||
import { colIndex } from "../../../A00_Common/spreadsheet/spreadsheet_address";
|
||||
import type {
|
||||
GridHandle,
|
||||
SpreadsheetContext,
|
||||
} from "../../../A00_Common/spreadsheet/spreadsheet_view_types";
|
||||
|
||||
const sheet: Sheet = {
|
||||
id: "s1",
|
||||
이름: "시험",
|
||||
칸: {
|
||||
A1: { 값: "□격자 시험 — 병합 · 틀고정 · 숨김 · hair 선", 서식: 1 },
|
||||
A3: { 값: "공종", 서식: 2 },
|
||||
B3: { 값: "규격", 서식: 2 },
|
||||
C3: { 값: "산출 내역", 서식: 2 },
|
||||
G3: { 값: "단위", 서식: 2 },
|
||||
H3: { 값: "수량", 서식: 2 },
|
||||
A4: { 값: "터파기", 서식: 3 },
|
||||
C4: { 값: "균등 분할 정렬 보기 글", 서식: 4 },
|
||||
H4: { 값: -12.5, 서식: 6 },
|
||||
A6: { 값: "선택 영역 가운데(centerContinuous) 보기", 서식: 5 },
|
||||
A8: { 값: "hair 테두리 칸", 서식: 3 },
|
||||
B8: { 값: 3.14159, 서식: 7 },
|
||||
J10000: { 값: "맨 끝 칸(1만 행 스크롤 확인용)" },
|
||||
},
|
||||
comments: { H4: "메모 — ROUNDDOWN 끝수 확인" },
|
||||
병합: ["A1:H1", "B8:C9"],
|
||||
열: { A: { 폭: 14 }, D: { 숨김: true } },
|
||||
행: { "1": { 높이: 22 }, "5": { 숨김: true } },
|
||||
틀고정: { 행: 3, 열: 1 },
|
||||
보기: { 눈금선: true },
|
||||
};
|
||||
|
||||
const book: Workbook = {
|
||||
종류: "통합문서",
|
||||
판: 1,
|
||||
열: "test",
|
||||
기본: { 글꼴: "굴림", 크기: 10, 열폭: 8.43, 행높이: 15 },
|
||||
서식: [
|
||||
{},
|
||||
{ 크기: 14, 굵게: true },
|
||||
{
|
||||
가로: "center",
|
||||
세로: "center",
|
||||
채움: "#F2F2F2",
|
||||
테두리: {
|
||||
위: { 선: "thin" },
|
||||
아래: { 선: "thin" },
|
||||
왼: { 선: "thin" },
|
||||
오른: { 선: "thin" },
|
||||
},
|
||||
},
|
||||
{ 세로: "center", 테두리: { 위: { 선: "hair" }, 아래: { 선: "hair" } } },
|
||||
{ 가로: "distributed", 세로: "center" },
|
||||
{ 가로: "centerContinuous", 세로: "center" },
|
||||
{ 형식: "0.00;[Red]-0.00" },
|
||||
{
|
||||
형식: "0.0000",
|
||||
테두리: {
|
||||
위: { 선: "hair" },
|
||||
아래: { 선: "hair" },
|
||||
왼: { 선: "hair" },
|
||||
오른: { 선: "hair" },
|
||||
},
|
||||
},
|
||||
],
|
||||
시트: [sheet],
|
||||
활성: "s1",
|
||||
};
|
||||
|
||||
function noopEngine(): CalcEngine {
|
||||
return {
|
||||
update: () => [],
|
||||
rebuild: () => {},
|
||||
value: () => null,
|
||||
precedents: () => [],
|
||||
snapshot: () => ({}),
|
||||
};
|
||||
}
|
||||
|
||||
function noopHistory(): History {
|
||||
return {
|
||||
push: () => {},
|
||||
undo: () => null,
|
||||
redo: () => null,
|
||||
canUndo: () => false,
|
||||
canRedo: () => false,
|
||||
clear: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
let grid!: GridHandle;
|
||||
|
||||
const ctx: SpreadsheetContext = {
|
||||
root: document.getElementById("app")!,
|
||||
book,
|
||||
readOnly: false,
|
||||
engine: noopEngine(),
|
||||
history: noopHistory(),
|
||||
selection: {
|
||||
시트: "s1",
|
||||
범위: [{ r0: 2, c0: 2, r1: 2, c1: 2 }],
|
||||
활성: { r: 2, c: 2 },
|
||||
기준: { r: 2, c: 2 },
|
||||
},
|
||||
sheet: () => sheet,
|
||||
dispatch: (command: Command) => {
|
||||
if (command.종류 === "열폭") {
|
||||
const c0 = command.열[0];
|
||||
const key = c0 >= 0 ? String.fromCharCode(65 + c0) : "A"; // 시험 데이터가 A~J 안쪽이라 단순 변환으로 충분
|
||||
sheet.열 = { ...sheet.열, [key]: { ...sheet.열?.[key], 폭: command.폭 ?? undefined } };
|
||||
} else if (command.종류 === "행높이") {
|
||||
const r0 = command.행[0];
|
||||
sheet.행 = {
|
||||
...sheet.행,
|
||||
[String(r0 + 1)]: { ...sheet.행?.[String(r0 + 1)], 높이: command.높이 ?? undefined },
|
||||
};
|
||||
}
|
||||
grid.render();
|
||||
},
|
||||
undo: () => {},
|
||||
redo: () => {},
|
||||
select: () => grid.renderSelection(),
|
||||
showSheet: () => {},
|
||||
get grid() {
|
||||
return grid;
|
||||
},
|
||||
editor: {
|
||||
editing: () => false,
|
||||
begin: () => {},
|
||||
commit: () => {},
|
||||
cancel: () => {},
|
||||
sync: () => {},
|
||||
},
|
||||
} as SpreadsheetContext;
|
||||
|
||||
grid = createGrid(ctx);
|
||||
ctx.root.append(grid.root);
|
||||
|
||||
// ORCA 수치 확인용 — 눈에 안 보이는 전역에 걸어 둠(콘솔 · eval 로 셈).
|
||||
(window as unknown as { __gridTest: unknown }).__gridTest = {
|
||||
grid,
|
||||
sheet,
|
||||
book,
|
||||
colIndex,
|
||||
countCells: () => grid.root.querySelectorAll(".aislo-cell").length,
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_numfmt_survey_entry.ts (B 자기 시험)
|
||||
* 실무 구조도 xlsx 세 벌에서 뽑은 실제 숫자 형식 코드 전부를 numfmt 로 돌려 사람이 대조.
|
||||
* 입력 = 시험이 openpyxl 로 미리 뽑은 JSON(코드 · 실측 수 · 실측 글 견본).
|
||||
* ========================================================================== */
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { toFrac } from "@ui/sheet/ui_template_sheet_frac";
|
||||
import { formatValue } from "../../../A00_Common/spreadsheet/spreadsheet_numfmt";
|
||||
|
||||
interface CodeSample {
|
||||
code: string;
|
||||
count: number;
|
||||
num: number | null;
|
||||
str: string | null;
|
||||
}
|
||||
|
||||
const inputPath = process.argv[2];
|
||||
const samples: CodeSample[] = JSON.parse(readFileSync(inputPath, "utf-8"));
|
||||
|
||||
const rows = samples.map((s) => {
|
||||
let numOut: string | null = null;
|
||||
let numErr: string | null = null;
|
||||
if (s.num !== null) {
|
||||
try {
|
||||
numOut = formatValue(toFrac(s.num), s.code).글;
|
||||
} catch (e) {
|
||||
numErr = String(e);
|
||||
}
|
||||
}
|
||||
let strOut: string | null = null;
|
||||
let strErr: string | null = null;
|
||||
if (s.str !== null) {
|
||||
try {
|
||||
strOut = formatValue(s.str, s.code).글;
|
||||
} catch (e) {
|
||||
strErr = String(e);
|
||||
}
|
||||
}
|
||||
return { ...s, numOut, numErr, strOut, strErr };
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(rows, null, 1));
|
||||
@@ -60,7 +60,10 @@ def test_M02_만_늦게_불러옴():
|
||||
check=True,
|
||||
).stdout.splitlines()
|
||||
for rel in listed:
|
||||
if rel.startswith("A00_Common/spreadsheet/") or not (ROOT / rel).is_file():
|
||||
# 시험용 진입(`resources/tester/`)은 앱 번들 밖
|
||||
if rel.startswith(("A00_Common/spreadsheet/", "resources/tester/")):
|
||||
continue
|
||||
if not (ROOT / rel).is_file():
|
||||
continue
|
||||
text = (ROOT / rel).read_text(encoding="utf-8", errors="replace")
|
||||
if "spreadsheet/spreadsheet" not in text:
|
||||
|
||||
Reference in New Issue
Block a user