Skip to content

Commit

Permalink
simplify wrappedselection & migrate feature.test.ts to typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
giabao committed Apr 21, 2019
1 parent 338df69 commit 51486ee
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 156 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/qunit": "^2.5.4",
"bowser": "^2.3.0",
"glob": "^7.1.3",
"log4javascript": "^1.4.15",
"npm-run-all": "^4.1.5",
Expand Down
16 changes: 11 additions & 5 deletions src/core/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ export interface Features {
* Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
* rangy2 don't support IE < 9. I have tested in browserstack.com with updated IE => not crash */
crashyTextNodes: false;
/** Always use window.getSelection */
/** @deprecated Always use window.getSelection */
implementsWinGetSelection: true;
/** document.selection should only be used for IE < 9 which rangy2 don't support */
/** @deprecated document.selection should only be used for IE < 9 which rangy2 don't support */
implementsDocSelection: false;
selectionHasAnchorAndFocus?: boolean;
selectionHasExtend?: boolean;
selectionHasRangeCount?: boolean;
/** @deprecated Always true */
selectionHasAnchorAndFocus: true;
/** @deprecated Always true */
selectionHasExtend: true;
/** @deprecated Always true */
selectionHasRangeCount: true;
selectionSupportsMultipleRanges: boolean;
implementsControlRange: false;
collapsedNonEditableSelectionsSupported: boolean;
Expand All @@ -34,6 +37,9 @@ export const features: Features = {
crashyTextNodes: false,
implementsWinGetSelection: true,
implementsDocSelection: false,
selectionHasAnchorAndFocus: true,
selectionHasExtend: true,
selectionHasRangeCount: true,
selectionSupportsMultipleRanges: window.navigator.userAgent.indexOf("Firefox") > -1,
implementsControlRange: false,
collapsedNonEditableSelectionsSupported: true,
Expand Down
141 changes: 53 additions & 88 deletions src/core/internal/wrappedselection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,39 +72,19 @@ export function isSelectionValid() {
const testRange = createNativeRange(document);

// Obtaining a range from a selection
const selectionHasAnchorAndFocus =
features.selectionHasAnchorAndFocus =
util.areHostProperties(testSelection, ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);

// Test for existence of native selection extend() method
const selectionHasExtend =
features.selectionHasExtend =
isHostMethod(testSelection, "extend");

// Test if rangeCount exists
const selectionHasRangeCount =
features.selectionHasRangeCount =
(typeof testSelection.rangeCount == NUMBER);

const addRangeBackwardToNative = selectionHasExtend
? function(nativeSelection: Selection, range: AbstractRange) {

function addRangeBackwardToNative(nativeSelection: Selection, range: AbstractRange) {
var doc = DomRange.getRangeDocument(range);
var endRange = createRange(doc);
endRange.collapseToPoint(range.endContainer, range.endOffset);
nativeSelection.addRange(getNativeRange(endRange));
nativeSelection.extend(range.startContainer, range.startOffset);
}
: null;

// Selection collapsedness
let selectionIsCollapsed =
selectionHasAnchorAndFocus
? function(sel) {
function selectionIsCollapsed(sel) {
return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
}
: function(sel) {
return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
};

function updateAnchorAndFocusFromRange(sel: WrappedSelection, range, backward) {
const anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
Expand Down Expand Up @@ -145,21 +125,20 @@ let selectionIsCollapsed =
return nativeRange;
}

let getSelectionRangeAt: undefined | ((sel: Selection|RangySel, index: number) => RangyRange | Range | null);
const getSelectionRangeAt: ((sel: Selection|RangySel, index: number) => RangyRange | Range | null) =

if (isHostMethod(testSelection, "getRangeAt")) {
isHostMethod(testSelection, "getRangeAt")
// try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
// Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
// lesson to us all, especially me.
getSelectionRangeAt = function(sel, index) {
? function(sel, index) {
try {
return sel.getRangeAt(index);
} catch (ex) {
return null;
}
};
} else if (selectionHasAnchorAndFocus) {
getSelectionRangeAt = function(sel, index) {
}
: function(sel, index) {
var doc = getDocument(sel.anchorNode);
var range = createRange(doc);
range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
Expand All @@ -172,7 +151,6 @@ let selectionIsCollapsed =

return range;
};
}

function deleteProperties(sel: WrappedSelection) {
sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
Expand Down Expand Up @@ -247,7 +225,7 @@ let selectionIsCollapsed =
updateEmptySelection(sel);
}
};
} else if (selectionHasAnchorAndFocus &&
} else if (
typeof testSelection.isCollapsed == BOOLEAN &&
typeof testRange.collapsed == BOOLEAN)
{
Expand All @@ -269,62 +247,6 @@ let selectionIsCollapsed =
}

// function createWrappedSelection<TBase extends Constructor<WrappedSelBase>>(Base: TBase) {
function addRangeBackward(sel: WrappedSelection, range: AbstractRange) {
addRangeBackwardToNative(sel.nativeSelection, range);
sel.refresh();
};
const addRange = selectionHasRangeCount
? function(this: WrappedSelection, range: RangyRangeEx, direction?: string|boolean): void {
if (isDirectionBackward(direction) && selectionHasExtend) {
addRangeBackward(this, range);
} else {
const previousRangeCount = this.rangeCount;
if (! features.selectionSupportsMultipleRanges && previousRangeCount > 0) {
// https://www.chromestatus.com/features/6680566019653632
return;
}
// Clone the native range so that changing the selected range does not affect the selection.
// This is contrary to the spec but is the only way to achieve consistency between browsers. See
// issue 80.
var clonedNativeRange = getNativeRange(range).cloneRange();
try {
this.nativeSelection.addRange(clonedNativeRange);
} catch (ex) {
log.error("Native addRange threw error '" + ex + "' with range " + DomRange.inspect(clonedNativeRange), ex);
}

// Check whether adding the range was successful
this.rangeCount = this.nativeSelection.rangeCount;

if (this.rangeCount == previousRangeCount + 1) {
// The range was added successfully

// Check whether the range that we added to the selection is reflected in the last range extracted from
// the selection
if (config.checkSelectionRanges) {
var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1) as Range;
if (nativeRange && !rangesEqual(nativeRange, range)) {
// Happens in WebKit with, for example, a selection placed at the start of a text node
range = new WrappedRange(nativeRange);
}
}
this._ranges[this.rangeCount - 1] = range;
updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
this.isCollapsed = selectionIsCollapsed(this);
} else {
// The range was not added successfully. The simplest thing is to refresh
this.refresh();
}
}
}
: function(this: WrappedSelection, range: RangyRangeEx, direction?: string|boolean): void {
if (isDirectionBackward(direction) && selectionHasExtend) {
addRangeBackward(this, range);
} else {
this.nativeSelection.addRange(getNativeRange(range));
this.refresh();
}
};

// Removal of a single range
function removeRangeManually(sel: WrappedSelection, range) {
Expand Down Expand Up @@ -409,7 +331,50 @@ let selectionIsCollapsed =
empty = this.removeAllRanges;

//if (selectionHasRangeCount) {
addRange = addRange.bind(this);
addRange(range: RangyRangeEx, direction?: string|boolean): void {
if (isDirectionBackward(direction)) {
addRangeBackwardToNative(this.nativeSelection, range);
this.refresh();
} else {
const previousRangeCount = this.rangeCount;
if (! features.selectionSupportsMultipleRanges && previousRangeCount > 0) {
// https://www.chromestatus.com/features/6680566019653632
return;
}
// Clone the native range so that changing the selected range does not affect the selection.
// This is contrary to the spec but is the only way to achieve consistency between browsers. See
// issue 80.
var clonedNativeRange = getNativeRange(range).cloneRange();
try {
this.nativeSelection.addRange(clonedNativeRange);
} catch (ex) {
log.error("Native addRange threw error '" + ex + "' with range " + DomRange.inspect(clonedNativeRange), ex);
}

// Check whether adding the range was successful
this.rangeCount = this.nativeSelection.rangeCount;

if (this.rangeCount == previousRangeCount + 1) {
// The range was added successfully

// Check whether the range that we added to the selection is reflected in the last range extracted from
// the selection
if (config.checkSelectionRanges) {
var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1) as Range;
if (nativeRange && !rangesEqual(nativeRange, range)) {
// Happens in WebKit with, for example, a selection placed at the start of a text node
range = new WrappedRange(nativeRange);
}
}
this._ranges[this.rangeCount - 1] = range;
updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
this.isCollapsed = selectionIsCollapsed(this);
} else {
// The range was not added successfully. The simplest thing is to refresh
this.refresh();
}
}
}

setRanges(ranges: WrappedRange[]): void {
this.removeAllRanges();
Expand Down
48 changes: 16 additions & 32 deletions test/core/feature.test.html
Original file line number Diff line number Diff line change
@@ -1,36 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>Rangy - TextRange-to-Range Performace Tests</title>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta name="referrer" content="no-referrer"/>
<title>Rangy - Features Tests</title>

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="../external/log4javascript.js"></script>
<script type="text/javascript" src="../external/jshashtable.js"></script>
<script type="text/javascript" src="xntest.js"></script>
<script type="text/javascript" src="testutils.js"></script>
<script type="text/javascript">
xn.test.enableStackTraces = true;
</script>
<script type="text/javascript">
var appender = new log4javascript.InPageAppender();
//log4javascript.getRootLogger().addAppender(appender);
var log = log4javascript.getRootLogger();
log4javascript.setShowStackTraces(true);

</script>
<script type="text/javascript">
//log4javascript.setEnabled(false);
</script>
<script type="text/javascript" src="../src/core/core.js"></script>
<script type="text/javascript" src="../src/core/dom.js"></script>
<script type="text/javascript" src="../src/core/domrange.js"></script>
<script type="text/javascript" src="../src/core/wrappedrange.js"></script>
<script type="text/javascript" src="../src/core/wrappedselection.js"></script>
<script type="text/javascript" src="featuretests.js"></script>
<link rel="stylesheet" type="text/css" href="tests.css"/>
</head>
<body>
<div id="test"></div>
<div id="messages"></div>
</body>
<!-- load rangy2 here in `head` to verify that rangy2 can init without need dom ready -->
<script src="../../dist/core/bundles/index.umd.js"></script>
<script src="../../node_modules/qunit/qunit/qunit.js"></script>
<link rel="stylesheet" href="../../node_modules/qunit/qunit/qunit.css">
<script src="../qunit-ex.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="feature.test.js"></script>
</body>
</html>
57 changes: 28 additions & 29 deletions test/core/feature.test.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,52 @@
xn.test.suite("Browser feature tests", function(s) {
rangy.init();
import Bowser from "bowser";

const browser = Bowser.getParser(window.navigator.userAgent);
QUnit.module("Browser feature tests");

// Detect browser version roughly. It doesn't matter too much: these are only rough tests designed to test whether
// Rangy's feature detection is hopelessly wrong

const isIe = browser.satisfies({ie: '>0'});
const isMozilla = browser.isEngine('gecko');
const isOpera = browser.isEngine('presto');

var browser = jQuery.browser;
var isIe = !!browser.msie;
var isMozilla = !!browser.mozilla;
var isOpera = !!browser.opera;
var version = parseFloat(browser.version);

s.test("DOM Range support", function(t) {
t.assertEquals(rangy.features.implementsDomRange, !isIe || version >= 9);
QUnit.test("DOM Range support", function(t) {
t.equal(rangy.features.implementsDomRange, !browser.satisfies({ie: '<9'}));
});

s.test("TextRange support", function(t) {
t.assertEquals(rangy.features.implementsTextRange, isIe && version >= 4);
QUnit.test("TextRange support", function(t) {
t.equal(false, !!undefined);
t.equal(rangy.features.implementsTextRange, !!browser.satisfies({ie: '>=4'}));
});

s.test("document.selection support", function(t) {
t.assertEquals(rangy.features.implementsTextRange, isIe && version >= 4);
QUnit.test("document.selection support", function(t) {
t.equal(rangy.features.implementsTextRange, !!browser.satisfies({ie: '>=4'}));
});

s.test("window.getSelection() support", function(t) {
t.assertEquals(rangy.features.implementsWinGetSelection, !isIe || version >= 9);
QUnit.test("window.getSelection() support", function(t) {
t.equal(rangy.features.implementsWinGetSelection, !browser.satisfies({ie: '<9'}));
});

s.test("selection has rangeCount", function(t) {
t.assertEquals(rangy.features.selectionHasRangeCount, !isIe || version >= 9);
QUnit.test("selection has rangeCount", function(t) {
t.equal(rangy.features.selectionHasRangeCount, !browser.satisfies({ie: '<9'}));
});

s.test("selection has anchor and focus support", function(t) {
t.assertEquals(rangy.features.selectionHasAnchorAndFocus, !isIe || version >= 9);
QUnit.test("selection has anchor and focus support", function(t) {
t.equal(rangy.features.selectionHasAnchorAndFocus, !browser.satisfies({ie: '<9'}));
});

s.test("selection has extend() method", function(t) {
t.assertEquals(rangy.features.selectionHasExtend, !isIe);
QUnit.test("selection has extend() method", function(t) {
t.equal(rangy.features.selectionHasExtend, !isIe);
});

s.test("HTML parsing", function(t) {
t.assertEquals(rangy.features.htmlParsingConforms, !isIe);
QUnit.test("HTML parsing", function(t) {
t.equal(rangy.features.htmlParsingConforms, !isIe);
});

s.test("Multiple ranges per selection support", function(t) {
t.assertEquals(rangy.features.selectionSupportsMultipleRanges, isMozilla);
QUnit.test("Multiple ranges per selection support", function(t) {
t.equal(rangy.features.selectionSupportsMultipleRanges, isMozilla);
});

s.test("Collapsed non-editable selections support", function(t) {
t.assertEquals(rangy.features.collapsedNonEditableSelectionsSupported, !isOpera);
QUnit.test("Collapsed non-editable selections support", function(t) {
t.equal(rangy.features.collapsedNonEditableSelectionsSupported, !isOpera);
});
}, false);
4 changes: 2 additions & 2 deletions test/core/selection.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {WrappedSelection} from "rangy2";
import {RangyRangeEx, WrappedSelection} from "rangy2";

var hasNativeGetSelection = "getSelection" in window;
var hasNativeDomRange = "createRange" in document;
Expand Down Expand Up @@ -488,7 +488,7 @@ function testSelectionAndRangeCreators(wins, winName,
}, setUp_noRangeCheck, tearDown_noRangeCheck);
}

function testRefresh(name: string, testRangeCreator: (t: Assert) => AbstractRange) {
function testRefresh(name: string, testRangeCreator: (t: Assert) => RangyRangeEx) {
if (isWrappedSel(selectionCreator, 'refresh')) {
QUnit.test("Refresh test: " + name, function(t) {
const sel = selectionCreator(win);
Expand Down
1 change: 1 addition & 0 deletions test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"esModuleInterop": true,
"moduleResolution": "node",
"sourceMap": true,
"stripInternal": true,
Expand Down
Loading

0 comments on commit 51486ee

Please sign in to comment.