From 8748d93eb6ef9a858ca400a7546bdcfec6b56188 Mon Sep 17 00:00:00 2001 From: Oleg Isonen Date: Thu, 14 Apr 2022 11:40:26 +0100 Subject: [PATCH] add tests for parsing css value input --- .../style-panel/parse-css-value.test.ts | 120 ++++++++++++++++++ .../{parse-value.ts => parse-css-value.ts} | 21 +-- .../features/style-panel/use-style-data.ts | 13 +- 3 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 app/designer/features/style-panel/parse-css-value.test.ts rename app/designer/features/style-panel/{parse-value.ts => parse-css-value.ts} (80%) diff --git a/app/designer/features/style-panel/parse-css-value.test.ts b/app/designer/features/style-panel/parse-css-value.test.ts new file mode 100644 index 0000000000..8a188daf0d --- /dev/null +++ b/app/designer/features/style-panel/parse-css-value.test.ts @@ -0,0 +1,120 @@ +import { parseCssValue } from "./parse-css-value"; + +describe("Parse CSS value", () => { + describe("number value", () => { + test("unitless", () => { + expect(parseCssValue("lineHeight", "10")).toEqual({ + type: "unit", + unit: "number", + value: 10, + }); + }); + }); + + describe("unit value", () => { + test("with unit", () => { + expect(parseCssValue("width", "10px")).toEqual({ + type: "unit", + unit: "px", + value: 10, + }); + }); + + test("with default unit", () => { + expect(parseCssValue("width", "10", "px")).toEqual({ + type: "unit", + unit: "px", + value: 10, + }); + }); + + test("with unit and default unit", () => { + expect(parseCssValue("width", "10px", "rem")).toEqual({ + type: "unit", + unit: "px", + value: 10, + }); + }); + + test("empty input", () => { + expect(parseCssValue("width", "")).toEqual({ + type: "invalid", + value: "", + }); + }); + + test("empty input with default unit", () => { + expect(parseCssValue("width", "", "px")).toEqual({ + type: "invalid", + value: "", + }); + }); + }); + + describe("keyword value", () => { + test("keyword", () => { + expect(parseCssValue("width", "auto")).toEqual({ + type: "keyword", + value: "auto", + }); + }); + + test("keyword with unit", () => { + expect(parseCssValue("width", "autopx")).toEqual({ + type: "keyword", + value: "autopx", + }); + }); + + test("incomplete", () => { + // This will return invalid unit when `CSSStyleValue.parse` is available + expect(parseCssValue("width", "10p")).toEqual({ + type: "unit", + unit: "number", + value: 10, + }); + }); + }); + + describe("math expression", () => { + test("addition with unit", () => { + expect(parseCssValue("width", "10+1px")).toEqual({ + type: "unit", + unit: "px", + value: 11, + }); + }); + + test("addition without unit", () => { + expect(parseCssValue("width", "10+1", "px")).toEqual({ + type: "unit", + unit: "px", + value: 11, + }); + }); + + test("subtraction with unit", () => { + expect(parseCssValue("width", "10-1px")).toEqual({ + type: "unit", + unit: "px", + value: 9, + }); + }); + + test("multiplication with unit", () => { + expect(parseCssValue("width", "2*2px")).toEqual({ + type: "unit", + unit: "px", + value: 4, + }); + }); + + test("division with unit", () => { + expect(parseCssValue("width", "4/2px")).toEqual({ + type: "unit", + unit: "px", + value: 2, + }); + }); + }); +}); diff --git a/app/designer/features/style-panel/parse-value.ts b/app/designer/features/style-panel/parse-css-value.ts similarity index 80% rename from app/designer/features/style-panel/parse-value.ts rename to app/designer/features/style-panel/parse-css-value.ts index 912d0edc4a..bac7a0d98d 100644 --- a/app/designer/features/style-panel/parse-value.ts +++ b/app/designer/features/style-panel/parse-css-value.ts @@ -1,6 +1,6 @@ import hyphenate from "hyphenate-style-name"; import { units } from "@webstudio-is/sdk"; -import type { Style, StyleProperty, StyleValue, Unit } from "@webstudio-is/sdk"; +import type { StyleProperty, StyleValue, Unit } from "@webstudio-is/sdk"; const unitRegex = new RegExp(`${units.join("|")}`); @@ -55,10 +55,10 @@ const evaluate = (input: string, parsedUnit: [Unit] | null) => { // - 10px > px as unit // - empty string // - a keyword -export const parseValue = ( +export const parseCssValue = ( property: StyleProperty, input: string, - style: Style + defaultUnit?: Unit ): StyleValue => { const invalidValue = { type: "invalid", @@ -84,20 +84,11 @@ export const parseValue = ( return invalidValue; } - const previousValue = style[property]; // If user didn't enter a unit, use the previous known unit otherwise fallback to px. - const defaultUnit: Unit = - previousValue !== undefined && "unit" in previousValue - ? previousValue.unit - : "number"; - const [unit] = parsedUnit || [defaultUnit]; + const fallbackUnit: Unit = defaultUnit ?? "number"; + const [unit] = parsedUnit || [fallbackUnit]; - if ( - isValid(property, input) || - isValid(property, input + unit) || - isValid(property, number) || - isValid(property, number + unit) - ) { + if (isValid(property, number) || isValid(property, number + unit)) { return { type: "unit", unit, diff --git a/app/designer/features/style-panel/use-style-data.ts b/app/designer/features/style-panel/use-style-data.ts index 91634d61fd..269f8fa3f1 100644 --- a/app/designer/features/style-panel/use-style-data.ts +++ b/app/designer/features/style-panel/use-style-data.ts @@ -3,7 +3,7 @@ import type { SelectedInstanceData, StyleUpdates } from "~/shared/component"; import { type Style, type StyleProperty } from "@webstudio-is/sdk"; import { type Publish } from "~/designer/features/canvas-iframe"; import { useRootInstance } from "../../shared/nano-values"; -import { parseValue } from "./parse-value"; +import { parseCssValue } from "./parse-css-value"; import { getInheritedStyle, type InheritedStyle } from "./get-inherited-style"; type UseStyleData = { @@ -64,13 +64,16 @@ export const useStyleData = ({ const setProperty: SetProperty = (property) => { return (input, options = { isEphemeral: false }) => { if (currentStyle === undefined) return; - const value = parseValue(property, input, currentStyle); - if (value.type !== "invalid") { - const updates = [{ property, value }]; + const currentValue = currentStyle[property]; + const defaultUnit = + currentValue?.type === "unit" ? currentValue?.unit : undefined; + const nextValue = parseCssValue(property, input, defaultUnit); + if (nextValue.type !== "invalid") { + const updates = [{ property, value: nextValue }]; const type = options.isEphemeral ? "preview" : "update"; publishUpdates(type, updates); } - setCurrentStyle({ ...currentStyle, [property]: value }); + setCurrentStyle({ ...currentStyle, [property]: nextValue }); }; };