285 lines
9.5 KiB
TypeScript
285 lines
9.5 KiB
TypeScript
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 为 undefined,fallback 到 base
|
||
padding: "8px", // overrides 为 null,fallback 到 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 为 undefined,fallback
|
||
color: "#111", // overrides 为空字符串,fallback
|
||
});
|
||
});
|
||
});
|
||
});
|