From a8b1938f5fe5c0c76fac5e086de88d1e0453afb3 Mon Sep 17 00:00:00 2001 From: Conrad Chan Date: Tue, 8 Sep 2020 13:20:30 -0700 Subject: [PATCH] feat(eventing): Emit new annotations_staged_change event (#570) --- src/@types/events.ts | 1 + src/store/eventing/__tests__/staged-test.ts | 117 ++++++++++++++++++++ src/store/eventing/middleware.ts | 4 + src/store/eventing/staged.ts | 60 ++++++++++ 4 files changed, 182 insertions(+) create mode 100644 src/store/eventing/__tests__/staged-test.ts create mode 100644 src/store/eventing/staged.ts diff --git a/src/@types/events.ts b/src/@types/events.ts index d1a46394d..fea57c4c2 100644 --- a/src/@types/events.ts +++ b/src/@types/events.ts @@ -1,6 +1,7 @@ enum Event { ACTIVE_CHANGE = 'annotations_active_change', ACTIVE_SET = 'annotations_active_set', + CREATOR_STAGED_CHANGE = 'creator_staged_change', ANNOTATION_CREATE = 'annotations_create', ANNOTATION_FETCH_ERROR = 'annotations_fetch_error', ANNOTATION_REMOVE = 'annotations_remove', diff --git a/src/store/eventing/__tests__/staged-test.ts b/src/store/eventing/__tests__/staged-test.ts new file mode 100644 index 000000000..e79437524 --- /dev/null +++ b/src/store/eventing/__tests__/staged-test.ts @@ -0,0 +1,117 @@ +import eventManager from '../../../common/EventManager'; +import { AppState } from '../../types'; +import { getStatus, getType, handleSetStagedAction, handleResetCreatorAction } from '../staged'; +import { createStore } from '../..'; +import { CreatorItemHighlight, CreatorItemRegion, CreatorState } from '../../creator'; +import { Event } from '../../../@types'; + +jest.mock('../../../common/EventManager'); + +describe('store/eventing/staged', () => { + const getStagedHighlight = (): CreatorItemHighlight => ({ + location: 1, + shapes: [ + { + type: 'rect', + height: 50, + width: 50, + x: 10, + y: 10, + }, + ], + }); + + const getStagedRegion = (): CreatorItemRegion => ({ + location: 1, + shape: { + type: 'rect', + height: 50, + width: 50, + x: 10, + y: 10, + }, + }); + + const getCreatorHighlight = (): CreatorState => + ({ + staged: getStagedHighlight(), + } as CreatorState); + + const getCreatorRegion = (): CreatorState => + ({ + staged: getStagedRegion(), + } as CreatorState); + + const getCreatorState = (isHighlight = true): AppState => { + const creator = isHighlight ? getCreatorHighlight() : getCreatorRegion(); + return createStore({ creator }).getState(); + }; + + describe('getStatus()', () => { + test.each` + prev | next | expectedStatus + ${null} | ${null} | ${null} + ${null} | ${'notnull'} | ${'create'} + ${'notnull'} | ${null} | ${null} + ${'notnull'} | ${'notnull'} | ${'update'} + `( + 'should return $expectedStatus if prevStaged=$prev and nextStaged=$next', + ({ prev, next, expectedStatus }) => { + expect(getStatus(prev, next)).toBe(expectedStatus); + }, + ); + }); + + describe('getType()', () => { + test.each` + staged | expectedType + ${null} | ${null} + ${getStagedHighlight()} | ${'highlight'} + ${getStagedRegion()} | ${'region'} + `('should returned $expectedType if staged=$staged', ({ staged, expectedType }) => { + expect(getType(staged)).toBe(expectedType); + }); + }); + + describe('handleSetStagedAction()', () => { + test.each` + prev | next + ${createStore().getState()} | ${createStore().getState()} + ${getCreatorState()} | ${createStore().getState()} + `('should not emit event if status or type is null', ({ prev, next }) => { + handleSetStagedAction(prev, next); + + expect(eventManager.emit).not.toHaveBeenCalled(); + }); + + test.each` + prev | next | type | status + ${createStore().getState()} | ${getCreatorState()} | ${'highlight'} | ${'create'} + ${createStore().getState()} | ${getCreatorState(false)} | ${'region'} | ${'create'} + ${getCreatorState(false)} | ${getCreatorState(false)} | ${'region'} | ${'update'} + `('should emit event with type=$type and status=$status', ({ prev, next, type, status }) => { + handleSetStagedAction(prev, next); + + expect(eventManager.emit).toHaveBeenCalledWith(Event.CREATOR_STAGED_CHANGE, { type, status }); + }); + }); + + describe('handleResetCreatorAction()', () => { + test('should not emit event if type is null', () => { + const prevState = createStore().getState(); + handleResetCreatorAction(prevState); + + expect(eventManager.emit).not.toHaveBeenCalled(); + }); + + test.each` + prev | type + ${getCreatorState()} | ${'highlight'} + ${getCreatorState(false)} | ${'region'} + `('should emit cancel event if with type=$type', ({ prev, type }) => { + handleResetCreatorAction(prev); + + expect(eventManager.emit).toHaveBeenCalledWith(Event.CREATOR_STAGED_CHANGE, { type, status: 'cancel' }); + }); + }); +}); diff --git a/src/store/eventing/middleware.ts b/src/store/eventing/middleware.ts index ba9bcbaed..dc2c3e0c3 100644 --- a/src/store/eventing/middleware.ts +++ b/src/store/eventing/middleware.ts @@ -11,7 +11,9 @@ import { handleActiveAnnotationEvents } from './active'; import { handleAnnotationsInitialized } from './init'; import { handleCreateErrorEvents, handleCreatePendingEvents, handleCreateSuccessEvents } from './create'; import { handleFetchErrorEvents } from './fetch'; +import { handleResetCreatorAction, handleSetStagedAction } from './staged'; import { handleToggleAnnotationModeAction } from './mode'; +import { resetCreatorAction, setStagedAction } from '../creator'; import { toggleAnnotationModeAction } from '../common/actions'; // Array of event handlers based on redux action. To add handling for new events add an entry keyed by action @@ -20,8 +22,10 @@ const eventHandlers: EventHandlerMap = { [createAnnotationAction.pending.toString()]: handleCreatePendingEvents, [createAnnotationAction.rejected.toString()]: handleCreateErrorEvents, [fetchAnnotationsAction.rejected.toString()]: handleFetchErrorEvents, + [resetCreatorAction.toString()]: handleResetCreatorAction, [setActiveAnnotationIdAction.toString()]: handleActiveAnnotationEvents, [setIsInitialized.toString()]: handleAnnotationsInitialized, + [setStagedAction.toString()]: handleSetStagedAction, [toggleAnnotationModeAction.toString()]: handleToggleAnnotationModeAction, }; diff --git a/src/store/eventing/staged.ts b/src/store/eventing/staged.ts new file mode 100644 index 000000000..009a44071 --- /dev/null +++ b/src/store/eventing/staged.ts @@ -0,0 +1,60 @@ +import eventManager from '../../common/EventManager'; +import { AppState } from '../types'; +import { CreatorItem, getCreatorStaged, isCreatorStagedHighlight, isCreatorStagedRegion } from '../creator'; +import { Event } from '../../@types'; + +type Status = 'create' | 'update' | 'cancel'; + +type Type = 'highlight' | 'region'; + +export const getStatus = (prevStaged: CreatorItem, nextStaged: CreatorItem): Status | null => { + let status: Status | null = null; + + if (prevStaged === null && nextStaged !== null) { + status = 'create'; + } + + if (prevStaged !== null && nextStaged !== null) { + status = 'update'; + } + + return status; +}; + +export const getType = (staged: CreatorItem): Type | null => { + let type: Type | null = null; + + if (isCreatorStagedRegion(staged)) { + type = 'region'; + } + + if (isCreatorStagedHighlight(staged)) { + type = 'highlight'; + } + + return type; +}; + +export const handleSetStagedAction = (prevState: AppState, nextState: AppState): void => { + const prevStaged = getCreatorStaged(prevState); + const nextStaged = getCreatorStaged(nextState); + const status = getStatus(prevStaged, nextStaged); + const type = getType(prevStaged) || getType(nextStaged); + + if (!status || !type) { + return; + } + + eventManager.emit(Event.CREATOR_STAGED_CHANGE, { type, status }); +}; + +export const handleResetCreatorAction = (prevState: AppState): void => { + const prevStaged = getCreatorStaged(prevState); + const type = getType(prevStaged); + + if (!type) { + return; + } + + eventManager.emit(Event.CREATOR_STAGED_CHANGE, { type, status: 'cancel' }); +};