event-store.test.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { describe, expect, it, vi } from "vitest";
  2. import { createEventStore } from "./event-store";
  3. import type { DevtoolsEvent } from "./types";
  4. function patchEvent(at = Date.now()): DevtoolsEvent {
  5. return {
  6. kind: "stream-patch",
  7. at,
  8. patch: {
  9. op: "add",
  10. path: "/elements/x",
  11. value: { type: "Card", props: {} },
  12. },
  13. source: "json",
  14. };
  15. }
  16. describe("createEventStore", () => {
  17. it("stores pushed events in insertion order", () => {
  18. const store = createEventStore();
  19. const a = patchEvent(1);
  20. const b = patchEvent(2);
  21. store.push(a);
  22. store.push(b);
  23. expect(store.snapshot()).toEqual([a, b]);
  24. expect(store.size()).toBe(2);
  25. });
  26. it("drops the oldest event when bufferSize is exceeded", () => {
  27. const store = createEventStore({ bufferSize: 3 });
  28. const events = [1, 2, 3, 4, 5].map((n) => patchEvent(n));
  29. for (const e of events) store.push(e);
  30. expect(store.snapshot()).toEqual([events[2], events[3], events[4]]);
  31. expect(store.size()).toBe(3);
  32. });
  33. it("clamps bufferSize to a minimum of 1", () => {
  34. const store = createEventStore({ bufferSize: 0 });
  35. store.push(patchEvent(1));
  36. store.push(patchEvent(2));
  37. expect(store.size()).toBe(1);
  38. });
  39. it("notifies subscribers on every push and on clear", () => {
  40. const store = createEventStore();
  41. const listener = vi.fn();
  42. const unsub = store.subscribe(listener);
  43. store.push(patchEvent(1));
  44. store.push(patchEvent(2));
  45. expect(listener).toHaveBeenCalledTimes(2);
  46. store.clear();
  47. expect(listener).toHaveBeenCalledTimes(3);
  48. expect(store.size()).toBe(0);
  49. unsub();
  50. store.push(patchEvent(3));
  51. expect(listener).toHaveBeenCalledTimes(3);
  52. });
  53. it("clear is a no-op when already empty (no extra notify)", () => {
  54. const store = createEventStore();
  55. const listener = vi.fn();
  56. store.subscribe(listener);
  57. store.clear();
  58. expect(listener).not.toHaveBeenCalled();
  59. });
  60. it("returns a fresh array from snapshot each call", () => {
  61. const store = createEventStore();
  62. store.push(patchEvent(1));
  63. const a = store.snapshot();
  64. const b = store.snapshot();
  65. expect(a).toEqual(b);
  66. expect(a).not.toBe(b);
  67. });
  68. it("isolates listener errors from other listeners", () => {
  69. const store = createEventStore();
  70. const listenerA = vi.fn(() => {
  71. throw new Error("boom");
  72. });
  73. const listenerB = vi.fn();
  74. store.subscribe(listenerA);
  75. store.subscribe(listenerB);
  76. const consoleSpy = vi
  77. .spyOn(console, "error")
  78. .mockImplementation(() => undefined);
  79. store.push(patchEvent(1));
  80. expect(listenerA).toHaveBeenCalledTimes(1);
  81. expect(listenerB).toHaveBeenCalledTimes(1);
  82. consoleSpy.mockRestore();
  83. });
  84. });