| <!doctype html> |
| <script src="../resources/testharness.js"></script> |
| <script src="../resources/testharnessreport.js"></script> |
| <script src="assert_selection.js"></script> |
| <script> |
| function checked_assert_selection(input, command, output) { |
| try { |
| assert_selection(input, command, output); |
| } catch (exception) { |
| return exception.message; |
| } |
| return 'no exception'; |
| } |
| |
| test(() => { |
| assert_selection('foo', 'noop', 'foo'); |
| assert_selection('|foo', 'noop', '|foo'); |
| assert_selection('f|oo', 'noop', 'f|oo'); |
| assert_selection('foo|', 'noop', 'foo|'); |
| assert_selection('^foo|', 'noop', '^foo|'); |
| assert_selection('f^oo|', 'noop', 'f^oo|'); |
| assert_selection('f^o|o', 'noop', 'f^o|o'); |
| assert_selection('|foo^', 'noop', '|foo^'); |
| assert_selection( |
| '|foo^', |
| selection => selection.modify('extend', 'forward', 'character'), |
| 'f|oo^', 'selection.modify'); |
| assert_selection( |
| '<div contenteditable>^foo|</div>', |
| 'bold', |
| '<div contenteditable><b>^foo|</b></div>', |
| 'execCommand'); |
| }, 'markers in text'); |
| |
| test(() => { |
| assert_selection('|<img>', 'noop', '|<img>'); |
| assert_selection('^<img>|', 'noop', '^<img>|'); |
| assert_selection('|<img>^', 'noop', '|<img>^'); |
| assert_selection('<a>|<br></a>', 'noop', '<a>|<br></a>'); |
| assert_selection('<a><img>|</a>', 'noop', '<a><img>|</a>'); |
| assert_selection('<a>^<img>|</a>', 'noop', '<a>^<img>|</a>'); |
| assert_selection('<a>|<img>^</a>', 'noop', '<a>|<img>^</a>'); |
| assert_selection('<a><img>|<img></a>', 'noop', '<a><img>|<img></a>'); |
| assert_selection('<a><img>|<img>^</a>', 'noop', '<a><img>|<img>^</a>'); |
| assert_selection('<a><img>^<img>|</a>', 'noop', '<a><img>^<img>|</a>'); |
| }, 'markers at element'); |
| |
| test(() => { |
| assert_selection( |
| '|<table><tr><td>foo</td></tr></table>', 'noop', |
| '|<table><tbody><tr><td>foo</td></tr></tbody></table>'); |
| assert_selection( |
| '<table><tr><td>foo</td></tr></table>|', 'noop', |
| '<table><tbody><tr><td>foo</td></tr></tbody></table>|'); |
| assert_selection( |
| '^<table><tr><td>foo</td></tr></table>|', 'noop', |
| '^<table><tbody><tr><td>foo</td></tr></tbody></table>|'); |
| assert_selection( |
| '|<table><tr><td>foo</td></tr></table>^', 'noop', |
| '|<table><tbody><tr><td>foo</td></tr></tbody></table>^'); |
| }, 'markers around table'); |
| |
| selection_test( |
| '<div>foo</div>', |
| selection => { |
| let doc = selection.document; |
| doc.documentElement.replaceChild( |
| doc.createTextNode('baz'), doc.body); |
| }, |
| '<html><head></head>baz</html>', |
| 'Serialize document element instead of document.body when it is null.'); |
| |
| // Tests for TEXTAREA |
| selection_test( |
| '<div>|<textarea>foo</textarea></div>', |
| 'noop', |
| '<div>|<textarea>foo</textarea></div>', |
| 'We can place caret before TEXTAREA'); |
| |
| selection_test( |
| '<div><textarea>foo</textarea>|</div>', |
| 'noop', |
| '<div><textarea>foo</textarea>|</div>', |
| 'We can place caret after TEXTAREA'); |
| |
| selection_test( |
| '<div>^<textarea>foo</textarea>|</div>', |
| 'noop', |
| '<div>^<textarea>foo</textarea>|</div>', |
| 'We can select TEXTAREA'); |
| |
| selection_test( |
| '<div><textarea>f|oo</textarea></div>', |
| selection => { |
| const textarea = selection.document.querySelector('textarea'); |
| textarea.value = 'bar'; |
| }, |
| '<div><textarea>bar|</textarea></div>', |
| 'TEXTAREA is serialized with value property'); |
| |
| selection_test( |
| '<div><textarea>01234|56789</textarea></div>', |
| selection => { |
| const textarea = selection.document.querySelector('textarea'); |
| textarea.setSelectionRange(3, 7); |
| }, |
| '<div><textarea>012^3456|789</textarea></div>', |
| 'HTMLTextArea#setSelectionRange()'); |
| |
| selection_test( |
| '<textarea>0123^456|789</textarea>', |
| selection => { |
| const textarea = selection.document.querySelector('textarea'); |
| assert_equals(textarea.selectionStart, 4, 'selectionStart'); |
| assert_equals(textarea.selectionEnd, 7, 'selectionEnd'); |
| }, |
| '<textarea>0123^456|789</textarea>', |
| 'range selection'); |
| |
| selection_test( |
| '<textarea>0123|456^789</textarea>', |
| selection => { |
| const textarea = selection.document.querySelector('textarea'); |
| assert_equals(textarea.selectionStart, 4, 'selectionStart'); |
| assert_equals(textarea.selectionEnd, 7, 'selectionEnd'); |
| assert_equals(textarea.selectionDirection, 'backward', 'selectionEnd'); |
| }, |
| '<textarea>0123|456^789</textarea>', |
| 'backward range selection'); |
| |
| selection_test( |
| '<textarea>01234|56789</textarea>', |
| selection => { |
| const textarea = selection.document.querySelector('textarea'); |
| assert_equals(textarea.selectionStart, 5, 'selectionStart'); |
| assert_equals(textarea.selectionEnd, 5, 'selectionEnd'); |
| }, |
| '<textarea>01234|56789</textarea>', |
| 'caret selection'); |
| |
| // Shadow DOM |
| selection_test( |
| [ |
| '<div id="host">', |
| '<b id="abc" slot="abc">abc</b>', |
| '<b id="def" slot="def">def</b>', |
| '</div>', |
| ].join(''), |
| selection => { |
| const document = selection.document; |
| const host = document.getElementById('host'); |
| const shadowRoot = host.attachShadow({mode: 'open'}); |
| shadowRoot.innerHTML = [ |
| '<span id="ghi">ghi</span>', |
| '<slot name="def"></slot>', |
| '<span id="jkl">jkl</span>', |
| '<slot name="abc"></slot>', |
| '<span id="mno">mno</span>', |
| ].join(''); |
| selection.collapse(document.getElementById('abc'), 0); |
| selection.extend(document.getElementById('def'), 0); |
| }, |
| [ |
| '<div id="host">', |
| '<span id="ghi">ghi</span>', |
| '<slot name="def">', |
| '<b id="def" slot="def">|def</b>', |
| '</slot>', |
| '<span id="jkl">jkl</span>', |
| '<slot name="abc">', |
| '<b id="abc" slot="abc">^abc</b>', |
| '</slot>', |
| '<span id="mno">mno</span>', |
| '</div>', |
| ].join(''), |
| {dumpAs: 'flattree'}, 'dump flat tree for shadow DOM V1'); |
| |
| selection_test( |
| [ |
| '<style>* { font-family: monospace; }</style>', |
| '<div>', |
| '<span id="start">ABC</span>', |
| '<p><a slot="a">111</a><b slot="b">222</b></p>', |
| 'DEF', |
| '</div>', |
| ], |
| selection => { |
| if (!window.eventSender) |
| throw 'This test requires eventSender.'; |
| const document = selection.document; |
| const host = document.querySelector('p'); |
| const shadowRoot = host.attachShadow({mode: 'open'}); |
| shadowRoot.innerHTML = [ |
| 'ghi', |
| '<slot name="b"></slot>', |
| '<span id="end">jkl</span>', |
| '<slot name="a"></slot>', |
| 'mno', |
| ].join(''); |
| const start = document.getElementById('start'); |
| const end = shadowRoot.getElementById('end'); |
| eventSender.mouseMoveTo( |
| selection.computeLeft(start), |
| selection.computeTop(start) + start.offsetHeight / 2); |
| eventSender.mouseDown(); |
| eventSender.leapForward(300); |
| eventSender.mouseMoveTo( |
| selection.computeLeft(end) + end.offsetWidth - 1, |
| selection.computeTop(end) + end.offsetHeight / 2); |
| eventSender.mouseUp(); |
| }, |
| [ |
| '<style>* { font-family: monospace; }</style>', |
| '<div>', |
| '<span id="start">^ABC</span>', |
| '<p>', |
| 'ghi', |
| '<slot name="b"><b slot="b">222</b></slot>', |
| '<span id="end">jkl|</span>', |
| '<slot name="a"><a slot="a">111</a></slot>', |
| 'mno', |
| '</p>', |
| 'DEF', |
| '</div>', |
| ], |
| {dumpAs: 'flattree'}, 'selection crossing shadow boundary'); |
| |
| selection_test( |
| '<br>', |
| selection => { |
| selection.document.documentElement.prepend('Pre-HEAD'); |
| selection.document.body.before('Pre-BODY'); |
| selection.document.documentElement.append('Post-BODY'); |
| }, |
| '<html>Pre-HEAD<head></head>Pre-BODY<body><br></body>Post-BODY</html>', |
| {dumpFromRoot: true}, |
| 'Serialize with dumpFromRoot option'); |
| |
| test(() => { |
| assert_equals(checked_assert_selection('fo|o', 'noop', 'fo|o'), |
| 'no exception'); |
| }, 'no marker in output'); |
| |
| test(() => { |
| assert_equals(checked_assert_selection('fo|o|', 'noop', 'foo'), |
| 'You should have at least one focus marker "|" in "fo|o|".'); |
| }, 'multiple focus markers in input'); |
| |
| test(() => { |
| assert_equals(checked_assert_selection('fo|o', 'noop', '|fo|o'), |
| 'You should have at most one focus marker "|" in "|fo|o".'); |
| }, 'multiple focus markers in output'); |
| |
| test(() => { |
| assert_equals(checked_assert_selection('^fo|o^', 'noop', 'foo'), |
| 'You should have at most one anchor marker "^" in "^fo|o^".'); |
| }, 'multiple anchor markers in input'); |
| |
| test(() => { |
| assert_equals(checked_assert_selection('fo|o', 'noop', '^fo|o^'), |
| 'You should have at most one anchor marker "^" in "^fo|o^".'); |
| }, 'multiple anchor markers in output'); |
| |
| test(() => { |
| assert_equals(checked_assert_selection('^foo', 'noop', 'baz'), |
| 'You should specify caret position in "^foo".'); |
| }, 'anchor marker only in input'); |
| |
| test(() => { |
| assert_equals(checked_assert_selection('|foo', 'noop', 'b^az'), |
| 'You should have a focus marker "|" in "b^az".'); |
| }, 'anchor marker only in output'); |
| |
| test(() => { |
| assert_equals(checked_assert_selection('^|foo', 'noop', 'baz'), |
| 'You should have focus marker and should not have anchor marker if and only if selection is a caret in "^|foo".'); |
| }, 'anchor == focus in input'); |
| |
| test(() => { |
| assert_equals(checked_assert_selection('|foo', 'noop', 'b^|az'), |
| 'You should have focus marker and should not have anchor marker if and only if selection is a caret in "b^|az".'); |
| }, 'anchor == focus in output'); |
| |
| // TODO: It's better to have more powerful diff like |
| // |CreateUnifiedDiff()| in gtest or "Longest common substring" |
| test(() => { |
| assert_equals(checked_assert_selection('foo', 'noop', 'foz'), |
| `editing/assert_selection.html:8:9)\n` + |
| `\t expected foz,\n` + |
| `\t but got foo,\n` + |
| `\t sameupto fo`); |
| }, 'Compare result'); |
| |
| selection_test( |
| '<div contenteditable><p>^test|</p></div>', |
| 'insertHTML <span style="color: green">green</span>', |
| '<div contenteditable><p><span style="color: green">green|</span></p></div>', |
| 'multiple spaces in function'); |
| |
| test(() => { |
| assert_selection( |
| '<div contenteditable>|</div>', |
| selection => { |
| selection.setClipboardData('<b>foo</b>'); |
| selection.document.execCommand('paste'); |
| }, |
| '<div contenteditable><b>foo|</b></div>', |
| 'set HTML fragment to clipboard and paste'); |
| |
| assert_selection( |
| '<div contenteditable>|</div>', |
| selection => { |
| selection.setClipboardData('<b>foo</b>'); |
| selection.document.execCommand('pasteAndMatchStyle'); |
| }, |
| '<div contenteditable>foo|</div>', |
| 'set HTML fragment to clipboard and pasteAndMatchStyle'); |
| |
| assert_selection( |
| '<div contenteditable>|</div>', |
| selection => { |
| selection.setClipboardData('<b>foo</b>', 'FOO'); |
| selection.document.execCommand('pasteAndMatchStyle'); |
| }, |
| '<div contenteditable>FOO|</div>', |
| 'set HTML fragment and text to clipboard and pasteAndMatchStyle'); |
| }, 'selection.setClipboardData'); |
| |
| test(() => { |
| const sample1 = assert_selection_and_return_sample('abc', '', 'abc', |
| {removeSampleIfSucceeded: false}); |
| assert_equals(sample1.iframe_.parentNode, document.body, |
| 'removeSampleIfSucceeded: false'); |
| |
| const sample2 = assert_selection_and_return_sample('abc', '', 'abc', |
| {removeSampleIfSucceeded: true}); |
| assert_equals(sample2.iframe_.id, Sample.playgroundId, |
| 'removeSampleIfSucceeded: true: id'); |
| assert_equals(sample2.iframe_.style.display, 'none', |
| 'removeSampleIfSucceeded: true: style.display'); |
| |
| const sample3 = assert_selection_and_return_sample('abc', '', 'abc'); |
| assert_equals(sample3.iframe_.id, Sample.playgroundId, |
| 'with default options: id'); |
| assert_equals(sample3.iframe_.style.display, 'none', |
| 'with default options: id'); |
| |
| const sample4 = assert_selection_and_return_sample('abc', '', 'abc', 'description'); |
| assert_equals(sample4.iframe_.id, Sample.playgroundId, |
| 'with description: id'); |
| assert_equals(sample4.iframe_.style.display, 'none', |
| 'with description: id'); |
| }, 'removeSampleIfSucceeded'); |
| |
| test(() => { |
| assert_own_property(window, 'internals'); |
| assert_own_property(window, 'eventSender'); |
| assert_selection( |
| 'foo|bar', |
| () => { assert_equals(internals.textAffinity, 'Downstream'); }, |
| 'foo|bar'); |
| assert_selection( |
| '<div contenteditable style="width: 25px;">foobar</div>', |
| selection => { |
| eventSender.dragMode = false; |
| var document = selection.document; |
| var div = document.querySelector('div'); |
| eventSender.mouseMoveTo(document.offsetLeft + div.offsetLeft + 20, |
| document.offsetTop + div.offsetTop + 5); |
| eventSender.mouseDown(); |
| eventSender.mouseUp(); |
| assert_equals(internals.textAffinity, 'Upstream'); }, |
| '<div contenteditable style="width: 25px;">foo|bar</div>'); |
| }, 'Textaffinity'); |
| |
| test(() => { |
| assert_own_property(window, 'eventSender'); |
| assert_selection( |
| [ |
| '<div id="first">one <span id="start"></span>two three</div>', |
| '<div id="second">four <span id="end"></span>five six</div>', |
| ].join(''), |
| selection => { |
| const start = selection.document.getElementById('start'); |
| const end = selection.document.getElementById('end'); |
| eventSender.mouseMoveTo(selection.computeLeft(start), |
| selection.computeTop(start)); |
| eventSender.mouseDown(); |
| eventSender.mouseMoveTo(selection.computeLeft(end), |
| selection.computeTop(end)); |
| eventSender.mouseUp(); |
| }, |
| [ |
| '<div id="first">one <span id="start"></span>^two three</div>', |
| '<div id="second">four |<span id="end"></span>five six</div>', |
| ].join('')); |
| }, 'computeLeft() and computeTop()'); |
| |
| // When |selection_test()| revives AsyncFunction as tester, |selection_test()| |
| // uses |promise_test()| API instead of |test()| API. |
| selection_test('abc', async () => 1, 'abc', 'use promise_test()'); |
| </script> |