diff --git a/packages/app/obojobo-document-engine/__tests__/oboeditor/components/visual-editor.test.js b/packages/app/obojobo-document-engine/__tests__/oboeditor/components/visual-editor.test.js
index 7bd38f546e..7f11446b51 100644
--- a/packages/app/obojobo-document-engine/__tests__/oboeditor/components/visual-editor.test.js
+++ b/packages/app/obojobo-document-engine/__tests__/oboeditor/components/visual-editor.test.js
@@ -1272,6 +1272,122 @@ describe('VisualEditor', () => {
expect(onKeyDown).toHaveBeenCalled()
})
+ describe('moving to node above', () => {
+ const editorDefaults = {
+ children: [
+ { type: 'mock node 1', children: [{ text: 'one' }] },
+ { type: 'mock node 2', children: [{ text: 'one' }, { text: 'two' }] },
+ { type: 'mock node 3', children: [{ text: 'one' }] }
+ ],
+ apply: jest.fn()
+ }
+
+ const visualEditorProps = {
+ page: {
+ attributes: { children: [] },
+ get: jest.fn(),
+ toJSON: () => ({ children: [{}] }),
+ set: jest.fn(),
+ children: []
+ },
+ model: {
+ title: 'Mock Title',
+ flatJSON: () => ({ content: {} }),
+ children: []
+ },
+ draft: { accessLevel: FULL }
+ }
+
+ test('onKeyDown handles ArrowUp if node above has one child', () => {
+ jest.spyOn(Array, 'from').mockReturnValue([])
+ const spySetSelection = jest.spyOn(Transforms, 'setSelection')
+
+ const editor = {
+ ...editorDefaults,
+ selection: {
+ anchor: { path: [1], offset: 0 },
+ focus: { path: [1], offset: 0 }
+ }
+ }
+
+ const component = mount()
+ const instance = component.instance()
+ instance.editor = editor
+
+ instance.onKeyDown({
+ preventDefault: jest.fn(),
+ key: 'ArrowUp',
+ defaultPrevented: false
+ })
+
+ expect(spySetSelection).toHaveBeenCalled()
+ expect(spySetSelection.mock.calls[0].length).toEqual(2)
+
+ const focusSelection = spySetSelection.mock.calls[0][1].focus.path
+ const anchorSelection = spySetSelection.mock.calls[0][1].anchor.path
+
+ expect(focusSelection).toEqual([0, 0])
+ expect(anchorSelection).toEqual([0, 0])
+ })
+
+ test('onKeyDown handles ArrowUp if node above has multiple children', () => {
+ jest.spyOn(Array, 'from').mockReturnValue([])
+ const spySetSelection = jest.spyOn(Transforms, 'setSelection')
+
+ const editor = {
+ ...editorDefaults,
+ selection: {
+ anchor: { path: [2], offset: 0 },
+ focus: { path: [2], offset: 0 }
+ }
+ }
+
+ const component = mount()
+ const instance = component.instance()
+ instance.editor = editor
+
+ instance.onKeyDown({
+ preventDefault: jest.fn(),
+ key: 'ArrowUp',
+ defaultPrevented: false
+ })
+
+ expect(spySetSelection).toHaveBeenCalled()
+ expect(spySetSelection.mock.calls[0].length).toEqual(2)
+
+ const focusSelection = spySetSelection.mock.calls[0][1].focus.path
+ const anchorSelection = spySetSelection.mock.calls[0][1].anchor.path
+
+ expect(focusSelection).toEqual([1, 1])
+ expect(anchorSelection).toEqual([1, 1])
+ })
+
+ test('onKeyDown handles ArrowUp if no node above', () => {
+ jest.spyOn(Array, 'from').mockReturnValue([])
+ const spySetSelection = jest.spyOn(Transforms, 'setSelection')
+
+ const editor = {
+ ...editorDefaults,
+ selection: {
+ anchor: { path: [0], offset: 0 },
+ focus: { path: [0], offset: 0 }
+ }
+ }
+
+ const component = mount()
+ const instance = component.instance()
+ instance.editor = editor
+
+ instance.onKeyDown({
+ preventDefault: jest.fn(),
+ key: 'ArrowUp',
+ defaultPrevented: false
+ })
+
+ expect(spySetSelection).not.toHaveBeenCalled()
+ })
+ })
+
test('reload disables event listener and calls location.reload', () => {
jest.spyOn(window, 'removeEventListener').mockReturnValueOnce()
Object.defineProperty(window, 'location', {
diff --git a/packages/app/obojobo-document-engine/src/scripts/oboeditor/components/visual-editor.js b/packages/app/obojobo-document-engine/src/scripts/oboeditor/components/visual-editor.js
index 700f66b907..544f1d526c 100644
--- a/packages/app/obojobo-document-engine/src/scripts/oboeditor/components/visual-editor.js
+++ b/packages/app/obojobo-document-engine/src/scripts/oboeditor/components/visual-editor.js
@@ -540,6 +540,36 @@ class VisualEditor extends React.Component {
item.plugins.onKeyDown(entry, this.editor, event)
}
}
+
+ // Handle ArrowUp from a node - this is default behavior
+ // in Chrome and Safari but not Firefox
+ if (event.key === 'ArrowUp' && !event.defaultPrevented) {
+ event.preventDefault()
+
+ const currentNode = this.editor.selection.anchor.path[0]
+
+ // Break out if already at top node
+ if (currentNode === 0) return
+
+ const aboveNode = currentNode - 1
+ const numChildrenAbove = this.editor.children[aboveNode].children.length
+
+ let abovePath = [aboveNode]
+
+ // If entering a node with multiple children (a table),
+ // go to the last child (bottom row)
+ if (numChildrenAbove > 1) {
+ abovePath = [aboveNode, numChildrenAbove - 1]
+ }
+
+ const focus = Editor.start(this.editor, abovePath)
+ const anchor = Editor.start(this.editor, abovePath)
+
+ Transforms.setSelection(this.editor, {
+ focus,
+ anchor
+ })
+ }
}
// Generates any necessary decorations, such as place holders
diff --git a/packages/obonode/obojobo-chunks-table/components/cell/__snapshots__/editor-component.test.js.snap b/packages/obonode/obojobo-chunks-table/components/cell/__snapshots__/editor-component.test.js.snap
index 823fbac872..6c4ed33d5a 100644
--- a/packages/obonode/obojobo-chunks-table/components/cell/__snapshots__/editor-component.test.js.snap
+++ b/packages/obonode/obojobo-chunks-table/components/cell/__snapshots__/editor-component.test.js.snap
@@ -9,6 +9,7 @@ exports[`Cell Editor Node Cell component as selected header 1`] = `