@@ -31,6 +31,9 @@ import {
3131 type IOpenIDCredentials ,
3232 type ISendEventFromWidgetResponseData ,
3333 WidgetApiResponseError ,
34+ UnstableApiVersion ,
35+ type ApiVersion ,
36+ type IRoomEvent ,
3437} from "matrix-widget-api" ;
3538
3639import { createRoomWidgetClient , MatrixError , MsgType , UpdateDelayedEventAction } from "../../src/matrix" ;
@@ -40,6 +43,9 @@ import { type ICapabilities, type RoomWidgetClient } from "../../src/embedded";
4043import { MatrixEvent } from "../../src/models/event" ;
4144import { type ToDeviceBatch } from "../../src/models/ToDeviceMessage" ;
4245import { sleep } from "../../src/utils" ;
46+ import { SlidingSync } from "../../src/sliding-sync" ;
47+ import { logger } from "../../src/logger" ;
48+ import { flushPromises } from "../test-utils/flushPromises" ;
4349
4450const testOIDCToken = {
4551 access_token : "12345678" ,
@@ -49,6 +55,7 @@ const testOIDCToken = {
4955} ;
5056class MockWidgetApi extends EventEmitter {
5157 public start = jest . fn ( ) . mockResolvedValue ( undefined ) ;
58+ public getClientVersions = jest . fn ( ) ;
5259 public requestCapability = jest . fn ( ) . mockResolvedValue ( undefined ) ;
5360 public requestCapabilities = jest . fn ( ) . mockResolvedValue ( undefined ) ;
5461 public requestCapabilityForRoomTimeline = jest . fn ( ) . mockResolvedValue ( undefined ) ;
@@ -96,6 +103,15 @@ class MockWidgetApi extends EventEmitter {
96103 send : jest . fn ( ) ,
97104 sendComplete : jest . fn ( ) ,
98105 } ;
106+
107+ /**
108+ * This mocks the widget's view of what is supported by its environment.
109+ * @param clientVersions The versions that the widget believes are supported by the host client's widget driver.
110+ */
111+ public constructor ( clientVersions : ApiVersion [ ] ) {
112+ super ( ) ;
113+ this . getClientVersions . mockResolvedValue ( clientVersions ) ;
114+ }
99115}
100116
101117declare module "../../src/types" {
@@ -117,7 +133,7 @@ describe("RoomWidgetClient", () => {
117133 let client : MatrixClient ;
118134
119135 beforeEach ( ( ) => {
120- widgetApi = new MockWidgetApi ( ) as unknown as MockedObject < WidgetApi > ;
136+ widgetApi = new MockWidgetApi ( [ UnstableApiVersion . MSC2762_UPDATE_STATE ] ) as unknown as MockedObject < WidgetApi > ;
121137 } ) ;
122138
123139 afterEach ( ( ) => {
@@ -128,6 +144,7 @@ describe("RoomWidgetClient", () => {
128144 capabilities : ICapabilities ,
129145 sendContentLoaded : boolean | undefined = undefined ,
130146 userId ?: string ,
147+ useSlidingSync ?: boolean ,
131148 ) : Promise < void > => {
132149 const baseUrl = "https://example.org" ;
133150 client = createRoomWidgetClient (
@@ -139,7 +156,7 @@ describe("RoomWidgetClient", () => {
139156 ) ;
140157 expect ( widgetApi . start ) . toHaveBeenCalled ( ) ; // needs to have been called early in order to not miss messages
141158 widgetApi . emit ( "ready" ) ;
142- await client . startClient ( ) ;
159+ await client . startClient ( useSlidingSync ? { slidingSync : new SlidingSync ( "" , new Map ( ) , { } , client , 0 ) } : { } ) ;
143160 } ;
144161
145162 describe ( "events" , ( ) => {
@@ -668,10 +685,106 @@ describe("RoomWidgetClient", () => {
668685 detail : { data : { state : [ event ] } } ,
669686 } ) ,
670687 ) ;
688+ // Allow the getClientVersions promise to resolve
689+ await flushPromises ( ) ;
671690 // It should now have changed the room state
672691 expect ( room ! . currentState . getStateEvents ( "org.example.foo" , "bar" ) ?. getEffectiveEvent ( ) ) . toEqual ( event ) ;
673692 } ) ;
674693
694+ describe ( "without support for update_state" , ( ) => {
695+ beforeEach ( ( ) => {
696+ widgetApi = new MockWidgetApi ( [ ] ) as unknown as MockedObject < WidgetApi > ;
697+ } ) ;
698+
699+ it ( "receives" , async ( ) => {
700+ await makeClient ( { receiveState : [ { eventType : "org.example.foo" , stateKey : "bar" } ] } ) ;
701+ expect ( widgetApi . requestCapabilityForRoomTimeline ) . toHaveBeenCalledWith ( "!1:example.org" ) ;
702+ expect ( widgetApi . requestCapabilityToReceiveState ) . toHaveBeenCalledWith ( "org.example.foo" , "bar" ) ;
703+
704+ const emittedEvent = new Promise < MatrixEvent > ( ( resolve ) => client . once ( ClientEvent . Event , resolve ) ) ;
705+ const emittedSync = new Promise < SyncState > ( ( resolve ) => client . once ( ClientEvent . Sync , resolve ) ) ;
706+ widgetApi . emit (
707+ `action:${ WidgetApiToWidgetAction . SendEvent } ` ,
708+ new CustomEvent ( `action:${ WidgetApiToWidgetAction . SendEvent } ` , { detail : { data : event } } ) ,
709+ ) ;
710+
711+ // The client should've emitted about the received event
712+ expect ( ( await emittedEvent ) . getEffectiveEvent ( ) ) . toEqual ( event ) ;
713+ expect ( await emittedSync ) . toEqual ( SyncState . Syncing ) ;
714+ // It should've also inserted the event into the room object
715+ const room = client . getRoom ( "!1:example.org" ) ;
716+ expect ( room ) . not . toBeNull ( ) ;
717+ expect ( room ! . currentState . getStateEvents ( "org.example.foo" , "bar" ) ?. getEffectiveEvent ( ) ) . toEqual ( event ) ;
718+ } ) ;
719+
720+ it ( "does not receive with sliding sync (update_state is needed for sliding sync)" , async ( ) => {
721+ await makeClient (
722+ { receiveState : [ { eventType : "org.example.foo" , stateKey : "bar" } ] } ,
723+ undefined ,
724+ undefined ,
725+ true ,
726+ ) ;
727+ expect ( widgetApi . requestCapabilityForRoomTimeline ) . toHaveBeenCalledWith ( "!1:example.org" ) ;
728+ expect ( widgetApi . requestCapabilityToReceiveState ) . toHaveBeenCalledWith ( "org.example.foo" , "bar" ) ;
729+
730+ const emittedEvent = new Promise < MatrixEvent > ( ( resolve ) => client . once ( ClientEvent . Event , resolve ) ) ;
731+ const emittedSync = new Promise < SyncState > ( ( resolve ) => client . once ( ClientEvent . Sync , resolve ) ) ;
732+ const logSpy = jest . spyOn ( logger , "error" ) ;
733+ widgetApi . emit (
734+ `action:${ WidgetApiToWidgetAction . SendEvent } ` ,
735+ new CustomEvent ( `action:${ WidgetApiToWidgetAction . SendEvent } ` , { detail : { data : event } } ) ,
736+ ) ;
737+
738+ // The client should've emitted about the received event
739+ expect ( ( await emittedEvent ) . getEffectiveEvent ( ) ) . toEqual ( event ) ;
740+ expect ( await emittedSync ) . toEqual ( SyncState . Syncing ) ;
741+
742+ // The incompatibility of sliding sync without update_state to get logged.
743+ expect ( logSpy ) . toHaveBeenCalledWith (
744+ "slididng sync cannot be used in widget mode if the client widget driver does not support the version: 'org.matrix.msc2762_update_state'" ,
745+ ) ;
746+ // It should not have inserted the event into the room object
747+ const room = client . getRoom ( "!1:example.org" ) ;
748+ expect ( room ) . not . toBeNull ( ) ;
749+ expect ( room ! . currentState . getStateEvents ( "org.example.foo" , "bar" ) ) . toEqual ( null ) ;
750+ } ) ;
751+
752+ it ( "backfills" , async ( ) => {
753+ widgetApi . readStateEvents . mockImplementation ( async ( eventType , limit , stateKey ) =>
754+ eventType === "org.example.foo" && ( limit ?? Infinity ) > 0 && stateKey === "bar"
755+ ? [ event as IRoomEvent ]
756+ : [ ] ,
757+ ) ;
758+
759+ await makeClient ( { receiveState : [ { eventType : "org.example.foo" , stateKey : "bar" } ] } ) ;
760+ expect ( widgetApi . requestCapabilityForRoomTimeline ) . toHaveBeenCalledWith ( "!1:example.org" ) ;
761+ expect ( widgetApi . requestCapabilityToReceiveState ) . toHaveBeenCalledWith ( "org.example.foo" , "bar" ) ;
762+
763+ const room = client . getRoom ( "!1:example.org" ) ;
764+ expect ( room ) . not . toBeNull ( ) ;
765+ expect ( room ! . currentState . getStateEvents ( "org.example.foo" , "bar" ) ?. getEffectiveEvent ( ) ) . toEqual ( event ) ;
766+ } ) ;
767+ it ( "backfills with sliding sync" , async ( ) => {
768+ widgetApi . readStateEvents . mockImplementation ( async ( eventType , limit , stateKey ) =>
769+ eventType === "org.example.foo" && ( limit ?? Infinity ) > 0 && stateKey === "bar"
770+ ? [ event as IRoomEvent ]
771+ : [ ] ,
772+ ) ;
773+ await makeClient (
774+ { receiveState : [ { eventType : "org.example.foo" , stateKey : "bar" } ] } ,
775+ undefined ,
776+ undefined ,
777+ true ,
778+ ) ;
779+ expect ( widgetApi . requestCapabilityForRoomTimeline ) . toHaveBeenCalledWith ( "!1:example.org" ) ;
780+ expect ( widgetApi . requestCapabilityToReceiveState ) . toHaveBeenCalledWith ( "org.example.foo" , "bar" ) ;
781+
782+ const room = client . getRoom ( "!1:example.org" ) ;
783+ expect ( room ) . not . toBeNull ( ) ;
784+ expect ( room ! . currentState . getStateEvents ( "org.example.foo" , "bar" ) ?. getEffectiveEvent ( ) ) . toEqual ( event ) ;
785+ } ) ;
786+ } ) ;
787+
675788 it ( "ignores state updates for other rooms" , async ( ) => {
676789 const init = makeClient ( { receiveState : [ { eventType : "org.example.foo" , stateKey : "bar" } ] } ) ;
677790 // Client needs to be told that the room state is loaded
0 commit comments