import { strict as assert } from "assert" ; import { IGCTestProvider , runGCTests } from "@fluid-internal/test-dds-utils" ; import { MockFluidDataStoreRuntime , MockContainerRuntimeFactory , MockContainerRuntimeFactoryForReconnection , MockContainerRuntimeForReconnection , MockEmptyDeltaConnection , MockStorage , MockHandle , } from "@fluidframework/test-runtime-utils" ; import { IFluidHandle } from "@fluidframework/core-interfaces" ; import { MatrixItem , SharedMatrix , SharedMatrixFactory } from ".." ; import { fill , check , insertFragmented , extract , expectSize } from "./utils" ; import { TestConsumer } from "./testconsumer" ; function createConnectedMatrix ( id , runtimeFactory ) { const dataStoreRuntime = new MockFluidDataStoreRuntime ( ) ; const matrix = new SharedMatrix ( dataStoreRuntime , id , SharedMatrixFactory . Attributes ) ; matrix . connect ( { deltaConnection : runtimeFactory . createContainerRuntime ( dataStoreRuntime ) . createDeltaConnection ( ) , objectStorage : new MockStorage ( ) } ) ; return matrix ; } function createLocalMatrix ( id ) { const factory = new SharedMatrixFactory ( ) ; return factory . create ( new MockFluidDataStoreRuntime ( ) , id ) ; } function createMatrixForReconnection ( id , runtimeFactory ) { const dataStoreRuntime = new MockFluidDataStoreRuntime ( ) ; const containerRuntime = runtimeFactory . createContainerRuntime ( dataStoreRuntime ) ; const services = { deltaConnection : containerRuntime . createDeltaConnection ( ) , objectStorage : new MockStorage ( ) } ; const matrix = new SharedMatrix ( dataStoreRuntime , id , SharedMatrixFactory . Attributes ) ; matrix . connect ( services ) ; return { matrix , containerRuntime } ; } describe ( "Matrix" , ( ) => { describe ( "local client" , ( ) => { let matrix ; let consumer ; 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 } ) ; expectSize ( matrix2 , matrix . rowCount , matrix . colCount ) ; assert . deepEqual ( extract ( matrix ) , extract ( matrix2 ) , "Matrix must round-trip through summarize/load." ) ; return matrix2 ; } async function expect < T > ( expected ) { const actual = extract ( matrix ) ; assert . deepEqual ( actual , expected , "Matrix must match expected." ) ; assert . deepEqual ( extract ( consumer ) , actual , "Matrix must notify IMatrixConsumers of all changes." ) ; return summarize ( matrix ) ; } beforeEach ( async ( ) => { matrix = new SharedMatrix ( new MockFluidDataStoreRuntime ( ) , "matrix1" , SharedMatrixFactory . Attributes ) ; consumer = new TestConsumer ( matrix ) ; } ) ; afterEach ( async ( ) => { await summarize ( await summarize ( matrix ) ) ; assert . deepEqual ( extract ( consumer ) , extract ( matrix ) , "Matrix must notify IMatrixConsumers of all changes." ) ; matrix . closeMatrix ( consumer ) ; matrix . insertCols ( 0 , 1 ) ; assert . equal ( consumer . colCount , matrix . colCount - 1 ) ; } ) ; describe ( "empty matrices" , ( ) => { it ( "0x0" , async ( ) => { expectSize ( matrix , 0 , 0 ) ; expectSize ( await expect ( [ ] ) , 0 , 0 ) ; } ) ; it ( "0x1" , async ( ) => { matrix . insertCols ( 0 , 1 ) ; expectSize ( matrix , 0 , 1 ) ; expectSize ( await expect ( [ ] ) , 0 , 1 ) ; } ) ; it ( "1x0" , async ( ) => { matrix . insertRows ( 0 , 1 ) ; expectSize ( matrix , 1 , 0 ) ; expectSize ( await expect ( [ [ ] ] ) , 1 , 0 ) ; } ) ; } ) ; it ( "get/set cell" , async ( ) => { matrix . insertRows ( 0 , 1 ) ; matrix . insertCols ( 0 , 1 ) ; await expect ( [ [ undefined ] ] ) ; matrix . setCell ( 0 , 0 , 1 ) ; await expect ( [ [ 1 ] ] ) ; } ) ; it ( "get/set cells" , async ( ) => { matrix . insertRows ( 0 , 4 ) ; matrix . insertCols ( 0 , 4 ) ; await expect ( [ [ undefined , undefined , undefined , undefined ] , [ undefined , undefined , undefined , undefined ] , [ undefined , undefined , undefined , undefined ] , [ undefined , undefined , undefined , undefined ] , ] ) ; matrix . setCells ( 1 , 1 , 2 , [ 1 , 2 , 3 , 4 , 5 , ] ) ; await expect ( [ [ undefined , undefined , undefined , undefined ] , [ undefined , 1 , 2 , undefined ] , [ undefined , 3 , 4 , undefined ] , [ undefined , 5 , undefined , undefined ] , ] ) ; } ) ; it ( "out-of-bounds read must throw" , async ( ) => { assert . throws ( ( ) => { matrix . getCell ( 0 , 0 ) ; } ) ; matrix . insertRows ( 0 , 1 ) ; matrix . insertCols ( 0 , 2 ) ; assert . throws ( ( ) => { matrix . getCell ( - 1 , 0 ) ; } ) ; assert . throws ( ( ) => { matrix . getCell ( 0 , - 1 ) ; } ) ; assert . throws ( ( ) => { matrix . getCell ( 1 , 0 ) ; } ) ; assert . throws ( ( ) => { matrix . getCell ( 0 , 2 ) ; } ) ; } ) ; it ( "column insertion" , async ( ) => { matrix . insertRows ( 0 , 1 ) ; matrix . insertCols ( 0 , 2 ) ; await expect ( [ [ undefined , undefined ] ] ) ; matrix . setCell ( 0 , 0 , 0 ) ; matrix . setCell ( 0 , 1 , 1 ) ; await expect ( [ [ 0 , 1 ] ] ) ; matrix . insertCols ( 1 , 1 ) ; await expect ( [ [ 0 , undefined , 1 ] ] ) ; } ) ; it ( "row insertion" , async ( ) => { matrix . insertRows ( 0 , 2 ) ; matrix . insertCols ( 0 , 1 ) ; await expect ( [ [ undefined ] , [ undefined ] ] ) ; matrix . setCell ( 0 , 0 , 0 ) ; matrix . setCell ( 1 , 0 , 1 ) ; await expect ( [ [ 0 ] , [ 1 ] , ] ) ; matrix . insertRows ( 1 , 1 ) ; await expect ( [ [ 0 ] , [ undefined ] , [ 1 ] , ] ) ; } ) ; it ( "remove 1 row, insert 2 rows" , async ( ) => { matrix . insertRows ( 0 , 4 ) ; matrix . insertCols ( 0 , 1 ) ; matrix . setCells ( 0 , 0 , 1 , [ 0 , 1 , 2 , 3 ] ) ; matrix . removeRows ( 2 , 1 ) ; matrix . insertRows ( 0 , 2 ) ; matrix . setCells ( 0 , 0 , 1 , [ 84 , 45 ] ) ; await expect ( [ [ 84 ] , [ 45 ] , [ 0 ] , [ 1 ] , [ 3 ] , ] ) ; } ) ; describe ( "contiguous" , ( ) => { it ( "read/write 256x256" , ( ) => { matrix . insertRows ( 0 , 256 ) ; matrix . insertCols ( 0 , 256 ) ; fill ( matrix , 0 , 0 , 256 , 256 ) ; check ( matrix , 0 , 0 , 256 , 256 ) ; } ) ; } ) ; describe ( "fragmented" , ( ) => { it ( "read/write 256x256" , ( ) => { insertFragmented ( matrix , 256 , 256 ) ; fill ( matrix , 0 , 0 , 256 , 256 ) ; check ( matrix , 0 , 0 , 256 , 256 ) ; } ) ; } ) ; describe ( "summarize" , ( ) => { it ( "mutate after load" , async ( ) => { matrix . insertCols ( 0 , 2 ) ; matrix . insertRows ( 0 , 2 ) ; matrix . setCells ( 0 , 0 , 2 , [ 0 , 1 , 2 , 3 , ] ) ; const matrix2 = await expect ( [ [ 0 , 1 ] , [ 2 , 3 ] , ] ) ; matrix2 . insertRows ( 1 , 1 ) ; assert . deepEqual ( extract ( matrix2 ) , [ [ 0 , 1 ] , [ undefined , undefined ] , [ 2 , 3 ] , ] ) ; matrix2 . setCells ( 1 , 0 , 2 , [ 10 , 11 ] ) ; assert . deepEqual ( extract ( matrix2 ) , [ [ 0 , 1 ] , [ 10 , 11 ] , [ 2 , 3 ] , ] ) ; matrix2 . insertCols ( 1 , 1 ) ; assert . deepEqual ( extract ( matrix2 ) , [ [ 0 , undefined , 1 ] , [ 10 , undefined , 11 ] , [ 2 , undefined , 3 ] , ] ) ; } ) ; } ) ; } ) ; describe ( "Connected with two clients" , ( ) => { let matrix1 ; let matrix2 ; let consumer1 ; let consumer2 ; let containerRuntimeFactory ; const 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 ( ) ; matrix1 = createConnectedMatrix ( "matrix1" , containerRuntimeFactory ) ; consumer1 = new TestConsumer ( matrix1 ) ; matrix2 = createConnectedMatrix ( "matrix2" , containerRuntimeFactory ) ; consumer2 = new TestConsumer ( matrix2 ) ; } ) ; describe ( "conflict" , ( ) => { afterEach ( async ( ) => { await expect ( ) ; matrix1 . closeMatrix ( consumer1 ) ; matrix2 . closeMatrix ( consumer2 ) ; } ) ; it ( "setCell" , async ( ) => { matrix1 . insertCols ( 0 , 1 ) ; matrix1 . insertRows ( 0 , 1 ) ; await expect ( [ [ undefined ] , ] ) ; matrix1 . setCell ( 0 , 0 , "1st" ) ; matrix2 . setCell ( 0 , 0 , "2nd" ) ; await expect ( [ [ "2nd" ] , ] ) ; } ) ; it ( "clear unallocated cell" , async ( ) => { matrix1 . insertCols ( 0 , 1 ) ; matrix1 . insertRows ( 0 , 1 ) ; await expect ( [ [ undefined ] , ] ) ; matrix1 . setCell ( 0 , 0 , "x" ) ; matrix2 . setCell ( 0 , 0 , undefined ) ; await expect ( [ [ undefined ] , ] ) ; } ) ; it ( "insert and set in new row" , async ( ) => { matrix1 . insertCols ( 0 , 2 ) ; await expect ( ) ; matrix1 . insertRows ( 0 , 1 ) ; matrix1 . setCells ( 0 , 1 , 1 , [ "x" ] ) ; await expect ( [ [ undefined , "x" ] ] ) ; } ) ; it ( "insert and set in new col" , async ( ) => { matrix1 . insertRows ( 0 , 2 ) ; await expect ( [ [ ] , [ ] , ] ) ; matrix1 . insertCols ( 0 , 1 ) ; matrix1 . setCells ( 1 , 0 , 1 , [ "x" ] ) ; await expect ( [ [ undefined ] , [ "x" ] , ] ) ; } ) ; it ( "insert col conflict" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; await expect ( [ [ ] , ] ) ; matrix1 . insertCols ( 0 , 1 ) ; matrix1 . setCell ( 0 , 0 , "1st" ) ; matrix2 . insertCols ( 0 , 1 ) ; matrix2 . setCell ( 0 , 0 , "2nd" ) ; await expect ( [ [ "2nd" , "1st" ] , ] ) ; } ) ; it ( "insert row conflict" , async ( ) => { matrix1 . insertCols ( 0 , 1 ) ; await expect ( [ ] ) ; matrix1 . insertRows ( 0 , 1 ) ; matrix1 . setCell ( 0 , 0 , "1st" ) ; matrix2 . insertRows ( 0 , 1 ) ; matrix2 . setCell ( 0 , 0 , "2nd" ) ; await expect ( [ [ "2nd" ] , [ "1st" ] , ] ) ; } ) ; it ( "overlapping remove col" , async ( ) => { matrix1 . insertCols ( 0 , 3 ) ; matrix1 . insertRows ( 0 , 1 ) ; matrix1 . setCell ( 0 , 0 , "A" ) ; matrix1 . setCell ( 0 , 1 , "B" ) ; matrix1 . setCell ( 0 , 2 , "C" ) ; await expect ( [ [ "A" , "B" , "C" ] , ] ) ; matrix1 . removeCols ( 1 , 1 ) ; matrix2 . removeCols ( 1 , 1 ) ; await expect ( [ [ "A" , "C" ] , ] ) ; } ) ; it ( "overlapping remove row" , async ( ) => { matrix1 . insertCols ( 0 , 1 ) ; matrix1 . insertRows ( 0 , 3 ) ; matrix1 . setCell ( 0 , 0 , "A" ) ; matrix1 . setCell ( 1 , 0 , "B" ) ; matrix1 . setCell ( 2 , 0 , "C" ) ; await expect ( [ [ "A" ] , [ "B" ] , [ "C" ] , ] ) ; matrix1 . removeRows ( 1 , 1 ) ; matrix2 . removeRows ( 1 , 1 ) ; await expect ( [ [ "A" ] , [ "C" ] , ] ) ; } ) ; it ( "insert col vs. remove row" , async ( ) => { matrix1 . insertCols ( 0 , 2 ) ; matrix1 . insertRows ( 0 , 3 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ "A1" , "C1" , "A2" , "C2" , "A3" , "C3" , ] ) ; await expect ( [ [ "A1" , "C1" ] , [ "A2" , "C2" ] , [ "A3" , "C3" ] , ] ) ; matrix1 . insertCols ( 1 , 1 ) ; matrix1 . setCells ( 0 , 1 , 1 , [ "B1" , "B2" , "B3" , ] ) ; matrix2 . removeRows ( 1 , 1 ) ; await expect ( [ [ "A1" , "B1" , "C1" ] , [ "A3" , "B3" , "C3" ] , ] ) ; } ) ; it ( "insert row vs. remove col" , async ( ) => { matrix1 . insertRows ( 0 , 2 ) ; matrix1 . insertCols ( 0 , 3 ) ; matrix1 . setCells ( 0 , 0 , 3 , [ "A1" , "B1" , "C1" , "A3" , "B3" , "C3" , ] ) ; await expect ( [ [ "A1" , "B1" , "C1" ] , [ "A3" , "B3" , "C3" ] , ] ) ; matrix1 . insertRows ( 1 , 1 ) ; matrix1 . setCells ( 1 , 0 , 3 , [ "A2" , "B2" , "C2" , ] ) ; matrix2 . removeCols ( 1 , 1 ) ; await expect ( [ [ "A1" , "C1" ] , [ "A2" , "C2" ] , [ "A3" , "C3" ] , ] ) ; } ) ; it ( "insert row vs. remove col" , async ( ) => { matrix1 . insertRows ( 0 , 2 ) ; matrix1 . insertCols ( 0 , 3 ) ; matrix1 . setCells ( 0 , 0 , 3 , [ "A1" , "B1" , "C1" , "A3" , "B3" , "C3" , ] ) ; await expect ( [ [ "A1" , "B1" , "C1" ] , [ "A3" , "B3" , "C3" ] , ] ) ; matrix1 . insertRows ( 1 , 1 ) ; matrix1 . setCells ( 1 , 0 , 3 , [ "A2" , "B2" , "C2" , ] ) ; matrix2 . removeCols ( 1 , 1 ) ; await expect ( [ [ "A1" , "C1" ] , [ "A2" , "C2" ] , [ "A3" , "C3" ] , ] ) ; } ) ; it ( "insert col vs. insert & remove row" , async ( ) => { matrix1 . insertRows ( 0 , 2 ) ; matrix1 . insertCols ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ "A1" , "C1" , "A2" , "C2" , ] ) ; matrix1 . removeRows ( 1 , 1 ) ; matrix1 . insertCols ( 1 , 1 ) ; await expect ( [ [ "A1" , undefined , "C1" ] , ] ) ; } ) ; it ( "insert row & col vs. insert row and set" , async ( ) => { matrix1 . insertRows ( 0 , 4 ) ; matrix1 . insertCols ( 0 , 4 ) ; const v = new Array ( 16 ) . fill ( 0 ) . map ( ( _ , index ) => index ) ; matrix1 . setCells ( 0 , 0 , 4 , v ) ; await expect ( ) ; matrix1 . insertRows ( 0 , 1 ) ; matrix2 . insertRows ( 0 , 2 ) ; matrix2 . setCells ( 0 , 0 , 4 , [ "A" , "B" , "C" , "D" ] ) ; matrix1 . insertCols ( 1 , 1 ) ; await expect ( ) ; } ) ; it ( "remove rows vs. set cells" , async ( ) => { matrix1 . insertRows ( 0 , 3 ) ; matrix1 . insertCols ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 0 , 1 , 2 , 3 ] ) ; matrix2 . insertRows ( 0 , 1 ) ; await expect ( ) ; matrix1 . removeRows ( 1 , 1 ) ; matrix2 . setCells ( 0 , 0 , 1 , [ "A" , "B" , "C" ] ) ; await expect ( ) ; } ) ; it ( "overlapping insert/set vs. remove/insert/set" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; matrix1 . insertCols ( 0 , 4 ) ; matrix1 . setCells ( 0 , 0 , 4 , [ 0 , 1 , 2 , 3 ] ) ; await expect ( [ [ 0 , 1 , 2 , 3 ] , ] ) ; matrix2 . insertCols ( 1 , 1 ) ; matrix2 . setCells ( 0 , 1 , 1 , [ "A" ] ) ; matrix1 . removeCols ( 0 , 2 ) ; matrix1 . insertCols ( 0 , 1 ) ; matrix1 . setCells ( 0 , 0 , 1 , [ "B" ] ) ; await expect ( [ [ "B" , "A" , 2 , 3 ] , ] ) ; } ) ; } ) ; } ) ; describe ( "Reconnection" , ( ) => { let matrix1 ; let matrix2 ; let consumer1 ; let consumer2 ; let containerRuntimeFactory ; let containerRuntime1 ; let containerRuntime2 ; let mockHandle ; const 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 MockContainerRuntimeFactoryForReconnection ( ) ; const response1 = createMatrixForReconnection ( "matrix1" , containerRuntimeFactory ) ; matrix1 = response1 . matrix ; containerRuntime1 = response1 . containerRuntime ; consumer1 = new TestConsumer ( matrix1 ) ; const response2 = createMatrixForReconnection ( "matrix2" , containerRuntimeFactory ) ; matrix2 = response2 . matrix ; containerRuntime2 = response2 . containerRuntime ; consumer2 = new TestConsumer ( matrix2 ) ; mockHandle = new MockHandle ( { } ) ; } ) ; afterEach ( async ( ) => { await expect ( ) ; matrix1 . closeMatrix ( consumer1 ) ; matrix2 . closeMatrix ( consumer2 ) ; } ) ; it ( "can resend 'setCell()' at correct position when later ops shift the original position" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; matrix1 . insertCols ( 0 , 1 ) ; await expect ( [ [ undefined ] ] ) ; matrix1 . setCells ( 0 , 0 , 1 , [ "A" ] ) ; matrix1 . insertCols ( 0 , 3 ) ; containerRuntime1 . connected = false ; containerRuntime1 . connected = true ; await expect ( [ [ undefined , undefined , undefined , "A" ] , ] ) ; } ) ; it ( "can resend 'setCell()' at correct position when multiple reconnects occur" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; matrix1 . insertCols ( 0 , 1 ) ; await expect ( [ [ undefined ] ] ) ; matrix1 . setCells ( 0 , 0 , 1 , [ "A" ] ) ; matrix1 . insertCols ( 0 , 3 ) ; containerRuntime1 . connected = false ; containerRuntime1 . connected = true ; containerRuntime1 . connected = false ; containerRuntime1 . connected = true ; await expect ( [ [ undefined , undefined , undefined , "A" ] , ] ) ; } ) ; it ( "can resend unacked ops on reconnection" , async ( ) => { matrix1 . insertCols ( 0 , 1 ) ; matrix1 . insertRows ( 0 , 1 ) ; containerRuntime1 . connected = false ; containerRuntime1 . connected = true ; await expect ( [ [ undefined ] , ] ) ; matrix2 . setCell ( 0 , 0 , "2nd" ) ; containerRuntime2 . connected = false ; containerRuntime2 . connected = true ; await expect ( [ [ "2nd" ] , ] ) ; } ) ; it ( "can store ops in disconnected state and resend them on reconnection" , async ( ) => { containerRuntime1 . connected = false ; matrix1 . insertCols ( 0 , 1 ) ; matrix1 . insertRows ( 0 , 1 ) ; containerRuntime1 . connected = true ; await expect ( [ [ undefined ] , ] ) ; containerRuntime2 . connected = false ; matrix2 . setCell ( 0 , 0 , "2nd" ) ; containerRuntime2 . connected = true ; await expect ( [ [ "2nd" ] , ] ) ; } ) ; it ( "setCell(IFluidHandle) is preserved when resubmitted" , async ( ) => { containerRuntime1 . connected = false ; matrix1 . insertCols ( 0 , 1 ) ; matrix1 . insertRows ( 0 , 1 ) ; matrix1 . setCell ( 0 , 0 , mockHandle ) ; containerRuntime1 . connected = true ; containerRuntimeFactory . processAllMessages ( ) ; const handle1 = matrix1 . getCell ( 0 , 0 ) as IFluidHandle ; const handle2 = matrix2 . getCell ( 0 , 0 ) as IFluidHandle ; assert . equal ( handle1 . IFluidHandle . absolutePath , handle2 . IFluidHandle . absolutePath ) ; matrix1 . setCell ( 0 , 0 , undefined ) ; } ) ; it ( "resubmission omits writes to recycled row/col handles" , async ( ) => { matrix1 . insertRows ( 0 , 2 ) ; matrix1 . insertCols ( 0 , 2 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 0 , 1 , 2 , 3 , ] ) ; matrix1 . removeRows ( 1 , 1 ) ; containerRuntime1 . connected = false ; matrix1 . insertRows ( 0 , 1 ) ; matrix1 . setCells ( 0 , 0 , 2 , [ 28 , 49 , ] ) ; containerRuntime1 . connected = true ; await expect ( [ [ 28 , 49 ] , [ 0 , 1 ] , ] ) ; } ) ; it ( "omits not-yet-locally deleted row/cols during resubmission" , async ( ) => { matrix1 . insertRows ( 0 , 2 ) ; matrix1 . insertCols ( 0 , 4 ) ; matrix1 . setCells ( 0 , 0 , 4 , [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , ] ) ; matrix1 . insertRows ( 0 , 1 ) ; matrix1 . setCells ( 0 , 0 , 4 , [ 61 , 57 , 7 , 62 , ] ) ; containerRuntime1 . connected = false ; containerRuntime1 . connected = true ; await expect ( [ [ 61 , 57 , 7 , 62 ] , [ 0 , 1 , 2 , 3 ] , [ 4 , 5 , 6 , 7 ] , ] ) ; matrix1 . setCells ( 2 , 3 , 1 , [ 65 ] ) ; containerRuntime1 . connected = false ; matrix1 . removeRows ( 0 , 1 ) ; containerRuntime1 . connected = true ; await expect ( [ [ 0 , 1 , 2 , 3 ] , [ 4 , 5 , 6 , 65 ] , ] ) ; } ) ; it ( "resets row/col handles for resubmitted ops" , async ( ) => { matrix1 . insertRows ( 0 , 1 ) ; matrix1 . insertCols ( 0 , 1 ) ; matrix1 . setCells ( 0 , 0 , 1 , [ 0 ] ) ; matrix2 . insertCols ( 0 , 1 ) ; matrix2 . insertRows ( 0 , 1 ) ; matrix2 . setCells ( 0 , 0 , 1 , [ 90 ] ) ; containerRuntime2 . connected = false ; containerRuntime2 . connected = true ; await expect ( [ [ 90 , undefined ] , [ undefined , 0 ] , ] ) ; } ) ; } ) ; describe ( "Garbage Collection" , ( ) => { class GCSharedMatrixProvider implements IGCTestProvider { private colCount = 0 ; private subMatrixCount = 0 ; private _expectedRoutes : string [ ] = [ ] ; private readonly matrix1 : SharedMatrix ; private readonly matrix2 : SharedMatrix ; private readonly containerRuntimeFactory : MockContainerRuntimeFactory ; constructor ( ) { this . containerRuntimeFactory = new MockContainerRuntimeFactory ( ) ; this . matrix1 = createConnectedMatrix ( "matrix1" , this . containerRuntimeFactory ) ; this . matrix2 = createConnectedMatrix ( "matrix2" , this . containerRuntimeFactory ) ; this . matrix1 . insertRows ( 0 , 1 ) ; } public get sharedObject ( ) { return this . matrix2 ; } public get expectedOutboundRoutes ( ) { return this . _expectedRoutes ; } public async addOutboundRoutes ( ) { const newSubMatrixId = `subMatrix-${ ++ this . subMatrixCount }` ; const subMatrix = createLocalMatrix ( newSubMatrixId ) ; this . matrix1 . insertCols ( this . colCount , 1 ) ; this . matrix1 . setCell ( 0 , this . colCount , subMatrix . handle ) ; this . colCount ++ ; this . _expectedRoutes . push ( subMatrix . handle . absolutePath ) ; this . containerRuntimeFactory . processAllMessages ( ) ; } public async deleteOutboundRoutes ( ) { const lastAddedCol = this . colCount - 1 ; const deletedHandle = this . matrix1 . getCell ( 0 , lastAddedCol ) as IFluidHandle ; assert ( deletedHandle , "Route must be added before deleting" ) ; this . matrix1 . setCell ( 0 , lastAddedCol , undefined ) ; this . _expectedRoutes = this . _expectedRoutes . filter ( ( route ) => route !== deletedHandle . absolutePath ) ; this . containerRuntimeFactory . processAllMessages ( ) ; } public async addNestedHandles ( ) { const subMatrix = createLocalMatrix ( `subMatrix-${ ++ this . subMatrixCount }` ) ; const subMatrix2 = createLocalMatrix ( `subMatrix-${ ++ this . subMatrixCount }` ) ; const containingObject = { subMatrixHandle : subMatrix . handle , nestedObj : { subMatrix2Handle : subMatrix2 . handle } } ; this . matrix1 . insertCols ( this . colCount , 1 ) ; this . matrix1 . setCell ( 0 , this . colCount , containingObject ) ; this . colCount ++ ; this . _expectedRoutes . push ( subMatrix . handle . absolutePath , subMatrix2 . handle . absolutePath ) ; this . containerRuntimeFactory . processAllMessages ( ) ; } } runGCTests ( GCSharedMatrixProvider ) ; } ) ; } ) ;