From 6693824427211c0064298df4a2fb5e3c34c53830 Mon Sep 17 00:00:00 2001 From: Lexus Drumgold Date: Tue, 8 Aug 2023 12:38:50 -0400 Subject: [PATCH] fix(utils): [`define`] invalid property descriptor - cannot both specify accessors and a value or writable attribute - https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty Signed-off-by: Lexus Drumgold --- src/utils/__tests__/define.spec.ts | 111 +++++++++++++++++++++++------ src/utils/define.ts | 15 ++-- 2 files changed, 95 insertions(+), 31 deletions(-) diff --git a/src/utils/__tests__/define.spec.ts b/src/utils/__tests__/define.spec.ts index 67ccd432..4ecf0261 100644 --- a/src/utils/__tests__/define.spec.ts +++ b/src/utils/__tests__/define.spec.ts @@ -5,8 +5,12 @@ import VEHICLE from '#fixtures/vehicle' import type { PropertyDescriptor } from '#src/interfaces' +import type { Assign, EmptyArray, Fn } from '#src/types' +import clone from '../clone' +import constant from '../constant' import testSubject from '../define' import description from '../descriptor' +import omit from '../omit' describe('unit:utils/define', () => { describe('add property', () => { @@ -18,16 +22,48 @@ describe('unit:utils/define', () => { vrm = faker.vehicle.vrm() }) - it('should return obj with new property', () => { - // Act - const result = testSubject(VEHICLE, property, { value: vrm }) + describe('accessor descriptor', () => { + let vehicle: typeof VEHICLE - // Expect - expect(result).to.have.descriptor(property, { - configurable: true, - enumerable: true, - value: vrm, - writable: true + beforeAll(() => { + vehicle = clone(VEHICLE) + }) + + it('should return obj with new accessor descriptor', () => { + // Arrange + const get: Fn = constant(vrm) + + // Act + const result = testSubject(vehicle, property, { get }) + + // Expect + expect(result).to.have.descriptor(property, { + configurable: true, + enumerable: true, + get, + set: undefined + }) + }) + }) + + describe('data descriptor', () => { + let vehicle: typeof VEHICLE + + beforeAll(() => { + vehicle = clone(VEHICLE) + }) + + it('should return obj with new data descriptor', () => { + // Act + const result = testSubject(vehicle, property, { value: vrm }) + + // Expect + expect(result).to.have.descriptor(property, { + configurable: true, + enumerable: true, + value: vrm, + writable: true + }) }) }) }) @@ -41,20 +77,53 @@ describe('unit:utils/define', () => { vin = faker.vehicle.vin() }) - it('should return obj with modified property', () => { - // Arrange - const descriptor: PropertyDescriptor = { - enumerable: false, - value: vin - } + describe('accessor descriptor', () => { + let vehicle: Assign + + beforeAll(() => { + vehicle = Object.defineProperty(omit(clone(VEHICLE), ['vin']), 'vin', { + configurable: true, + get: constant(VEHICLE.vin) + }) + }) + + it('should return obj with modified accessor descriptor', () => { + // Arrange + const get: Fn = constant(vin) + + // Act + const result = testSubject(vehicle, property, { get }) + + // Expect + expect(result).to.have.descriptor(property, { + ...description(vehicle, property), + get + }) + }) + }) + + describe('data descriptor', () => { + let vehicle: typeof VEHICLE + + beforeAll(() => { + vehicle = clone(VEHICLE) + }) + + it('should return obj with modified data descriptor', () => { + // Arrange + const descriptor: PropertyDescriptor = { + enumerable: false, + value: vin + } - // Act - const result = testSubject(VEHICLE, property, descriptor) + // Act + const result = testSubject(vehicle, property, descriptor) - // Expect - expect(result).to.have.descriptor(property, { - ...description(VEHICLE, property), - ...descriptor + // Expect + expect(result).to.have.descriptor(property, { + ...description(vehicle, property), + ...descriptor + }) }) }) }) diff --git a/src/utils/define.ts b/src/utils/define.ts index c8558b5f..c4748dd3 100644 --- a/src/utils/define.ts +++ b/src/utils/define.ts @@ -30,17 +30,12 @@ const define = ( property: PropertyKey, descriptor: PropertyDescriptor = {} ): U => { - /** - * Default attributes. - * - * @const {PropertyDescriptor} defaults - */ - const defaults: PropertyDescriptor = hasOwn(obj, property) - ? description(obj, property) - : { configurable: true, enumerable: true, writable: true } - return Object.defineProperty(cast(obj), property, { - ...defaults, + ...(hasOwn(obj, property) + ? description(obj, property) + : hasOwn(descriptor, 'get') || hasOwn(descriptor, 'set') + ? { configurable: true, enumerable: true } + : { configurable: true, enumerable: true, writable: true }), ...descriptor }) }