import { describe, it, expect } from "vitest"; import { expectNoPxInStyleObject, expectNoPxInStyleString } from "./styleInvariants"; import { computeFormInputExportTokens, computeFormSelectExportTokens, computeFormCheckboxExportTokens, computeFormRadioExportTokens, computeFormInputEditorTokens, computeFormSelectEditorTokens, computeFormOptionGroupEditorTokens, computeFormInputPublicTokens, computeFormSelectPublicTokens, computeFormCheckboxPublicTokens, computeFormRadioPublicTokens, computeFormControllerPublicTokens, computeFormBlockExportTokens, } from "@/features/editor/utils/formHelpers"; import type { Block, FormBlockProps, FormInputBlockProps, FormSelectBlockProps, FormCheckboxBlockProps, FormRadioBlockProps, } from "@/features/editor/state/editorStore"; const baseInput: FormInputBlockProps = { label: "이름", formFieldName: "name", }; const baseSelect: FormSelectBlockProps = { label: "플랜", formFieldName: "plan", options: [], }; const baseCheckbox: FormCheckboxBlockProps = { groupLabel: "옵션", formFieldName: "options", options: [], }; const baseRadio: FormRadioBlockProps = { groupLabel: "선택", formFieldName: "choice", options: [], }; describe("formHelpers.computeFormInputExportTokens", () => { it("텍스트 색상/배경/테두리/패딩/너비/둥글기 스타일을 계산해야 한다", () => { const tokens = computeFormInputExportTokens({ ...baseInput, textColorCustom: " #ff0000 ", fillColorCustom: " #111827 ", strokeColorCustom: " #4b5563 ", paddingX: 8, paddingY: 4, widthMode: "fixed", widthPx: 300, borderRadius: "lg", }); expect(tokens.labelStyleAttr).toBe(' style="color:#ff0000"'); expect(tokens.inputStyleAttr).toContain("color:#ff0000"); expect(tokens.inputStyleAttr).toContain("background-color:#111827"); expect(tokens.inputStyleAttr).toContain("border-color:#4b5563"); expect(tokens.inputStyleAttr).toContain("padding-left:0.5em"); expect(tokens.inputStyleAttr).toContain("padding-right:0.5em"); expect(tokens.inputStyleAttr).toContain("padding-top:0.25em"); expect(tokens.inputStyleAttr).toContain("padding-bottom:0.25em"); expect(tokens.inputStyleAttr).toContain("width:18.75em"); expect(tokens.inputStyleAttr).toContain("border-radius:6px"); // Export 인풋 스타일에는 border-radius 등의 예외를 제외하고 px 단위가 포함되지 않아야 한다. expectNoPxInStyleString(tokens.inputStyleAttr, { allowPatterns: [/border-radius:\d+px/, /border-width:\d+px/], }); }); it("스타일 값이 없으면 style 속성이 비어 있어야 한다", () => { const tokens = computeFormInputExportTokens(baseInput); expect(tokens.labelStyleAttr).toBe(""); expect(tokens.inputStyleAttr).toBe(' style="border-radius:4px"'); }); }); describe("formHelpers.computeFormSelectExportTokens", () => { it("텍스트 색상/배경/테두리/패딩/너비/둥글기 스타일을 계산해야 한다", () => { const tokens = computeFormSelectExportTokens({ ...baseSelect, textColorCustom: " #00ff00 ", fillColorCustom: " #020617 ", strokeColorCustom: " #64748b ", paddingX: 12, paddingY: 6, widthMode: "fixed", widthPx: 240, borderRadius: "full", }); expect(tokens.labelStyleAttr).toBe(' style="color:#00ff00"'); expect(tokens.selectStyleAttr).toContain("color:#00ff00"); expect(tokens.selectStyleAttr).toContain("background-color:#020617"); expect(tokens.selectStyleAttr).toContain("border-color:#64748b"); expect(tokens.selectStyleAttr).toContain("padding-left:0.75em"); expect(tokens.selectStyleAttr).toContain("padding-right:0.75em"); expect(tokens.selectStyleAttr).toContain("padding-top:0.375em"); expect(tokens.selectStyleAttr).toContain("padding-bottom:0.375em"); expect(tokens.selectStyleAttr).toContain("width:15em"); expect(tokens.selectStyleAttr).toContain("border-radius:9999px"); // Export 셀렉트 스타일에는 border-radius 등의 예외를 제외하고 px 단위가 포함되지 않아야 한다. expectNoPxInStyleString(tokens.selectStyleAttr, { allowPatterns: [/border-radius:\d+px/, /border-width:\d+px/], }); }); it("스타일 값이 없으면 style 속성이 비어 있어야 한다", () => { const tokens = computeFormSelectExportTokens(baseSelect); expect(tokens.labelStyleAttr).toBe(""); expect(tokens.selectStyleAttr).toBe(' style="border-radius:4px"'); }); }); describe("formHelpers.computeFormCheckboxExportTokens", () => { it("텍스트 색상/배경/테두리/패딩/둥글기 스타일을 계산해야 한다", () => { const tokens = computeFormCheckboxExportTokens({ ...baseCheckbox, textColorCustom: " #38bdf8 ", fillColorCustom: " #0f172a ", strokeColorCustom: " #e5e7eb ", paddingX: 10, paddingY: 5, borderRadius: "sm", }); expect(tokens.labelStyleAttr).toBe(' style="color:#38bdf8"'); expect(tokens.optionStyleAttr).toContain("color:#38bdf8"); expect(tokens.optionStyleAttr).toContain("background-color:#0f172a"); expect(tokens.optionStyleAttr).toContain("border-color:#e5e7eb"); expect(tokens.optionStyleAttr).toContain("border-width:1px"); expect(tokens.optionStyleAttr).toContain("border-style:solid"); expect(tokens.optionStyleAttr).toContain("padding-left:0.625em"); expect(tokens.optionStyleAttr).toContain("padding-right:0.625em"); expect(tokens.optionStyleAttr).toContain("padding-top:0.3125em"); expect(tokens.optionStyleAttr).toContain("padding-bottom:0.3125em"); expect(tokens.optionStyleAttr).toContain("border-radius:2px"); // Export 체크박스 옵션 스타일에는 border/border-radius 를 제외하고 px 단위가 포함되지 않아야 한다. expectNoPxInStyleString(tokens.optionStyleAttr, { allowPatterns: [/border-radius:\d+px/, /border-width:\d+px/], }); }); it("스타일 값이 없으면 style 속성이 비어 있어야 한다", () => { const tokens = computeFormCheckboxExportTokens(baseCheckbox); expect(tokens.labelStyleAttr).toBe(""); expect(tokens.optionStyleAttr).toBe(' style="border-radius:4px"'); }); }); describe("formHelpers.computeFormRadioExportTokens", () => { it("체크박스와 동일한 규칙으로 옵션 스타일을 계산해야 한다", () => { const tokens = computeFormRadioExportTokens({ ...baseRadio, textColorCustom: " #fbbf24 ", fillColorCustom: " #111827 ", strokeColorCustom: " #facc15 ", paddingX: 4, paddingY: 2, borderRadius: "md", }); expect(tokens.labelStyleAttr).toBe(' style="color:#fbbf24"'); expect(tokens.optionStyleAttr).toContain("color:#fbbf24"); expect(tokens.optionStyleAttr).toContain("background-color:#111827"); expect(tokens.optionStyleAttr).toContain("border-color:#facc15"); expect(tokens.optionStyleAttr).toContain("border-width:1px"); expect(tokens.optionStyleAttr).toContain("border-style:solid"); expect(tokens.optionStyleAttr).toContain("padding-left:0.25em"); expect(tokens.optionStyleAttr).toContain("padding-right:0.25em"); expect(tokens.optionStyleAttr).toContain("padding-top:0.125em"); expect(tokens.optionStyleAttr).toContain("padding-bottom:0.125em"); expect(tokens.optionStyleAttr).toContain("border-radius:4px"); // Export 라디오 옵션 스타일에는 border/border-radius 를 제외하고 px 단위가 포함되지 않아야 한다. expectNoPxInStyleString(tokens.optionStyleAttr, { allowPatterns: [/border-radius:\d+px/, /border-width:\d+px/], }); }); }); describe("formHelpers - editor tokens", () => { it("computeFormInputEditorTokens: 에디터 인풋 필드 스타일을 계산해야 한다", () => { const tokens = computeFormInputEditorTokens({ ...baseInput, textColorCustom: "#ff0000", fillColorCustom: "#123456", strokeColorCustom: "#654321", widthMode: "fixed", widthPx: 240, borderRadius: "full", align: "center", labelLayout: "stacked", } as any); expect(tokens.fieldStyle.color).toBe("#ff0000"); expect(tokens.fieldStyle.backgroundColor).toBe("#123456"); expect(tokens.fieldStyle.borderColor).toBe("#654321"); expect(tokens.fieldStyle.width).toBe("240px"); expect(tokens.fieldStyle.borderRadius).toBe(9999); }); it("computeFormSelectEditorTokens: 에디터 셀렉트 필드 스타일을 계산해야 한다", () => { const tokens = computeFormSelectEditorTokens({ ...baseSelect, textColorCustom: "#ff0000", fillColorCustom: "#123456", strokeColorCustom: "#654321", widthMode: "fixed", widthPx: 260, borderRadius: "lg", paddingX: 12, paddingY: 6, fontSizeCustom: "14px", lineHeightCustom: "20px", letterSpacingCustom: "1px", } as any); expect(tokens.fieldStyle.color).toBe("#ff0000"); expect(tokens.fieldStyle.backgroundColor).toBe("#123456"); expect(tokens.fieldStyle.borderColor).toBe("#654321"); expect(tokens.fieldStyle.width).toBe("260px"); expect(tokens.fieldStyle.borderRadius).toBe(6); expect(tokens.fieldStyle.paddingInline).toBe("12px"); expect(tokens.fieldStyle.paddingBlock).toBe("6px"); expect(tokens.fieldStyle.fontSize).toBe("14px"); expect(tokens.fieldStyle.lineHeight).toBe("20px"); expect(tokens.fieldStyle.letterSpacing).toBe("1px"); }); it("computeFormOptionGroupEditorTokens: 에디터 라디오/체크박스 그룹 필드 스타일을 계산해야 한다", () => { const tokens = computeFormOptionGroupEditorTokens({ ...baseRadio, textColorCustom: "#ff0000", fillColorCustom: "#123456", strokeColorCustom: "#654321", widthMode: "fixed", widthPx: 280, borderRadius: "lg", paddingX: 8, paddingY: 4, fontSizeCustom: "15px", lineHeightCustom: "22px", letterSpacingCustom: "0.5px", } as any); expect(tokens.fieldStyle.color).toBe("#ff0000"); expect(tokens.fieldStyle.backgroundColor).toBe("#123456"); expect(tokens.fieldStyle.borderColor).toBe("#654321"); expect(tokens.fieldStyle.width).toBe("280px"); expect(tokens.fieldStyle.borderRadius).toBe(6); expect(tokens.fieldStyle.paddingInline).toBe("8px"); expect(tokens.fieldStyle.paddingBlock).toBe("4px"); expect(tokens.fieldStyle.fontSize).toBe("15px"); expect(tokens.fieldStyle.lineHeight).toBe("22px"); expect(tokens.fieldStyle.letterSpacing).toBe("0.5px"); }); }); describe("formHelpers - public tokens", () => { it("computeFormInputPublicTokens: 퍼블릭 인풋 필드 스타일을 계산해야 한다", () => { const tokens = computeFormInputPublicTokens({ ...baseInput, align: "center", widthMode: "fixed", widthPx: 320, paddingX: 16, paddingY: 8, textColorCustom: "#112233", fillColorCustom: "#123456", strokeColorCustom: "#ff0000", borderRadius: "lg", } as any); expect(tokens.wrapperLayoutClass).toBe("flex flex-col gap-1"); expect(tokens.widthClass).toBe(""); expect(tokens.inputStyle.textAlign).toBe("center"); expect(tokens.inputStyle.width).toBe("20em"); expect(tokens.inputStyle.paddingInline).toBe("1em"); expect(tokens.inputStyle.paddingBlock).toBe("0.5em"); expect(tokens.inputStyle.color).toBe("#112233"); expect(tokens.inputStyle.backgroundColor).toBe("#123456"); expect(tokens.inputStyle.borderColor).toBe("#ff0000"); expect(tokens.inputStyle.borderRadius).toBe("6px"); // 퍼블릭 인풋 토큰 스타일에는 borderRadius/borderWidth 를 제외하고 px 단위가 없어야 한다. expectNoPxInStyleObject(tokens.wrapperStyle); expectNoPxInStyleObject(tokens.inputStyle, { allowKeys: ["borderRadius", "borderWidth"] }); }); it("computeFormInputPublicTokens: 색상 커스텀 값이 없으면 color/backgroundColor/borderColor 를 설정하지 않아야 한다", () => { const tokens = computeFormInputPublicTokens({ ...baseInput, } as any); expect(tokens.inputStyle.color).toBeUndefined(); expect(tokens.inputStyle.backgroundColor).toBeUndefined(); expect(tokens.inputStyle.borderColor).toBeUndefined(); }); it("computeFormSelectPublicTokens: 퍼블릭 셀렉트 필드 스타일을 계산해야 한다", () => { const tokens = computeFormSelectPublicTokens({ ...baseSelect, widthMode: "fixed", widthPx: 240, paddingX: 16, paddingY: 8, textColorCustom: "#00ff00", fillColorCustom: "#123456", strokeColorCustom: "#ff0000", borderRadius: "lg", } as any); expect(tokens.widthClass).toBe(""); expect(tokens.wrapperStyle.width).toBe("15em"); expect(tokens.selectStyle.paddingInline).toBe("1em"); expect(tokens.selectStyle.paddingBlock).toBe("0.5em"); expect(tokens.selectStyle.color).toBe("#00ff00"); expect(tokens.selectStyle.backgroundColor).toBe("#123456"); expect(tokens.selectStyle.borderColor).toBe("#ff0000"); expect(tokens.selectStyle.borderRadius).toBe("6px"); // 퍼블릭 셀렉트 토큰 스타일에는 borderRadius/borderWidth 를 제외하고 px 단위가 없어야 한다. expectNoPxInStyleObject(tokens.wrapperStyle); expectNoPxInStyleObject(tokens.selectStyle, { allowKeys: ["borderRadius", "borderWidth"] }); }); it("computeFormSelectPublicTokens: 색상 커스텀 값이 없으면 color/backgroundColor/borderColor 를 설정하지 않아야 한다", () => { const tokens = computeFormSelectPublicTokens({ ...baseSelect, } as any); expect(tokens.wrapperStyle.color).toBeUndefined(); expect(tokens.selectStyle.color).toBeUndefined(); expect(tokens.selectStyle.backgroundColor).toBeUndefined(); expect(tokens.selectStyle.borderColor).toBeUndefined(); }); it("computeFormCheckboxPublicTokens: 퍼블릭 체크박스 그룹 스타일을 계산해야 한다", () => { const tokens = computeFormCheckboxPublicTokens({ ...baseCheckbox, widthMode: "fixed", widthPx: 320, paddingX: 8, paddingY: 4, optionGapPx: 16, textColorCustom: "#ffffff", fillColorCustom: "#123456", strokeColorCustom: "#ff0000", borderRadius: "lg", } as any); expect(tokens.widthClass).toBe(""); expect(tokens.groupStyle.width).toBe("20em"); expect(tokens.optionsStyle.rowGap).toBe("1em"); expect(tokens.optionContainerStyle.paddingInline).toBe("0.5em"); expect(tokens.optionContainerStyle.paddingBlock).toBe("0.25em"); expect(tokens.optionContainerStyle.backgroundColor).toBe("#123456"); expect(tokens.optionContainerStyle.borderColor).toBe("#ff0000"); expect(tokens.optionContainerStyle.borderWidth).toBe("1px"); expect(tokens.optionContainerStyle.borderStyle).toBe("solid"); expect(tokens.optionContainerStyle.borderRadius).toBe("6px"); expect(tokens.groupTextStyle.color).toBe("#ffffff"); expect(tokens.optionTextStyle.color).toBe("#ffffff"); // 퍼블릭 체크박스 토큰 스타일에는 borderRadius/borderWidth 를 제외하고 px 단위가 없어야 한다. expectNoPxInStyleObject(tokens.groupStyle); expectNoPxInStyleObject(tokens.optionsStyle); expectNoPxInStyleObject(tokens.optionContainerStyle, { allowKeys: ["borderRadius", "borderWidth"] }); expectNoPxInStyleObject(tokens.groupTextStyle); expectNoPxInStyleObject(tokens.optionTextStyle); }); it("computeFormCheckboxPublicTokens: 색상 커스텀 값이 없으면 텍스트/옵션 컨테이너에 색상 스타일을 설정하지 않아야 한다", () => { const tokens = computeFormCheckboxPublicTokens({ ...baseCheckbox, } as any); expect(tokens.groupTextStyle.color).toBeUndefined(); expect(tokens.optionTextStyle.color).toBeUndefined(); expect(tokens.optionContainerStyle.backgroundColor).toBeUndefined(); expect(tokens.optionContainerStyle.borderColor).toBeUndefined(); }); it("computeFormRadioPublicTokens: 퍼블릭 라디오 그룹 스타일을 계산해야 한다", () => { const tokens = computeFormRadioPublicTokens({ ...baseRadio, widthMode: "fixed", widthPx: 320, paddingX: 8, paddingY: 4, optionGapPx: 16, textColorCustom: "#ffffff", fillColorCustom: "#123456", strokeColorCustom: "#ff0000", borderRadius: "lg", } as any); expect(tokens.widthClass).toBe(""); expect(tokens.groupStyle.width).toBe("20em"); expect(tokens.optionsStyle.rowGap).toBe("1em"); expect(tokens.optionContainerStyle.paddingInline).toBe("0.5em"); expect(tokens.optionContainerStyle.paddingBlock).toBe("0.25em"); expect(tokens.optionContainerStyle.backgroundColor).toBe("#123456"); expect(tokens.optionContainerStyle.borderColor).toBe("#ff0000"); expect(tokens.optionContainerStyle.borderWidth).toBe("1px"); expect(tokens.optionContainerStyle.borderStyle).toBe("solid"); expect(tokens.optionContainerStyle.borderRadius).toBe("6px"); expect(tokens.groupTextStyle.color).toBe("#ffffff"); expect(tokens.optionTextStyle.color).toBe("#ffffff"); }); it("computeFormRadioPublicTokens: 색상 커스텀 값이 없으면 텍스트/옵션 컨테이너에 색상 스타일을 설정하지 않아야 한다", () => { const tokens = computeFormRadioPublicTokens({ ...baseRadio, } as any); expect(tokens.groupTextStyle.color).toBeUndefined(); expect(tokens.optionTextStyle.color).toBeUndefined(); expect(tokens.optionContainerStyle.backgroundColor).toBeUndefined(); expect(tokens.optionContainerStyle.borderColor).toBeUndefined(); }); }); describe("formHelpers.computeFormControllerPublicTokens", () => { it("fieldIds 를 기반으로 컨트롤러 필드를 매핑하고 레이아웃/submitLabel 을 계산해야 한다", () => { const formBlock: Block = { id: "form-1", type: "form", props: { kind: "contact", submitTarget: "internal", fieldIds: ["input-1", "checkbox-1"], requiredFieldIds: ["input-1", "checkbox-1"], submitButtonId: "btn-1", formWidthMode: "fixed", formWidthPx: 320, marginYPx: 32, backgroundColorCustom: " #123456 ", } as FormBlockProps, } as Block; const inputBlock: Block = { id: "input-1", type: "formInput", props: { label: "이름", formFieldName: "name", inputType: "email", required: true, } as any, } as Block; const checkboxBlock: Block = { id: "checkbox-1", type: "formCheckbox", props: { groupLabel: "옵션", formFieldName: "options", required: true, groupLabelMode: "image", groupLabelImageUrl: "https://example.com/group.png", options: [ { label: "A", value: "a" }, { label: "B", value: "b" }, ], } as any, } as Block; const buttonBlock: Block = { id: "btn-1", type: "button", props: { label: "컨트롤러 버튼", } as any, } as Block; const blocks: Block[] = [formBlock, inputBlock, checkboxBlock, buttonBlock]; const tokens = computeFormControllerPublicTokens(formBlock, blocks); expect(tokens.fields).toHaveLength(2); const inputField = tokens.fields[0]; const checkboxField = tokens.fields[1]; expect(inputField.id).toBe("input-1"); expect(inputField.name).toBe("name"); expect(inputField.label).toBe("이름"); expect(inputField.type).toBe("text"); expect(inputField.required).toBe(true); expect(checkboxField.id).toBe("checkbox-1"); expect(checkboxField.name).toBe("options"); expect(checkboxField.label).toBe("옵션"); expect(checkboxField.type).toBe("checkbox"); expect(checkboxField.required).toBe(true); expect(checkboxField.options).toHaveLength(2); expect(checkboxField.groupLabelMode).toBe("image"); expect(checkboxField.groupLabelImageUrl).toBe("https://example.com/group.png"); expect(tokens.formClassName).toBe("space-y-3"); // FormBlock 은 레이아웃 정보는 가지지 않지만, backgroundColorCustom 으로 폼 래퍼 배경색만 제어할 수 있다. expect(tokens.formStyle).toEqual({ backgroundColor: "#123456" }); expect(tokens.submitLabel).toBe("컨트롤러 버튼"); }); it("fieldIds 가 없으면 fields 설정과 관계없이 빈 배열을 반환해야 한다", () => { const formWithFields: Block = { id: "form-2", type: "form", props: { kind: "contact", submitTarget: "internal", fields: [ { id: "f1", name: "email", label: "이메일", type: "email", required: true, }, ], } as FormBlockProps, } as Block; const tokensWithFields = computeFormControllerPublicTokens(formWithFields, [formWithFields]); // 프리뷰/퍼블릭 렌더러에서는 fieldIds 로 연결된 필드 블록만 사용하고, // props.fields(v1 설정)는 무시해야 한다. expect(tokensWithFields.fields).toHaveLength(0); const formFallback: Block = { id: "form-3", type: "form", props: { kind: "contact", submitTarget: "internal", } as FormBlockProps, } as Block; const tokensFallback = computeFormControllerPublicTokens(formFallback, [formFallback]); expect(tokensFallback.fields).toHaveLength(0); }); }); describe("formHelpers.computeFormBlockExportTokens", () => { it("fieldIds 가 있을 때 컨트롤러 필드와 폼 배경 스타일을 계산해야 한다", () => { const formBlock: Block = { id: "form-export-1", type: "form", props: { kind: "contact", submitTarget: "internal", fieldIds: ["input-1", "select-1"], fields: [], backgroundColorCustom: " #123456 ", } as FormBlockProps, } as Block; const inputBlock: Block = { id: "input-1", type: "formInput", props: { label: "이름", formFieldName: "name", inputType: "email", required: true, } as any, } as Block; const selectBlock: Block = { id: "select-1", type: "formSelect", props: { label: "플랜", formFieldName: "plan", options: [ { label: "Basic", value: "basic" }, { label: "Pro", value: "pro" }, ], } as any, } as Block; const blocks: Block[] = [formBlock, inputBlock, selectBlock]; const tokens = computeFormBlockExportTokens(formBlock, blocks); expect(tokens.hasControllerFields).toBe(true); expect(tokens.controllerFields).toHaveLength(2); expect(tokens.controllerFields[0].id).toBe("input-1"); expect(tokens.controllerFields[1].id).toBe("select-1"); expect(tokens.fallbackFields).toHaveLength(0); // FormBlock 은 Export 레이어에서도 컨테이너 배경/레이아웃 스타일을 가지지 않는다. expect(tokens.formStyleParts).toEqual([]); }); it("fieldIds 가 없고 fields 가 있을 때 fallbackFields 를 사용해야 한다", () => { const formBlock: Block = { id: "form-export-2", type: "form", props: { kind: "contact", submitTarget: "internal", fieldIds: [], fields: [ { id: "f1", name: "email", label: "이메일", type: "email", required: true, }, ], backgroundColorCustom: undefined, } as FormBlockProps, } as Block; const tokens = computeFormBlockExportTokens(formBlock, [formBlock]); expect(tokens.hasControllerFields).toBe(false); expect(tokens.controllerFields).toHaveLength(0); expect(tokens.fallbackFields).toHaveLength(1); expect(tokens.fallbackFields[0].id).toBe("f1"); expect(tokens.formStyleParts).toEqual([]); }); it("fieldIds 와 fields 가 모두 없으면 fallbackFields 도 빈 배열이어야 한다", () => { const formBlock: Block = { id: "form-export-3", type: "form", props: { kind: "contact", submitTarget: "internal", } as FormBlockProps, } as Block; const tokens = computeFormBlockExportTokens(formBlock, [formBlock]); expect(tokens.hasControllerFields).toBe(false); expect(tokens.controllerFields).toHaveLength(0); expect(tokens.fallbackFields).toHaveLength(0); }); });