File: dom.js

package info (click to toggle)
aseba-plugin-blockly 20180211%2Bgit-2
  • links: PTS
  • area: non-free
  • in suites: buster
  • size: 64,472 kB
  • sloc: xml: 7,976; python: 2,314; sh: 261; lisp: 24; makefile: 10
file content (294 lines) | stat: -rw-r--r-- 12,593 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Testing utilities for editor specific DOM related tests.
 *
 */

goog.setTestOnly('goog.testing.editor.dom');
goog.provide('goog.testing.editor.dom');

goog.require('goog.dom.NodeType');
goog.require('goog.dom.TagIterator');
goog.require('goog.dom.TagWalkType');
goog.require('goog.iter');
goog.require('goog.string');
goog.require('goog.testing.asserts');


/**
 * Returns the previous (in document order) node from the given node that is a
 * non-empty text node, or null if none is found or opt_stopAt is not an
 * ancestor of node. Note that if the given node has children, the search will
 * start from the end tag of the node, meaning all its descendants will be
 * included in the search, unless opt_skipDescendants is true.
 * @param {Node} node Node to start searching from.
 * @param {Node=} opt_stopAt Node to stop searching at (search will be
 *     restricted to this node's subtree), defaults to the body of the document
 *     containing node.
 * @param {boolean=} opt_skipDescendants Whether to skip searching the given
 *     node's descentants.
 * @return {Text} The previous (in document order) node from the given node
 *     that is a non-empty text node, or null if none is found.
 */
goog.testing.editor.dom.getPreviousNonEmptyTextNode = function(
    node, opt_stopAt, opt_skipDescendants) {
  return goog.testing.editor.dom.getPreviousNextNonEmptyTextNodeHelper_(
      node, opt_stopAt, opt_skipDescendants, true);
};


/**
 * Returns the next (in document order) node from the given node that is a
 * non-empty text node, or null if none is found or opt_stopAt is not an
 * ancestor of node. Note that if the given node has children, the search will
 * start from the start tag of the node, meaning all its descendants will be
 * included in the search, unless opt_skipDescendants is true.
 * @param {Node} node Node to start searching from.
 * @param {Node=} opt_stopAt Node to stop searching at (search will be
 *     restricted to this node's subtree), defaults to the body of the document
 *     containing node.
 * @param {boolean=} opt_skipDescendants Whether to skip searching the given
 *     node's descentants.
 * @return {Text} The next (in document order) node from the given node that
 *     is a non-empty text node, or null if none is found or opt_stopAt is not
 *     an ancestor of node.
 */
goog.testing.editor.dom.getNextNonEmptyTextNode = function(
    node, opt_stopAt, opt_skipDescendants) {
  return goog.testing.editor.dom.getPreviousNextNonEmptyTextNodeHelper_(
      node, opt_stopAt, opt_skipDescendants, false);
};


/**
 * Helper that returns the previous or next (in document order) node from the
 * given node that is a non-empty text node, or null if none is found or
 * opt_stopAt is not an ancestor of node. Note that if the given node has
 * children, the search will start from the end or start tag of the node
 * (depending on whether it's searching for the previous or next node), meaning
 * all its descendants will be included in the search, unless
 * opt_skipDescendants is true.
 * @param {Node} node Node to start searching from.
 * @param {Node=} opt_stopAt Node to stop searching at (search will be
 *     restricted to this node's subtree), defaults to the body of the document
 *     containing node.
 * @param {boolean=} opt_skipDescendants Whether to skip searching the given
 *   node's descentants.
 * @param {boolean=} opt_isPrevious Whether to search for the previous non-empty
 *     text node instead of the next one.
 * @return {Text} The next (in document order) node from the given node that
 *     is a non-empty text node, or null if none is found or opt_stopAt is not
 *     an ancestor of node.
 * @private
 */
goog.testing.editor.dom.getPreviousNextNonEmptyTextNodeHelper_ = function(
    node, opt_stopAt, opt_skipDescendants, opt_isPrevious) {
  opt_stopAt = opt_stopAt || node.ownerDocument.body;
  // Initializing the iterator to iterate over the children of opt_stopAt
  // makes it stop only when it finishes iterating through all of that
  // node's children, even though we will start at a different node and exit
  // that starting node's subtree in the process.
  var iter = new goog.dom.TagIterator(opt_stopAt, opt_isPrevious);

  // TODO(user): Move this logic to a new method in TagIterator such as
  // skipToNode().
  // Then we set the iterator to start at the given start node, not opt_stopAt.
  var walkType;  // Let TagIterator set the initial walk type by default.
  var depth = goog.testing.editor.dom.getRelativeDepth_(node, opt_stopAt);
  if (depth == -1) {
    return null;  // Fail because opt_stopAt is not an ancestor of node.
  }
  if (node.nodeType == goog.dom.NodeType.ELEMENT) {
    if (opt_skipDescendants) {
      // Specifically set the initial walk type so that we skip the descendant
      // subtree by starting at the start if going backwards or at the end if
      // going forwards.
      walkType = opt_isPrevious ? goog.dom.TagWalkType.START_TAG :
                                  goog.dom.TagWalkType.END_TAG;
    } else {
      // We're starting "inside" an element node so the depth needs to be one
      // deeper than the node's actual depth. That's how TagIterator works!
      depth++;
    }
  }
  iter.setPosition(node, walkType, depth);

  // Advance the iterator so it skips the start node.
  try {
    iter.next();
  } catch (e) {
    return null;  // It could have been a leaf node.
  }
  // Now just get the first non-empty text node the iterator finds.
  var filter =
      goog.iter.filter(iter, goog.testing.editor.dom.isNonEmptyTextNode_);
  try {
    return /** @type {Text} */ (filter.next());
  } catch (e) {  // No next item is available so return null.
    return null;
  }
};


