From 2b0346fe84c968195671a349c30bbfe35a89e38b Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Wed, 23 Feb 2022 11:55:53 -0500 Subject: [PATCH] chore: add tests for EditorHeader hooks (#18) --- src/editors/components/EditorHeader/hooks.js | 69 +++++--- .../components/EditorHeader/hooks.test.js | 154 ++++++++++++++++++ 2 files changed, 199 insertions(+), 24 deletions(-) create mode 100644 src/editors/components/EditorHeader/hooks.test.js diff --git a/src/editors/components/EditorHeader/hooks.js b/src/editors/components/EditorHeader/hooks.js index 2b2c7fd6c..42a490a32 100644 --- a/src/editors/components/EditorHeader/hooks.js +++ b/src/editors/components/EditorHeader/hooks.js @@ -1,23 +1,31 @@ import React from 'react'; +import * as module from './hooks'; -/* eslint-disable import/prefer-default-export */ -export const localTitleHooks = ({ - editorRef, - setBlockTitle, - typeHeader, -}) => { - console.log('localTitleHooks'); - const [isEditing, setIsEditing] = React.useState(false); - const startEditing = () => setIsEditing(true); - const stopEditing = () => setIsEditing(false); - const [localTitle, setLocalTitle] = React.useState(typeHeader); - const inputRef = React.createRef(); - const updateTitle = () => { - setBlockTitle(localTitle); - stopEditing(); - }; - - const handleKeyDown = (e) => { +export const hooks = { + isEditing: () => { + const [isEditing, setIsEditing] = React.useState(false); + return { + isEditing, + startEditing: () => setIsEditing(true), + stopEditing: () => setIsEditing(false), + }; + }, + localTitle: ({ + setBlockTitle, + stopEditing, + typeHeader, + }) => { + const [localTitle, setLocalTitle] = React.useState(typeHeader); + return { + updateTitle: () => { + setBlockTitle(localTitle); + stopEditing(); + }, + handleChange: (e) => setLocalTitle(e.target.value), + localTitle, + }; + }, + handleKeyDown: ({ stopEditing, editorRef }) => (e) => { if (e.key === 'Enter') { stopEditing(); } @@ -25,18 +33,31 @@ export const localTitleHooks = ({ e.preventDefault(); editorRef.current.focus(); } - }; - - const handleChange = (e) => setLocalTitle(e.target.value); + }, +}; +/* eslint-disable import/prefer-default-export */ +export const localTitleHooks = ({ + editorRef, + setBlockTitle, + typeHeader, +}) => { + const { isEditing, startEditing, stopEditing } = module.hooks.isEditing(); + const { localTitle, handleChange, updateTitle } = module.hooks.localTitle({ + setBlockTitle, + stopEditing, + typeHeader, + }); return { isEditing, - handleChange, startEditing, stopEditing, + localTitle, - inputRef, - handleKeyDown, updateTitle, + handleChange, + + inputRef: React.createRef(), + handleKeyDown: module.hooks.handleKeyDown({ stopEditing, editorRef }), }; }; diff --git a/src/editors/components/EditorHeader/hooks.test.js b/src/editors/components/EditorHeader/hooks.test.js new file mode 100644 index 000000000..a6f182150 --- /dev/null +++ b/src/editors/components/EditorHeader/hooks.test.js @@ -0,0 +1,154 @@ +import React from 'react'; +import * as module from './hooks'; + +jest.mock('react', () => { + const updateState = jest.fn(); + return { + updateState, + useState: jest.fn(val => ([{ state: val }, (newVal) => updateState({ val, newVal })])), + createRef: jest.fn(val => ({ ref: val })), + }; +}); + +describe('EditorHeader hooks', () => { + const editorRef = { current: {} }; + const typeHeader = 'Type Header'; + let setBlockTitle; + let output; + beforeEach(() => { + setBlockTitle = jest.fn(); + }); + describe('base hooks', () => { + describe('isEditing', () => { + beforeEach(() => { + output = module.hooks.isEditing(); + }); + test('returns isEditing field, defaulted to false', () => { + expect(output.isEditing).toEqual({ state: false }); + }); + test('startEditing calls the setter function with true', () => { + output.startEditing(); + expect(React.updateState).toHaveBeenCalledWith({ val: false, newVal: true }); + }); + test('stopEditign calls the setter function with false', () => { + output.stopEditing(); + expect(React.updateState).toHaveBeenCalledWith({ val: false, newVal: false }); + }); + }); + describe('localTitle', () => { + let stopEditing; + beforeEach(() => { + stopEditing = jest.fn(); + output = module.hooks.localTitle({ + setBlockTitle, + stopEditing, + typeHeader, + }); + }); + test('returns the state value for localTitle, defaulted to typeHeader', () => { + expect(output.localTitle).toEqual({ state: typeHeader }); + }); + describe('updateTitle hook', () => { + it('calls setBlockTitle with localTitle, and stopEditing', () => { + output.updateTitle(); + expect(setBlockTitle).toHaveBeenCalledWith(output.localTitle); + expect(stopEditing).toHaveBeenCalled(); + }); + }); + describe('handleChange', () => { + it('calls setLocalTitle with the event target value', () => { + const value = 'SOME VALUe'; + output.handleChange({ target: { value } }); + expect(React.updateState).toHaveBeenCalledWith({ + val: typeHeader, + newVal: value, + }); + }); + }); + }); + describe('handleKeyDown', () => { + let stopEditing; + beforeEach(() => { + stopEditing = jest.fn(); + editorRef.current.focus = jest.fn(); + output = module.hooks.handleKeyDown({ stopEditing, editorRef }); + }); + describe('Enter-key event', () => { + it('calls stopEditing', () => { + output({ key: 'Enter' }); + expect(stopEditing).toHaveBeenCalled(); + }); + }); + describe('tab event', () => { + it('calls preventDefault on the event, and focuses to the editorRef', () => { + const preventDefault = jest.fn(); + output({ key: 'Tab', preventDefault }); + expect(preventDefault).toHaveBeenCalled(); + expect(editorRef.current.focus).toHaveBeenCalled(); + }); + it('does nothing if editorRef is not instantiated', () => { + const preventDefault = jest.fn(); + output = module.hooks.handleKeyDown({ stopEditing, editorRef: null }); + output({ key: 'Tab', preventDefault }); + expect(preventDefault).not.toHaveBeenCalled(); + }); + }); + }); + }); + describe('local title hooks', () => { + let oldHooks; + const values = { + isEditing: 'ISeDITING', + startEditing: 'STARTeDITING', + stopEditing: 'STOPeDITING', + handleChange: 'HANDLEcHANGE', + localTitle: 'LOCALtITLE', + }; + const newHooks = { + isEditing: () => ({ + isEditing: values.isEditing, + startEditing: values.startEditing, + stopEditing: values.stopEditing, + }), + localTitle: jest.fn((args) => ({ + updateTitle: args, + handleChange: values.handleChange, + localTitle: values.localTitle, + })), + handleKeyDown: jest.fn(args => ({ handleKeyDown: args })), + }; + beforeEach(() => { + oldHooks = module.hooks; + module.hooks.isEditing = newHooks.isEditing; + module.hooks.localTitle = newHooks.localTitle; + module.hooks.handleKeyDown = newHooks.handleKeyDown; + output = module.localTitleHooks({ editorRef, setBlockTitle, typeHeader }); + }); + afterEach(() => { + module.hooks = oldHooks; + }); + it('returns isEditing, startEditing, and stopEditing, tied to the isEditing hook', () => { + expect(output.isEditing).toEqual(values.isEditing); + expect(output.startEditing).toEqual(values.startEditing); + expect(output.stopEditing).toEqual(values.stopEditing); + }); + it('returns localTitle, updateTitle, and handleChange, tied to the localTitle hook', () => { + expect(output.updateTitle).toEqual({ + setBlockTitle, + stopEditing: values.stopEditing, + typeHeader, + }); + expect(output.handleChange).toEqual(values.handleChange); + expect(output.localTitle).toEqual(values.localTitle); + }); + it('returns a new ref for inputRef', () => { + expect(output.inputRef).toEqual({ ref: undefined }); + }); + it('returns handleKeyDown, tied to handleKeyDown hook', () => { + expect(output.handleKeyDown).toEqual(newHooks.handleKeyDown({ + stopEditing: values.stopEditing, + editorRef, + })); + }); + }); +});