Repository: sindresorhus/got
Pull request #953 (2019-12-01 23:28:07)

Fix `got.mergeOptions(...)` types
Adds test coverage for mergeOptions usage similar to that in the readme.

Not passing because I'm not sure what the fix should be, willing to make the change. Partial?

szmarczak: The fix is to replace

https://github.com/sindresorhus/got/blob/d11dbe40a4e8da094afc000973973fd19ef6ae2a/source/create.ts#L73

with

```ts
	mergeOptions(...sources: Options[]): NormalizedOptions;
```

I forgot to update it in #921

I'm not sure how to test the types though. Should we use [`tsd`](https://github.com/SamVerschueren/tsd)?

/cc @sindresorhus 

Patch:
diff --git a/package.json b/package.json
index 119e4e9..0808b98 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
 	],
 	"dependencies": {
 		"@sindresorhus/is": "^1.0.0",
-		"@szmarczak/http-timer": "^3.1.0",
+		"@szmarczak/http-timer": "^3.1.1",
 		"@types/cacheable-request": "^6.0.1",
 		"cacheable-lookup": "^0.2.1",
 		"cacheable-request": "^7.0.0",
diff --git a/readme.md b/readme.md
index dbae2c8..2fda8f6 100644
--- a/readme.md
+++ b/readme.md
@@ -180,11 +180,11 @@ Returns a `Stream` instead of a `Promise`. This is equivalent to calling `got.st
 
 Type: `string | Buffer | stream.Readable` or [`form-data` instance](https://github.com/form-data/form-data)
 
-**Note:** The `body` option cannot be used with the `json` or `form` option.
+**Note #1:** The `body` option cannot be used with the `json` or `form` option.
 
-**Note:** If you provide this option, `got.stream()` will be read-only.
+**Note #2:** If you provide this option, `got.stream()` will be read-only.
 
-If present in `options` and `options.method` is not set, it will throw a `TypeError`.
+**Note #3:** If you provide a payload with the `GET` or `HEAD` method, it will throw a `TypeError`.
 
 The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / `fs.createReadStream` instance / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
 
@@ -804,7 +804,7 @@ const client = got.extend({
 	}
 });
 
-client.get('/demo');
+client.get('demo');
 
 /* HTTP Request =>
  * GET /demo HTTP/1.1
@@ -821,7 +821,7 @@ client.get('/demo');
 			'x-foo': 'bar'
 		}
 	});
-	const {headers} = await client.get('/headers').json();
+	const {headers} = await client.get('headers').json();
 	//=> headers['x-foo'] === 'bar'
 
 	const jsonClient = client.extend({
@@ -831,7 +831,7 @@ client.get('/demo');
 			'x-baz': 'qux'
 		}
 	});
-	const {headers: headers2} = await jsonClient.get('/headers');
+	const {headers: headers2} = await jsonClient.get('headers');
 	//=> headers2['x-foo'] === 'bar'
 	//=> headers2['x-baz'] === 'qux'
 })();
@@ -1338,7 +1338,7 @@ const got = require('got');
 
 (async () => {
 	const body = await got.post('https://httpbin.org/anything', {
-		body: {
+		json: {
 			hello: 'world'
 		}
 	}).json();
@@ -1391,7 +1391,7 @@ const custom = got.extend({
 
 // Use `custom` exactly how you use `got`
 (async () => {
-	const list = await custom('/v1/users/list');
+	const list = await custom('v1/users/list');
 })();
 ```
 
diff --git a/source/create.ts b/source/create.ts
index 923a7d5..d2ce534 100644
--- a/source/create.ts
+++ b/source/create.ts
@@ -1,4 +1,4 @@
-import {Merge, PartialDeep} from 'type-fest';
+import {Merge} from 'type-fest';
 import asPromise from './as-promise';
 import asStream, {ProxyStream} from './as-stream';
 import * as errors from './errors';
@@ -70,7 +70,7 @@ export interface Got extends Record<HTTPAlias, GotFunctions>, GotFunctions {
 
 	extend(...instancesOrOptions: Array<Got | ExtendOptions>): Got;
 	mergeInstances(parent: Got, ...instances: Got[]): Got;
-	mergeOptions<T extends PartialDeep<Options>>(...sources: T[]): T;
+	mergeOptions(...sources: Options[]): NormalizedOptions;
 }
 
 export interface GotStream extends Record<HTTPAlias, ReturnStream> {
diff --git a/source/utils/timed-out.ts b/source/utils/timed-out.ts
index 004b05a..f480df4 100644
--- a/source/utils/timed-out.ts
+++ b/source/utils/timed-out.ts
@@ -58,6 +58,11 @@ export default (request: ClientRequest, delays: Delays, options: TimedOutOptions
 	const {host, hostname} = options;
 
 	const timeoutHandler = (delay: number, event: string): void => {
+		if (request.socket) {
+			// @ts-ignore We do not want the `socket hang up` error
+			request.socket._hadError = true;
+		}
+
 		request.abort();
 		request.emit('error', new TimeoutError(delay, event));
 	};
@@ -71,9 +76,7 @@ export default (request: ClientRequest, delays: Delays, options: TimedOutOptions
 	};
 
 	request.once('error', error => {
-		if (error.message !== 'socket hang up') {
-			cancelTimeouts();
-		}
+		cancelTimeouts();
 
 		// Save original behavior
 		if (request.listenerCount('error') === 0) {


Test patch:
diff --git a/test/normalize-arguments.ts b/test/normalize-arguments.ts
new file mode 100644
index 0000000..4f60b12
--- /dev/null
+++ b/test/normalize-arguments.ts
@@ -0,0 +1,11 @@
+import test from 'ava';
+import got from '../source';
+
+test('should merge options replacing responseType', t => {
+	const responseType = 'json';
+	const options = got.mergeOptions(got.defaults.options, {
+		responseType
+	});
+
+	t.is(options.responseType, responseType);
+});
diff --git a/test/timeout.ts b/test/timeout.ts
index 0f1580a..25efc33 100644
--- a/test/timeout.ts
+++ b/test/timeout.ts
@@ -6,6 +6,7 @@ import http = require('http');
 import net = require('net');
 import getStream = require('get-stream');
 import test from 'ava';
+import delay = require('delay');
 import CacheableLookup from 'cacheable-lookup';
 import {Handler} from 'express';
 import pEvent = require('p-event');
@@ -426,6 +427,26 @@ test.serial('no unhandled `socket hung up` errors', withServerAndLolex, async (t
 	);
 });
 
+// TODO: use lolex here
+test.serial('no unhandled timeout errors', withServer, async (t, _server, got) => {
+	await t.throwsAsync(got({
+		retry: 0,
+		timeout: 100,
+		request: (...args: any[]) => {
+			// @ts-ignore
+			const result = http.request(...args);
+
+			result.once('socket', () => {
+				result.socket.destroy();
+			});
+
+			return result;
+		}
+	}));
+
+	await delay(200);
+});
+
 test.serial('no more timeouts after an error', withServerAndLolex, async (t, _server, got, clock) => {
 	await t.throwsAsync(got(`http://${Date.now()}.dev`, {
 		retry: 1,