r-util-js/packages/vue3/test/style-helper.test.ts

285 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from "vitest";
import { normalizeStyle } from "../src/vue-helper/style-helper";
describe("normalizeStyle", () => {
// ========== 单参数:纯转换 ==========
// ---------- 空值 / falsy ----------
it("应将 undefined 转为空对象", () => {
expect(normalizeStyle(undefined as any)).toEqual({});
});
it("应将 null 转为空对象", () => {
expect(normalizeStyle(null as any)).toEqual({});
});
it("应将 false 转为空对象", () => {
expect(normalizeStyle(false as any)).toEqual({});
});
// ---------- CSSProperties 对象 ----------
it("应直接返回 CSSProperties 对象", () => {
const style = { color: "red", fontSize: "14px" };
expect(normalizeStyle(style)).toEqual(style);
});
it("应保留 CSSProperties 中的数字值", () => {
const style = { zIndex: 10, opacity: 0.5 };
expect(normalizeStyle(style)).toEqual({ zIndex: 10, opacity: 0.5 });
});
it("应保留 CSS 变量", () => {
const style = { "--custom-color": "red" } as any;
expect(normalizeStyle(style)).toEqual({ "--custom-color": "red" });
});
it("空对象应返回空对象", () => {
expect(normalizeStyle({})).toEqual({});
});
// ---------- 字符串 ----------
it("应解析简单的 CSS 字符串", () => {
expect(normalizeStyle("color: red")).toEqual({ color: "red" });
});
it("应解析多条声明的 CSS 字符串", () => {
expect(normalizeStyle("color: red; font-size: 14px")).toEqual({
color: "red",
"font-size": "14px",
});
});
it("应解析带空格的 CSS 字符串", () => {
expect(normalizeStyle(" color : red ; font-size : 14px ")).toEqual({
color: "red",
"font-size": "14px",
});
});
it("应忽略空声明", () => {
expect(normalizeStyle("color: red;; font-size:14px;")).toEqual({
color: "red",
"font-size": "14px",
});
});
it("应忽略尾部分号后的空项", () => {
expect(normalizeStyle("color: red;")).toEqual({ color: "red" });
});
it("应去除 CSS 注释", () => {
expect(normalizeStyle("color: red; /* 注释 */ font-size: 14px")).toEqual({
color: "red",
"font-size": "14px",
});
});
it("应解析含 url() 的值(不拆分括号内的分号)", () => {
expect(normalizeStyle("background: url(data:image/png;base64,abc)")).toEqual({
background: "url(data:image/png;base64,abc)",
});
});
it("应解析含冒号的值(如 url 中的协议)", () => {
expect(normalizeStyle("background: url(http://example.com/img.png)")).toEqual({
background: "url(http://example.com/img.png)",
});
});
it("空字符串应返回空对象", () => {
expect(normalizeStyle("")).toEqual({});
});
// ---------- 数组 ----------
it("应合并对象数组", () => {
expect(normalizeStyle([{ color: "red" }, { fontSize: "14px" }])).toEqual({
color: "red",
fontSize: "14px",
});
});
it("应合并字符串数组", () => {
expect(normalizeStyle(["color: red", "font-size: 14px"])).toEqual({
color: "red",
"font-size": "14px",
});
});
it("应合并混合类型数组", () => {
expect(normalizeStyle(["color: red", { fontSize: "14px" }])).toEqual({
color: "red",
fontSize: "14px",
});
});
it("应处理嵌套数组", () => {
expect(normalizeStyle([["color: red", { fontSize: "14px" }], { fontWeight: "bold" }])).toEqual({
color: "red",
fontSize: "14px",
fontWeight: "bold",
});
});
it("应跳过数组中的 falsy 值", () => {
expect(normalizeStyle([null, false, undefined, { color: "red" }] as any)).toEqual({
color: "red",
});
});
it("数组中后面的属性应覆盖前面的", () => {
expect(normalizeStyle([{ color: "red" }, { color: "blue" }])).toEqual({
color: "blue",
});
});
it("空数组应返回空对象", () => {
expect(normalizeStyle([])).toEqual({});
});
// ---------- 实际使用场景 ----------
it("应处理 AppButton 典型的 customStyle 用法", () => {
// 字符串形式(旧用法兼容)
expect(normalizeStyle("color: red; font-size: 28rpx")).toEqual({
color: "red",
"font-size": "28rpx",
});
// 对象形式(推荐用法)
expect(normalizeStyle({ color: "red", fontSize: "28rpx" })).toEqual({
color: "red",
fontSize: "28rpx",
});
// 数组形式
expect(normalizeStyle([{ color: "red" }, { fontSize: "28rpx" }])).toEqual({
color: "red",
fontSize: "28rpx",
});
});
// ========== 双参数:合并覆盖 ==========
describe("合并覆盖(第二个参数)", () => {
it("不传第二个参数时,行为与单参数一致", () => {
expect(normalizeStyle({ color: "red" })).toEqual({ color: "red" });
});
it("第二个参数为 undefined 时,行为与单参数一致", () => {
expect(normalizeStyle({ color: "red" }, undefined)).toEqual({ color: "red" });
});
it("第二个参数为 null 时,行为与单参数一致", () => {
expect(normalizeStyle({ color: "red" }, null as any)).toEqual({ color: "red" });
});
it("overrides 有值的属性应覆盖 base", () => {
expect(normalizeStyle({ color: "red" }, { color: "blue" })).toEqual({ color: "blue" });
});
it("overrides 新增的属性应合并进来", () => {
expect(normalizeStyle({ color: "red" }, { fontSize: "14px" })).toEqual({
color: "red",
fontSize: "14px",
});
});
it("overrides 和 base 各有不同的属性时,应全部合并", () => {
expect(
normalizeStyle({ color: "red", padding: "8px" }, { fontSize: "14px", margin: "4px" }),
).toEqual({
color: "red",
padding: "8px",
fontSize: "14px",
margin: "4px",
});
});
// ---------- overrides 为空值时 fallback 到 base ----------
it("overrides 属性为 undefined 时应使用 base 的值", () => {
expect(normalizeStyle({ color: "red" }, { color: undefined } as any)).toEqual({
color: "red",
});
});
it("overrides 属性为 null 时应使用 base 的值", () => {
expect(normalizeStyle({ color: "red" }, { color: null } as any)).toEqual({ color: "red" });
});
it("overrides 属性为空字符串时应使用 base 的值", () => {
expect(normalizeStyle({ color: "red" }, { color: "" })).toEqual({ color: "red" });
});
it("overrides 属性为 false 时应使用 base 的值", () => {
expect(normalizeStyle({ color: "red" }, { color: false } as any)).toEqual({ color: "red" });
});
// ---------- 两者都为空时过滤属性 ----------
it("base 和 overrides 都为空时,应过滤掉该属性", () => {
expect(normalizeStyle({ color: "" }, { color: undefined } as any)).toEqual({});
});
it("base 和 overrides 都为 null 时,应过滤掉该属性", () => {
expect(normalizeStyle({ color: null } as any, { color: null } as any)).toEqual({});
});
it("base 有值但属性为空、overrides 未指定该属性时,应过滤掉", () => {
// base 有 color:"" (空值), overrides 没有 color → both empty → filtered
expect(normalizeStyle({ color: "" }, {})).toEqual({});
});
// ---------- 字符串形式的 overrides ----------
it("overrides 为字符串时应正常解析并合并", () => {
expect(normalizeStyle({ color: "red" }, "font-size: 14px")).toEqual({
color: "red",
"font-size": "14px",
});
});
it("base 为字符串、overrides 为对象时应正常合并", () => {
// 字符串解析出 kebab-case 键,对象用 camelCase 键,两者独立
expect(normalizeStyle("color: red", { fontSize: "14px" })).toEqual({
color: "red",
fontSize: "14px",
});
});
// ---------- 数组形式的 overrides ----------
it("overrides 为数组时应正常解析并合并", () => {
expect(normalizeStyle({ color: "red" }, [{ fontSize: "14px" }])).toEqual({
color: "red",
fontSize: "14px",
});
});
// ---------- 0 是有效值,不算空 ----------
it("数字 0 应视为有效值,不当作空值", () => {
expect(normalizeStyle({ opacity: 1 }, { opacity: 0 })).toEqual({ opacity: 0 });
});
it("overrides 为 0 时不应 fallback 到 base", () => {
expect(normalizeStyle({ zIndex: 10 }, { zIndex: 0 })).toEqual({ zIndex: 0 });
});
// ---------- 综合场景 ----------
it("应正确处理混合覆盖和 fallback", () => {
const base = { color: "red", fontSize: "14px", padding: "8px", margin: "" };
const overrides = { color: "blue", fontSize: undefined, padding: null, margin: "4px" } as any;
expect(normalizeStyle(base, overrides)).toEqual({
color: "blue", // overrides 有值,覆盖
fontSize: "14px", // overrides 为 undefinedfallback 到 base
padding: "8px", // overrides 为 nullfallback 到 base
margin: "4px", // base 为空overrides 有值,使用 overrides
});
});
it("appButton 场景customStyle 覆盖内部样式", () => {
// 模拟 AppButton 内部样式 + customStyle
const internal = { width: "200rpx", backgroundColor: "#3adceb", color: "#111" };
const custom = { width: "128rpx", backgroundColor: undefined, color: "" } as any;
expect(normalizeStyle(internal, custom)).toEqual({
width: "128rpx", // overrides 有值,覆盖
backgroundColor: "#3adceb", // overrides 为 undefinedfallback
color: "#111", // overrides 为空字符串fallback
});
});
});
});