1 import { DecryptedValue } from '@proton/docs-proto'
2 import type { NodeMeta } from '@proton/drive-store/lib'
3 import { UpdateDebouncer } from './UpdateDebouncer'
4 import { DocumentDebounceMode } from './DocumentDebounceMode'
5 import type { LoggerInterface } from '@proton/utils/logs'
7 describe('UpdateDebouncer', () => {
8 let debouncer: UpdateDebouncer
9 let onEvent = jest.fn()
12 debouncer = new UpdateDebouncer(
17 } as unknown as LoggerInterface,
21 debouncer.isReadyToFlush = true
29 describe('markAsReadyToFlush', () => {
30 it('should set isReadyToFlush to true', () => {
31 debouncer.isReadyToFlush = false
33 debouncer.markAsReadyToFlush()
35 expect(debouncer.isReadyToFlush).toBe(true)
38 it('should flush', () => {
39 debouncer.flush = jest.fn()
41 debouncer.markAsReadyToFlush()
43 expect(debouncer.flush).toHaveBeenCalledTimes(1)
47 describe('addUpdate', () => {
48 it('should add to buffer regardless of mode', () => {
49 debouncer.setMode(DocumentDebounceMode.SinglePlayer)
50 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
52 expect(debouncer.buffer.length).toBe(1)
54 debouncer.setMode(DocumentDebounceMode.Realtime)
55 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
57 expect(debouncer.buffer.length).toBe(2)
60 it('should post WillFlush event', () => {
61 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
63 expect(onEvent).toHaveBeenCalledTimes(1)
66 it('should clear existing single player idle timeout', () => {
67 debouncer.setMode(DocumentDebounceMode.SinglePlayer)
71 const previousTimeout = debouncer.singlePlayerIdleTimeout
73 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
75 expect(debouncer.singlePlayerIdleTimeout).not.toBe(previousTimeout)
78 it('should flush after single player debounce period', () => {
81 debouncer.flush = jest.fn()
83 debouncer.setMode(DocumentDebounceMode.SinglePlayer)
84 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
86 jest.advanceTimersByTime(debouncer.singlePlayerDebouncePeriod)
88 expect(debouncer.flush).toHaveBeenCalledTimes(1)
91 it('should increment current buffer size', () => {
92 const data = new Uint8Array([1, 2, 3, 4, 5])
93 debouncer.addUpdates([new DecryptedValue(data)])
95 expect(debouncer.currentBufferSize).toBe(data.byteLength)
98 it('should not flush if size limit is not exceeded', () => {
99 debouncer.flush = jest.fn()
101 const maxSize = debouncer.maxSizeBeforeFlush
103 debouncer.addUpdates([new DecryptedValue(new Uint8Array(maxSize - 1))])
105 expect(debouncer.flush).not.toHaveBeenCalled()
108 it('should flush if size limit is exceeded', () => {
109 debouncer.flush = jest.fn()
111 const maxSize = debouncer.maxSizeBeforeFlush
113 debouncer.addUpdates([new DecryptedValue(new Uint8Array(maxSize))])
115 expect(debouncer.flush).toHaveBeenCalledTimes(1)
119 describe('hasPendingUpdates', () => {
120 it('should return false if buffer is empty', () => {
121 expect(debouncer.hasPendingUpdates()).toBe(false)
124 it('should return true if buffer has updates', () => {
125 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
127 expect(debouncer.hasPendingUpdates()).toBe(true)
131 describe('flush', () => {
132 it('should do nothing if buffer is empty', () => {
135 expect(onEvent).not.toHaveBeenCalled()
138 it('should abort if not ready to flush', () => {
139 debouncer.isReadyToFlush = false
140 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
145 expect(onEvent).not.toHaveBeenCalled()
148 it('should reset the current buffer size', () => {
149 debouncer.addUpdates([new DecryptedValue(new Uint8Array([1]))])
151 expect(debouncer.currentBufferSize).toBeGreaterThan(0)
155 expect(debouncer.currentBufferSize).toBe(0)
158 it('should flush buffer', () => {
159 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
162 expect(onEvent).toHaveBeenCalledTimes(2)
163 expect(debouncer.buffer.length).toBe(0)
167 describe('setMode', () => {
168 it('should set realtime mode', () => {
169 debouncer.setMode(DocumentDebounceMode.Realtime)
171 expect(debouncer.getMode()).toBe(DocumentDebounceMode.Realtime)
174 it('should set singleplayer mode', () => {
175 debouncer.setMode(DocumentDebounceMode.SinglePlayer)
177 expect(debouncer.getMode()).toBe(DocumentDebounceMode.SinglePlayer)
180 it('should set realtime streaming interval if realtime mode', () => {
181 expect(debouncer.realtimeStreamingInterval).not.toBeTruthy()
183 debouncer.setMode(DocumentDebounceMode.Realtime)
185 expect(debouncer.realtimeStreamingInterval).toBeTruthy()
188 it('should clear realtime streaming interval if not realtime mode', () => {
189 debouncer.setMode(DocumentDebounceMode.Realtime)
191 expect(debouncer.realtimeStreamingInterval).toBeTruthy()
193 debouncer.setMode(DocumentDebounceMode.SinglePlayer)
195 expect(debouncer.realtimeStreamingInterval).not.toBeTruthy()
199 describe('realtime mode', () => {
200 it('should flush after realtime streaming period', () => {
203 debouncer.flush = jest.fn()
205 debouncer.setMode(DocumentDebounceMode.Realtime)
207 jest.advanceTimersByTime(debouncer.realtimeStreamingPeriod)
209 expect(debouncer.flush).toHaveBeenCalledTimes(1)
213 describe('singleplayer mode', () => {
214 it('should flush after debounce period', () => {
217 debouncer.flush = jest.fn()
219 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
221 jest.advanceTimersByTime(debouncer.singlePlayerDebouncePeriod)
223 expect(debouncer.flush).toHaveBeenCalledTimes(1)
226 it('should not flush if debounce period passes but updates are still being added', () => {
229 debouncer.flush = jest.fn()
231 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
233 jest.advanceTimersByTime(debouncer.singlePlayerDebouncePeriod - 100)
235 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
237 jest.advanceTimersByTime(debouncer.singlePlayerDebouncePeriod - 100)
239 expect(debouncer.flush).not.toHaveBeenCalled()
242 it('debounce period should reset after adding a new update', () => {
243 debouncer.flush = jest.fn()
247 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
249 jest.advanceTimersByTime(debouncer.singlePlayerDebouncePeriod / 2)
251 debouncer.addUpdates([new DecryptedValue(new Uint8Array())])
253 jest.advanceTimersByTime(debouncer.singlePlayerDebouncePeriod / 2)
255 expect(debouncer.flush).not.toHaveBeenCalled()
257 jest.advanceTimersByTime(debouncer.singlePlayerDebouncePeriod / 2)
259 expect(debouncer.flush).toHaveBeenCalledTimes(1)