Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eventing): Emit new annotations_staged_change event #570

Merged
merged 2 commits into from
Sep 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/@types/events.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
117 changes: 117 additions & 0 deletions src/store/eventing/__tests__/staged-test.ts
Original file line number Diff line number Diff line change
@@ -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' });
});
});
});
4 changes: 4 additions & 0 deletions src/store/eventing/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to handle setStatusAction? Or can it even take the place of setStagedAction here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should stick to setStagedAction since the transitions in state for staged allows us to infer the event status as well as the annotation type

[toggleAnnotationModeAction.toString()]: handleToggleAnnotationModeAction,
};

Expand Down
60 changes: 60 additions & 0 deletions src/store/eventing/staged.ts
Original file line number Diff line number Diff line change
@@ -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';
}
Comment on lines +13 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we handle if (prevStaged !== null && nextStaged === null)? Maybe states is cancel?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left it off since our pattern of usage for cancel is to use resetCreatorAction.


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' });
};