diff --git a/.changeset/light-zoos-raise.md b/.changeset/light-zoos-raise.md new file mode 100644 index 00000000000..48b798051af --- /dev/null +++ b/.changeset/light-zoos-raise.md @@ -0,0 +1,8 @@ +--- +"@aws-amplify/ui-react-core": patch +"@aws-amplify/ui": patch +"@aws-amplify/ui-vue": patch +"@aws-amplify/ui-angular": patch +--- + +chore(ui): fix defaultAuthHubListener unit tests, remove asynchronous declaration diff --git a/packages/angular/projects/ui-angular/src/lib/services/authenticator.service.ts b/packages/angular/projects/ui-angular/src/lib/services/authenticator.service.ts index e103aa8fb81..5c54fd3a212 100644 --- a/packages/angular/projects/ui-angular/src/lib/services/authenticator.service.ts +++ b/packages/angular/projects/ui-angular/src/lib/services/authenticator.service.ts @@ -220,8 +220,8 @@ export class AuthenticatorService implements OnDestroy { this._unsubscribeHub = listenToAuthHub( this._authService, - async (data, service) => { - await defaultAuthHubHandler(data, service, { onSignIn, onSignOut }); + (data, service) => { + defaultAuthHubHandler(data, service, { onSignIn, onSignOut }); this._hubSubject.next(); } ); diff --git a/packages/react-core/src/Authenticator/context/AuthenticatorProvider.tsx b/packages/react-core/src/Authenticator/context/AuthenticatorProvider.tsx index 093804a9234..011bc9979c3 100644 --- a/packages/react-core/src/Authenticator/context/AuthenticatorProvider.tsx +++ b/packages/react-core/src/Authenticator/context/AuthenticatorProvider.tsx @@ -16,8 +16,8 @@ type Options = Parameters[2]; const createHubHandler = (options: Options): AuthMachineHubHandler => - async (data, service) => { - await defaultAuthHubHandler(data, service, options); + (data, service) => { + defaultAuthHubHandler(data, service, options); }; export default function AuthenticatorProvider({ diff --git a/packages/ui/jest.config.ts b/packages/ui/jest.config.ts index 7a308b6b646..79d438d9522 100644 --- a/packages/ui/jest.config.ts +++ b/packages/ui/jest.config.ts @@ -11,10 +11,10 @@ const config: Config = { ], coverageThreshold: { global: { - branches: 50, - functions: 50, - lines: 50, - statements: 50, + branches: 77, + functions: 70, + lines: 87, + statements: 88, // @todo-migration: put back after fixing tests // branches: 80, // functions: 85, diff --git a/packages/ui/src/helpers/authenticator/__tests__/defaultAuthHubHandler.test.ts b/packages/ui/src/helpers/authenticator/__tests__/defaultAuthHubHandler.test.ts index f41643d3ab1..4307834a571 100644 --- a/packages/ui/src/helpers/authenticator/__tests__/defaultAuthHubHandler.test.ts +++ b/packages/ui/src/helpers/authenticator/__tests__/defaultAuthHubHandler.test.ts @@ -1,155 +1,93 @@ -import { defaultAuthHubHandler } from '../defaultAuthHubHandler'; +import { Hub } from 'aws-amplify/utils'; +import { + defaultAuthHubHandler, + listenToAuthHub, +} from '../defaultAuthHubHandler'; import { AuthInterpreter } from '../types'; -jest.mock('xstate/lib/waitFor', () => ({ - waitFor: jest.fn(), -})); - -jest.mock('../actor', () => ({ - getActorState: () => ({ matches: () => true }), -})); - -const authenticatedStateMachine = { - getSnapshot: () => ({ - // this is the state.matches function - matches: (state: string) => state === 'authenticated.idle', - }), - send: jest.fn(), -} as unknown as AuthInterpreter; - -const unauthenticatedStateMachine = { - getSnapshot: () => ({ - // this is the state.matches function - matches: (state: string) => state === 'signIn', - }), - send: jest.fn(), -} as unknown as AuthInterpreter; - -const authSendSpy = jest.spyOn(authenticatedStateMachine, 'send'); -const unauthSendSpy = jest.spyOn(unauthenticatedStateMachine, 'send'); - const onSignIn = jest.fn(); const onSignOut = jest.fn(); +const service = { send: jest.fn() } as unknown as AuthInterpreter; describe('defaultAuthHubHandler', () => { beforeEach(() => { - authSendSpy.mockClear(); - unauthSendSpy.mockClear(); - onSignIn.mockClear(); - onSignOut.mockClear(); + jest.clearAllMocks(); }); - // @todo-migration probably remove token refresh event handling - it.skip('responds to token refresh event when state is authenticated', async () => { - await defaultAuthHubHandler( - { channel: 'auth', payload: { event: 'tokenRefresh' } }, - authenticatedStateMachine - ); - expect(authSendSpy).toHaveBeenCalledWith('TOKEN_REFRESH'); - }); + it.each(['tokenRefresh_failure', 'signedOut'])( + 'handles a %s event as expected', + (event) => { + defaultAuthHubHandler({ channel: 'auth', payload: { event } }, service); + expect(service.send).toHaveBeenCalledTimes(1); + expect(service.send).toHaveBeenCalledWith('SIGN_OUT'); + } + ); - it('ignores token refresh event when state is unauthenticated', async () => { - await defaultAuthHubHandler( - { channel: 'auth', payload: { event: 'tokenRefresh' } }, - unauthenticatedStateMachine + it('handles a signInWithRedirect event as expected', () => { + defaultAuthHubHandler( + { channel: 'auth', payload: { event: 'signInWithRedirect' } }, + service ); - expect(unauthSendSpy).not.toHaveBeenCalled(); + expect(service.send).toHaveBeenCalledTimes(1); + expect(service.send).toHaveBeenCalledWith('SIGN_IN_WITH_REDIRECT'); }); - // @todo-migration - // expect(jest.fn()).toHaveBeenCalledWith(...expected) - // Expected: "SIGN_OUT" - // Number of calls: 0 - it.skip('responds to signOut event when state is authenticated', async () => { - await defaultAuthHubHandler( - { channel: 'auth', payload: { event: 'signOut' } }, - authenticatedStateMachine + it('calls onSignOut callabck on signedOut event if provided', () => { + defaultAuthHubHandler( + { channel: 'auth', payload: { event: 'signedOut' } }, + service, + { onSignOut } ); - expect(authSendSpy).toHaveBeenCalledWith('SIGN_OUT'); + expect(onSignOut).toHaveBeenCalledTimes(1); }); - it('ignores signOut event when state is unauthenticated', async () => { - await defaultAuthHubHandler( - { channel: 'auth', payload: { event: 'signOut' } }, - unauthenticatedStateMachine + it('does not call onSignOut callback on tokenRefreh_failure event if provided', () => { + defaultAuthHubHandler( + { channel: 'auth', payload: { event: 'tokenRefreh_failure' } }, + service, + { onSignOut } ); - expect(unauthSendSpy).not.toHaveBeenCalled(); + expect(onSignOut).not.toHaveBeenCalled(); }); - it('signs user out when token refresh failed in authenticated state', async () => { - await defaultAuthHubHandler( - { channel: 'auth', payload: { event: 'tokenRefresh_failure' } }, - authenticatedStateMachine + it('calls onSignIn callabck on signedIn event if provided', () => { + defaultAuthHubHandler( + { channel: 'auth', payload: { event: 'signedIn' } }, + service, + { onSignIn } ); - expect(authSendSpy).toHaveBeenCalledWith('SIGN_OUT'); + expect(onSignIn).toHaveBeenCalledTimes(1); }); +}); - // @todo-migration potentially remove - it.skip('ignores token refresh failure event when state is unauthenticated', async () => { - await defaultAuthHubHandler( - { channel: 'auth', payload: { event: 'tokenRefresh_failure' } }, - unauthenticatedStateMachine - ); - expect(unauthSendSpy).not.toHaveBeenCalled(); - }); +describe('listenToAuthHub', () => { + it('creates a Hub listener', () => { + // add empty mockImplementation to prevent logging "auth channel" warning output to the console + jest.spyOn(console, 'warn').mockImplementation(); - // @todo-migration - // expect(jest.fn()).toHaveBeenCalledWith(...expected) - // Expected: {"type": "AUTO_SIGN_IN"} - // Number of calls: 0 - it.skip('responds to autoSignIn event when state is unauthenticated', async () => { - await defaultAuthHubHandler( - { channel: 'auth', payload: { event: 'autoSignIn' } }, - unauthenticatedStateMachine - ); - expect(unauthSendSpy).toHaveBeenCalledWith({ type: 'AUTO_SIGN_IN' }); - }); + const hubListenSpy = jest.spyOn(Hub, 'listen'); + const handler = jest.fn(); - it('ignores autoSignIn event when state is authenticated', async () => { - await defaultAuthHubHandler( - { channel: 'auth', payload: { event: 'autoSignIn' } }, - authenticatedStateMachine - ); - expect(unauthSendSpy).not.toHaveBeenCalled(); - }); + listenToAuthHub(service, handler); - // @todo-migration - // expect(jest.fn()).toHaveBeenCalledWith(...expected) - // Expected: {"type": "AUTO_SIGN_IN_FAILURE"} - // Number of calls: 0 - it.skip('responds to autoSignIn_failure event', async () => { - await defaultAuthHubHandler( - { channel: 'auth', payload: { event: 'autoSignIn_failure' } }, - unauthenticatedStateMachine + expect(hubListenSpy).toHaveBeenCalledTimes(1); + expect(hubListenSpy).toHaveBeenCalledWith( + 'auth', + expect.any(Function), + 'authenticator-hub-handler' ); - expect(unauthSendSpy).toHaveBeenCalledWith({ - type: 'AUTO_SIGN_IN_FAILURE', - }); - }); - - // @todo-migration - // Expected number of calls: 1 - // Received number of calls: 0 - it.skip.each(['signIn', 'signOut'])( - 'calls the %s event handler as expected', - async (event) => { - const handler = event === 'signIn' ? onSignIn : onSignOut; - const handlerKey = event === 'signIn' ? 'onSignIn' : 'onSignOut'; - await defaultAuthHubHandler( - { channel: 'auth', payload: { event } }, - authenticatedStateMachine, - { [handlerKey]: handler } - ); - expect(handler).toHaveBeenCalledTimes(1); - } - ); - it("doesn't break when unsupported event is passed", async () => { - const spyError = jest.spyOn(console, 'error'); - await defaultAuthHubHandler( - { channel: 'auth', payload: { event: 'unsupported' } }, - unauthenticatedStateMachine + Hub.dispatch('auth', { event: 'signedIn' }, 'authenticator-hub-handler'); + + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith( + { + channel: 'auth', + patternInfo: [], + payload: { event: 'signedIn' }, + source: 'authenticator-hub-handler', + }, + { send: expect.any(Function) } ); - expect(spyError).not.toHaveBeenCalled(); }); }); diff --git a/packages/ui/src/helpers/authenticator/defaultAuthHubHandler.ts b/packages/ui/src/helpers/authenticator/defaultAuthHubHandler.ts index 0e3ee7547b9..94066919bf5 100644 --- a/packages/ui/src/helpers/authenticator/defaultAuthHubHandler.ts +++ b/packages/ui/src/helpers/authenticator/defaultAuthHubHandler.ts @@ -8,7 +8,7 @@ import { AuthInterpreter, AuthMachineHubHandler } from './types'; * Handles Amplify JS Auth hub events, by forwarding hub events as appropriate * xstate events. */ -export const defaultAuthHubHandler: AuthMachineHubHandler = async ( +export const defaultAuthHubHandler: AuthMachineHubHandler = ( { payload }, service, options @@ -42,29 +42,19 @@ export const defaultAuthHubHandler: AuthMachineHubHandler = async ( } }; -type HubHandler = Parameters[1]; -const getHubEventHandler = - (service: AuthInterpreter, handler: AuthMachineHubHandler): HubHandler => - (data) => { - handler(data, service); - }; - /** * Listens to external auth Hub events and sends corresponding event to - * the `authService` of interest - * - * @param send - `send` function associated with the `authService` of interest + * the `service.send` of interest * + * @param service - contains state machine `send` function + * @param handler - auth event handler * @returns function that unsubscribes to the hub evenmt */ export const listenToAuthHub = ( service: AuthInterpreter, - // angular passes its own `handler` param handler: AuthMachineHubHandler = defaultAuthHubHandler ) => { - return Hub.listen( - 'auth', - getHubEventHandler(service, handler), - 'authenticator-hub-handler' - ); + const eventHandler: Parameters[1] = (data) => + handler(data, service); + return Hub.listen('auth', eventHandler, 'authenticator-hub-handler'); }; diff --git a/packages/ui/src/helpers/authenticator/types.ts b/packages/ui/src/helpers/authenticator/types.ts index e510f9987e8..61b0b375195 100644 --- a/packages/ui/src/helpers/authenticator/types.ts +++ b/packages/ui/src/helpers/authenticator/types.ts @@ -37,4 +37,4 @@ export type AuthMachineHubHandler = ( onSignIn?: (user: AuthUser) => void; onSignOut?: () => void; } -) => Promise; +) => void; diff --git a/packages/vue/src/composables/useAuth.ts b/packages/vue/src/composables/useAuth.ts index 8eb75b202ce..e3580664f3a 100644 --- a/packages/vue/src/composables/useAuth.ts +++ b/packages/vue/src/composables/useAuth.ts @@ -38,9 +38,9 @@ export const useAuth = createSharedComposable((): UseAuth => { authStatus.value = 'unauthenticated'; }; - const unsubscribeHub = listenToAuthHub(service, async (data, service) => { - await defaultAuthHubHandler(data, service, { onSignIn, onSignOut }); - }); + const unsubscribeHub = listenToAuthHub(service, (data, service) => + defaultAuthHubHandler(data, service, { onSignIn, onSignOut }) + ); getCurrentUser() .then(() => {