diff --git a/change/@fluentui-utilities-6d708b55-ae70-43d3-bae5-d23b00ab9b73.json b/change/@fluentui-utilities-6d708b55-ae70-43d3-bae5-d23b00ab9b73.json new file mode 100644 index 00000000000000..5b3325f3ae2b9c --- /dev/null +++ b/change/@fluentui-utilities-6d708b55-ae70-43d3-bae5-d23b00ab9b73.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: handle null and undefined in shallowCompare, add tests", + "packageName": "@fluentui/utilities", + "email": "sarah.higley@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/utilities/src/object.test.ts b/packages/utilities/src/object.test.ts index 8760c7e0375bca..474a615baf81f0 100644 --- a/packages/utilities/src/object.test.ts +++ b/packages/utilities/src/object.test.ts @@ -1,4 +1,73 @@ -import { assign, filteredAssign, mapEnumByName, values, omit } from './object'; +import { assign, filteredAssign, mapEnumByName, values, omit, shallowCompare } from './object'; + +describe('shallowCompare', () => { + it('returns true for matching objects', () => { + const a = { + a: 1, + b: 'string', + c: { + d: 2, + }, + }; + + const b = { ...a }; + + expect(shallowCompare(a, b)).toBeTruthy(); + }); + + it('returns false when one object is a superset of the other', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const a: { [key: string]: any } = { + a: 1, + b: 'string', + c: { + d: 2, + }, + }; + + const b = { ...a, e: 'extra' }; + + expect(shallowCompare(a, b)).toBeFalsy(); + + a.e = 'extra'; + a.f = 3; + + expect(shallowCompare(a, b)).toBeFalsy(); + }); + + it('returns false when nested objects are not strictly equal', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const a: { [key: string]: any } = { + a: 1, + b: 'string', + c: { + d: 2, + }, + }; + + const b = { ...a, c: { ...a.c } }; + + expect(shallowCompare(a, b)).toBeFalsy(); + }); + + it('returns true for two empty objects', () => { + expect(shallowCompare({}, {})).toBeTruthy(); + }); + + it('returns true for two falsy values', () => { + expect(shallowCompare(null, null)).toBeTruthy(); + expect(shallowCompare(undefined, undefined)).toBeTruthy(); + expect(shallowCompare(null, undefined)).toBeTruthy(); + expect(shallowCompare(0, '')).toBeTruthy(); + expect(shallowCompare(null, '')).toBeTruthy(); + expect(shallowCompare(0, undefined)).toBeTruthy(); + }); + + it('returns false when comparing null or undefined against an object', () => { + expect(shallowCompare(null, { a: 1 })).toBeFalsy(); + expect(shallowCompare(undefined, { a: 1 })).toBeFalsy(); + }); +}); describe('assign', () => { it('can copy an object', () => { diff --git a/packages/utilities/src/object.ts b/packages/utilities/src/object.ts index 6b7006a08527eb..cc74a9c78c0f2a 100644 --- a/packages/utilities/src/object.ts +++ b/packages/utilities/src/object.ts @@ -5,6 +5,11 @@ */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function shallowCompare(a: TA, b: TB): boolean { + if (!a || !b) { + // only return true if both a and b are falsy + return !a && !b; + } + for (let propName in a) { if ((a as Object).hasOwnProperty(propName)) { if (!(b as Object).hasOwnProperty(propName) || (b as { [key: string]: unknown })[propName] !== a[propName]) {