@@ -16,15 +16,20 @@ limitations under the License.
1616*/
1717
1818import React from "react" ;
19- import { render , screen } from "@testing-library/react" ;
19+ import { fireEvent , render , screen , act } from "@testing-library/react" ;
2020import userEvent from "@testing-library/user-event" ;
21+ import { mocked } from "jest-mock" ;
2122
2223import InteractiveAuthDialog from "../../../../src/components/views/dialogs/InteractiveAuthDialog" ;
23- import { flushPromises , getMockClientWithEventEmitter , unmockClientPeg } from "../../../test-utils" ;
24+ import { clearAllModals , flushPromises , getMockClientWithEventEmitter , unmockClientPeg } from "../../../test-utils" ;
2425
2526describe ( "InteractiveAuthDialog" , function ( ) {
27+ const homeserverUrl = "https://matrix.org" ;
28+ const authUrl = "https://auth.com" ;
2629 const mockClient = getMockClientWithEventEmitter ( {
2730 generateClientSecret : jest . fn ( ) . mockReturnValue ( "t35tcl1Ent5ECr3T" ) ,
31+ getFallbackAuthUrl : jest . fn ( ) . mockReturnValue ( authUrl ) ,
32+ getHomeserverUrl : jest . fn ( ) . mockReturnValue ( homeserverUrl ) ,
2833 } ) ;
2934
3035 const defaultProps = {
@@ -37,13 +42,15 @@ describe("InteractiveAuthDialog", function () {
3742 const getPasswordField = ( ) => screen . getByLabelText ( "Password" ) ;
3843 const getSubmitButton = ( ) => screen . getByRole ( "button" , { name : "Continue" } ) ;
3944
40- beforeEach ( function ( ) {
45+ beforeEach ( async function ( ) {
4146 jest . clearAllMocks ( ) ;
4247 mockClient . credentials = { userId : null } ;
48+ await clearAllModals ( ) ;
4349 } ) ;
4450
45- afterAll ( ( ) => {
51+ afterAll ( async ( ) => {
4652 unmockClientPeg ( ) ;
53+ await clearAllModals ( ) ;
4754 } ) ;
4855
4956 it ( "Should successfully complete a password flow" , async ( ) => {
@@ -94,4 +101,95 @@ describe("InteractiveAuthDialog", function () {
94101 expect ( onFinished ) . toHaveBeenCalledTimes ( 1 ) ;
95102 expect ( onFinished ) . toHaveBeenCalledWith ( true , { a : 1 } ) ;
96103 } ) ;
104+
105+ describe ( "SSO flow" , ( ) => {
106+ it ( "should close on cancel" , ( ) => {
107+ const onFinished = jest . fn ( ) ;
108+ const makeRequest = jest . fn ( ) . mockResolvedValue ( { a : 1 } ) ;
109+
110+ mockClient . credentials = { userId : "@user:id" } ;
111+ const authData = {
112+ session : "sess" ,
113+ flows : [ { stages : [ "m.login.sso" ] } ] ,
114+ } ;
115+
116+ renderComponent ( { makeRequest, onFinished, authData } ) ;
117+
118+ expect ( screen . getByText ( "To continue, use Single Sign On to prove your identity." ) ) . toBeInTheDocument ( ) ;
119+
120+ fireEvent . click ( screen . getByText ( "Cancel" ) ) ;
121+
122+ expect ( onFinished ) . toHaveBeenCalledWith ( false , null ) ;
123+ } ) ;
124+
125+ it ( "should complete an sso flow" , async ( ) => {
126+ jest . spyOn ( global . window , "addEventListener" ) ;
127+ // @ts -ignore
128+ jest . spyOn ( global . window , "open" ) . mockImplementation ( ( ) => { } ) ;
129+ const onFinished = jest . fn ( ) ;
130+ const successfulResult = { test : 1 } ;
131+ const makeRequest = jest
132+ . fn ( )
133+ . mockRejectedValueOnce ( { httpStatus : 401 , data : { flows : [ { stages : [ "m.login.sso" ] } ] } } )
134+ . mockResolvedValue ( successfulResult ) ;
135+
136+ mockClient . credentials = { userId : "@user:id" } ;
137+ const authData = {
138+ session : "sess" ,
139+ flows : [ { stages : [ "m.login.sso" ] } ] ,
140+ } ;
141+
142+ renderComponent ( { makeRequest, onFinished, authData } ) ;
143+
144+ await flushPromises ( ) ;
145+
146+ expect ( screen . getByText ( "To continue, use Single Sign On to prove your identity." ) ) . toBeInTheDocument ( ) ;
147+ fireEvent . click ( screen . getByText ( "Single Sign On" ) ) ;
148+
149+ // no we're on the sso auth screen
150+ expect ( screen . getByText ( "Click the button below to confirm your identity." ) ) . toBeInTheDocument ( ) ;
151+
152+ // launch sso
153+ fireEvent . click ( screen . getByText ( "Confirm" ) ) ;
154+ expect ( global . window . open ) . toHaveBeenCalledWith ( authUrl , "_blank" ) ;
155+
156+ const onWindowReceiveMessageCall = mocked ( window . addEventListener ) . mock . calls . find (
157+ ( args ) => args [ 0 ] === "message" ,
158+ ) ;
159+ expect ( onWindowReceiveMessageCall ) . toBeTruthy ( ) ;
160+ // get the handle from SSO auth component
161+ // so we can pretend sso auth was completed
162+ const onWindowReceiveMessage = onWindowReceiveMessageCall ! [ 1 ] ;
163+
164+ // complete sso successfully
165+ act ( ( ) => {
166+ // @ts -ignore
167+ onWindowReceiveMessage ( { data : "authDone" , origin : homeserverUrl } ) ;
168+ } ) ;
169+
170+ // expect(makeRequest).toHaveBeenCalledWith({ session: authData.session })
171+
172+ // spinner displayed
173+ expect ( screen . getByRole ( "progressbar" ) ) . toBeInTheDocument ( ) ;
174+ // cancel/confirm buttons hidden while request in progress
175+ expect ( screen . queryByText ( "Confirm" ) ) . not . toBeInTheDocument ( ) ;
176+
177+ await flushPromises ( ) ;
178+ await flushPromises ( ) ;
179+
180+ // nothing in progress
181+ expect ( screen . queryByRole ( "progressbar" ) ) . not . toBeInTheDocument ( ) ;
182+
183+ // auth completed, now make the request again with auth
184+ fireEvent . click ( screen . getByText ( "Confirm" ) ) ;
185+ // loading while making request
186+ expect ( screen . getByRole ( "progressbar" ) ) . toBeInTheDocument ( ) ;
187+
188+ expect ( makeRequest ) . toHaveBeenCalledTimes ( 2 ) ;
189+
190+ await flushPromises ( ) ;
191+
192+ expect ( onFinished ) . toHaveBeenCalledWith ( true , successfulResult ) ;
193+ } ) ;
194+ } ) ;
97195} ) ;
0 commit comments