feat(spreadsheet): 3단계 — 조건부 서식(계약 더함)
Sheet.조건부서식·유효성·이름정의 계약 더함(0 계약 · git-sync 뒤 더함) · spreadsheet_condfmt.ts — 값 비교·사이·글 포함·수식 조건 · 채움·글자색 · 규칙 관리 창 · 우선순위 옮김 · 행열 넣기·지우기 범위 따라가기(remapCondFmtRanges). 엔진(A)·격자(C) 파일은 안 건드림 — 잇는 법을 머리 주석에 적어 둠(잇기는 sub7). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUypcnp5d1gU2aeKh9F2H7
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
/* =============================================================================
|
||||
* test_stage3_condfmt.ts — spreadsheet_condfmt 시험. 돌리기: npx tsx <이 파일>.
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
addCondFmtRule,
|
||||
moveCondFmtRule,
|
||||
remapCondFmtRanges,
|
||||
removeCondFmtRule,
|
||||
resolveCondFmtStyle,
|
||||
} from "../../../A00_Common/spreadsheet/spreadsheet_condfmt.ts";
|
||||
import { assertEqual, makeBook, makeCtx, makeSheet, v } from "./test_stage2_helpers.ts";
|
||||
import type { Sheet } from "../../../A00_Common/spreadsheet/spreadsheet_types.ts";
|
||||
|
||||
const range = { r0: 0, c0: 0, r1: 0, c1: 0 };
|
||||
const sheet = makeSheet(
|
||||
"s1",
|
||||
{ A1: v(120) },
|
||||
{
|
||||
조건부서식: [
|
||||
{
|
||||
id: "cf1",
|
||||
범위: [range],
|
||||
조건: { 종류: "비교", 연산: "보다큼", 값: 100 },
|
||||
스타일: { 채움: "#ffcccc" },
|
||||
},
|
||||
],
|
||||
},
|
||||
) as Sheet;
|
||||
const book = makeBook([sheet]);
|
||||
|
||||
const env = { value: () => 120 };
|
||||
assertEqual(
|
||||
resolveCondFmtStyle(sheet, 0, 0, env),
|
||||
{ 채움: "#ffcccc" },
|
||||
"값 100 넘으면 스타일 먹임",
|
||||
);
|
||||
assertEqual(resolveCondFmtStyle(sheet, 5, 5, env), null, "범위 밖은 안 먹임");
|
||||
|
||||
const envLow = { value: () => 50 };
|
||||
assertEqual(resolveCondFmtStyle(sheet, 0, 0, envLow), null, "조건 안 맞으면 null");
|
||||
|
||||
// 「사이」 · 「글포함」 · 「수식」 조건
|
||||
const between = {
|
||||
id: "b",
|
||||
범위: [range],
|
||||
조건: { 종류: "사이" as const, 아래: 10, 위: 20 },
|
||||
스타일: {},
|
||||
};
|
||||
assertEqual(
|
||||
resolveCondFmtStyle({ ...sheet, 조건부서식: [between] }, 0, 0, { value: () => 15 }),
|
||||
{},
|
||||
"사이 조건 참",
|
||||
);
|
||||
const contains = {
|
||||
id: "c",
|
||||
범위: [range],
|
||||
조건: { 종류: "글포함" as const, 글: "돌" },
|
||||
스타일: {},
|
||||
};
|
||||
assertEqual(
|
||||
resolveCondFmtStyle({ ...sheet, 조건부서식: [contains] }, 0, 0, { value: () => "돌뒷길이" }),
|
||||
{},
|
||||
"글포함 조건 참",
|
||||
);
|
||||
const formula = { id: "f", 범위: [range], 조건: { 종류: "수식" as const, 식: "A1>0" }, 스타일: {} };
|
||||
assertEqual(
|
||||
resolveCondFmtStyle({ ...sheet, 조건부서식: [formula] }, 0, 0, {
|
||||
value: () => 1,
|
||||
evalFormula: (식) => 식 === "A1>0",
|
||||
}),
|
||||
{},
|
||||
"수식 조건 — evalFormula 콜백으로 참",
|
||||
);
|
||||
assertEqual(
|
||||
resolveCondFmtStyle({ ...sheet, 조건부서식: [formula] }, 0, 0, { value: () => 1 }),
|
||||
null,
|
||||
"evalFormula 없으면 수식 조건은 늘 거짓",
|
||||
);
|
||||
|
||||
// 우선순위 — 배열 앞쪽이 이김
|
||||
const two = makeSheet(
|
||||
"s2",
|
||||
{ A1: v(1) },
|
||||
{
|
||||
조건부서식: [
|
||||
{
|
||||
id: "first",
|
||||
범위: [range],
|
||||
조건: { 종류: "비교" as const, 연산: "같음" as const, 값: 1 },
|
||||
스타일: { 채움: "red" },
|
||||
},
|
||||
{
|
||||
id: "second",
|
||||
범위: [range],
|
||||
조건: { 종류: "비교" as const, 연산: "같음" as const, 값: 1 },
|
||||
스타일: { 채움: "blue" },
|
||||
},
|
||||
],
|
||||
},
|
||||
) as Sheet;
|
||||
assertEqual(
|
||||
resolveCondFmtStyle(two, 0, 0, { value: () => 1 }),
|
||||
{ 채움: "red" },
|
||||
"먼저 나온 규칙이 이김",
|
||||
);
|
||||
|
||||
// CRUD — 시트통째로 나감
|
||||
const ctx = makeCtx(book, "s1");
|
||||
addCondFmtRule(ctx, { 범위: [range], 조건: { 종류: "글포함", 글: "x" }, 스타일: { 굵게: true } });
|
||||
assertEqual(ctx.applied.at(-1)?.종류, "시트통째", "addCondFmtRule 은 시트통째로 나감");
|
||||
assertEqual(book.시트[0].조건부서식?.length, 2, "규칙이 더해짐");
|
||||
|
||||
const addedId = book.시트[0].조건부서식![1].id;
|
||||
moveCondFmtRule(ctx, addedId, -1);
|
||||
assertEqual(book.시트[0].조건부서식![0].id, addedId, "우선순위 위로 옮김");
|
||||
|
||||
removeCondFmtRule(ctx, "cf1");
|
||||
assertEqual(
|
||||
book.시트[0].조건부서식?.map((r) => r.id),
|
||||
[addedId],
|
||||
"규칙 지움",
|
||||
);
|
||||
|
||||
// 행 · 열 넣기 뒤 범위 옮김
|
||||
const shifted = remapCondFmtRanges(
|
||||
[
|
||||
{
|
||||
id: "a",
|
||||
범위: [{ r0: 0, c0: 0, r1: 0, c1: 0 }],
|
||||
조건: { 종류: "글포함", 글: "x" },
|
||||
스타일: {},
|
||||
},
|
||||
],
|
||||
(r) => ({ ...r, r0: r.r0 + 1, r1: r.r1 + 1 }),
|
||||
);
|
||||
assertEqual(
|
||||
shifted[0].범위[0],
|
||||
{ r0: 1, c0: 0, r1: 1, c1: 0 },
|
||||
"remapCondFmtRanges — 행 하나 밀림",
|
||||
);
|
||||
|
||||
const dropped = remapCondFmtRanges(
|
||||
[
|
||||
{
|
||||
id: "a",
|
||||
범위: [{ r0: 0, c0: 0, r1: 0, c1: 0 }],
|
||||
조건: { 종류: "글포함", 글: "x" },
|
||||
스타일: {},
|
||||
},
|
||||
],
|
||||
() => null,
|
||||
);
|
||||
assertEqual(dropped, [], "remapCondFmtRanges — 범위를 다 잃으면 규칙째 버림");
|
||||
|
||||
if (process.exitCode) process.exit(process.exitCode);
|
||||
console.log("spreadsheet_condfmt 시험 끝");
|
||||
Reference in New Issue
Block a user