diff --git a/.changeset/sour-timers-fail.md b/.changeset/sour-timers-fail.md new file mode 100644 index 00000000..f39dddb5 --- /dev/null +++ b/.changeset/sour-timers-fail.md @@ -0,0 +1,5 @@ +--- +'@remote-dom/core': minor +--- + +add flush method to BatchingRemoteConnection diff --git a/packages/core/source/elements/connection.ts b/packages/core/source/elements/connection.ts index a565e121..b8d1b259 100644 --- a/packages/core/source/elements/connection.ts +++ b/packages/core/source/elements/connection.ts @@ -25,20 +25,24 @@ export class BatchingRemoteConnection { } mutate(records: any[]) { - let queued = this.#queued; + this.#queued ??= []; + this.#queued.push(...records); - if (queued) { - queued.push(...records); + this.#batch(() => { + if (this.#queued) { + this.#connection.mutate(this.#queued); + this.#queued = undefined; + } + }); + } + + flush() { + if (!this.#queued) { return; } - queued = [...records]; - this.#queued = queued; - - this.#batch(() => { - this.#connection.mutate(queued); - this.#queued = undefined; - }); + this.#connection.mutate(this.#queued); + this.#queued = undefined; } } diff --git a/packages/core/source/elements/tests/connection.test.ts b/packages/core/source/elements/tests/connection.test.ts new file mode 100644 index 00000000..45dfa7b9 --- /dev/null +++ b/packages/core/source/elements/tests/connection.test.ts @@ -0,0 +1,64 @@ +import '../../polyfill.ts'; + +import {describe, expect, it, vi, type MockedObject} from 'vitest'; + +import {BatchingRemoteConnection, RemoteConnection} from '../../elements'; + +describe('BatchingRemoteConnection', () => { + it('batches mutations', async () => { + const connection = createRemoteConnectionSpy(); + const batchingConnection = new BatchingRemoteConnection(connection); + + batchingConnection.mutate([1, 2, 3]); + batchingConnection.mutate([4, 5, 6]); + + expect(connection.mutate).not.toHaveBeenCalled(); + + await waitOneTick(); + + expect(connection.mutate).toHaveBeenCalledTimes(1); + expect(connection.mutate).toHaveBeenCalledWith([1, 2, 3, 4, 5, 6]); + + batchingConnection.mutate([7, 8, 9]); + + expect(connection.mutate).toHaveBeenCalledTimes(1); + + await waitOneTick(); + + expect(connection.mutate).toHaveBeenCalledTimes(2); + expect(connection.mutate).toHaveBeenCalledWith([7, 8, 9]); + }); + + it('flushes mutations', async () => { + const connection = createRemoteConnectionSpy(); + const batchingConnection = new BatchingRemoteConnection(connection); + + batchingConnection.mutate([1, 2, 3]); + batchingConnection.flush(); + + expect(connection.mutate).toHaveBeenCalledOnce(); + expect(connection.mutate).toHaveBeenCalledWith([1, 2, 3]); + + await waitOneTick(); + + // ensure it wasn't called again + expect(connection.mutate).toHaveBeenCalledOnce(); + batchingConnection.mutate([4, 5, 6]); + + await waitOneTick(); + + expect(connection.mutate).toHaveBeenCalledTimes(2); + expect(connection.mutate).toHaveBeenCalledWith([4, 5, 6]); + }); +}); + +async function waitOneTick() { + await new Promise((resolve) => setTimeout(resolve, 0)); +} + +function createRemoteConnectionSpy(): MockedObject { + return { + mutate: vi.fn(), + call: vi.fn(), + }; +}