Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316// src/plugin/__tests__/debounced-event-ref.test.tsimport { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'import { DebouncedEventRef } from '../utils/debounced-event-ref'import { Events } from '../events'import { Component } from '../component'
describe('DebouncedEventRef', () => { beforeEach(() => { vi.useFakeTimers() })
afterEach(() => { vi.useRealTimers() })
describe('debounced subscription', () => { it('debounces event delivery to subscriber', () => { const events = new Events() const callback = vi.fn() // Create ref to establish subscription (ref must stay in scope) new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'arg1') expect(callback).not.toHaveBeenCalled() vi.advanceTimersByTime(100) expect(callback).toHaveBeenCalledWith('arg1') })
it('coalesces rapid events', () => { const events = new Events() const callback = vi.fn() // Create ref to establish subscription (ref must stay in scope) new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'first') vi.advanceTimersByTime(50) events.trigger('test', 'second') vi.advanceTimersByTime(50) events.trigger('test', 'third') vi.advanceTimersByTime(100) expect(callback).toHaveBeenCalledTimes(1) expect(callback).toHaveBeenCalledWith('third') }) })
describe('dispose', () => { it('cancels pending and unsubscribes from events', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'pending') ref.dispose() vi.advanceTimersByTime(200) expect(callback).not.toHaveBeenCalled() // New triggers should also not reach callback events.trigger('test', 'after-dispose') vi.advanceTimersByTime(200) expect(callback).not.toHaveBeenCalled() }) })
describe('flush and cancel', () => { it('flush immediately invokes callback with pending args', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'pending') ref.flush() expect(callback).toHaveBeenCalledWith('pending') })
it('cancel stops pending without invoking', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'pending') ref.cancel() vi.advanceTimersByTime(200) expect(callback).not.toHaveBeenCalled() })
it('flush after dispose is a no-op', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'pending') ref.dispose() ref.flush() // Should not throw or invoke callback expect(callback).not.toHaveBeenCalled() })
it('cancel after dispose is a no-op', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'pending') ref.dispose() ref.cancel() // Should not throw expect(callback).not.toHaveBeenCalled() }) })
describe('Component integration', () => { it('cancels pending debounce when component unloads', () => { const events = new Events() const callback = vi.fn() const component = new Component() component.load() const ref = new DebouncedEventRef(events, 'test', callback, 100) component.registerEvent(ref) // Trigger event that starts debounce timer events.trigger('test', 'pending') expect(ref.isPending()).toBe(true) // Unload component - this calls offref which our proxy intercepts // Our proxy calls dispose() which cancels the debouncer component.unload() // Debounce should be cancelled expect(ref.isPending()).toBe(false) // Advancing time should not trigger callback vi.advanceTimersByTime(200) expect(callback).not.toHaveBeenCalled() // New triggers also won't reach the callback events.trigger('test', 'after-unload') vi.advanceTimersByTime(200) expect(callback).not.toHaveBeenCalled() })
it('delivers event if debounce completes before unload', () => { const events = new Events() const callback = vi.fn() const component = new Component() component.load() const ref = new DebouncedEventRef(events, 'test', callback, 100) component.registerEvent(ref) // Trigger and wait for debounce to complete events.trigger('test', 'data') vi.advanceTimersByTime(100) // Callback should have been called expect(callback).toHaveBeenCalledWith('data') expect(callback).toHaveBeenCalledTimes(1) // Now unload component.unload() // New triggers won't reach callback events.trigger('test', 'after') vi.advanceTimersByTime(200) expect(callback).toHaveBeenCalledTimes(1) }) })
describe('proxy', () => { it('calling offref on proxy disposes ref', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'pending') expect(ref.isPending()).toBe(true) // Call offref through the proxy (simulates Component.unload behavior) ref.e.offref(ref) expect(ref.isPending()).toBe(false) vi.advanceTimersByTime(200) expect(callback).not.toHaveBeenCalled() })
it('forwards other Events methods to underlying instance', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) // The proxy should forward trigger() to the real events instance const otherCallback = vi.fn() ref.e.on('other-event', otherCallback) ref.e.trigger('other-event', 'data') expect(otherCallback).toHaveBeenCalledWith('data') })
it('forwards offref for unrelated refs to underlying instance', () => { const events = new Events() const callback1 = vi.fn() const callback2 = vi.fn() const ref1 = new DebouncedEventRef(events, 'test', callback1, 100) const ref2 = events.on('other', callback2) // Calling offref on ref1's proxy for ref2 should forward to events ref1.e.offref(ref2) // ref2 should be unsubscribed events.trigger('other', 'data') expect(callback2).not.toHaveBeenCalled() // ref1 should still work events.trigger('test', 'data') vi.advanceTimersByTime(100) expect(callback1).toHaveBeenCalledWith('data') }) })
describe('isPending', () => { it('returns false when no event has been triggered', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) expect(ref.isPending()).toBe(false) })
it('returns true after event is triggered', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'data') expect(ref.isPending()).toBe(true) })
it('returns false after debounce delay expires', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'data') expect(ref.isPending()).toBe(true) vi.advanceTimersByTime(100) expect(ref.isPending()).toBe(false) expect(callback).toHaveBeenCalledWith('data') })
it('returns false after cancel', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'data') expect(ref.isPending()).toBe(true) ref.cancel() expect(ref.isPending()).toBe(false) })
it('returns false after flush', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'data') expect(ref.isPending()).toBe(true) ref.flush() expect(ref.isPending()).toBe(false) expect(callback).toHaveBeenCalledWith('data') })
it('returns false after dispose', () => { const events = new Events() const callback = vi.fn() const ref = new DebouncedEventRef(events, 'test', callback, 100) events.trigger('test', 'data') expect(ref.isPending()).toBe(true) ref.dispose() expect(ref.isPending()).toBe(false) }) })})