Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_3
# Conflicts: # A00_Common/spreadsheet/spreadsheet_types.ts
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 시험 끝");
|
||||
@@ -0,0 +1,71 @@
|
||||
/* =============================================================================
|
||||
* test_stage3_names.ts — spreadsheet_names 시험. 돌리기: npx tsx <이 파일>.
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
defineName,
|
||||
deleteName,
|
||||
invalidNameReason,
|
||||
listNames,
|
||||
renameName,
|
||||
resolveName,
|
||||
} from "../../../A00_Common/spreadsheet/spreadsheet_names.ts";
|
||||
import { assertEqual, makeBook, makeCtx, makeSheet, f, v } from "./test_stage2_helpers.ts";
|
||||
|
||||
assertEqual(invalidNameReason("단가", []), null, "정상 이름은 통과");
|
||||
assertEqual(invalidNameReason("", []), "이름이 비었음", "빈 이름 거절");
|
||||
assertEqual(invalidNameReason("A1", []), "칸 주소처럼 보이는 이름은 못 씀", "칸 주소 꼴 거절");
|
||||
assertEqual(
|
||||
invalidNameReason("1단가", []),
|
||||
"글자 · 숫자 · 밑줄만(첫 글자는 글자 · 밑줄)",
|
||||
"숫자로 시작 거절",
|
||||
);
|
||||
assertEqual(invalidNameReason("TRUE", []), "TRUE · FALSE 는 못 씀", "예약어 거절");
|
||||
assertEqual(
|
||||
invalidNameReason("단가", ["단가"]),
|
||||
"이미 있는 이름",
|
||||
"겹치는 이름 거절(대소문자 안 가림)",
|
||||
);
|
||||
|
||||
const range = { r0: 0, c0: 0, r1: 0, c1: 0 };
|
||||
const s1 = makeSheet("s1", { A1: v(100) });
|
||||
const s2 = makeSheet("s2", { B2: f("A1*2") });
|
||||
const book = makeBook([s1, s2]);
|
||||
const ctx = makeCtx(book, "s1");
|
||||
|
||||
assertEqual(defineName(ctx, "단가", range), null, "defineName 성공 — 까닭 없음");
|
||||
assertEqual(ctx.applied.at(-1)?.종류, "시트통째", "defineName 은 시트통째로 나감");
|
||||
assertEqual(listNames(book), [{ 이름: "단가", 시트: "s1", 범위: range }], "listNames 로 보임");
|
||||
|
||||
assertEqual(defineName(ctx, "단가", range), "이미 있는 이름", "같은 이름 두 번은 거절");
|
||||
|
||||
assertEqual(
|
||||
resolveName(book, "s1", "단가"),
|
||||
{ 시트: "s1", 범위: range },
|
||||
"resolveName 제 시트에서 찾음",
|
||||
);
|
||||
assertEqual(
|
||||
resolveName(book, "s2", "단가"),
|
||||
{ 시트: "s1", 범위: range },
|
||||
"resolveName 다른 시트에서도 느슨히 찾음",
|
||||
);
|
||||
assertEqual(resolveName(book, "s1", "없는이름"), null, "없는 이름은 null");
|
||||
|
||||
// 이름 바꾸기 — 정의 + 다른 시트 식 글 속 이름도 같이
|
||||
const s2f = makeSheet("s2b", { B2: f("단가*2") });
|
||||
const book2 = makeBook([s1, s2f]);
|
||||
const ctx2 = makeCtx(book2, "s1");
|
||||
defineName(ctx2, "단가", range);
|
||||
const reason = renameName(ctx2, "단가", "단가2");
|
||||
assertEqual(reason, null, "renameName 성공");
|
||||
assertEqual(book2.시트[0].이름정의?.[0].이름, "단가2", "정의 이름 바뀜");
|
||||
assertEqual(book2.시트[1].칸.B2.식, "단가2*2", "다른 시트 식 글 속 이름도 바뀜");
|
||||
|
||||
assertEqual(renameName(ctx2, "단가2", "TRUE"), "TRUE · FALSE 는 못 씀", "바꿀 이름도 규칙을 지킴");
|
||||
|
||||
deleteName(ctx2, "단가2");
|
||||
assertEqual(book2.시트[0].이름정의, [], "deleteName 으로 지움");
|
||||
assertEqual(book2.시트[1].칸.B2.식, "단가2*2", "지워도 식 글은 그대로 남음(#NAME? 은 풀이 때)");
|
||||
|
||||
if (process.exitCode) process.exit(process.exitCode);
|
||||
console.log("spreadsheet_names 시험 끝");
|
||||
@@ -0,0 +1,77 @@
|
||||
/* =============================================================================
|
||||
* test_stage3_validation.ts — spreadsheet_validation 시험. 돌리기: npx tsx <이 파일>.
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
addValidationRule,
|
||||
checkValue,
|
||||
findValidation,
|
||||
remapValidationRanges,
|
||||
removeValidationRule,
|
||||
} from "../../../A00_Common/spreadsheet/spreadsheet_validation.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("사과") },
|
||||
{
|
||||
유효성: [
|
||||
{ id: "v1", 범위: [range], 규칙: { 종류: "목록", 값들: ["사과", "배"] }, 어기면: "막음" },
|
||||
],
|
||||
},
|
||||
) as Sheet;
|
||||
const book = makeBook([sheet]);
|
||||
|
||||
assertEqual(findValidation(sheet, 0, 0)?.id, "v1", "findValidation 있음");
|
||||
assertEqual(findValidation(sheet, 9, 9), undefined, "범위 밖은 없음");
|
||||
|
||||
const rule = sheet.유효성![0];
|
||||
assertEqual(checkValue(rule, "배"), { ok: true, 메시지: "" }, "목록 안 값은 통과");
|
||||
assertEqual(checkValue(rule, "감").ok, false, "목록 밖 값은 막힘");
|
||||
assertEqual(checkValue(rule, "감").어기면, "막음", "어기면 = 막음 그대로 옴");
|
||||
|
||||
const rangeRule = {
|
||||
id: "v2",
|
||||
범위: [range],
|
||||
규칙: { 종류: "수범위" as const, 아래: 0, 위: 100 },
|
||||
어기면: "경고" as const,
|
||||
메시지: "0~100 사이만",
|
||||
};
|
||||
assertEqual(checkValue(rangeRule, 50), { ok: true, 메시지: "" }, "수범위 안 값은 통과");
|
||||
assertEqual(
|
||||
checkValue(rangeRule, 150),
|
||||
{ ok: false, 어기면: "경고", 메시지: "0~100 사이만" },
|
||||
"수범위 밖 · 안내 글 그대로",
|
||||
);
|
||||
assertEqual(checkValue(rangeRule, "글").ok, false, "글은 수범위 규칙에서 늘 거짓");
|
||||
|
||||
// CRUD — 시트통째로 나감
|
||||
const ctx = makeCtx(book, "s1");
|
||||
addValidationRule(ctx, { 범위: [range], 규칙: { 종류: "수범위", 위: 10 }, 어기면: "경고" });
|
||||
assertEqual(ctx.applied.at(-1)?.종류, "시트통째", "addValidationRule 은 시트통째로 나감");
|
||||
assertEqual(book.시트[0].유효성?.length, 2, "규칙이 더해짐");
|
||||
|
||||
removeValidationRule(ctx, "v1");
|
||||
assertEqual(book.시트[0].유효성?.map((r) => r.id).includes("v1"), false, "지운 규칙은 없음");
|
||||
|
||||
// 행 · 열 넣기 뒤 범위 옮김
|
||||
const shifted = remapValidationRanges(
|
||||
[{ id: "a", 범위: [{ r0: 0, c0: 0, r1: 0, c1: 0 }], 규칙: { 종류: "수범위" }, 어기면: "막음" }],
|
||||
(r) => ({ ...r, c0: r.c0 + 1, c1: r.c1 + 1 }),
|
||||
);
|
||||
assertEqual(
|
||||
shifted[0].범위[0],
|
||||
{ r0: 0, c0: 1, r1: 0, c1: 1 },
|
||||
"remapValidationRanges — 열 하나 밀림",
|
||||
);
|
||||
|
||||
const dropped = remapValidationRanges(
|
||||
[{ id: "a", 범위: [{ r0: 0, c0: 0, r1: 0, c1: 0 }], 규칙: { 종류: "수범위" }, 어기면: "막음" }],
|
||||
() => null,
|
||||
);
|
||||
assertEqual(dropped, [], "remapValidationRanges — 범위를 다 잃으면 규칙째 버림");
|
||||
|
||||
if (process.exitCode) process.exit(process.exitCode);
|
||||
console.log("spreadsheet_validation 시험 끝");
|
||||
Reference in New Issue
Block a user