@@ -3,11 +3,15 @@ import {
33 createRedoAction ,
44 createUndoAction ,
55} from "@excalidraw/excalidraw/actions/actionHistory" ;
6- import { syncInvalidIndices } from "@excalidraw/element/fractionalIndex " ;
6+ import { syncInvalidIndices } from "@excalidraw/element" ;
77import { API } from "@excalidraw/excalidraw/tests/helpers/api" ;
88import { act , render , waitFor } from "@excalidraw/excalidraw/tests/test-utils" ;
99import { vi } from "vitest" ;
1010
11+ import { StoreIncrement } from "@excalidraw/element" ;
12+
13+ import type { DurableIncrement , EphemeralIncrement } from "@excalidraw/element" ;
14+
1115import ExcalidrawApp from "../App" ;
1216
1317const { h } = window ;
@@ -65,6 +69,79 @@ vi.mock("socket.io-client", () => {
6569 * i.e. multiplayer history tests could be a good first candidate, as we could test both history stacks simultaneously.
6670 */
6771describe ( "collaboration" , ( ) => {
72+ it ( "should emit two ephemeral increments even though updates get batched" , async ( ) => {
73+ const durableIncrements : DurableIncrement [ ] = [ ] ;
74+ const ephemeralIncrements : EphemeralIncrement [ ] = [ ] ;
75+
76+ await render ( < ExcalidrawApp /> ) ;
77+
78+ h . store . onStoreIncrementEmitter . on ( ( increment ) => {
79+ if ( StoreIncrement . isDurable ( increment ) ) {
80+ durableIncrements . push ( increment ) ;
81+ } else {
82+ ephemeralIncrements . push ( increment ) ;
83+ }
84+ } ) ;
85+
86+ // eslint-disable-next-line dot-notation
87+ expect ( h . store [ "scheduledMicroActions" ] . length ) . toBe ( 0 ) ;
88+ expect ( durableIncrements . length ) . toBe ( 0 ) ;
89+ expect ( ephemeralIncrements . length ) . toBe ( 0 ) ;
90+
91+ const rectProps = {
92+ type : "rectangle" ,
93+ id : "A" ,
94+ height : 200 ,
95+ width : 100 ,
96+ x : 0 ,
97+ y : 0 ,
98+ } as const ;
99+
100+ const rect = API . createElement ( { ...rectProps } ) ;
101+
102+ API . updateScene ( {
103+ elements : [ rect ] ,
104+ captureUpdate : CaptureUpdateAction . IMMEDIATELY ,
105+ } ) ;
106+
107+ await waitFor ( ( ) => {
108+ // expect(commitSpy).toHaveBeenCalledTimes(1);
109+ expect ( durableIncrements . length ) . toBe ( 1 ) ;
110+ } ) ;
111+
112+ // simulate two batched remote updates
113+ act ( ( ) => {
114+ h . app . updateScene ( {
115+ elements : [ newElementWith ( h . elements [ 0 ] , { x : 100 } ) ] ,
116+ captureUpdate : CaptureUpdateAction . NEVER ,
117+ } ) ;
118+ h . app . updateScene ( {
119+ elements : [ newElementWith ( h . elements [ 0 ] , { x : 200 } ) ] ,
120+ captureUpdate : CaptureUpdateAction . NEVER ,
121+ } ) ;
122+
123+ // we scheduled two micro actions,
124+ // which confirms they are going to be executed as part of one batched component update
125+ // eslint-disable-next-line dot-notation
126+ expect ( h . store [ "scheduledMicroActions" ] . length ) . toBe ( 2 ) ;
127+ } ) ;
128+
129+ await waitFor ( ( ) => {
130+ // altough the updates get batched,
131+ // we expect two ephemeral increments for each update,
132+ // and each such update should have the expected change
133+ expect ( ephemeralIncrements . length ) . toBe ( 2 ) ;
134+ expect ( ephemeralIncrements [ 0 ] . change . elements . A ) . toEqual (
135+ expect . objectContaining ( { x : 100 } ) ,
136+ ) ;
137+ expect ( ephemeralIncrements [ 1 ] . change . elements . A ) . toEqual (
138+ expect . objectContaining ( { x : 200 } ) ,
139+ ) ;
140+ // eslint-disable-next-line dot-notation
141+ expect ( h . store [ "scheduledMicroActions" ] . length ) . toBe ( 0 ) ;
142+ } ) ;
143+ } ) ;
144+
68145 it ( "should allow to undo / redo even on force-deleted elements" , async ( ) => {
69146 await render ( < ExcalidrawApp /> ) ;
70147 const rect1Props = {
@@ -122,7 +199,7 @@ describe("collaboration", () => {
122199 expect ( h . elements ) . toEqual ( [ expect . objectContaining ( rect1Props ) ] ) ;
123200 } ) ;
124201
125- const undoAction = createUndoAction ( h . history , h . store ) ;
202+ const undoAction = createUndoAction ( h . history ) ;
126203 act ( ( ) => h . app . actionManager . executeAction ( undoAction ) ) ;
127204
128205 // with explicit undo (as addition) we expect our item to be restored from the snapshot!
@@ -154,7 +231,7 @@ describe("collaboration", () => {
154231 expect ( h . elements ) . toEqual ( [ expect . objectContaining ( rect1Props ) ] ) ;
155232 } ) ;
156233
157- const redoAction = createRedoAction ( h . history , h . store ) ;
234+ const redoAction = createRedoAction ( h . history ) ;
158235 act ( ( ) => h . app . actionManager . executeAction ( redoAction ) ) ;
159236
160237 // with explicit redo (as removal) we again restore the element from the snapshot!
0 commit comments