87968: Manual fixes after NgRx migration

Selector typing

Restored pre-13 behaviour where mock stores were reset after every test.
The upgrade caused multiple tests to start failing; we could solve this
by adjusting all of these one by one but that would take some time.
(see https://ngrx.io/guide/migration/v13#testing-reset-mock-store)
This commit is contained in:
Yura Bondarenko
2022-03-15 09:45:15 +01:00
parent 8e4f1993bf
commit 563956c5df
4 changed files with 16 additions and 26 deletions

View File

@@ -67,7 +67,6 @@ export class AuthEffects {
* Authenticate user. * Authenticate user.
* @method authenticate * @method authenticate
*/ */
public authenticate$: Observable<Action> = createEffect(() => this.actions$.pipe( public authenticate$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(AuthActionTypes.AUTHENTICATE), ofType(AuthActionTypes.AUTHENTICATE),
switchMap((action: AuthenticateAction) => { switchMap((action: AuthenticateAction) => {
@@ -79,13 +78,11 @@ export class AuthEffects {
}) })
)); ));
public authenticateSuccess$: Observable<Action> = createEffect(() => this.actions$.pipe( public authenticateSuccess$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(AuthActionTypes.AUTHENTICATE_SUCCESS), ofType(AuthActionTypes.AUTHENTICATE_SUCCESS),
map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload)) map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload))
)); ));
public authenticated$: Observable<Action> = createEffect(() => this.actions$.pipe( public authenticated$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(AuthActionTypes.AUTHENTICATED), ofType(AuthActionTypes.AUTHENTICATED),
switchMap((action: AuthenticatedAction) => { switchMap((action: AuthenticatedAction) => {
@@ -95,7 +92,6 @@ export class AuthEffects {
}) })
)); ));
public authenticatedSuccess$: Observable<Action> = createEffect(() => this.actions$.pipe( public authenticatedSuccess$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(AuthActionTypes.AUTHENTICATED_SUCCESS), ofType(AuthActionTypes.AUTHENTICATED_SUCCESS),
tap((action: AuthenticatedSuccessAction) => this.authService.storeToken(action.payload.authToken)), tap((action: AuthenticatedSuccessAction) => this.authService.storeToken(action.payload.authToken)),
@@ -112,7 +108,6 @@ export class AuthEffects {
}) })
)); ));
public redirectAfterLoginSuccess$: Observable<Action> = createEffect(() => this.actions$.pipe( public redirectAfterLoginSuccess$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(AuthActionTypes.REDIRECT_AFTER_LOGIN_SUCCESS), ofType(AuthActionTypes.REDIRECT_AFTER_LOGIN_SUCCESS),
tap((action: RedirectAfterLoginSuccessAction) => { tap((action: RedirectAfterLoginSuccessAction) => {
@@ -122,13 +117,11 @@ export class AuthEffects {
), { dispatch: false }); ), { dispatch: false });
// It means "reacts to this action but don't send another" // It means "reacts to this action but don't send another"
public authenticatedError$: Observable<Action> = createEffect(() => this.actions$.pipe( public authenticatedError$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(AuthActionTypes.AUTHENTICATED_ERROR), ofType(AuthActionTypes.AUTHENTICATED_ERROR),
tap((action: LogOutSuccessAction) => this.authService.removeToken()) tap((action: LogOutSuccessAction) => this.authService.removeToken())
), { dispatch: false }); ), { dispatch: false });
public retrieveAuthenticatedEperson$: Observable<Action> = createEffect(() => this.actions$.pipe( public retrieveAuthenticatedEperson$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON), ofType(AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON),
switchMap((action: RetrieveAuthenticatedEpersonAction) => { switchMap((action: RetrieveAuthenticatedEpersonAction) => {
@@ -145,7 +138,6 @@ export class AuthEffects {
}) })
)); ));
public checkToken$: Observable<Action> = createEffect(() => this.actions$.pipe(ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN), public checkToken$: Observable<Action> = createEffect(() => this.actions$.pipe(ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN),
switchMap(() => { switchMap(() => {
return this.authService.hasValidAuthenticationToken().pipe( return this.authService.hasValidAuthenticationToken().pipe(
@@ -155,7 +147,6 @@ export class AuthEffects {
}) })
)); ));
public checkTokenCookie$: Observable<Action> = createEffect(() => this.actions$.pipe( public checkTokenCookie$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE), ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE),
switchMap(() => { switchMap(() => {
@@ -173,7 +164,6 @@ export class AuthEffects {
}) })
)); ));
public retrieveToken$: Observable<Action> = createEffect(() => this.actions$.pipe( public retrieveToken$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(AuthActionTypes.RETRIEVE_TOKEN), ofType(AuthActionTypes.RETRIEVE_TOKEN),
switchMap((action: AuthenticateAction) => { switchMap((action: AuthenticateAction) => {
@@ -185,7 +175,6 @@ export class AuthEffects {
}) })
)); ));
public refreshToken$: Observable<Action> = createEffect(() => this.actions$.pipe(ofType(AuthActionTypes.REFRESH_TOKEN), public refreshToken$: Observable<Action> = createEffect(() => this.actions$.pipe(ofType(AuthActionTypes.REFRESH_TOKEN),
switchMap((action: RefreshTokenAction) => { switchMap((action: RefreshTokenAction) => {
return this.authService.refreshAuthenticationToken(action.payload).pipe( return this.authService.refreshAuthenticationToken(action.payload).pipe(
@@ -196,7 +185,6 @@ export class AuthEffects {
)); ));
// It means "reacts to this action but don't send another" // It means "reacts to this action but don't send another"
public refreshTokenSuccess$: Observable<Action> = createEffect(() => this.actions$.pipe( public refreshTokenSuccess$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(AuthActionTypes.REFRESH_TOKEN_SUCCESS), ofType(AuthActionTypes.REFRESH_TOKEN_SUCCESS),
tap((action: RefreshTokenSuccessAction) => this.authService.replaceToken(action.payload)) tap((action: RefreshTokenSuccessAction) => this.authService.replaceToken(action.payload))
@@ -206,13 +194,12 @@ export class AuthEffects {
* When the store is rehydrated in the browser, * When the store is rehydrated in the browser,
* clear a possible invalid token or authentication errors * clear a possible invalid token or authentication errors
*/ */
public clearInvalidTokenOnRehydrate$: Observable<any> = createEffect(() => this.actions$.pipe( public clearInvalidTokenOnRehydrate$: Observable<any> = createEffect(() => this.actions$.pipe(
ofType(StoreActionTypes.REHYDRATE), ofType(StoreActionTypes.REHYDRATE),
switchMap(() => { switchMap(() => {
const isLoaded$ = this.store.pipe(select(isAuthenticatedLoaded)); const isLoaded$ = this.store.pipe(select(isAuthenticatedLoaded));
const authenticated$ = this.store.pipe(select(isAuthenticated)); const authenticated$ = this.store.pipe(select(isAuthenticated));
return observableCombineLatest(isLoaded$, authenticated$).pipe( return observableCombineLatest([isLoaded$, authenticated$]).pipe(
take(1), take(1),
filter(([loaded, authenticated]) => loaded && !authenticated), filter(([loaded, authenticated]) => loaded && !authenticated),
tap(() => this.authService.removeToken()), tap(() => this.authService.removeToken()),
@@ -230,7 +217,6 @@ export class AuthEffects {
tap(() => this.authorizationsService.invalidateAuthorizationsRequestCache()) tap(() => this.authorizationsService.invalidateAuthorizationsRequestCache())
), { dispatch: false }); ), { dispatch: false });
public logOut$: Observable<Action> = createEffect(() => this.actions$ public logOut$: Observable<Action> = createEffect(() => this.actions$
.pipe( .pipe(
ofType(AuthActionTypes.LOG_OUT), ofType(AuthActionTypes.LOG_OUT),
@@ -243,7 +229,6 @@ export class AuthEffects {
}) })
)); ));
public logOutSuccess$: Observable<Action> = createEffect(() => this.actions$ public logOutSuccess$: Observable<Action> = createEffect(() => this.actions$
.pipe(ofType(AuthActionTypes.LOG_OUT_SUCCESS), .pipe(ofType(AuthActionTypes.LOG_OUT_SUCCESS),
tap(() => this.authService.removeToken()), tap(() => this.authService.removeToken()),
@@ -251,7 +236,6 @@ export class AuthEffects {
tap(() => this.authService.refreshAfterLogout()) tap(() => this.authService.refreshAfterLogout())
), { dispatch: false }); ), { dispatch: false });
public redirectToLoginTokenExpired$: Observable<Action> = createEffect(() => this.actions$ public redirectToLoginTokenExpired$: Observable<Action> = createEffect(() => this.actions$
.pipe( .pipe(
ofType(AuthActionTypes.REDIRECT_TOKEN_EXPIRED), ofType(AuthActionTypes.REDIRECT_TOKEN_EXPIRED),
@@ -259,7 +243,6 @@ export class AuthEffects {
tap(() => this.authService.redirectToLoginWhenTokenExpired()) tap(() => this.authService.redirectToLoginWhenTokenExpired())
), { dispatch: false }); ), { dispatch: false });
public retrieveMethods$: Observable<Action> = createEffect(() => this.actions$ public retrieveMethods$: Observable<Action> = createEffect(() => this.actions$
.pipe( .pipe(
ofType(AuthActionTypes.RETRIEVE_AUTH_METHODS), ofType(AuthActionTypes.RETRIEVE_AUTH_METHODS),
@@ -278,7 +261,6 @@ export class AuthEffects {
* => Return the action to set the user as idle ({@link SetUserAsIdleAction}) * => Return the action to set the user as idle ({@link SetUserAsIdleAction})
* @method trackIdleness * @method trackIdleness
*/ */
public trackIdleness$: Observable<Action> = createEffect(() => this.actions$.pipe( public trackIdleness$: Observable<Action> = createEffect(() => this.actions$.pipe(
filter((action: Action) => !IDLE_TIMER_IGNORE_TYPES.includes(action.type)), filter((action: Action) => !IDLE_TIMER_IGNORE_TYPES.includes(action.type)),
// Using switchMap the effect will stop subscribing to the previous timer if a new action comes // Using switchMap the effect will stop subscribing to the previous timer if a new action comes

View File

@@ -1,4 +1,4 @@
import { createSelector } from '@ngrx/store'; import { createFeatureSelector, createSelector } from '@ngrx/store';
/** /**
* Every reducer module's default export is the reducer function itself. In * Every reducer module's default export is the reducer function itself. In
@@ -8,6 +8,8 @@ import { createSelector } from '@ngrx/store';
*/ */
import { AuthState } from './auth.reducer'; import { AuthState } from './auth.reducer';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import { CoreState } from '../core.reducers';
import { coreSelector } from '../core.selectors';
/** /**
* Returns the user state. * Returns the user state.
@@ -15,7 +17,7 @@ import { AppState } from '../../app.reducer';
* @param {AppState} state Top level state. * @param {AppState} state Top level state.
* @return {AuthState} * @return {AuthState}
*/ */
export const getAuthState = (state: any) => state.core.auth; export const getAuthState = createSelector(coreSelector, (state: CoreState) => state.auth);
/** /**
* Returns true if the user is authenticated. * Returns true if the user is authenticated.

View File

@@ -8,7 +8,7 @@ import { SubmissionObjectEntry, SubmissionSectionObject } from './objects/submis
* Export a function to return a subset of the state by key * Export a function to return a subset of the state by key
*/ */
export function keySelector<T, V>(parentSelector: Selector<any, any>, subState: string, key: string): MemoizedSelector<T, V> { export function keySelector<T, V>(parentSelector: Selector<any, any>, subState: string, key: string): MemoizedSelector<T, V> {
return createSelector(parentSelector, (state: T) => { return createSelector<T,unknown[],V>(parentSelector, (state: T) => {
if (hasValue(state) && hasValue(state[subState])) { if (hasValue(state) && hasValue(state[subState])) {
return state[subState][key]; return state[subState][key];
} else { } else {
@@ -21,7 +21,7 @@ export function keySelector<T, V>(parentSelector: Selector<any, any>, subState:
* Export a function to return a subset of the state * Export a function to return a subset of the state
*/ */
export function subStateSelector<T, V>(parentSelector: Selector<any, any>, subState: string): MemoizedSelector<T, V> { export function subStateSelector<T, V>(parentSelector: Selector<any, any>, subState: string): MemoizedSelector<T, V> {
return createSelector(parentSelector, (state: T) => { return createSelector<T,unknown[],V>(parentSelector, (state: T) => {
if (hasValue(state) && hasValue(state[subState])) { if (hasValue(state) && hasValue(state[subState])) {
return state[subState]; return state[subState];
} else { } else {

View File

@@ -2,6 +2,7 @@
import 'zone.js/testing'; import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing'; import { getTestBed } from '@angular/core/testing';
import { MockStore } from '@ngrx/store/testing';
import { import {
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting platformBrowserDynamicTesting
@@ -12,10 +13,15 @@ declare const require: any;
// First, initialize the Angular testing environment. // First, initialize the Angular testing environment.
getTestBed().initTestEnvironment( getTestBed().initTestEnvironment(
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting(), { platformBrowserDynamicTesting(),
teardown: { destroyAfterEach: false } { teardown: { destroyAfterEach: false } }
}
); );
// If store is mocked, reset state after each test (see https://ngrx.io/guide/migration/v13)
jasmine.getEnv().afterEach(() => {
getTestBed().inject(MockStore, null)?.resetSelectors();
});
// Then we find all the tests. // Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/); const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules. // And load the modules.