diff --git a/src/LiveComponent/assets/dist/live_controller.js b/src/LiveComponent/assets/dist/live_controller.js index 7656af38a52..7cd308ecb2d 100644 --- a/src/LiveComponent/assets/dist/live_controller.js +++ b/src/LiveComponent/assets/dist/live_controller.js @@ -1946,7 +1946,6 @@ class Component { this.valueStore.flushDirtyPropsToPending(); this.isRequestPending = false; this.backendRequest.promise.then(async (response) => { - this.backendRequest = null; const backendResponse = new BackendResponse(response); const html = await backendResponse.getBody(); for (const input of Object.values(this.pendingFiles)) { @@ -1960,10 +1959,12 @@ class Component { if (controls.displayError) { this.renderError(html); } + this.backendRequest = null; thisPromiseResolve(backendResponse); return response; } this.processRerender(html, backendResponse); + this.backendRequest = null; thisPromiseResolve(backendResponse); if (this.isRequestPending) { this.isRequestPending = false; diff --git a/src/LiveComponent/assets/src/Component/index.ts b/src/LiveComponent/assets/src/Component/index.ts index 503379eb80d..49026af9f6c 100644 --- a/src/LiveComponent/assets/src/Component/index.ts +++ b/src/LiveComponent/assets/src/Component/index.ts @@ -390,7 +390,6 @@ export default class Component { this.isRequestPending = false; this.backendRequest.promise.then(async (response) => { - this.backendRequest = null; const backendResponse = new BackendResponse(response); const html = await backendResponse.getBody(); @@ -410,6 +409,7 @@ export default class Component { this.renderError(html); } + this.backendRequest = null; thisPromiseResolve(backendResponse); return response; @@ -418,6 +418,7 @@ export default class Component { this.processRerender(html, backendResponse); // finally resolve this promise + this.backendRequest = null; thisPromiseResolve(backendResponse); // do we already have another request pending? diff --git a/src/LiveComponent/assets/test/controller/render.test.ts b/src/LiveComponent/assets/test/controller/render.test.ts index cb93c578189..1ccc347a76f 100644 --- a/src/LiveComponent/assets/test/controller/render.test.ts +++ b/src/LiveComponent/assets/test/controller/render.test.ts @@ -329,6 +329,65 @@ describe('LiveController rendering Tests', () => { await waitFor(() => expect(test.element).toHaveTextContent('Title: "greetings to you"')); }); + it('waits for the rendering process of previous request to finish before starting a new one', async () => { + const test = await createTest({ + title: 'greetings', + contents: '', + }, (data: any) => ` +
+ + + Title: "${data.title}" + + +
+ `); + + let didSecondRenderStart = false; + let secondRenderStartedAt = 0; + test.component.on('render:started', () => { + if (didSecondRenderStart) { + return; + } + didSecondRenderStart = true; + + test.component.on('loading.state:started', () => { + secondRenderStartedAt = Date.now(); + }); + + test.expectsAjaxCall(); + test.component.render(); + }); + + let firstRenderFinishedAt = 0; + test.component.on('render:finished', () => { + // set the finish time for the first render only + if (firstRenderFinishedAt === 0) { + firstRenderFinishedAt = Date.now(); + } + + // the sleep guarantees that if the 2nd request was correctly + // delayed, its start time will be at least 10ms after the first + // render finished. Without this, even if the 2nd request is + // correctly delayed, the "first render finish" and "second render + // start" times could be the same, because no time has passed. + const sleep = (milliseconds: number) => { + const startTime = new Date().getTime(); + while (new Date().getTime() < startTime + milliseconds); + } + sleep(10); + }); + + test.expectsAjaxCall(); + + await test.component.render(); + + await waitFor(() => expect(didSecondRenderStart).toBe(true)); + await waitFor(() => expect(firstRenderFinishedAt).not.toBe(0)); + await waitFor(() => expect(secondRenderStartedAt).not.toBe(0)); + expect(secondRenderStartedAt).toBeGreaterThan(firstRenderFinishedAt); + }); + it('can update svg', async () => { const test = await createTest({ text: 'SVG' }, (data: any) => `