import { strict as assert } from "assert" ; import { MockFluidDataStoreRuntime , MockEmptyDeltaConnection , MockStorage , MockContainerRuntimeFactory , } from "@fluidframework/test-runtime-utils" ; import { MatrixItem , SharedMatrix , SharedMatrixFactory } from ".." ; import { extract , expectSize } from "./utils" ; import { TestConsumer } from "./testconsumer" ; import { UndoRedoStackManager } from "./undoRedoStackManager" ; describe ( "Matrix" , ( ) => { describe ( "undo/redo" , ( ) => { let dataStoreRuntime ; let matrix1 ; let consumer1 ; let undo1 ; let expect ; function singleClientTests ( ) { it ( "undo/redo setCell" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; matrix1 . insertCols ( 0 , 1 ) ; await expect ( [ [ undefined ] ] ) ; undo1 . closeCurrentOperation ( ) ; matrix1 . setCell ( 0 , 0 , 1 ) ; await expect ( [ [ 1 ] ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ undefined ] ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 1 ] ] ) ; } ) ; it ( "undo/redo insertRow" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; undo1 . closeCurrentOperation ( ) ; expectSize ( matrix1 , 1 , 0 ) ; undo1 . undoOperation ( ) ; expectSize ( matrix1 , 0 , 0 ) ; undo1 . redoOperation ( ) ; expectSize ( matrix1 , 1 , 0 ) ; } ) ; it ( "undo/redo insertRow 2x1" , async ( ) => { matrix1 . insertCols ( 0 , 1 ) ; undo1 . closeCurrentOperation ( ) ; matrix1 . insertRows ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 1 , [ 0 , 1 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 ] , [ 1 ] , ] ) ; undo1 . undoOperation ( ) ; expectSize ( matrix1 , 0 , 1 ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 0 ] , [ 1 ] , ] ) ; } ) ; it ( "undo/redo removeRow" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; matrix1 . insertCols ( 0 , 1 ) ; await expect ( [ [ undefined ] ] ) ; matrix1 . setCell ( 0 , 0 , 1 ) ; await expect ( [ [ 1 ] ] ) ; undo1 . closeCurrentOperation ( ) ; matrix1 . removeRows ( 0 , 1 ) ; undo1 . closeCurrentOperation ( ) ; expectSize ( matrix1 , 0 , 1 ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 1 ] ] ) ; undo1 . redoOperation ( ) ; expectSize ( matrix1 , 0 , 1 ) ; } ) ; it ( "undo/redo removeRow 0 of 2x2" , async ( ) => { matrix1 . insertRows ( 0 , 2 ) ; matrix1 . insertCols ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 0 , 1 , 2 , 3 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; matrix1 . removeRows ( 0 , 1 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 2 , 3 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 2 , 3 ] , ] ) ; } ) ; it ( "undo/redo removeRow 1 of 2x2" , async ( ) => { matrix1 . insertRows ( 0 , 2 ) ; matrix1 . insertCols ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 0 , 1 , 2 , 3 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; matrix1 . removeRows ( 1 , 1 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 0 , 1 ] , ] ) ; } ) ; it ( "undo/redo removeRow 0..1 of 3x3" , async ( ) => { matrix1 . insertRows ( 0 , 3 ) ; matrix1 . insertCols ( 0 , 3 ) ; matrix1 . setCells ( 0 , 0 , 3 , [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , [ 3 , 4 , 5 ] , [ 6 , 7 , 8 ] , ] ) ; matrix1 . removeRows ( 0 , 2 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 6 , 7 , 8 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , [ 3 , 4 , 5 ] , [ 6 , 7 , 8 ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 6 , 7 , 8 ] , ] ) ; } ) ; it ( "undo/redo removeRow 2..3 of 3x3" , async ( ) => { matrix1 . insertRows ( 0 , 3 ) ; matrix1 . insertCols ( 0 , 3 ) ; matrix1 . setCells ( 0 , 0 , 3 , [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , [ 3 , 4 , 5 ] , [ 6 , 7 , 8 ] , ] ) ; matrix1 . removeRows ( 1 , 2 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , [ 3 , 4 , 5 ] , [ 6 , 7 , 8 ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , ] ) ; } ) ; it ( "undo/redo insertCol" , async ( ) => { matrix1 . insertCols ( 0 , 1 ) ; undo1 . closeCurrentOperation ( ) ; expectSize ( matrix1 , 0 , 1 ) ; undo1 . undoOperation ( ) ; expectSize ( matrix1 , 0 , 0 ) ; undo1 . redoOperation ( ) ; expectSize ( matrix1 , 0 , 1 ) ; } ) ; it ( "undo/redo overlapping insertCol/removeCol in single undo group" , async ( ) => { matrix1 . insertCols ( 0 , 3 ) ; matrix1 . removeCols ( 0 , 1 ) ; expectSize ( matrix1 , 0 , 2 ) ; undo1 . undoOperation ( ) ; expectSize ( matrix1 , 0 , 0 ) ; undo1 . redoOperation ( ) ; expectSize ( matrix1 , 0 , 2 ) ; } ) ; it ( "undo/redo insertCol 1x2" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; undo1 . closeCurrentOperation ( ) ; matrix1 . insertCols ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 0 , 1 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 ] , ] ) ; undo1 . undoOperation ( ) ; expectSize ( matrix1 , 1 , 0 ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 0 , 1 ] , ] ) ; } ) ; it ( "undo/redo removeCol" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; matrix1 . insertCols ( 0 , 1 ) ; await expect ( [ [ undefined ] ] ) ; matrix1 . setCell ( 0 , 0 , 1 ) ; await expect ( [ [ 1 ] ] ) ; undo1 . closeCurrentOperation ( ) ; matrix1 . removeCols ( 0 , 1 ) ; undo1 . closeCurrentOperation ( ) ; expectSize ( matrix1 , 1 , 0 ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 1 ] ] ) ; undo1 . redoOperation ( ) ; expectSize ( matrix1 , 1 , 0 ) ; } ) ; it ( "undo/redo overlapping insertRow/removeRow in single undo group" , async ( ) => { matrix1 . insertRows ( 0 , 3 ) ; matrix1 . removeRows ( 0 , 1 ) ; expectSize ( matrix1 , 2 , 0 ) ; undo1 . undoOperation ( ) ; expectSize ( matrix1 , 0 , 0 ) ; undo1 . redoOperation ( ) ; expectSize ( matrix1 , 2 , 0 ) ; } ) ; it ( "undo/redo removeCol 0 of 2x2" , async ( ) => { matrix1 . insertRows ( 0 , 2 ) ; matrix1 . insertCols ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 0 , 1 , 2 , 3 , ] ) ; await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; undo1 . closeCurrentOperation ( ) ; matrix1 . removeCols ( 0 , 1 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 1 ] , [ 3 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 1 ] , [ 3 ] , ] ) ; } ) ; it ( "undo/redo removeCol 1 of 2x2" , async ( ) => { matrix1 . insertRows ( 0 , 2 ) ; matrix1 . insertCols ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 0 , 1 , 2 , 3 , ] ) ; await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; undo1 . closeCurrentOperation ( ) ; matrix1 . removeCols ( 1 , 1 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 ] , [ 2 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 0 ] , [ 2 ] , ] ) ; } ) ; it ( "undo/redo removeCol 0..1 of 3x3" , async ( ) => { matrix1 . insertRows ( 0 , 3 ) ; matrix1 . insertCols ( 0 , 3 ) ; matrix1 . setCells ( 0 , 0 , 3 , [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , [ 3 , 4 , 5 ] , [ 6 , 7 , 8 ] , ] ) ; matrix1 . removeCols ( 0 , 2 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 2 ] , [ 5 ] , [ 8 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , [ 3 , 4 , 5 ] , [ 6 , 7 , 8 ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 2 ] , [ 5 ] , [ 8 ] , ] ) ; } ) ; it ( "undo/redo removeCol 1..2 of 3x3" , async ( ) => { matrix1 . insertRows ( 0 , 3 ) ; matrix1 . insertCols ( 0 , 3 ) ; matrix1 . setCells ( 0 , 0 , 3 , [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , [ 3 , 4 , 5 ] , [ 6 , 7 , 8 ] , ] ) ; matrix1 . removeCols ( 1 , 2 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 ] , [ 3 ] , [ 6 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , [ 3 , 4 , 5 ] , [ 6 , 7 , 8 ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 0 ] , [ 3 ] , [ 6 ] , ] ) ; } ) ; } describe ( "local client" , ( ) => { async function summarize < T > ( matrix ) { const objectStorage = MockStorage . createFromSummary ( matrix . getAttachSummary ( ) . summary ) ; const dataStoreRuntime = new MockFluidDataStoreRuntime ( ) ; dataStoreRuntime . local = true ; const matrix2 = new SharedMatrix < T > ( dataStoreRuntime , `load(${ matrix . id })` , SharedMatrixFactory . Attributes ) ; await matrix2 . load ( { deltaConnection : new MockEmptyDeltaConnection ( ) , objectStorage } ) ; return matrix2 ; } before ( ( ) => { expect = async < T > ( expected ) => { const actual = extract ( matrix1 ) ; assert . deepEqual ( actual , expected , "Matrix must match expected." ) ; assert . deepEqual ( extract ( consumer1 ) , actual , "Matrix must notify IMatrixConsumers of all changes." ) ; } ; } ) ; beforeEach ( async ( ) => { dataStoreRuntime = new MockFluidDataStoreRuntime ( ) ; matrix1 = new SharedMatrix ( dataStoreRuntime , "matrix1" , SharedMatrixFactory . Attributes ) ; consumer1 = new TestConsumer ( matrix1 ) ; undo1 = new UndoRedoStackManager ( ) ; matrix1 . openUndo ( undo1 ) ; } ) ; afterEach ( async ( ) => { await summarize ( await summarize ( matrix1 ) ) ; assert . deepEqual ( extract ( consumer1 ) , extract ( matrix1 ) , "Matrix must notify IMatrixConsumers of all changes." ) ; matrix1 . closeMatrix ( consumer1 ) ; matrix1 . insertCols ( 0 , 1 ) ; assert . equal ( consumer1 . colCount , matrix1 . colCount - 1 ) ; } ) ; singleClientTests ( ) ; } ) ; describe ( "Connected with two clients" , ( ) => { let matrix2 ; let undo2 ; let consumer2 ; let containerRuntimeFactory ; before ( ( ) => { expect = async ( expected ? ) => { containerRuntimeFactory . processAllMessages ( ) ; const actual1 = extract ( matrix1 ) ; const actual2 = extract ( matrix2 ) ; assert . deepEqual ( actual1 , actual2 ) ; if ( expected !== undefined ) { assert . deepEqual ( actual1 , expected ) ; } for ( const consumer of [ consumer1 , consumer2 ] ) { assert . deepEqual ( extract ( consumer ) , actual1 , "Matrix must notify IMatrixConsumers of all changes." ) ; } } ; } ) ; beforeEach ( async ( ) => { containerRuntimeFactory = new MockContainerRuntimeFactory ( ) ; const dataStoreRuntime1 = new MockFluidDataStoreRuntime ( ) ; matrix1 = new SharedMatrix ( dataStoreRuntime1 , "matrix1" , SharedMatrixFactory . Attributes ) ; matrix1 . connect ( { deltaConnection : containerRuntimeFactory . createContainerRuntime ( dataStoreRuntime1 ) . createDeltaConnection ( ) , objectStorage : new MockStorage ( ) } ) ; consumer1 = new TestConsumer ( matrix1 ) ; undo1 = new UndoRedoStackManager ( ) ; matrix1 . openUndo ( undo1 ) ; const dataStoreRuntime2 = new MockFluidDataStoreRuntime ( ) ; matrix2 = new SharedMatrix ( dataStoreRuntime2 , "matrix2" , SharedMatrixFactory . Attributes ) ; matrix2 . connect ( { deltaConnection : containerRuntimeFactory . createContainerRuntime ( dataStoreRuntime2 ) . createDeltaConnection ( ) , objectStorage : new MockStorage ( ) } ) ; consumer2 = new TestConsumer ( matrix2 ) ; undo2 = new UndoRedoStackManager ( ) ; matrix2 . openUndo ( undo2 ) ; } ) ; afterEach ( async ( ) => { await expect ( undefined as any ) ; matrix1 . closeMatrix ( consumer1 ) ; matrix2 . closeMatrix ( consumer2 ) ; } ) ; singleClientTests ( ) ; it ( "reorder row insertion via undo/redo" , async ( ) => { matrix1 . insertCols ( 0 , 2 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ ] ) ; matrix2 . insertRows ( 0 , 1 ) ; matrix2 . setCells ( 0 , 0 , 2 , [ 2 , 3 , ] ) ; undo2 . closeCurrentOperation ( ) ; await expect ( [ [ 2 , 3 ] , ] ) ; matrix1 . insertRows ( 0 , 1 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 0 , 1 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; undo2 . undoOperation ( ) ; await expect ( [ [ 0 , 1 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ ] ) ; undo2 . redoOperation ( ) ; await expect ( [ [ 2 , 3 ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 2 , 3 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 2 , 3 ] , ] ) ; } ) ; it ( "undo/redo races split column span" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ ] , ] ) ; matrix1 . insertCols ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 0 , 2 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 2 ] , ] ) ; matrix2 . insertCols ( 1 , 1 ) ; matrix2 . setCell ( 0 , 1 , 1 ) ; undo2 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , ] ) ; undo1 . undoOperation ( ) ; await expect ( [ [ 1 ] , ] ) ; undo1 . redoOperation ( ) ; await expect ( [ [ 0 , 1 , 2 ] , ] ) ; } ) ; it ( "undo/redo races split column span" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ ] , ] ) ; matrix1 . insertCols ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 0 , 2 , ] ) ; undo1 . closeCurrentOperation ( ) ; await expect ( [ [ 0 , 2 ] , ] ) ; matrix2 . insertCols ( 1 , 1 ) ; matrix2 . setCell ( 0 , 1 , 1 ) ; undo2 . closeCurrentOperation ( ) ; undo1 . undoOperation ( ) ; undo1 . redoOperation ( ) ; await expect ( undefined as any ) ; } ) ; } ) ; } ) ; } ) ;