Skip to content

Commit

Permalink
feat(annotations): Handle annotations staged change event (#1247)
Browse files Browse the repository at this point in the history
* feat(annotations): Handle annotations staged change event

* feat(annotations): AnnotationControls dumber, FSM smarter

* feat(annotations): Address comments

* feat(annotations): Add FSM unit tests

* feat(annotations): More unit tests

* feat(annotations): Add tests for every possible inputs for every state

* feat(annotations): Address comments
  • Loading branch information
Mingze authored Sep 9, 2020
1 parent 4047402 commit 37e7003
Show file tree
Hide file tree
Showing 10 changed files with 337 additions and 88 deletions.
36 changes: 7 additions & 29 deletions src/lib/AnnotationControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ export enum AnnotationMode {
NONE = 'none',
REGION = 'region',
}
export type ClickHandler = ({ event }: { event: MouseEvent }) => void;
export type ClickHandler = ({ event, mode }: { event: MouseEvent; mode: AnnotationMode }) => void;
export type Options = {
fileId: string;
onClick?: ClickHandler;
onEscape?: () => void;
onHighlightClick?: ClickHandler;
onRegionClick?: ClickHandler;
showHighlightText: boolean;
};

Expand Down Expand Up @@ -155,21 +154,6 @@ export default class AnnotationControls {
this.updateButton(mode);
}

/**
* Annotation control button click handler
*/
private handleClick = (onClick: ClickHandler, mode: AnnotationMode) => (event: MouseEvent): void => {
const prevMode = this.currentMode;
this.resetControls();

if (prevMode !== mode) {
this.currentMode = mode as AnnotationMode;
this.updateButton(mode);
}

onClick({ event });
};

/**
* Escape key handler, reset all control buttons,
* and stop propagation to prevent preview modal from exiting
Expand All @@ -186,7 +170,7 @@ export default class AnnotationControls {
event.stopPropagation();
};

private addButton = (mode: AnnotationMode, handler: ClickHandler, parent: HTMLElement, fileId: string): void => {
private addButton = (mode: AnnotationMode, onClick: ClickHandler, parent: HTMLElement, fileId: string): void => {
const buttonProps = buttonPropsMap[mode];

if (!buttonProps) {
Expand All @@ -195,7 +179,7 @@ export default class AnnotationControls {

const buttonElement = this.controls.add(
buttonProps.text,
this.handleClick(handler, mode),
(event: MouseEvent) => onClick({ event, mode }),
buttonProps.classname,
buttonProps.icon,
'button',
Expand All @@ -210,22 +194,16 @@ export default class AnnotationControls {
/**
* Initialize the annotation controls with options.
*/
public init({
fileId,
onEscape = noop,
onRegionClick = noop,
onHighlightClick = noop,
showHighlightText = false,
}: Options): void {
public init({ fileId, onEscape = noop, onClick = noop, showHighlightText = false }: Options): void {
if (this.hasInit) {
return;
}
const groupElement = this.controls.addGroup(CLASS_ANNOTATIONS_GROUP);
groupElement.setAttribute('data-resin-feature', 'annotations');

this.addButton(AnnotationMode.REGION, onRegionClick, groupElement, fileId);
this.addButton(AnnotationMode.REGION, onClick, groupElement, fileId);
if (showHighlightText) {
this.addButton(AnnotationMode.HIGHLIGHT, onHighlightClick, groupElement, fileId);
this.addButton(AnnotationMode.HIGHLIGHT, onClick, groupElement, fileId);
}

this.onEscape = onEscape;
Expand Down
71 changes: 71 additions & 0 deletions src/lib/AnnotationControlsFSM.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { AnnotationMode } from './AnnotationControls';

export enum AnnotationState {
HIGHLIGHT = 'highlight',
HIGHLIGHT_TEMP = 'highlight_temp',
NONE = 'none',
REGION = 'region',
REGION_TEMP = 'region_temp',
}

export enum AnnotationInput {
CANCEL = 'cancel',
CLICK = 'click',
CREATE = 'create',
SUCCESS = 'success',
UPDATE = 'update',
}

export const modeStateMap = {
[AnnotationMode.HIGHLIGHT]: AnnotationState.HIGHLIGHT,
[AnnotationMode.NONE]: AnnotationState.NONE,
[AnnotationMode.REGION]: AnnotationState.REGION,
};

export const modeTempStateMap = {
[AnnotationMode.HIGHLIGHT]: AnnotationState.HIGHLIGHT_TEMP,
[AnnotationMode.NONE]: AnnotationState.NONE,
[AnnotationMode.REGION]: AnnotationState.REGION_TEMP,
};

export const stateModeMap = {
[AnnotationState.HIGHLIGHT]: AnnotationMode.HIGHLIGHT,
[AnnotationState.HIGHLIGHT_TEMP]: AnnotationMode.HIGHLIGHT,
[AnnotationState.NONE]: AnnotationMode.NONE,
[AnnotationState.REGION]: AnnotationMode.REGION,
[AnnotationState.REGION_TEMP]: AnnotationMode.REGION,
};

export default class AnnotationControlsFSM {
private currentState: AnnotationState;

constructor(initialState = AnnotationState.NONE) {
this.currentState = initialState;
}

public getState = (): AnnotationState => this.currentState;

public transition = (input: AnnotationInput, mode: AnnotationMode = AnnotationMode.NONE): AnnotationMode => {
if (input === AnnotationInput.CLICK) {
this.currentState = mode === stateModeMap[this.currentState] ? AnnotationState.NONE : modeStateMap[mode];
return stateModeMap[this.currentState];
}

switch (this.currentState) {
case AnnotationState.NONE:
if (input === AnnotationInput.CREATE) {
this.currentState = modeTempStateMap[mode] || AnnotationState.NONE;
}
break;
case AnnotationState.HIGHLIGHT_TEMP:
case AnnotationState.REGION_TEMP:
if (input === AnnotationInput.CANCEL || input === AnnotationInput.SUCCESS) {
this.currentState = AnnotationState.NONE;
}
break;
default:
}

return stateModeMap[this.currentState];
};
}
42 changes: 6 additions & 36 deletions src/lib/__tests__/AnnotationControls-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import AnnotationControls, {
AnnotationMode,
CLASS_ANNOTATIONS_BUTTON,
CLASS_ANNOTATIONS_GROUP,
CLASS_BUTTON_ACTIVE,
CLASS_GROUP_HIDE,
CLASS_REGION_BUTTON,
} from '../AnnotationControls';
Expand All @@ -24,8 +23,7 @@ describe('lib/AnnotationControls', () => {
fixture.load('__tests__/AnnotationControls-test.html');
stubs.classListAdd = sandbox.stub();
stubs.classListRemove = sandbox.stub();
stubs.onHighlightClick = sandbox.stub();
stubs.onRegionClick = sandbox.stub();
stubs.onClick = sandbox.stub();
stubs.querySelector = sandbox.stub().returns({
classList: {
add: stubs.classListAdd,
Expand Down Expand Up @@ -84,22 +82,22 @@ describe('lib/AnnotationControls', () => {
});

it('should only add region button', () => {
annotationControls.init({ fileId: '0', onRegionClick: stubs.onRegionClick });
annotationControls.init({ fileId: '0', onClick: stubs.onClick });

expect(annotationControls.addButton).to.be.calledOnceWith(
AnnotationMode.REGION,
stubs.onRegionClick,
stubs.onClick,
sinon.match.any,
'0',
);
});

it('should add highlight button', () => {
annotationControls.init({ fileId: '0', onHighlightClick: stubs.onHighlightClick, showHighlightText: true });
annotationControls.init({ fileId: '0', onClick: stubs.onClick, showHighlightText: true });

expect(annotationControls.addButton).to.be.calledWith(
AnnotationMode.HIGHLIGHT,
stubs.onHighlightClick,
stubs.onClick,
sinon.match.any,
'0',
);
Expand Down Expand Up @@ -166,32 +164,6 @@ describe('lib/AnnotationControls', () => {
});
});

describe('handleClick()', () => {
beforeEach(() => {
stubs.event = sandbox.stub({});
});

it('should activate region button then deactivate', () => {
expect(annotationControls.currentMode).to.equal(AnnotationMode.NONE);

annotationControls.handleClick(stubs.onRegionClick, AnnotationMode.REGION)(stubs.event);
expect(annotationControls.currentMode).to.equal(AnnotationMode.REGION);
expect(stubs.classListAdd).to.be.calledWith(CLASS_BUTTON_ACTIVE);

annotationControls.handleClick(stubs.onRegionClick, AnnotationMode.REGION)(stubs.event);
expect(annotationControls.currentMode).to.equal(AnnotationMode.NONE);
expect(stubs.classListRemove).to.be.calledWith(CLASS_BUTTON_ACTIVE);
});

it('should call onRegionClick', () => {
annotationControls.handleClick(stubs.onRegionClick, AnnotationMode.REGION)(stubs.event);

expect(stubs.onRegionClick).to.be.calledWith({
event: stubs.event,
});
});
});

describe('resetControls()', () => {
beforeEach(() => {
sandbox.stub(annotationControls, 'updateButton');
Expand Down Expand Up @@ -233,9 +205,7 @@ describe('lib/AnnotationControls', () => {
stubs.buttonElement = {
setAttribute: sandbox.stub(),
};
stubs.clickHandler = sandbox.stub();

sandbox.stub(annotationControls, 'handleClick').returns(stubs.clickHandler);
sandbox.stub(annotationControls.controls, 'add').returns(stubs.buttonElement);
});

Expand All @@ -250,7 +220,7 @@ describe('lib/AnnotationControls', () => {

expect(annotationControls.controls.add).to.be.calledWith(
__('region_comment'),
stubs.clickHandler,
sinon.match.func,
`${CLASS_ANNOTATIONS_BUTTON} ${CLASS_REGION_BUTTON}`,
ICON_REGION_COMMENT,
'button',
Expand Down
Loading

0 comments on commit 37e7003

Please sign in to comment.