import { strict as assert } from "assert" ; import { PropertySet } from "@fluidframework/merge-tree" ; import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils" ; import { SharedString , SharedStringFactory } from "@fluidframework/sequence" ; import { IFluidDataStoreContext } from "@fluidframework/runtime-definitions" ; import { createSharedStringWithInterception } from "../sequence" ; describe ( "Shared String with Interception" , ( ) => { /**
     * The following tests test simple user attribution in SharedString with interception.
     * In the callback function of the SharedString with interception, it adds the user
     * information to the passed properties and returns it.
     */ describe ( "Simple User Attribution" , ( ) => { const userAttributes = { userId : "Fake User" } ; const documentId = "fakeId" ; let sharedString ; let dataStoreContext ; function orderSequentially ( callback ) { callback ( ) ; } function propertyInterceptionCb ( props ? ) { const newProps = { ... props , ... userAttributes } ; return newProps ; } function verifyString ( ss , text , props , position ) { assert . equal ( ss . getText ( ) , text , "The retrieved text should match the inserted text" ) ; assert . deepEqual ( { ... ss . getPropertiesAtPosition ( position ) } , { ... props } , "The properties set via the interception callback should exist" ) ; } beforeEach ( ( ) => { const dataStoreRuntime = new MockFluidDataStoreRuntime ( ) ; sharedString = new SharedString ( dataStoreRuntime , documentId , SharedStringFactory . Attributes ) ; dataStoreContext = { containerRuntime : { orderSequentially } } as IFluidDataStoreContext ; } ) ; it ( "should be able to intercept SharedString methods by the wrapper" , async ( ) => { const sharedStringWithInterception = createSharedStringWithInterception ( sharedString , dataStoreContext , propertyInterceptionCb ) ; let text = "123" ; let syleProps = { style : "bold" } ; sharedStringWithInterception . insertText ( 0 , text , syleProps ) ; verifyString ( sharedStringWithInterception , text , { ... syleProps , ... userAttributes } , 2 ) ; text = "aaa" ; syleProps = { style : "italics " } ; sharedStringWithInterception . replaceText ( 2 , 3 , "aaa" , syleProps ) ; verifyString ( sharedStringWithInterception , "12aaa" , { ... syleProps , ... userAttributes } , 2 ) ; const colorProps = { color : "green" } ; sharedStringWithInterception . annotateRange ( 0 , 5 , colorProps ) ; verifyString ( sharedStringWithInterception , "12aaa" , { ... syleProps , ... colorProps , ... userAttributes } , 2 ) ; } ) ; it ( "should be able to see changes made by the wrapper from the underlying shared string" , async ( ) => { const sharedStringWithInterception = createSharedStringWithInterception ( sharedString , dataStoreContext , propertyInterceptionCb ) ; let text = "123" ; let syleProps = { style : "bold" } ; sharedStringWithInterception . insertText ( 0 , text , syleProps ) ; verifyString ( sharedString , text , { ... syleProps , ... userAttributes } , 2 ) ; text = "aaa" ; syleProps = { style : "italics " } ; sharedStringWithInterception . replaceText ( 2 , 3 , "aaa" , syleProps ) ; verifyString ( sharedString , "12aaa" , { ... syleProps , ... userAttributes } , 2 ) ; const colorProps = { color : "green" } ; sharedStringWithInterception . annotateRange ( 0 , 5 , colorProps ) ; verifyString ( sharedString , "12aaa" , { ... syleProps , ... colorProps , ... userAttributes } , 2 ) ; } ) ; it ( "should be able to see changes made by the underlying shared string from the wrapper" , async ( ) => { const sharedStringWithInterception = createSharedStringWithInterception ( sharedString , dataStoreContext , propertyInterceptionCb ) ; let text = "123" ; let syleProps = { style : "bold" } ; sharedString . insertText ( 0 , text , syleProps ) ; verifyString ( sharedStringWithInterception , text , syleProps , 2 ) ; text = "aaa" ; syleProps = { style : "italics " } ; sharedString . replaceText ( 2 , 3 , "aaa" , syleProps ) ; verifyString ( sharedStringWithInterception , "12aaa" , syleProps , 2 ) ; const colorProps = { color : "green" } ; sharedString . annotateRange ( 0 , 5 , colorProps ) ; verifyString ( sharedStringWithInterception , "12aaa" , { ... syleProps , ... colorProps } , 2 ) ; } ) ; /**
         * This test calls a method on the wrapper from the interception callback which will cause an infinite
         * recursion. Verify that the wrapper detects this and asserts.
         * Also, verify that the object is not unusable after the assert.
         */ it ( "should assert if a wrapper method is called from the callback causing infinite recursion" , async ( ) => { let sharedStringWithInterception ; const propsInRecursiveCb = { fromRecursiveCb : "true" } ; let useWrapper = true ; function recursiveInterceptionCb ( properties ? ) { const ss = useWrapper ? sharedStringWithInterception : sharedString ; ss . annotateRange ( 0 , 1 , propsInRecursiveCb ) ; return { ... properties , ... userAttributes } ; } sharedStringWithInterception = createSharedStringWithInterception ( sharedString , dataStoreContext , recursiveInterceptionCb ) ; let text = "123" ; const props = { style : "bold" } ; sharedString . insertText ( 0 , text , props ) ; let asserted = false ; try { text = "abc" ; sharedStringWithInterception . replaceText ( 1 , 2 , text ) ; } catch ( error ) { assert . strictEqual ( error . message , "0x0c8" , "We should have caught an assert in replaceText because it detects an infinite recursion" ) ; asserted = true ; } assert . equal ( asserted , true , "replaceText should have asserted because it detects inifinite recursion" ) ; useWrapper = false ; text = "test" ; sharedStringWithInterception . replaceText ( 2 , 3 , text , props ) ; verifyString ( sharedStringWithInterception , "12test" , { ... props , ... userAttributes } , 2 ) ; verifyString ( sharedStringWithInterception , "12test" , { ... props , ... propsInRecursiveCb } , 0 ) ; } ) ; } ) ; } ) ;