@@ -31,6 +31,9 @@ import {
31
31
type IOpenIDCredentials ,
32
32
type ISendEventFromWidgetResponseData ,
33
33
WidgetApiResponseError ,
34
+ UnstableApiVersion ,
35
+ type ApiVersion ,
36
+ type IRoomEvent ,
34
37
} from "matrix-widget-api" ;
35
38
36
39
import { createRoomWidgetClient , MatrixError , MsgType , UpdateDelayedEventAction } from "../../src/matrix" ;
@@ -40,6 +43,9 @@ import { type ICapabilities, type RoomWidgetClient } from "../../src/embedded";
40
43
import { MatrixEvent } from "../../src/models/event" ;
41
44
import { type ToDeviceBatch } from "../../src/models/ToDeviceMessage" ;
42
45
import { sleep } from "../../src/utils" ;
46
+ import { SlidingSync } from "../../src/sliding-sync" ;
47
+ import { logger } from "../../src/logger" ;
48
+ import { flushPromises } from "../test-utils/flushPromises" ;
43
49
44
50
const testOIDCToken = {
45
51
access_token : "12345678" ,
@@ -49,6 +55,7 @@ const testOIDCToken = {
49
55
} ;
50
56
class MockWidgetApi extends EventEmitter {
51
57
public start = jest . fn ( ) . mockResolvedValue ( undefined ) ;
58
+ public getClientVersions = jest . fn ( ) ;
52
59
public requestCapability = jest . fn ( ) . mockResolvedValue ( undefined ) ;
53
60
public requestCapabilities = jest . fn ( ) . mockResolvedValue ( undefined ) ;
54
61
public requestCapabilityForRoomTimeline = jest . fn ( ) . mockResolvedValue ( undefined ) ;
@@ -96,6 +103,15 @@ class MockWidgetApi extends EventEmitter {
96
103
send : jest . fn ( ) ,
97
104
sendComplete : jest . fn ( ) ,
98
105
} ;
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
+ }
99
115
}
100
116
101
117
declare module "../../src/types" {
@@ -117,7 +133,7 @@ describe("RoomWidgetClient", () => {
117
133
let client : MatrixClient ;
118
134
119
135
beforeEach ( ( ) => {
120
- widgetApi = new MockWidgetApi ( ) as unknown as MockedObject < WidgetApi > ;
136
+ widgetApi = new MockWidgetApi ( [ UnstableApiVersion . MSC2762_UPDATE_STATE ] ) as unknown as MockedObject < WidgetApi > ;
121
137
} ) ;
122
138
123
139
afterEach ( ( ) => {
@@ -128,6 +144,7 @@ describe("RoomWidgetClient", () => {
128
144
capabilities : ICapabilities ,
129
145
sendContentLoaded : boolean | undefined = undefined ,
130
146
userId ?: string ,
147
+ useSlidingSync ?: boolean ,
131
148
) : Promise < void > => {
132
149
const baseUrl = "https://example.org" ;
133
150
client = createRoomWidgetClient (
@@ -139,7 +156,7 @@ describe("RoomWidgetClient", () => {
139
156
) ;
140
157
expect ( widgetApi . start ) . toHaveBeenCalled ( ) ; // needs to have been called early in order to not miss messages
141
158
widgetApi . emit ( "ready" ) ;
142
- await client . startClient ( ) ;
159
+ await client . startClient ( useSlidingSync ? { slidingSync : new SlidingSync ( "" , new Map ( ) , { } , client , 0 ) } : { } ) ;
143
160
} ;
144
161
145
162
describe ( "events" , ( ) => {
@@ -668,10 +685,106 @@ describe("RoomWidgetClient", () => {
668
685
detail : { data : { state : [ event ] } } ,
669
686
} ) ,
670
687
) ;
688
+ // Allow the getClientVersions promise to resolve
689
+ await flushPromises ( ) ;
671
690
// It should now have changed the room state
672
691
expect ( room ! . currentState . getStateEvents ( "org.example.foo" , "bar" ) ?. getEffectiveEvent ( ) ) . toEqual ( event ) ;
673
692
} ) ;
674
693
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
+
675
788
it ( "ignores state updates for other rooms" , async ( ) => {
676
789
const init = makeClient ( { receiveState : [ { eventType : "org.example.foo" , stateKey : "bar" } ] } ) ;
677
790
// Client needs to be told that the room state is loaded
0 commit comments