Skip to content
Draft
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "liquidjs",
"version": "3.1.0",
"version": "3.1.1",
"description": "Liquid template engine by pure JavaScript: compatible to shopify, easy to extend.",
"main": "index.js",
"scripts": {
Expand Down
25 changes: 25 additions & 0 deletions sd-custom/custom-operator/equals.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,32 @@ function arrayEquals(l, r) {
});
}

/**
* Default equality handler for primitive values.
*
* Treats `null` and `undefined` as equivalent — both represent an absent/nil value.
* This aligns with JavaScript's loose-equality semantics for nil (`null == undefined`)
* and fixes the IS_PRESENT computation bug where a questionnaire field initialised as
* `null` (never filled) was being treated as "present" because:
* `NIL` (undefined variable) → `undefined`
* `null !== undefined` → `true` ← incorrect: null should equal NIL
*
* Examples:
* defaultHandler(null, undefined) → true (both are nil)
* defaultHandler(null, null) → true
* defaultHandler(undefined, undefined) → true
* defaultHandler(0, null) → false (0 is a real value, not nil)
* defaultHandler(0, false) → false (unchanged strict equality)
*/
function defaultHandler(l, r) {
const lIsNil = l === null || l === undefined;
const rIsNil = r === null || r === undefined;

// Both nil → equal
if (lIsNil && rIsNil) return true;
// Only one nil → not equal
if (lIsNil !== rIsNil) return false;
// Neither nil → strict equality
return l === r;
}

Expand Down
29 changes: 29 additions & 0 deletions test/sd-custom/custom-operator/equals.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,32 @@ describe("Custom equals operator", () => {
});
});
});

describe("nil equality — null and undefined should be treated as equal", () => {
it("should return true for null == null", () => {
expect(equals(null, null)).to.equal(true);
});
it("should return true for undefined == undefined", () => {
expect(equals(undefined, undefined)).to.equal(true);
});
it("should return true for null == undefined (NIL comparison fix)", () => {
// NIL in LiquidJS resolves to `undefined` (undefined variable).
// A questionnaire field that was never filled is sent as `null` by Django.
// Without this fix, `null != NIL` evaluated to `true` causing IS_PRESENT
// to incorrectly return true for blank fields.
expect(equals(null, undefined)).to.equal(true);
expect(equals(undefined, null)).to.equal(true);
});
it("should return false for null == 0 (0 is a real number value, not nil)", () => {
expect(equals(null, 0)).to.equal(false);
expect(equals(0, null)).to.equal(false);
});
it("should return false for null == false (false is a real boolean value, not nil)", () => {
expect(equals(null, false)).to.equal(false);
expect(equals(false, null)).to.equal(false);
});
it("should return false for null == empty string (empty string is not nil)", () => {
expect(equals(null, "")).to.equal(false);
expect(equals("", null)).to.equal(false);
});
});