From d58618d6f20b3b4ae198368b7a93aa4cbc67f664 Mon Sep 17 00:00:00 2001 From: Guilherme Hermeto Date: Thu, 10 Sep 2020 15:45:06 -0700 Subject: [PATCH] Make property enumerable --- readme.md | 4 +--- source/core/index.ts | 12 +++--------- source/types.ts | 2 +- test/arguments.ts | 33 ++++++++++++++++++++++++++------- test/hooks.ts | 2 +- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/readme.md b/readme.md index 4eab6b14d..5b930e80c 100644 --- a/readme.md +++ b/readme.md @@ -251,9 +251,7 @@ JSON body. If the `Content-Type` header is not set, it will be set to `applicati Type: `object` -User data. In contrast to other options, `context` is not enumerable. - -**Note:** The object is never merged, it's just passed through. Got will not modify the object in any way. +User data. `context` is shallow merged and enumerable. If it contains non-enumerable properties they will NOT be merged. It's very useful for storing auth tokens: diff --git a/source/core/index.ts b/source/core/index.ts index 9b1821140..448b07a3c 100644 --- a/source/core/index.ts +++ b/source/core/index.ts @@ -515,10 +515,7 @@ interface PlainOptions extends URLOptions { dnsCache?: CacheableLookup | boolean; /** - User data. In contrast to other options, `context` is not enumerable. - - __Note__: The object is never merged, it's just passed through. - Got will not modify the object in any way. + User data. `context` is shallow merged and enumerable. If it contains non-enumerable properties they will NOT be merged. @example ``` @@ -1138,9 +1135,8 @@ const waitForOpenFile = async (file: ReadStream): Promise => new Promise(( const redirectCodes: ReadonlySet = new Set([300, 301, 302, 303, 304, 307, 308]); -type NonEnumerableProperty = 'context' | 'body' | 'json' | 'form'; +type NonEnumerableProperty = 'body' | 'json' | 'form'; const nonEnumerableProperties: NonEnumerableProperty[] = [ - 'context', 'body', 'json', 'form' @@ -1764,9 +1760,7 @@ export default class Request extends Duplex implements RequestEvents { } // `options.context` - if (!options.context) { - options.context = {}; - } + options.context = {...defaults?.context, ...options.context}; // `options.hooks` const areHooksDefault = options.hooks === defaults?.hooks; diff --git a/source/types.ts b/source/types.ts index 1cf29c378..d78866740 100644 --- a/source/types.ts +++ b/source/types.ts @@ -371,7 +371,7 @@ export interface Got extends Record, GotRequestFu - If the parent property is a plain `object` too, both values are merged recursively into a new `object`. - Otherwise, only the new value is deeply cloned. - If the new property is an `Array`, it overwrites the old one with a deep clone of the new property. - - Properties that are not enumerable, such as `context`, `body`, `json`, and `form`, will not be merged. + - Properties that are not enumerable, such as `body`, `json`, and `form`, will not be merged. - Otherwise, the new value is assigned to the key. **Note:** Only Got options are merged! Custom user options should be defined via [`options.context`](#context). diff --git a/test/arguments.ts b/test/arguments.ts index 5088b898a..aedee1a59 100644 --- a/test/arguments.ts +++ b/test/arguments.ts @@ -328,7 +328,7 @@ test('throws if the `searchParams` value is invalid', async t => { }); }); -test('`context` option is not enumerable', withServer, async (t, server, got) => { +test('`context` option is enumerable', withServer, async (t, server, got) => { server.get('/', echoUrl); const context = { @@ -340,8 +340,8 @@ test('`context` option is not enumerable', withServer, async (t, server, got) => hooks: { beforeRequest: [ options => { - t.is(options.context, context); - t.false({}.propertyIsEnumerable.call(options, 'context')); + t.deepEqual(options.context, context); + t.true({}.propertyIsEnumerable.call(options, 'context')); } ] } @@ -360,8 +360,8 @@ test('`context` option is accessible when using hooks', withServer, async (t, se hooks: { beforeRequest: [ options => { - t.is(options.context, context); - t.false({}.propertyIsEnumerable.call(options, 'context')); + t.deepEqual(options.context, context); + t.true({}.propertyIsEnumerable.call(options, 'context')); } ] } @@ -375,8 +375,27 @@ test('`context` option is accessible when extending instances', t => { const instance = got.extend({context}); - t.is(instance.defaults.options.context, context); - t.false({}.propertyIsEnumerable.call(instance.defaults.options, 'context')); + t.deepEqual(instance.defaults.options.context, context); + t.true({}.propertyIsEnumerable.call(instance.defaults.options, 'context')); +}); + +test('`context` option is shallow merged', t => { + const context = { + foo: 'bar' + }; + + const context2 = { + bar: 'baz' + }; + + const instance1 = got.extend({context}); + + t.deepEqual(instance1.defaults.options.context, context); + t.true({}.propertyIsEnumerable.call(instance1.defaults.options, 'context')); + + const instance2 = instance1.extend({context: context2}); + + t.deepEqual(instance2.defaults.options.context, {...context, ...context2}); }); test('throws if `options.encoding` is `null`', async t => { diff --git a/test/hooks.ts b/test/hooks.ts index e73ff578a..9fc39724d 100644 --- a/test/hooks.ts +++ b/test/hooks.ts @@ -421,7 +421,7 @@ test('beforeRetry is called with options', withServer, async (t, server, got) => beforeRetry: [ (options, error, retryCount) => { t.is(options.url.hostname, 'localhost'); - t.is(options.context, context); + t.deepEqual(options.context, context); t.truthy(error); t.true(retryCount! >= 1); }