Repository: capricorn86/happy-dom (TypeScript)

Issue:
Cloning a response twice locks the ReadableStream
**Describe the bug**

> A continuation of #1216 

In `Response`, when cloning a clone, none of them can then be consumed, they throw an "Invalid state: ReadableStream is locked" error.

**To Reproduce**

```ts
// test.mjs
import { Window } from "happy-dom";

const window = new Window();

const originalResponse = window.Response.json({ foo: 'bar' });

const clone1 = originalResponse.clone();
const clone2 = clone1.clone();

// Any of the following statements, in any order
// will throw an Invalid State Error
console.log(await clone1.text());
console.log(await clone2.text());
```

**Expected behavior**
Both response objects return the correct response text even if the other one has been consumed.
You can copy the code (minus the `new Window()`) to your browser console and see it working correctly.

**Device:**
node v20.11.0 on macOS Sonoma 14.3.1

**Additional context**
I'm trying to run [ky](https://github.com/sindresorhus/ky/) on happy-dom, which uses the following pattern:

```ts
// (Pseudo code)

const response = await fetch(...)
const cloneA = response.clone()

const cloneB = cloneA.clone()
if ((await cloneB.arrayBuffer().byteLength === 0) return ""

return cloneA.json()
```

https://github.com/sindresorhus/ky/blob/051d7ab4fd39743758b49012b62aaf420f7ca53d/source/core/Ky.ts#L82-L106


Discussion:
Thank you @cprecioso for reporting! :slightly_smiling_face: 

There is a fix in now.

You can read more about the release here:
https://github.com/capricorn86/happy-dom/releases/tag/v13.7.6
Works on my project, thanks!



Patch:
diff --git a/packages/happy-dom/src/fetch/Response.ts b/packages/happy-dom/src/fetch/Response.ts
index 098387825..3f5a47f21 100644
--- a/packages/happy-dom/src/fetch/Response.ts
+++ b/packages/happy-dom/src/fetch/Response.ts
@@ -45,7 +45,7 @@ export default class Response implements IResponse {
 	public readonly ok: boolean;
 	public readonly headers: IHeaders;
 	public [PropertySymbol.cachedResponse]: ICachedResponse | null = null;
-	public readonly [PropertySymbol.buffer]: Buffer | null = null;
+	public [PropertySymbol.buffer]: Buffer | null = null;
 	readonly #window: IBrowserWindow;
 	readonly #browserFrame: IBrowserFrame;
 
@@ -275,6 +275,8 @@ export default class Response implements IResponse {
 			headers: this.headers
 		});
 
+		response[PropertySymbol.cachedResponse] = this[PropertySymbol.cachedResponse];
+		response[PropertySymbol.buffer] = this[PropertySymbol.buffer];
 		(<boolean>response.ok) = this.ok;
 		(<boolean>response.redirected) = this.redirected;
 		(<string>response.type) = this.type;


Test patch:
diff --git a/packages/happy-dom/test/fetch/Response.test.ts b/packages/happy-dom/test/fetch/Response.test.ts
index 12ef46e3a..dca724b32 100644
--- a/packages/happy-dom/test/fetch/Response.test.ts
+++ b/packages/happy-dom/test/fetch/Response.test.ts
@@ -488,7 +488,24 @@ describe('Response', () => {
 			expect(bodyText).toBe('Hello World');
 		});
 
-		it('Can use the body of the cloned Response independently (cached).', async () => {
+		it('Can use the body of the cloned Response with a buffer independently.', async () => {
+			const originalResponse = new window.Response('Hello World', {
+				status: 200,
+				statusText: 'OK',
+				headers: { 'Content-Type': 'text/plain' }
+			});
+			const clonedResponse = originalResponse.clone();
+			const clonedResponse2 = clonedResponse.clone();
+
+			const originalResponseText = await originalResponse.text();
+			const clonedResponseText = await clonedResponse.text();
+			const clonedResponseText2 = await clonedResponse2.text();
+			expect(originalResponseText).toBe('Hello World');
+			expect(clonedResponseText).toBe('Hello World');
+			expect(clonedResponseText2).toBe('Hello World');
+		});
+
+		it('Can use the body of the cloned Response with a buffer independently when cloned multiple times.', async () => {
 			const originalResponse = new window.Response('Hello World', {
 				status: 200,
 				statusText: 'OK',
@@ -536,11 +553,14 @@ describe('Response', () => {
 				headers: { 'Content-Type': 'text/plain' }
 			});
 			const clonedResponse = originalResponse.clone();
+			const clonedResponse2 = clonedResponse.clone();
 
 			const originalResponseText = await originalResponse.text();
 			const clonedResponseText = await clonedResponse.text();
+			const clonedResponseText2 = await clonedResponse2.text();
 			expect(originalResponseText).toBe('chunk1chunk2chunk3');
 			expect(clonedResponseText).toBe('chunk1chunk2chunk3');
+			expect(clonedResponseText2).toBe('chunk1chunk2chunk3');
 		});
 
 		it('Fails if the body of the original Response is already used.', async () => {