/**
 * Returns whether the given node is a non-empty text node.
 * @param {Node} node Node to be checked.
 * @return {boolean} Whether the given node is a non-empty text node.
 * @private
 */
goog.testing.editor.dom.isNonEmptyTextNode_ = function(node) {
  return !!node && node.nodeType == goog.dom.NodeType.TEXT && node.length > 0;
};


/**
 * Returns the depth of the given node relative to the given parent node, or -1
 * if the given node is not a descendant of the given parent node. E.g. if
 * node == parentNode returns 0, if node.parentNode == parentNode returns 1,
 * etc.
 * @param {Node} node Node whose depth to get.
 * @param {Node} parentNode Node relative to which the depth should be
 *     calculated.
 * @return {number} The depth of the given node relative to the given parent
 *     node, or -1 if the given node is not a descendant of the given parent
 *     node.
 * @private
 */
goog.testing.editor.dom.getRelativeDepth_ = function(node, parentNode) {
  var depth = 0;
  while (node) {
    if (node == parentNode) {
      return depth;
    }
    node = node.parentNode;
    depth++;
  }
  return -1;
};


/**
 * Assert that the range is surrounded by the given strings. This is useful
 * because different browsers can place the range endpoints inside different
 * nodes even when visually the range looks the same. Also, there may be empty
 * text nodes in the way (again depending on the browser) making it difficult to
 * use assertRangeEquals.
 * @param {string} before String that should occur immediately before the start
 *     point of the range. If this is the empty string, assert will only succeed
 *     if there is no text before the start point of the range.
 * @param {string} after String that should occur immediately after the end
 *     point of the range. If this is the empty string, assert will only succeed
 *     if there is no text after the end point of the range.
 * @param {goog.dom.AbstractRange} range The range to be tested.
 * @param {Node=} opt_stopAt Node to stop searching at (search will be
 *     restricted to this node's subtree).
 */
goog.testing.editor.dom.assertRangeBetweenText = function(
    before, after, range, opt_stopAt) {
  var previousText =
      goog.testing.editor.dom.getTextFollowingRange_(range, true, opt_stopAt);
  if (before == '') {
    assertNull(
        'Expected nothing before range but found <' + previousText + '>',
        previousText);
  } else {
    assertNotNull(
        'Expected <' + before + '> before range but found nothing',
        previousText);
    assertTrue(
        'Expected <' + before + '> before range but found <' + previousText +
            '>',
        goog.string.endsWith(
            /** @type {string} */ (previousText), before));
  }
  var nextText =
      goog.testing.editor.dom.getTextFollowingRange_(range, false, opt_stopAt);
  if (after == '') {
    assertNull(
        'Expected nothing after range but found <' + nextText + '>', nextText);
  } else {
    assertNotNull(
        'Expected <' + after + '> after range but found nothing', nextText);
    assertTrue(
        'Expected <' + after + '> after range but found <' + nextText + '>',
        goog.string.startsWith(
            /** @type {string} */ (nextText), after));
  }
};


/**
 * Returns the text that follows the given range, where the term "follows" means
 * "comes immediately before the start of the range" if isBefore is true, and
 * "comes immediately after the end of the range" if isBefore is false, or null
 * if no non-empty text node is found.
 * @param {goog.dom.AbstractRange} range The range to search from.
 * @param {boolean} isBefore Whether to search before the range instead of
 *     after it.
 * @param {Node=} opt_stopAt Node to stop searching at (search will be
 *     restricted to this node's subtree).
 * @return {?string} The text that follows the given range, or null if no
 *     non-empty text node is found.
 * @private
 */
goog.testing.editor.dom.getTextFollowingRange_ = function(
    range, isBefore, opt_stopAt) {
  var followingTextNode;
  var endpointNode = isBefore ? range.getStartNode() : range.getEndNode();
  var endpointOffset = isBefore ? range.getStartOffset() : range.getEndOffset();
  var getFollowingTextNode = isBefore ?
      goog.testing.editor.dom.getPreviousNonEmptyTextNode :
      goog.testing.editor.dom.getNextNonEmptyTextNode;

  if (endpointNode.nodeType == goog.dom.NodeType.TEXT) {
    // Range endpoint is in a text node.
    var endText = endpointNode.nodeValue;
    if (isBefore ? endpointOffset > 0 : endpointOffset < endText.length) {
      // There is text in this node following the endpoint so return the portion
      // that follows the endpoint.
      return isBefore ? endText.substr(0, endpointOffset) :
                        endText.substr(endpointOffset);
    } else {
      // There is no text following the endpoint so look for the follwing text
      // node.
      followingTextNode = getFollowingTextNode(endpointNode, opt_stopAt);
      return followingTextNode && followingTextNode.nodeValue;
    }
  } else {
    // Range endpoint is in an element node.
    var numChildren = endpointNode.childNodes.length;
    if (isBefore ? endpointOffset > 0 : endpointOffset < numChildren) {
      // There is at least one child following the endpoint.
      var followingChild =
          endpointNode
              .childNodes[isBefore ? endpointOffset - 1 : endpointOffset];
      if (goog.testing.editor.dom.isNonEmptyTextNode_(followingChild)) {
        // The following child has text so return that.
        return followingChild.nodeValue;
      } else {
        // The following child has no text so look for the following text node.
        followingTextNode = getFollowingTextNode(followingChild, opt_stopAt);
        return followingTextNode && followingTextNode.nodeValue;
      }
    } else {
      // There is no child following the endpoint, so search from the endpoint
      // node, but don't search its children because they are not following the
      // endpoint!
      followingTextNode = getFollowingTextNode(endpointNode, opt_stopAt, true);
      return followingTextNode && followingTextNode.nodeValue;
    }
  }
};