From 8f2750a5de115324755ce0a8aa2dfe5537266a97 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Mon, 15 Mar 2021 21:42:22 -0400 Subject: [PATCH] Added loading the scan types via the message system [#643] --- .../controller/comic/ScanTypeController.java | 26 ++-- .../ComiXedWebSocketAuthenticationConfig.java | 18 +++ .../comic/ScanTypeControllerTest.java | 19 ++- .../app/library/actions/scan-type.actions.ts | 27 ++++ .../library/effects/scan-type.effects.spec.ts | 52 +++++++ .../app/library/effects/scan-type.effects.ts | 29 ++++ comixed-web/src/app/library/index.ts | 9 +- .../src/app/library/library.constants.ts | 4 + comixed-web/src/app/library/library.module.ts | 9 +- .../reducers/scan-type.reducer.spec.ts | 71 ++++++++++ .../app/library/reducers/scan-type.reducer.ts | 48 +++++++ .../selectors/scan-type.selectors.spec.ts | 47 +++++++ .../library/selectors/scan-type.selectors.ts | 27 ++++ .../services/scan-type.service.spec.ts | 130 ++++++++++++++++++ .../app/library/services/scan-type.service.ts | 63 +++++++++ 15 files changed, 556 insertions(+), 23 deletions(-) create mode 100644 comixed-web/src/app/library/actions/scan-type.actions.ts create mode 100644 comixed-web/src/app/library/effects/scan-type.effects.spec.ts create mode 100644 comixed-web/src/app/library/effects/scan-type.effects.ts create mode 100644 comixed-web/src/app/library/reducers/scan-type.reducer.spec.ts create mode 100644 comixed-web/src/app/library/reducers/scan-type.reducer.ts create mode 100644 comixed-web/src/app/library/selectors/scan-type.selectors.spec.ts create mode 100644 comixed-web/src/app/library/selectors/scan-type.selectors.ts create mode 100644 comixed-web/src/app/library/services/scan-type.service.spec.ts create mode 100644 comixed-web/src/app/library/services/scan-type.service.ts diff --git a/comixed-rest-api/src/main/java/org/comixedproject/controller/comic/ScanTypeController.java b/comixed-rest-api/src/main/java/org/comixedproject/controller/comic/ScanTypeController.java index d89c5f48c0..4db2a41cc4 100644 --- a/comixed-rest-api/src/main/java/org/comixedproject/controller/comic/ScanTypeController.java +++ b/comixed-rest-api/src/main/java/org/comixedproject/controller/comic/ScanTypeController.java @@ -18,14 +18,11 @@ package org.comixedproject.controller.comic; -import java.util.List; import lombok.extern.log4j.Log4j2; -import org.comixedproject.auditlog.AuditableEndpoint; -import org.comixedproject.model.comic.ScanType; import org.comixedproject.service.comic.ScanTypeService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.web.bind.annotation.RestController; /** @@ -37,17 +34,18 @@ @RestController @Log4j2 public class ScanTypeController { + private static final String LOAD_SCAN_TYPES = "load.scan-types"; + public static final String ADD_SCAN_TYPE_QUEUE = "/topic/scan-type.add"; + @Autowired private ScanTypeService scanTypeService; + @Autowired private SimpMessagingTemplate messagingTemplate; - /** - * Retrieves the list of all scan types. - * - * @return the scan type list - */ - @GetMapping(value = "/api/comics/scantypes", produces = MediaType.APPLICATION_JSON_VALUE) - @AuditableEndpoint - public List getScanTypes() { + /** Retrieves the list of all scan types and publishes them. */ + @MessageMapping(LOAD_SCAN_TYPES) + public void getScanTypes() { log.info("Getting all scan types"); - return this.scanTypeService.getAll(); + this.scanTypeService + .getAll() + .forEach(scanType -> this.messagingTemplate.convertAndSend(ADD_SCAN_TYPE_QUEUE, scanType)); } } diff --git a/comixed-rest-api/src/main/java/org/comixedproject/messaging/ComiXedWebSocketAuthenticationConfig.java b/comixed-rest-api/src/main/java/org/comixedproject/messaging/ComiXedWebSocketAuthenticationConfig.java index 0eb79193ae..87419abf0d 100644 --- a/comixed-rest-api/src/main/java/org/comixedproject/messaging/ComiXedWebSocketAuthenticationConfig.java +++ b/comixed-rest-api/src/main/java/org/comixedproject/messaging/ComiXedWebSocketAuthenticationConfig.java @@ -1,3 +1,21 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2021, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + package org.comixedproject.messaging; import lombok.extern.log4j.Log4j2; diff --git a/comixed-rest-api/src/test/java/org/comixedproject/controller/comic/ScanTypeControllerTest.java b/comixed-rest-api/src/test/java/org/comixedproject/controller/comic/ScanTypeControllerTest.java index 7dbb7d2aee..922d5a5012 100644 --- a/comixed-rest-api/src/test/java/org/comixedproject/controller/comic/ScanTypeControllerTest.java +++ b/comixed-rest-api/src/test/java/org/comixedproject/controller/comic/ScanTypeControllerTest.java @@ -18,9 +18,9 @@ package org.comixedproject.controller.comic; -import static junit.framework.TestCase.assertNotNull; -import static junit.framework.TestCase.assertSame; +import static org.comixedproject.controller.comic.ScanTypeController.ADD_SCAN_TYPE_QUEUE; +import java.util.ArrayList; import java.util.List; import org.comixedproject.model.comic.ScanType; import org.comixedproject.service.comic.ScanTypeService; @@ -30,22 +30,27 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.messaging.simp.SimpMessagingTemplate; @RunWith(MockitoJUnitRunner.class) public class ScanTypeControllerTest { @InjectMocks private ScanTypeController controller; @Mock private ScanTypeService scanTypeService; - @Mock private List scanTypeList; + @Mock private SimpMessagingTemplate messagingTemplate; + @Mock private ScanType scanType; + + private List scanTypeList = new ArrayList(); @Test public void testGetScanTypes() { - Mockito.when(scanTypeService.getAll()).thenReturn(scanTypeList); + scanTypeList.add(scanType); - final List result = controller.getScanTypes(); + Mockito.when(scanTypeService.getAll()).thenReturn(scanTypeList); - assertNotNull(result); - assertSame(scanTypeList, result); + controller.getScanTypes(); Mockito.verify(scanTypeService, Mockito.times(1)).getAll(); + Mockito.verify(messagingTemplate, Mockito.times(scanTypeList.size())) + .convertAndSend(ADD_SCAN_TYPE_QUEUE, scanType); } } diff --git a/comixed-web/src/app/library/actions/scan-type.actions.ts b/comixed-web/src/app/library/actions/scan-type.actions.ts new file mode 100644 index 0000000000..aa66227699 --- /dev/null +++ b/comixed-web/src/app/library/actions/scan-type.actions.ts @@ -0,0 +1,27 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2021, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { createAction, props } from '@ngrx/store'; +import { ScanType } from '@app/library'; + +export const loadScanTypes = createAction('[Scan Type] Load all scan types'); + +export const addScanType = createAction( + '[Scan Type] A scan type was received', + props<{ scanType: ScanType }>() +); diff --git a/comixed-web/src/app/library/effects/scan-type.effects.spec.ts b/comixed-web/src/app/library/effects/scan-type.effects.spec.ts new file mode 100644 index 0000000000..828276f995 --- /dev/null +++ b/comixed-web/src/app/library/effects/scan-type.effects.spec.ts @@ -0,0 +1,52 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2021, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { TestBed } from '@angular/core/testing'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { Observable } from 'rxjs'; + +import { ScanTypeEffects } from './scan-type.effects'; +import { ScanTypeService } from '@app/library/services/scan-type.service'; + +describe('ScanTypeEffects', () => { + const actions$: Observable = null; + let effects: ScanTypeEffects; + let scanTypeService: jasmine.SpyObj; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + ScanTypeEffects, + provideMockActions(() => actions$), + { + provide: ScanTypeService, + useValue: {} + } + ] + }); + + effects = TestBed.inject(ScanTypeEffects); + scanTypeService = TestBed.inject( + ScanTypeService + ) as jasmine.SpyObj; + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); +}); diff --git a/comixed-web/src/app/library/effects/scan-type.effects.ts b/comixed-web/src/app/library/effects/scan-type.effects.ts new file mode 100644 index 0000000000..e641bd61ae --- /dev/null +++ b/comixed-web/src/app/library/effects/scan-type.effects.ts @@ -0,0 +1,29 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2021, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { Injectable } from '@angular/core'; +import { Actions } from '@ngrx/effects'; +import { ScanTypeService } from '@app/library/services/scan-type.service'; + +@Injectable() +export class ScanTypeEffects { + constructor( + private actions$: Actions, + private scanTypeService: ScanTypeService + ) {} +} diff --git a/comixed-web/src/app/library/index.ts b/comixed-web/src/app/library/index.ts index d08ceb1c91..d93463720c 100644 --- a/comixed-web/src/app/library/index.ts +++ b/comixed-web/src/app/library/index.ts @@ -44,6 +44,11 @@ import { BlockedPageState, reducer as blockedPageReducer } from '@app/library/reducers/blocked-page.reducer'; +import { + reducer as scanTypeReducer, + SCAN_TYPE_FEATURE_KEY, + ScanTypeState +} from '@app/library/reducers/scan-type.reducer'; export { Comic } from '@app/library/models/comic'; export { ComicCredit } from '@app/library/models/comic-credit'; @@ -70,6 +75,7 @@ export interface LibraryModuleState { [LIBRARY_FEATURE_KEY]: LibraryState; [SCRAPING_FEATURE_KEY]: ScrapingState; [BLOCKED_PAGE_FEATURE_KEY]: BlockedPageState; + [SCAN_TYPE_FEATURE_KEY]: ScanTypeState; } export type ModuleState = LibraryModuleState; @@ -80,5 +86,6 @@ export const reducers: ActionReducerMap = { [COMIC_IMPORT_FEATURE_KEY]: libraryImportReducer, [LIBRARY_FEATURE_KEY]: libraryReducer, [SCRAPING_FEATURE_KEY]: scrapingReducer, - [BLOCKED_PAGE_FEATURE_KEY]: blockedPageReducer + [BLOCKED_PAGE_FEATURE_KEY]: blockedPageReducer, + [SCAN_TYPE_FEATURE_KEY]: scanTypeReducer }; diff --git a/comixed-web/src/app/library/library.constants.ts b/comixed-web/src/app/library/library.constants.ts index 1538247356..ae36db2fe4 100644 --- a/comixed-web/src/app/library/library.constants.ts +++ b/comixed-web/src/app/library/library.constants.ts @@ -69,3 +69,7 @@ export const PAGE_SIZE_DEFAULT = 400; export const PAGINATION_OPTIONS = [10, 25, 50, 100]; export const PAGINATION_PREFERENCE = 'preference.pagination'; export const PAGINATION_DEFAULT = PAGINATION_OPTIONS[0]; + +// messaging +export const LOAD_SCAN_TYPES_MESSAGE = '/comixed/load.scan-types'; +export const SCAN_TYPE_ADD_QUEUE = '/topic/scan-type.add'; diff --git a/comixed-web/src/app/library/library.module.ts b/comixed-web/src/app/library/library.module.ts index 7e8bdd921a..02398e4388 100644 --- a/comixed-web/src/app/library/library.module.ts +++ b/comixed-web/src/app/library/library.module.ts @@ -99,6 +99,11 @@ import { BlockedPageEffects } from '@app/library/effects/blocked-page.effects'; import { ComicPageComponent } from '@app/library/components/comic-page/comic-page.component'; import { ComicDetailCardComponent } from '@app/library/components/comic-detail-card/comic-detail-card.component'; import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { + SCAN_TYPE_FEATURE_KEY, + reducer as scanTypeReducer +} from '@app/library/reducers/scan-type.reducer'; +import { ScanTypeEffects } from '@app/library/effects/scan-type.effects'; @NgModule({ declarations: [ @@ -144,12 +149,14 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; StoreModule.forFeature(LIBRARY_FEATURE_KEY, libraryReducer), StoreModule.forFeature(SCRAPING_FEATURE_KEY, scrapingReducer), StoreModule.forFeature(BLOCKED_PAGE_FEATURE_KEY, blockedPageReducer), + StoreModule.forFeature(SCAN_TYPE_FEATURE_KEY, scanTypeReducer), EffectsModule.forFeature([ DisplayEffects, ComicImportEffects, LibraryEffects, ScrapingEffects, - BlockedPageEffects + BlockedPageEffects, + ScanTypeEffects ]), MatInputModule, MatSelectModule, diff --git a/comixed-web/src/app/library/reducers/scan-type.reducer.spec.ts b/comixed-web/src/app/library/reducers/scan-type.reducer.spec.ts new file mode 100644 index 0000000000..3ef2bef02d --- /dev/null +++ b/comixed-web/src/app/library/reducers/scan-type.reducer.spec.ts @@ -0,0 +1,71 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2021, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { initialState, reducer, ScanTypeState } from './scan-type.reducer'; +import { SCAN_TYPE_1 } from '@app/library/library.fixtures'; +import { + addScanType, + loadScanTypes +} from '@app/library/actions/scan-type.actions'; + +describe('ScanType Reducer', () => { + const SCAN_TYPE = SCAN_TYPE_1; + + let state: ScanTypeState; + + beforeEach(() => { + state = { ...initialState }; + }); + + describe('the initial state', () => { + beforeEach(() => { + state = reducer({ ...state }, {} as any); + }); + + it('has no scan types', () => { + expect(state.types).toEqual([]); + }); + }); + + describe('loading the scan types', () => { + beforeEach(() => { + state = reducer({ ...state, initialized: true }, loadScanTypes()); + }); + + it('clears the initialized flag', () => { + expect(state.initialized).toBeFalse(); + }); + }); + + describe('receiving a scan type', () => { + beforeEach(() => { + state = reducer( + { ...state, initialized: false, types: [] }, + addScanType({ scanType: SCAN_TYPE }) + ); + }); + + it('sets the initialized flag', () => { + expect(state.initialized).toBeTrue(); + }); + + it('adds the scan type', () => { + expect(state.types).toContain(SCAN_TYPE); + }); + }); +}); diff --git a/comixed-web/src/app/library/reducers/scan-type.reducer.ts b/comixed-web/src/app/library/reducers/scan-type.reducer.ts new file mode 100644 index 0000000000..9a034a1332 --- /dev/null +++ b/comixed-web/src/app/library/reducers/scan-type.reducer.ts @@ -0,0 +1,48 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2021, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { createReducer, on } from '@ngrx/store'; +import { addScanType, loadScanTypes } from '../actions/scan-type.actions'; +import { ScanType } from '@app/library'; + +export const SCAN_TYPE_FEATURE_KEY = 'scan_type_state'; + +export interface ScanTypeState { + initialized: boolean; + types: ScanType[]; +} + +export const initialState: ScanTypeState = { + initialized: false, + types: [] +}; + +export const reducer = createReducer( + initialState, + + on(loadScanTypes, state => ({ ...state, initialized: false })), + on(addScanType, (state, action) => { + const types = [].concat(state.types); + types.push(action.scanType); + return { + ...state, + initialized: true, + types + }; + }) +); diff --git a/comixed-web/src/app/library/selectors/scan-type.selectors.spec.ts b/comixed-web/src/app/library/selectors/scan-type.selectors.spec.ts new file mode 100644 index 0000000000..8c38b5ec7d --- /dev/null +++ b/comixed-web/src/app/library/selectors/scan-type.selectors.spec.ts @@ -0,0 +1,47 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2021, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { + SCAN_TYPE_FEATURE_KEY, + ScanTypeState +} from '../reducers/scan-type.reducer'; +import { selectScanTypeState } from './scan-type.selectors'; +import { + SCAN_TYPE_1, + SCAN_TYPE_3, + SCAN_TYPE_5, + SCAN_TYPE_7 +} from '@app/library/library.fixtures'; + +describe('ScanType Selectors', () => { + const SCAN_TYPES = [SCAN_TYPE_1, SCAN_TYPE_3, SCAN_TYPE_5, SCAN_TYPE_7]; + + let state: ScanTypeState; + + beforeEach(() => { + state = { types: SCAN_TYPES, initialized: Math.random() > 0.5 }; + }); + + it('should select the feature state', () => { + expect( + selectScanTypeState({ + [SCAN_TYPE_FEATURE_KEY]: state + }) + ).toEqual(state); + }); +}); diff --git a/comixed-web/src/app/library/selectors/scan-type.selectors.ts b/comixed-web/src/app/library/selectors/scan-type.selectors.ts new file mode 100644 index 0000000000..c552a49c11 --- /dev/null +++ b/comixed-web/src/app/library/selectors/scan-type.selectors.ts @@ -0,0 +1,27 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2021, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { createFeatureSelector } from '@ngrx/store'; +import { + SCAN_TYPE_FEATURE_KEY, + ScanTypeState +} from '../reducers/scan-type.reducer'; + +export const selectScanTypeState = createFeatureSelector( + SCAN_TYPE_FEATURE_KEY +); diff --git a/comixed-web/src/app/library/services/scan-type.service.spec.ts b/comixed-web/src/app/library/services/scan-type.service.spec.ts new file mode 100644 index 0000000000..f7daf61ff6 --- /dev/null +++ b/comixed-web/src/app/library/services/scan-type.service.spec.ts @@ -0,0 +1,130 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2021, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { TestBed } from '@angular/core/testing'; + +import { ScanTypeService } from './scan-type.service'; +import { WebSocketService } from '@app/messaging'; +import { + initialState as initialMessagingState, + MESSAGING_FEATURE_KEY +} from '@app/messaging/reducers/messaging.reducer'; +import { Frame, Subscription } from 'webstomp-client'; +import { addScanType } from '@app/library/actions/scan-type.actions'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { + LOAD_SCAN_TYPES_MESSAGE, + SCAN_TYPE_ADD_QUEUE +} from '@app/library/library.constants'; +import { LoggerModule } from '@angular-ru/logger'; +import { SCAN_TYPE_6 } from '@app/library/library.fixtures'; + +describe('ScanTypeService', () => { + const SCAN_TYPE = SCAN_TYPE_6; + + const initialState = { + [MESSAGING_FEATURE_KEY]: { ...initialMessagingState } + }; + + let service: ScanTypeService; + let webSocketService: jasmine.SpyObj; + const subscription = jasmine.createSpyObj(['unsubscribe']); + subscription.unsubscribe = jasmine.createSpy('Subscription.unsubscribe()'); + let store: MockStore; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [LoggerModule.forRoot()], + providers: [ + provideMockStore({ initialState }), + { + provide: WebSocketService, + useValue: { + subscribe: jasmine.createSpy('WebSocketService.subscribe()'), + send: jasmine.createSpy('WebSocketService.send()') + } + } + ] + }); + + service = TestBed.inject(ScanTypeService); + webSocketService = TestBed.inject( + WebSocketService + ) as jasmine.SpyObj; + store = TestBed.inject(MockStore); + spyOn(store, 'dispatch'); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + describe('when messaging starts', () => { + const MESSAGE = new Frame('scan type', {}, JSON.stringify(SCAN_TYPE)); + + beforeEach(() => { + console.log('*** webSocketService:', webSocketService); + webSocketService.subscribe.and.callFake((topic, callback) => { + callback(MESSAGE); + return {} as Subscription; + }); + store.setState({ + ...initialState, + [MESSAGING_FEATURE_KEY]: { ...initialMessagingState, started: true } + }); + }); + + it('subscribes to the scan types topic', () => { + expect(webSocketService.subscribe).toHaveBeenCalledWith( + SCAN_TYPE_ADD_QUEUE, + jasmine.anything() + ); + }); + + it('publishes a message', () => { + expect(webSocketService.send).toHaveBeenCalledWith( + LOAD_SCAN_TYPES_MESSAGE, + '' + ); + }); + + it('fires an action', () => { + expect(store.dispatch).toHaveBeenCalledWith( + addScanType({ scanType: SCAN_TYPE }) + ); + }); + }); + + describe('when messaging stops', () => { + beforeEach(() => { + service.subscription = subscription; + store.setState({ + ...initialState, + [MESSAGING_FEATURE_KEY]: { ...initialMessagingState, started: false } + }); + }); + + it('unsubscribes from the add scan type queue', () => { + expect(subscription.unsubscribe).toHaveBeenCalled(); + }); + + it('clears the subscription', () => { + expect(service.subscription).toBeNull(); + }); + }); +}); diff --git a/comixed-web/src/app/library/services/scan-type.service.ts b/comixed-web/src/app/library/services/scan-type.service.ts new file mode 100644 index 0000000000..ddd01cedca --- /dev/null +++ b/comixed-web/src/app/library/services/scan-type.service.ts @@ -0,0 +1,63 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2021, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { Injectable } from '@angular/core'; +import { WebSocketService } from '@app/messaging'; +import { selectMessagingState } from '@app/messaging/selectors/messaging.selectors'; +import { Store } from '@ngrx/store'; +import { LoggerService } from '@angular-ru/logger'; +import { Subscription } from 'webstomp-client'; +import { + LOAD_SCAN_TYPES_MESSAGE, + SCAN_TYPE_ADD_QUEUE +} from '@app/library/library.constants'; +import { addScanType } from '@app/library/actions/scan-type.actions'; + +@Injectable({ + providedIn: 'root' +}) +export class ScanTypeService { + let; + subscription: Subscription; + + constructor( + private logger: LoggerService, + private store: Store, + private webSocketService: WebSocketService + ) { + this.store.select(selectMessagingState).subscribe(state => { + if (state.started && !this.subscription) { + this.logger.trace('Subscribing to scan type updates'); + this.subscription = this.webSocketService.subscribe( + SCAN_TYPE_ADD_QUEUE, + frame => { + const scanType = JSON.parse(frame.body); + this.logger.debug('Received scan type:', scanType); + this.store.dispatch(addScanType({ scanType })); + } + ); + this.webSocketService.send(LOAD_SCAN_TYPES_MESSAGE, ''); + } + if (!state.started && !!this.subscription) { + this.logger.trace('Unsubscribing from scan type updates'); + this.subscription.unsubscribe(); + this.subscription = null; + } + }); + } +}