/*!
 * VisualEditor ContentEditable linear arrow key down handler tests.
 *
 * @copyright See AUTHORS.txt
 */

QUnit.module( 've.ce.LinearArrowKeyDownHandler', {
	// See https://github.com/platinumazure/eslint-plugin-qunit/issues/68
	// eslint-disable-next-line qunit/resolve-async
	beforeEach: function ( assert ) {
		const done = assert.async();
		return ve.init.platform.getInitializedPromise().then( done );
	}
} );

QUnit.test( 'special key down: linear arrow keys', ( assert ) => {
	const done = assert.async(),
		supportsSelectionExtend = ve.supportsSelectionExtend,
		complexTableDoc = ve.dm.example.createExampleDocument( 'complexTable' ),
		slugDoc = ve.dm.example.createExampleDocumentFromData( [
			{ type: 'alienBlock' }, { type: '/alienBlock' },
			{ type: 'internalList' }, { type: '/internalList' }
		] ),
		inlineFocusableDoc = ve.dm.example.createExampleDocumentFromData( [
			{ type: 'paragraph' }, ...'Foo ', { type: 'alienInline' }, { type: '/alienInline' }, ...' Bar', { type: '/paragraph' },
			{ type: 'internalList' }, { type: '/internalList' }
		] ),
		blockImageDoc = ve.dm.example.createExampleDocumentFromData( [
			{ type: 'paragraph' }, ...'Foo', { type: '/paragraph' },
			...ve.dm.example.blockImage.data,
			{ type: 'paragraph' }, ...'Bar', { type: '/paragraph' },
			{ type: 'internalList' }, { type: '/internalList' }
		] ),
		cases = [
			// Within normal text. NOTE: these tests manually force the cursor to
			// move, because we rely on native browser actions for that.
			// As such, these are mostly testing to make sure that other
			// behavior doesn't trigger when it shouldn't.
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 2 ),
				keys: [ 'LEFT' ],
				forceSelection: new ve.Range( 1 ),
				expectedRangeOrSelection: new ve.Range( 1 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor left in text'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 2 ),
				keys: [ 'RIGHT' ],
				forceSelection: new ve.Range( 3 ),
				expectedRangeOrSelection: new ve.Range( 3 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor right in text'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 4 ),
				keys: [ 'UP' ],
				forceSelection: new ve.Range( 1 ),
				expectedRangeOrSelection: new ve.Range( 1 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor up in text'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 20 ),
				keys: [ 'DOWN' ],
				forceSelection: new ve.Range( 22 ),
				expectedRangeOrSelection: new ve.Range( 22 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor down in text'
			},
			// Cursor with shift held to adjust selection
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 2 ),
				keys: [ 'SHIFT+LEFT' ],
				forceSelection: new ve.Range( 1 ),
				expectedRangeOrSelection: new ve.Range( 2, 1 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor left in text with shift'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 3, 2 ),
				keys: [ 'SHIFT+LEFT' ],
				// No forceSelection, so just check the selection is preserved
				expectedRangeOrSelection: new ve.Range( 3, 2 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor left in text with shift on a backwards selection'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 3, 2 ),
				setup: function () {
					ve.supportsSelectionExtend = false;
				},
				teardown: function () {
					ve.supportsSelectionExtend = supportsSelectionExtend;
				},
				keys: [ 'SHIFT+LEFT' ],
				// No forceSelection, so just check the selection is preserved
				expectedRangeOrSelection: new ve.Range( 3, 2 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor left in text with shift on a backwards selection with no extend'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 2 ),
				keys: [ 'SHIFT+RIGHT' ],
				forceSelection: new ve.Range( 3 ),
				expectedRangeOrSelection: new ve.Range( 2, 3 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor right in text with shift'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 4 ),
				keys: [ 'SHIFT+UP' ],
				forceSelection: new ve.Range( 1 ),
				expectedRangeOrSelection: new ve.Range( 4, 1 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor up in text with shift'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 20 ),
				keys: [ 'SHIFT+DOWN' ],
				forceSelection: new ve.Range( 22 ),
				expectedRangeOrSelection: new ve.Range( 20, 22 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor down in text with shift'
			},
			// While focusing a block node
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'LEFT' ],
				expectedRangeOrSelection: new ve.Range( 4 ),
				msg: 'Cursor left off a block node'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'HOME' ],
				expectedRangeOrSelection: new ve.Range( 4 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor home off a block node'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'UP' ],
				expectedRangeOrSelection: new ve.Range( 4 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor up off a block node'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'PAGEUP' ],
				expectedRangeOrSelection: new ve.Range( 4 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor page up off a block node'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'RIGHT' ],
				expectedRangeOrSelection: new ve.Range( 19 ),
				msg: 'Cursor right off a block node'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'END' ],
				expectedRangeOrSelection: new ve.Range( 19 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor end off a block node'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'DOWN' ],
				expectedRangeOrSelection: new ve.Range( 19 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor down off a block node'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'PAGEDOWN' ],
				expectedRangeOrSelection: new ve.Range( 19 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor page down off a block node'
			},
			// ...with shift
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'SHIFT+LEFT' ],
				expectedRangeOrSelection: new ve.Range( 18, 4 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor left off a block node with shift'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'SHIFT+UP' ],
				expectedRangeOrSelection: new ve.Range( 18, 4 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor up off a block node with shift'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'SHIFT+RIGHT' ],
				expectedRangeOrSelection: new ve.Range( 5, 19 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor right off a block node with shift'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 5, 18 ),
				keys: [ 'SHIFT+DOWN' ],
				expectedRangeOrSelection: new ve.Range( 5, 19 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor down off a block node with shift'
			},
			// Cursoring onto a block node, which should focus it
			// Again, these are forcibly moving the cursor, so it's not a perfect
			// test; it's more checking how we fix up the selection afterwards.
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 4 ),
				keys: [ 'RIGHT' ],
				// Force cursor into the cursor holder before the block image
				forceSelection: {
					anchorNode: '.ve-ce-cursorHolder-before',
					// Emulating Chromium 50, right arrow lands at offset 0
					anchorOffset: 0,
					focusNode: '.ve-ce-cursorHolder-before',
					focusOffset: 0
				},
				expectedRangeOrSelection: new ve.Range( 5, 18 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor right onto a block node'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 19 ),
				keys: [ 'LEFT' ],
				// Force cursor into the cursor holder after the block image
				forceSelection: {
					anchorNode: '.ve-ce-cursorHolder-after',
					// Emulating Chromium 50, left arrow lands at offset 1
					anchorOffset: 1,
					focusNode: '.ve-ce-cursorHolder-after',
					focusOffset: 1
				},
				expectedRangeOrSelection: new ve.Range( 18, 5 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor left onto a block node'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 4 ),
				keys: [ 'DOWN' ],
				// Force cursor into the cursor holder before the block image
				forceSelection: {
					anchorNode: '.ve-ce-cursorHolder-before',
					// Emulating Chromium 50, down arrow lands at offset 0
					anchorOffset: 0,
					focusNode: '.ve-ce-cursorHolder-before',
					focusOffset: 0
				},
				expectedRangeOrSelection: new ve.Range( 5, 18 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor down onto a block node'
			},
			{
				htmlOrDoc: blockImageDoc,
				rangeOrSelection: new ve.Range( 20 ),
				keys: [ 'UP' ],
				// Force cursor into the cursor holder after the block image
				forceSelection: {
					anchorNode: '.ve-ce-cursorHolder-after',
					// Emulating Chromium 50, up arrow lands at offset 0
					anchorOffset: 0,
					focusNode: '.ve-ce-cursorHolder-after',
					focusOffset: 0
				},
				expectedRangeOrSelection: new ve.Range( 18, 5 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Cursor up onto a block node'
			},
			{
				htmlOrDoc: complexTableDoc,
				rangeOrSelection: new ve.Range( 3 ),
				keys: [ 'TAB' ],
				expectedRangeOrSelection: {
					type: 'table',
					tableRange: new ve.Range( 0, 51 ),
					fromCol: 0,
					fromRow: 0
				},
				msg: 'Tab inside a table caption moves to first row of table'
			},
			{
				htmlOrDoc: complexTableDoc,
				rangeOrSelection: new ve.Range( 3 ),
				keys: [ 'SHIFT+TAB' ],
				expectedRangeOrSelection: new ve.Range( 0 ),
				msg: 'Shift+tab inside a table caption moves out of table'
			},
			{
				htmlOrDoc: complexTableDoc,
				rangeOrSelection: new ve.Range( 3 ),
				keys: [ 'CTRL+TAB' ],
				expectedRangeOrSelection: new ve.Range( 3 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Ctrl+tab is always ignored'
			},
			{
				rangeOrSelection: new ve.Range( 1 ),
				keys: [ 'TAB' ],
				expectedRangeOrSelection: new ve.Range( 1 ),
				msg: 'Tab outside a table prevents default'
			},
			{
				htmlOrDoc: slugDoc,
				rangeOrSelection: new ve.Range( 0 ),
				keys: [ 'DOWN' ],
				expectedRangeOrSelection: new ve.Range( 0, 2 ),
				msg: 'Down from a block slug'
			},
			{
				htmlOrDoc: slugDoc,
				rangeOrSelection: new ve.Range( 0 ),
				keys: [ 'RIGHT' ],
				expectedRangeOrSelection: new ve.Range( 0, 2 ),
				msg: 'Right from a block slug'
			},
			{
				htmlOrDoc: slugDoc,
				rangeOrSelection: new ve.Range( 2 ),
				keys: [ 'UP' ],
				expectedRangeOrSelection: new ve.Range( 2, 0 ),
				msg: 'Up from a block slug'
			},
			{
				htmlOrDoc: slugDoc,
				rangeOrSelection: new ve.Range( 2 ),
				keys: [ 'LEFT' ],
				expectedRangeOrSelection: new ve.Range( 2, 0 ),
				msg: 'Left from a block slug'
			},
			{
				htmlOrDoc: inlineFocusableDoc,
				rangeOrSelection: new ve.Range( 5, 7 ),
				keys: [ 'LEFT' ],
				expectedRangeOrSelection: new ve.Range( 5 ),
				msg: 'Left off an inline focusable'
			},
			{
				htmlOrDoc: inlineFocusableDoc,
				rangeOrSelection: new ve.Range( 5, 7 ),
				keys: [ 'RIGHT' ],
				expectedRangeOrSelection: new ve.Range( 7 ),
				msg: 'Right off an inline focusable'
			},
			{
				htmlOrDoc: inlineFocusableDoc,
				rangeOrSelection: new ve.Range( 5, 7 ),
				keys: [ 'SHIFT+RIGHT' ],
				forceSelection: new ve.Range( 8 ),
				expectedRangeOrSelection: new ve.Range( 5, 8 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Shift+right off an inline focusable'
			},
			{
				htmlOrDoc: inlineFocusableDoc,
				rangeOrSelection: new ve.Range( 5, 7 ),
				keys: [ 'SHIFT+LEFT' ],
				forceSelection: new ve.Range( 4 ),
				expectedRangeOrSelection: new ve.Range( 7, 4 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Shift+left off an inline focusable'
			},
			{
				htmlOrDoc: inlineFocusableDoc,
				rangeOrSelection: new ve.Range( 7, 5 ),
				keys: [ 'SHIFT+LEFT' ],
				forceSelection: new ve.Range( 4 ),
				expectedRangeOrSelection: new ve.Range( 7, 4 ),
				expectedDefaultPrevented: [ false ],
				msg: 'Shift+left off an inline focusable (backwards selection)'
			}
		];

	let promise = Promise.resolve();
	cases.forEach( ( caseItem ) => {
		promise = promise.then( () => ve.test.utils.runSurfaceHandleSpecialKeyTest( assert, caseItem ) );
	} );

	promise.finally( () => done() );
} );
