Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "fix: enhance boolean resolution in resolveExpression function",
"packageName": "@microsoft/fast-html",
"email": "[email protected]",
"dependentChangeType": "none"
}
147 changes: 146 additions & 1 deletion packages/fast-html/src/components/utilities.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from "@playwright/test";
import { type JSONSchema, refPropertyName } from "./schema.js";
import { type JSONSchema, refPropertyName, Schema } from "./schema.js";
import {
type AttributeDataBindingBehaviorConfig,
type ContentDataBindingBehaviorConfig,
Expand All @@ -13,6 +13,7 @@ import {
extractPathsFromChainedExpression,
getChildrenMap,
findDef,
resolveWhen,
} from "./utilities.js";

test.describe("utilities", async () => {
Expand Down Expand Up @@ -465,6 +466,150 @@ test.describe("utilities", async () => {
});
});

test.describe("resolveWhen - default case truthiness evaluation", async () => {
// Helper to create a basic schema for testing
const createTestSchema = (rootPropertyName: string, propertyName: string) => {
const schema = new Schema("test-element");
schema.addPath({
rootPropertyName,
pathConfig: {
type: "access",
path: propertyName,
currentContext: null,
parentContext: null,
},
childrenMap: null,
});
return schema;
};

test("should evaluate boolean true as truthy", async () => {
const schema = createTestSchema("testData", "boolTrue");
const expression = getExpressionChain("boolTrue");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ boolTrue: true } as any, null);
expect(result).toBe(true);
});

test("should evaluate boolean false as falsy", async () => {
const schema = createTestSchema("testData", "boolFalse");
const expression = getExpressionChain("boolFalse");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ boolFalse: false } as any, null);
expect(result).toBe(false);
});

test("should evaluate number 0 as falsy", async () => {
const schema = createTestSchema("testData", "numberZero");
const expression = getExpressionChain("numberZero");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ numberZero: 0 } as any, null);
expect(result).toBe(false);
});

test("should evaluate positive number as truthy", async () => {
const schema = createTestSchema("testData", "numberPositive");
const expression = getExpressionChain("numberPositive");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ numberPositive: 42 } as any, null);
expect(result).toBe(true);
});

test("should evaluate negative number as truthy", async () => {
const schema = createTestSchema("testData", "numberNegative");
const expression = getExpressionChain("numberNegative");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ numberNegative: -5 } as any, null);
expect(result).toBe(true);
});

test("should evaluate empty string as falsy", async () => {
const schema = createTestSchema("testData", "stringEmpty");
const expression = getExpressionChain("stringEmpty");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ stringEmpty: "" } as any, null);
expect(result).toBe(false);
});

test("should evaluate non-empty string as truthy", async () => {
const schema = createTestSchema("testData", "stringNonEmpty");
const expression = getExpressionChain("stringNonEmpty");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ stringNonEmpty: "hello" } as any, null);
expect(result).toBe(true);
});

test("should evaluate null as falsy", async () => {
const schema = createTestSchema("testData", "objectNull");
const expression = getExpressionChain("objectNull");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ objectNull: null } as any, null);
expect(result).toBe(false);
});

test("should evaluate undefined as falsy", async () => {
const schema = createTestSchema("testData", "undefinedProp");
const expression = getExpressionChain("undefinedProp");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ undefinedProp: undefined } as any, null);
expect(result).toBe(false);
});

test("should evaluate non-null object as truthy", async () => {
const schema = createTestSchema("testData", "objectNonNull");
const expression = getExpressionChain("objectNonNull");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ objectNonNull: { foo: "bar" } } as any, null);
expect(result).toBe(true);
});

test("should evaluate empty array as truthy", async () => {
const schema = createTestSchema("testData", "arrayEmpty");
const expression = getExpressionChain("arrayEmpty");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ arrayEmpty: [] } as any, null);
expect(result).toBe(true);
});

test("should evaluate non-empty array as truthy", async () => {
const schema = createTestSchema("testData", "arrayNonEmpty");
const expression = getExpressionChain("arrayNonEmpty");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ arrayNonEmpty: [1, 2, 3] } as any, null);
expect(result).toBe(true);
});

test("should evaluate string with only whitespace as truthy", async () => {
const schema = createTestSchema("testData", "stringWhitespace");
const expression = getExpressionChain("stringWhitespace");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ stringWhitespace: " " } as any, null);
expect(result).toBe(true);
});

test("should evaluate number NaN as truthy", async () => {
const schema = createTestSchema("testData", "numberNaN");
const expression = getExpressionChain("numberNaN");
const resolver = resolveWhen("testData", expression!, null, 0, schema);

const result = resolver({ numberNaN: NaN } as any, null);
expect(result).toBe(true);
});
});

test.describe("getChildrenMap", async () => {
test("should get a ChildrenMap if an attribute is part of a custom element", async () => {
const childrenMap = getChildrenMap(`<template><my-element foo="`);
Expand Down
14 changes: 13 additions & 1 deletion packages/fast-html/src/components/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,19 @@ function resolveExpression(
}

default: {
return resolvedLeft;
if (typeof resolvedLeft === "boolean") {
return resolvedLeft;
}

if (typeof resolvedLeft === "number") {
return resolvedLeft !== 0;
}

if (typeof resolvedLeft === "string") {
return resolvedLeft.length > 0;
}

return !!resolvedLeft;
}
}
}
Expand Down