diff --git a/package.json b/package.json index f24936638..f1dd670bf 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@popperjs/core": "^2.4.0", "@reduxjs/toolkit": "^1.3.5", "@types/react-redux": "^7.1.7", + "@types/uuid": "^8.3.0", "axios": "^0.19.2", "box-ui-elements": "^12.0.0-beta.146", "classnames": "^2.2.5", @@ -34,7 +35,8 @@ "react-tether": "1.0.5", "react-textarea-autosize": "^7.1.2", "redux": "^4.0.5", - "scroll-into-view-if-needed": "^2.2.24" + "scroll-into-view-if-needed": "^2.2.24", + "uuid": "^8.3.1" }, "devDependencies": { "@babel/cli": "^7.8.4", diff --git a/src/common/__tests__/useWindowSize-test.tsx b/src/common/__tests__/useWindowSize-test.tsx deleted file mode 100644 index e5e60e40a..000000000 --- a/src/common/__tests__/useWindowSize-test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import * as React from 'react'; -import { act } from 'react-dom/test-utils'; -import { mount, ReactWrapper } from 'enzyme'; -import useWindowSize from '../useWindowSize'; - -describe('useWindowSize', () => { - function TestComponent(): JSX.Element { - const windowSize = useWindowSize(); - - return ( -
- {windowSize?.height} - {windowSize?.width} -
- ); - } - - const getWrapper = (): ReactWrapper => - mount( -
- -
, - { attachTo: document.getElementById('test') }, - ); - - beforeEach(() => { - document.body.innerHTML = '
'; - global.window.innerHeight = 100; - global.window.innerWidth = 100; - }); - - test('should return the window size', () => { - const wrapper = getWrapper(); - - expect(wrapper.find('#height').text()).toBe('100'); - expect(wrapper.find('#width').text()).toBe('100'); - - global.window.innerHeight = 200; - global.window.innerWidth = 200; - - act(() => { - global.window.dispatchEvent(new Event('resize')); - }); - - expect(wrapper.find('#height').text()).toBe('200'); - expect(wrapper.find('#width').text()).toBe('200'); - }); -}); diff --git a/src/common/useWindowSize.ts b/src/common/useWindowSize.ts deleted file mode 100644 index 80ff29384..000000000 --- a/src/common/useWindowSize.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from 'react'; - -type WindowSize = { - height: number; - width: number; -}; - -export default function useWindowSize(): WindowSize | null { - const [windowSize, setWindowSize] = React.useState(null); - - const handleResize = (): void => { - // Set window width/height to state - setWindowSize({ - width: window.innerWidth, - height: window.innerHeight, - }); - }; - - React.useEffect(() => { - // Add event listener - window.addEventListener('resize', handleResize); - - // Call handler right away so state gets updated with initial window size - handleResize(); - - // Remove event listener on cleanup - return () => window.removeEventListener('resize', handleResize); - }, []); // Empty array ensures that effect is only run on mount - - return windowSize; -} diff --git a/src/highlight/HighlightAnnotations.tsx b/src/highlight/HighlightAnnotations.tsx index 8923ee104..ba72fdd5b 100644 --- a/src/highlight/HighlightAnnotations.tsx +++ b/src/highlight/HighlightAnnotations.tsx @@ -5,7 +5,6 @@ import HighlightSvg from './HighlightSvg'; import HighlightTarget from './HighlightTarget'; import PopupHighlight from '../components/Popups/PopupHighlight'; import PopupHighlightError from '../components/Popups/PopupHighlightError'; -import useWindowSize from '../common/useWindowSize'; import { AnnotationHighlight } from '../@types'; import { CreateArg } from './actions'; import { CreatorItemHighlight, CreatorStatus, SelectionArg, SelectionItem } from '../store'; @@ -23,7 +22,7 @@ type Props = { selection: SelectionItem | null; setActiveAnnotationId: (annotationId: string | null) => void; setIsPromoting: (isPromoting: boolean) => void; - setReferenceShape: (rect: DOMRect) => void; + setReferenceId: (uuid: string) => void; setSelection: (selection: SelectionArg | null) => void; setStaged: (staged: CreatorItemHighlight | null) => void; setStatus: (status: CreatorStatus) => void; @@ -40,15 +39,12 @@ const HighlightAnnotations = (props: Props): JSX.Element => { selection, setActiveAnnotationId, setIsPromoting, - setReferenceShape, + setReferenceId, setSelection, setStaged, setStatus, staged, } = props; - const [highlightRef, setHighlightRef] = React.useState(null); - const windowSize = useWindowSize(); - const canCreate = isCreating || isPromoting; const handleAnnotationActive = (annotationId: string | null): void => { @@ -82,6 +78,10 @@ const HighlightAnnotations = (props: Props): JSX.Element => { setSelection(null); }; + const handleStagedMount = (uuid: string): void => { + setReferenceId(uuid); + }; + React.useEffect(() => { if (!isSelecting) { return; @@ -99,14 +99,6 @@ const HighlightAnnotations = (props: Props): JSX.Element => { stageSelection(); }, [isCreating, selection]); // eslint-disable-line react-hooks/exhaustive-deps - React.useEffect(() => { - if (highlightRef === null) { - return; - } - - setReferenceShape(highlightRef.getBoundingClientRect()); - }, [highlightRef, windowSize]); // eslint-disable-line react-hooks/exhaustive-deps - return ( <> {/* Layer 1: Saved annotations */} @@ -117,7 +109,12 @@ const HighlightAnnotations = (props: Props): JSX.Element => {
- +
)} diff --git a/src/highlight/HighlightContainer.tsx b/src/highlight/HighlightContainer.tsx index 95d50520b..8d0f4146e 100644 --- a/src/highlight/HighlightContainer.tsx +++ b/src/highlight/HighlightContainer.tsx @@ -17,7 +17,7 @@ import { SelectionItem, setActiveAnnotationIdAction, setIsPromotingAction, - setReferenceShapeAction, + setReferenceIdAction, setSelectionAction, setStagedAction, setStatusAction, @@ -51,7 +51,7 @@ export const mapStateToProps = (state: AppState, { location }: { location: numbe export const mapDispatchToProps = { setActiveAnnotationId: setActiveAnnotationIdAction, setIsPromoting: setIsPromotingAction, - setReferenceShape: setReferenceShapeAction, + setReferenceId: setReferenceIdAction, setSelection: setSelectionAction, setStaged: setStagedAction, setStatus: setStatusAction, diff --git a/src/highlight/HighlightTarget.tsx b/src/highlight/HighlightTarget.tsx index 61502b577..77a3ca77c 100644 --- a/src/highlight/HighlightTarget.tsx +++ b/src/highlight/HighlightTarget.tsx @@ -1,5 +1,6 @@ -import * as React from 'react'; +import React from 'react'; import * as ReactRedux from 'react-redux'; +import * as uuid from 'uuid'; import classNames from 'classnames'; import noop from 'lodash/noop'; import { getIsCurrentFileVersion } from '../store'; @@ -12,6 +13,7 @@ type Props = { className?: string; isActive?: boolean; onHover?: (annotationId: string | null) => void; + onMount?: (uuid: string) => void; onSelect?: (annotationId: string) => void; shapes: Array; }; @@ -19,7 +21,8 @@ type Props = { export type HighlightTargetRef = HTMLAnchorElement; const HighlightTarget = (props: Props, ref: React.Ref): JSX.Element => { - const { annotationId, className, isActive, onHover = noop, onSelect = noop, shapes } = props; + const { annotationId, className, isActive, onHover = noop, onMount = noop, onSelect = noop, shapes } = props; + const uuidRef = React.useRef(uuid.v4()); const isCurrentFileVersion = ReactRedux.useSelector(getIsCurrentFileVersion); const handleClick = (event: React.MouseEvent): void => { @@ -61,11 +64,16 @@ const HighlightTarget = (props: Props, ref: React.Ref): JSX. event.currentTarget.focus(); }; + React.useEffect(() => { + onMount(uuidRef.current); + }, [onMount]); + return ( // eslint-disable-next-line jsx-a11y/anchor-is-valid { selection: null, setActiveAnnotationId: jest.fn(), setIsPromoting: jest.fn(), - setReferenceShape: jest.fn(), + setReferenceId: jest.fn(), setSelection: jest.fn(), setStaged: jest.fn(), setStatus: jest.fn(), diff --git a/src/highlight/__tests__/HighlightContainer-test.tsx b/src/highlight/__tests__/HighlightContainer-test.tsx index 1af57996b..8dc96cbe3 100644 --- a/src/highlight/__tests__/HighlightContainer-test.tsx +++ b/src/highlight/__tests__/HighlightContainer-test.tsx @@ -42,7 +42,7 @@ describe('HighlightContainer', () => { selection: null, setActiveAnnotationId: expect.any(Function), setIsPromoting: expect.any(Function), - setReferenceShape: expect.any(Function), + setReferenceId: expect.any(Function), setStaged: expect.any(Function), setStatus: expect.any(Function), staged: null, diff --git a/src/highlight/__tests__/HighlightTarget-test.tsx b/src/highlight/__tests__/HighlightTarget-test.tsx index 604890792..847ab252e 100644 --- a/src/highlight/__tests__/HighlightTarget-test.tsx +++ b/src/highlight/__tests__/HighlightTarget-test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { MutableRefObject } from 'react'; import * as ReactRedux from 'react-redux'; import { shallow, ShallowWrapper } from 'enzyme'; import HighlightTarget from '../HighlightTarget'; @@ -23,12 +23,14 @@ describe('HighlightTarget', () => { }, ], }; - const mockSetIsHover = jest.fn(); + const getWrapper = (props = {}): ShallowWrapper => shallow(); + const mockInitalRef = { current: '123' } as MutableRefObject; beforeEach(() => { + jest.spyOn(React, 'useEffect').mockImplementation(f => f()); + jest.spyOn(React, 'useRef').mockImplementation(() => mockInitalRef); jest.spyOn(ReactRedux, 'useSelector').mockImplementation(() => true); - jest.spyOn(React, 'useState').mockImplementation(() => [false, mockSetIsHover]); }); describe('render()', () => { @@ -43,9 +45,10 @@ describe('HighlightTarget', () => { test.each([true, false])('should render classNames correctly when isActive is %s', isActive => { const wrapper = getWrapper({ isActive }); + const anchor = wrapper.find('a'); - expect(wrapper.hasClass('ba-HighlightTarget')).toBe(true); - expect(wrapper.hasClass('is-active')).toBe(isActive); + expect(anchor.hasClass('ba-HighlightTarget')).toBe(true); + expect(anchor.hasClass('is-active')).toBe(isActive); }); }); @@ -88,12 +91,8 @@ describe('HighlightTarget', () => { test('should call onSelect', () => { const wrapper = getWrapper(); const anchor = wrapper.find('a'); - const event = { - ...mockEvent, - buttons: 1, - }; - anchor.simulate('mousedown', event); + anchor.prop('onMouseDown')!((mockEvent as unknown) as React.MouseEvent); expect(defaults.onSelect).toHaveBeenCalledWith('123'); expect(mockEvent.preventDefault).toHaveBeenCalled(); @@ -140,4 +139,13 @@ describe('HighlightTarget', () => { }); }); }); + + describe('onMount()', () => { + test('should call onMount with a generated uuid', () => { + const handleMount = jest.fn(); + getWrapper({ onMount: handleMount }); + + expect(handleMount).toHaveBeenCalledWith(expect.any(String)); + }); + }); }); diff --git a/src/popup/PopupContainer.tsx b/src/popup/PopupContainer.tsx index 2b34e2ef0..559f168b4 100644 --- a/src/popup/PopupContainer.tsx +++ b/src/popup/PopupContainer.tsx @@ -13,17 +13,16 @@ import { Mode, resetCreatorAction, setMessageAction, - getCreatorReferenceShape, + getCreatorReferenceId, } from '../store'; import { createHighlightAction } from '../highlight/actions'; -import { Shape } from '../@types'; import { createRegionAction } from '../region'; export type Props = { isPromoting: boolean; message: string; mode: Mode; - popupReference?: Shape; + referenceId: string | null; staged: CreatorItem | null; status: CreatorStatus; }; @@ -33,7 +32,7 @@ export const mapStateToProps = (state: AppState, { location }: { location: numbe isPromoting: getIsPromoting(state), message: getCreatorMessage(state), mode: getAnnotationMode(state), - popupReference: getCreatorReferenceShape(state), + referenceId: getCreatorReferenceId(state), staged: getCreatorStagedForLocation(state, location), status: getCreatorStatus(state), }; diff --git a/src/popup/PopupLayer.tsx b/src/popup/PopupLayer.tsx index 92bb0b98e..28a31adbb 100644 --- a/src/popup/PopupLayer.tsx +++ b/src/popup/PopupLayer.tsx @@ -5,7 +5,6 @@ import { CreateArg as HighlightCreateArg } from '../highlight/actions'; import { CreateArg as RegionCreateArg } from '../region/actions'; import { CreatorItem, CreatorStatus, isCreatorStagedHighlight, isCreatorStagedRegion, Mode } from '../store'; import { PopupReference } from '../components/Popups/Popper'; -import { Shape } from '../@types'; import './PopupLayer.scss'; type Props = { @@ -15,7 +14,7 @@ type Props = { location: number; message: string; mode: Mode; - popupReference?: Shape; + referenceId: string | null; resetCreator: () => void; setMessage: (message: string) => void; staged?: CreatorItem | null; @@ -34,7 +33,7 @@ const PopupLayer = (props: Props): JSX.Element | null => { isPromoting = false, message, mode, - popupReference, + referenceId, resetCreator, setMessage, staged, @@ -67,25 +66,8 @@ const PopupLayer = (props: Props): JSX.Element | null => { }; React.useEffect(() => { - if (!popupReference) { - return; - } - - const { height, width, x, y } = popupReference; - - const virtualElement = { - getBoundingClientRect: () => ({ - bottom: y + height, - height, - left: x, - right: x + width, - top: y, - width, - }), - }; - - setReference(virtualElement); - }, [popupReference]); + setReference(referenceId ? document.querySelector(`[data-ba-reference-id="${referenceId}"]`) : null); + }, [referenceId]); return ( <> diff --git a/src/popup/__tests__/PopupContainer-test.tsx b/src/popup/__tests__/PopupContainer-test.tsx index 70d6aba0f..7b53f2c5e 100644 --- a/src/popup/__tests__/PopupContainer-test.tsx +++ b/src/popup/__tests__/PopupContainer-test.tsx @@ -24,6 +24,7 @@ describe('PopupContainer', () => { expect(wrapper.find(PopupLayer).props()).toMatchObject({ isPromoting: false, mode: Mode.NONE, + referenceId: null, staged: null, status: CreatorStatus.init, store: defaults.store, diff --git a/src/popup/__tests__/PopupLayer-test.tsx b/src/popup/__tests__/PopupLayer-test.tsx index 105bf210a..7effa1964 100644 --- a/src/popup/__tests__/PopupLayer-test.tsx +++ b/src/popup/__tests__/PopupLayer-test.tsx @@ -8,7 +8,6 @@ import { Rect } from '../../@types'; jest.mock('../../components/Popups/PopupReply'); describe('PopupLayer', () => { - const popupReference = { height: 10, width: 10, x: 10, y: 10 }; const getRect = (): Rect => ({ type: 'rect', height: 50, @@ -31,7 +30,7 @@ describe('PopupLayer', () => { location: 1, message: '', mode: Mode.HIGHLIGHT, - popupReference, + referenceId: '123', resetCreator: jest.fn(), setMessage: jest.fn(), staged: getStagedHighlight(), @@ -39,18 +38,8 @@ describe('PopupLayer', () => { }; const getWrapper = (props = {}): ReactWrapper => mount(); - const mockSetReference = jest.fn(); - const mockReference = { - getBoundingClientRect: () => ({ - height: 10, - width: 10, - top: 10, - left: 10, - }), - }; - beforeEach(() => { - jest.spyOn(React, 'useState').mockImplementation(() => [mockReference, mockSetReference]); + document.body.innerHTML = `
`; }); describe('render()', () => { diff --git a/src/region/RegionCreation.tsx b/src/region/RegionCreation.tsx index 528d5a99a..82b6a10ca 100644 --- a/src/region/RegionCreation.tsx +++ b/src/region/RegionCreation.tsx @@ -12,7 +12,7 @@ type Props = { isRotated: boolean; location: number; resetCreator: () => void; - setReferenceShape: (rect: DOMRect) => void; + setReferenceId: (uuid: string) => void; setStaged: (staged: CreatorItemRegion | null) => void; setStatus: (status: CreatorStatus) => void; staged?: CreatorItemRegion | null; @@ -31,16 +31,6 @@ export default class RegionCreation extends React.PureComponent { state: State = {}; - componentDidUpdate(_prevProps: Props, prevState: State): void { - const { setReferenceShape } = this.props; - const { rectRef } = this.state; - const { rectRef: prevRectRef } = prevState; - - if (prevRectRef !== rectRef && rectRef) { - setReferenceShape(rectRef.getBoundingClientRect()); - } - } - handleAbort = (): void => { const { resetCreator } = this.props; resetCreator(); @@ -52,16 +42,17 @@ export default class RegionCreation extends React.PureComponent { setStatus(CreatorStatus.started); }; + handleStagedMount = (uuid: string): void => { + const { setReferenceId } = this.props; + setReferenceId(uuid); + }; + handleStop = (shape: Rect): void => { const { location, setStaged, setStatus } = this.props; setStaged({ location, shape }); setStatus(CreatorStatus.staged); }; - setRectRef = (rectRef: RegionRectRef): void => { - this.setState({ rectRef }); - }; - render(): JSX.Element | null { const { isCreating, isDiscoverabilityEnabled, isRotated, staged } = this.props; const canCreate = isCreating && !isRotated; @@ -83,7 +74,7 @@ export default class RegionCreation extends React.PureComponent { {staged && (
- +
)} diff --git a/src/region/RegionCreationContainer.tsx b/src/region/RegionCreationContainer.tsx index 58b8530f4..d89be0c9c 100644 --- a/src/region/RegionCreationContainer.tsx +++ b/src/region/RegionCreationContainer.tsx @@ -9,7 +9,7 @@ import { isFeatureEnabled, Mode, resetCreatorAction, - setReferenceShapeAction, + setReferenceIdAction, setStagedAction, setStatusAction, } from '../store'; @@ -36,7 +36,7 @@ export const mapStateToProps = (state: AppState, { location }: { location: numbe export const mapDispatchToProps = { resetCreator: resetCreatorAction, - setReferenceShape: setReferenceShapeAction, + setReferenceId: setReferenceIdAction, setStaged: setStagedAction, setStatus: setStatusAction, }; diff --git a/src/region/RegionRect.tsx b/src/region/RegionRect.tsx index 1c1d2dde2..9ff64214c 100644 --- a/src/region/RegionRect.tsx +++ b/src/region/RegionRect.tsx @@ -1,4 +1,6 @@ -import * as React from 'react'; +import React from 'react'; +import * as uuid from 'uuid'; +import noop from 'lodash/noop'; import classNames from 'classnames'; import { Shape } from '../@types'; import { styleShape } from './regionUtil'; @@ -7,18 +9,25 @@ import './RegionRect.scss'; type Props = { className?: string; isActive?: boolean; + onMount?: (uuid: string) => void; shape?: Shape; }; export type RegionRectRef = HTMLDivElement; export function RegionRect(props: Props, ref: React.Ref): JSX.Element { - const { className, isActive, shape } = props; + const uuidRef = React.useRef(uuid.v4()); + const { className, isActive, onMount = noop, shape } = props; + + React.useEffect(() => { + onMount(uuidRef.current); + }, [onMount]); return (
); diff --git a/src/region/__tests__/RegionCreation-test.tsx b/src/region/__tests__/RegionCreation-test.tsx index 89186715d..fd52d56e2 100644 --- a/src/region/__tests__/RegionCreation-test.tsx +++ b/src/region/__tests__/RegionCreation-test.tsx @@ -15,7 +15,7 @@ describe('RegionCreation', () => { location: 1, resetCreator: jest.fn(), setMessage: jest.fn(), - setReferenceShape: jest.fn(), + setReferenceId: jest.fn(), setStaged: jest.fn(), setStatus: jest.fn(), staged: null, @@ -78,6 +78,14 @@ describe('RegionCreation', () => { expect(defaults.setStatus).toHaveBeenCalledWith(CreatorStatus.staged); }); }); + + describe('handleStagedMount()', () => { + test('should dispatch setReferenceId with received uuid', () => { + instance.handleStagedMount('123'); + + expect(defaults.setReferenceId).toHaveBeenCalledWith('123'); + }); + }); }); describe('render()', () => { @@ -132,43 +140,4 @@ describe('RegionCreation', () => { }, ); }); - - describe('componentDidUpdate', () => { - const mockRectRef = { - getBoundingClientRect: () => ({ - height: 10, - width: 10, - top: 10, - left: 10, - }), - }; - - test('should call setReferenceShape if current rectRef and staged exist', () => { - const wrapper = getWrapper({ staged: {} }); - - wrapper.setState({ rectRef: mockRectRef }); - - expect(defaults.setReferenceShape).toHaveBeenCalled(); - }); - - test('should not call setReferenceShape if prev and current rectRef are the same', () => { - const wrapper = getWrapper(); - - wrapper.setProps({ staged: {} }); - - expect(defaults.setReferenceShape).not.toHaveBeenCalled(); - }); - - test('should not call setReferenceShape if rectRef does not exist', () => { - const wrapper = getWrapper(); - - wrapper.setState({ rectRef: mockRectRef }); - - expect(defaults.setReferenceShape).toHaveBeenCalledTimes(1); - - wrapper.setState({ rectRef: undefined }); - - expect(defaults.setReferenceShape).toHaveBeenCalledTimes(1); - }); - }); }); diff --git a/src/region/__tests__/RegionCreationContainer-test.tsx b/src/region/__tests__/RegionCreationContainer-test.tsx index 5b0dd3bd2..b6bacb4a9 100644 --- a/src/region/__tests__/RegionCreationContainer-test.tsx +++ b/src/region/__tests__/RegionCreationContainer-test.tsx @@ -26,7 +26,7 @@ describe('RegionCreationContainer', () => { isDiscoverabilityEnabled: false, location: 1, staged: null, - setReferenceShape: expect.any(Function), + setReferenceId: expect.any(Function), setStaged: expect.any(Function), setStatus: expect.any(Function), store: defaults.store, diff --git a/src/region/__tests__/RegionRect-test.tsx b/src/region/__tests__/RegionRect-test.tsx index 5a706965b..b785cda6a 100644 --- a/src/region/__tests__/RegionRect-test.tsx +++ b/src/region/__tests__/RegionRect-test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { MutableRefObject } from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import RegionRect from '../RegionRect'; import { styleShape } from '../regionUtil'; @@ -9,6 +9,12 @@ jest.mock('../regionUtil', () => ({ describe('RegionRect', () => { const getWrapper = (props = {}): ShallowWrapper => shallow(); + const mockInitalRef = { current: '123' } as MutableRefObject; + + beforeEach(() => { + jest.spyOn(React, 'useEffect').mockImplementation(f => f()); + jest.spyOn(React, 'useRef').mockImplementation(() => mockInitalRef); + }); describe('render', () => { test('should call styleShape with the provided shape prop value', () => { @@ -16,14 +22,24 @@ describe('RegionRect', () => { const wrapper = getWrapper({ shape }); expect(styleShape).toHaveBeenCalledWith(shape); - expect(wrapper.prop('style')).toEqual(shape); + expect(wrapper.find('div').prop('style')).toEqual(shape); }); test.each([true, false])('should render classNames correctly when isActive is %s', isActive => { const wrapper = getWrapper({ isActive }); + const divEl = wrapper.find('div'); + + expect(divEl.hasClass('ba-RegionRect')).toBe(true); + expect(divEl.hasClass('is-active')).toBe(isActive); + }); + }); + + describe('onMount()', () => { + test('should call onMount with generated uuid', () => { + const handleMount = jest.fn(); + getWrapper({ onMount: handleMount }); - expect(wrapper.hasClass('ba-RegionRect')).toBe(true); - expect(wrapper.hasClass('is-active')).toBe(isActive); + expect(handleMount).toHaveBeenCalledWith(expect.any(String)); }); }); }); diff --git a/src/store/creator/__mocks__/creatorState.ts b/src/store/creator/__mocks__/creatorState.ts index 86368f3fc..517c9b4ba 100644 --- a/src/store/creator/__mocks__/creatorState.ts +++ b/src/store/creator/__mocks__/creatorState.ts @@ -4,12 +4,7 @@ export default { cursor: 0, error: null, message: 'test', - referenceShape: { - height: 10, - width: 10, - x: 10, - y: 10, - }, + referenceId: '100001', staged: { location: 1, shape: { diff --git a/src/store/creator/__tests__/actions-test.ts b/src/store/creator/__tests__/actions-test.ts deleted file mode 100644 index c95339251..000000000 --- a/src/store/creator/__tests__/actions-test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import state from '../__mocks__/creatorState'; -import { setReferenceShapeAction } from '../actions'; - -describe('creator/actions', () => { - describe('setReferenceShapeAction', () => { - const arg = { - height: 10, - width: 10, - left: 10, - top: 10, - } as DOMRect; - test('should prepare the argument for the payload', async () => { - expect(setReferenceShapeAction(arg)).toEqual({ - payload: state.referenceShape, - type: 'SET_REFERENCE_SHAPE', - }); - }); - }); -}); diff --git a/src/store/creator/__tests__/reducer-test.ts b/src/store/creator/__tests__/reducer-test.ts index 245a6643e..991f8ec53 100644 --- a/src/store/creator/__tests__/reducer-test.ts +++ b/src/store/creator/__tests__/reducer-test.ts @@ -4,9 +4,10 @@ import { createAnnotationAction } from '../../annotations'; import { CreatorStatus } from '../types'; import { NewAnnotation } from '../../../@types'; import { + resetCreatorAction, setCursorAction, setMessageAction, - setReferenceShapeAction, + setReferenceIdAction, setStagedAction, setStatusAction, } from '../actions'; @@ -58,12 +59,12 @@ describe('store/creator/reducer', () => { }); }); - describe('setReferenceShapeAction', () => { + describe('setReferenceIdAction', () => { test('should set the reference shape in state', () => { - const payload = { height: 10, left: 10, top: 10, width: 10 } as DOMRect; - const newState = reducer(state, setReferenceShapeAction(payload)); + const payload = '123123'; + const newState = reducer(state, setReferenceIdAction(payload)); - expect(newState.referenceShape).toEqual({ height: 10, width: 10, x: 10, y: 10 }); + expect(newState.referenceId).toEqual('123123'); }); }); @@ -104,11 +105,39 @@ describe('store/creator/reducer', () => { toggleAnnotationModeAction, ); - expect(newState.cursor).toEqual(0); - expect(newState.error).toEqual(null); - expect(newState.message).toEqual(''); - expect(newState.staged).toEqual(null); - expect(newState.status).toEqual(CreatorStatus.init); + expect(newState).toEqual({ + cursor: 0, + error: null, + message: '', + referenceId: null, + staged: null, + status: CreatorStatus.init, + }); + }); + }); + + describe('resetCreatorAction', () => { + test('should reset some of the creator state', () => { + const error = new Error('error'); + const newState = reducer( + { + ...state, + cursor: 1, + error, + referenceId: '123', + status: CreatorStatus.rejected, + }, + resetCreatorAction, + ); + + expect(newState).toEqual({ + cursor: 1, + error, + message: '', + referenceId: null, + staged: null, + status: CreatorStatus.init, + }); }); }); }); diff --git a/src/store/creator/__tests__/selectors-test.ts b/src/store/creator/__tests__/selectors-test.ts index 397e7029d..41a4a8f99 100644 --- a/src/store/creator/__tests__/selectors-test.ts +++ b/src/store/creator/__tests__/selectors-test.ts @@ -2,7 +2,7 @@ import creatorState from '../__mocks__/creatorState'; import { CreatorStatus } from '../types'; import { getCreatorMessage, - getCreatorReferenceShape, + getCreatorReferenceId, getCreatorStaged, getCreatorStagedForLocation, getCreatorStatus, @@ -17,14 +17,9 @@ describe('store/annotations/selectors', () => { }); }); - describe('getCreatorReferenceShape', () => { - test('should return the current creator reference shape', () => { - expect(getCreatorReferenceShape(state)).toEqual({ - height: 10, - width: 10, - x: 10, - y: 10, - }); + describe('getCreatorReferenceId', () => { + test('should return the current creator reference id', () => { + expect(getCreatorReferenceId(state)).toEqual('100001'); }); }); diff --git a/src/store/creator/actions.ts b/src/store/creator/actions.ts index 063555439..c4d4bd2c0 100644 --- a/src/store/creator/actions.ts +++ b/src/store/creator/actions.ts @@ -1,27 +1,9 @@ import { createAction } from '@reduxjs/toolkit'; import { CreatorItem, CreatorStatus } from './types'; -import { Shape } from '../../@types'; - -type Payload = { - payload: Shape; -}; export const resetCreatorAction = createAction('RESET_CREATOR'); export const setCursorAction = createAction('SET_CREATOR_CURSOR'); export const setMessageAction = createAction('SET_CREATOR_MESSAGE'); -export const setReferenceShapeAction = createAction( - 'SET_REFERENCE_SHAPE', - (arg: DOMRect): Payload => { - const { height, left, top, width } = arg; - return { - payload: { - height, - width, - x: left, - y: top, - }, - }; - }, -); +export const setReferenceIdAction = createAction('SET_CREATOR_REFERENCE_ID'); export const setStagedAction = createAction('SET_CREATOR_STAGED'); export const setStatusAction = createAction('SET_CREATOR_STATUS'); diff --git a/src/store/creator/reducer.ts b/src/store/creator/reducer.ts index f7826d460..1d36a9dd5 100644 --- a/src/store/creator/reducer.ts +++ b/src/store/creator/reducer.ts @@ -5,7 +5,7 @@ import { resetCreatorAction, setCursorAction, setMessageAction, - setReferenceShapeAction, + setReferenceIdAction, setStagedAction, setStatusAction, } from './actions'; @@ -15,6 +15,7 @@ export const initialState = { cursor: 0, error: null, message: '', + referenceId: null, staged: null, status: CreatorStatus.init, }; @@ -32,14 +33,15 @@ export default createReducer(initialState, builder => }) .addCase(resetCreatorAction, state => { state.message = ''; + state.referenceId = null; state.staged = null; state.status = CreatorStatus.init; }) .addCase(setMessageAction, (state, { payload }) => { state.message = payload; }) - .addCase(setReferenceShapeAction, (state, { payload }) => { - state.referenceShape = payload; + .addCase(setReferenceIdAction, (state, { payload }) => { + state.referenceId = payload; }) .addCase(setStagedAction, (state, { payload }) => { state.staged = payload; diff --git a/src/store/creator/selectors.ts b/src/store/creator/selectors.ts index a7166f51e..da2b0c1da 100644 --- a/src/store/creator/selectors.ts +++ b/src/store/creator/selectors.ts @@ -1,12 +1,11 @@ import { AppState } from '../types'; import { CreatorItem, CreatorItemHighlight, CreatorItemRegion, CreatorStatus } from './types'; -import { Shape } from '../../@types'; type State = Pick; export const getCreatorCursor = (state: State): number => state.creator.cursor; export const getCreatorMessage = (state: State): string => state.creator.message; -export const getCreatorReferenceShape = (state: State): Shape | undefined => state.creator.referenceShape; +export const getCreatorReferenceId = (state: State): string | null => state.creator.referenceId; export const getCreatorStaged = (state: State): CreatorItem | null => state.creator.staged; export const getCreatorStagedForLocation = (state: State, location: number): CreatorItem | null => { const staged = getCreatorStaged(state); diff --git a/src/store/creator/types.ts b/src/store/creator/types.ts index 0ce619649..ad2d2c2e0 100644 --- a/src/store/creator/types.ts +++ b/src/store/creator/types.ts @@ -1,4 +1,4 @@ -import { Rect, SerializedError, Shape } from '../../@types'; +import { Rect, SerializedError } from '../../@types'; export enum CreatorStatus { init = 'init', @@ -26,7 +26,7 @@ export type CreatorState = { cursor: number; error: SerializedError | null; message: string; - referenceShape?: Shape; + referenceId: string | null; staged: CreatorItem; status: CreatorStatus; }; diff --git a/yarn.lock b/yarn.lock index 01c06a79e..9e7b2adaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1943,6 +1943,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +"@types/uuid@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" + integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== + "@types/vfile-message@*": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/vfile-message/-/vfile-message-2.0.0.tgz#690e46af0fdfc1f9faae00cd049cc888957927d5" @@ -13504,6 +13509,11 @@ uuid@^3.3.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.1: + version "8.3.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" + integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== + v8-compile-cache@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"