Commit 260f8b29 authored by Lei Shi's avatar Lei Shi Committed by Chromium LUCI CQ

Resume from the beginning of the sentence in Select To Speak

When a user resumes from a pause, STS will speak from the beginning of the current sentence. If the user paused on a sentence start, STS will continue reading from the pause.

This CL also refactors the functions in sentence_utils.js and uses staticTextNode directly to query sentenceStarts.

AX-Relnotes: N/A

Bug: 1143817
Change-Id: Iaf73a5656273db499604d3f7f4a008c37e5a4eb6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2572905
Commit-Queue: Lei Shi <leileilei@google.com>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835806}
parent 67a6625b
......@@ -632,14 +632,28 @@ class SelectToSpeak {
}
/**
* Resume the TTS. Currently, we just ask the TTS engine to pick up where it
* quited last time in |this.startCurrentNodeGroup_|.
* Resume the TTS from the beginning of the current sentence.
* @private
*/
resume_() {
if (this.isPaused_()) {
this.startCurrentNodeGroup_();
// If TTS is not paused, return early.
if (!this.isPaused_()) {
return;
}
if (!SentenceUtils.isSentenceStart(
this.currentNodeGroup_, this.navigationState_.currentCharIndex)) {
// If the current position is not a sentence start, move to the start of
// the current sentence.
const currentSentenceStart = SentenceUtils.getSentenceStart(
this.currentNodeGroup_, this.navigationState_.currentCharIndex,
constants.Dir.BACKWARD);
// Only navigate to the current sentence start if it exists, otherwise
// move to the beginning of the current nodeGroup.
// TODO(leileilei): check if the 0 char index would cause issues.
this.navigationState_.currentCharIndex =
currentSentenceStart === null ? 0 : currentSentenceStart;
}
this.startCurrentNodeGroup_();
}
/**
......
......@@ -146,36 +146,109 @@ TEST_F(
});
});
TEST_F('SelectToSpeakNavigationControlTest', 'PauseAndResume', function() {
const bodyHtml = `
<p id="p1">Paragraph 1</p>'
TEST_F(
'SelectToSpeakNavigationControlTest', 'PauseResumeWithinTheSentence',
function() {
const bodyHtml = `
<p id="p1">First sentence. Second sentence. Third sentence.</p>'
`;
this.runWithLoadedTree(
this.generateHtmlWithSelectedElement('p1', bodyHtml), () => {
this.triggerReadSelectedText();
this.runWithLoadedTree(
this.generateHtmlWithSelectedElement('p1', bodyHtml), () => {
this.triggerReadSelectedText();
// Speaks the first word.
this.mockTts.speakUntilCharIndex(10);
assertTrue(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 1);
this.assertEqualsCollapseWhitespace(
this.mockTts.pendingUtterances()[0], 'Paragraph 1');
// Speaks until the second word of the second sentence.
this.mockTts.speakUntilCharIndex(23);
assertTrue(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 1);
this.assertEqualsCollapseWhitespace(
this.mockTts.pendingUtterances()[0],
'First sentence. Second sentence. Third sentence.');
// Hitting pause will stop the current TTS.
selectToSpeak.onSelectToSpeakPanelAction_(
chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
assertFalse(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 0);
// Hitting pause will stop the current TTS.
selectToSpeak.onSelectToSpeakPanelAction_(
chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
assertFalse(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 0);
// Hitting resume will start from the beginning of the second
// sentence.
selectToSpeak.onSelectToSpeakPanelAction_(
chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
assertTrue(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 1);
this.assertEqualsCollapseWhitespace(
this.mockTts.pendingUtterances()[0],
'Second sentence. Third sentence.');
});
});
// Hitting resume will start from the next position.
selectToSpeak.onSelectToSpeakPanelAction_(
chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
assertTrue(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 1);
this.assertEqualsCollapseWhitespace(
this.mockTts.pendingUtterances()[0], '1');
});
});
TEST_F(
'SelectToSpeakNavigationControlTest', 'PauseResumeAtTheBeginningOfSentence',
function() {
const bodyHtml = `
<p id="p1">First sentence. Second sentence. Third sentence.</p>'
`;
this.runWithLoadedTree(
this.generateHtmlWithSelectedElement('p1', bodyHtml), () => {
this.triggerReadSelectedText();
// Speaks until the third sentence.
this.mockTts.speakUntilCharIndex(33);
assertTrue(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 1);
this.assertEqualsCollapseWhitespace(
this.mockTts.pendingUtterances()[0],
'First sentence. Second sentence. Third sentence.');
// Hitting pause will stop the current TTS.
selectToSpeak.onSelectToSpeakPanelAction_(
chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
assertFalse(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 0);
// Hitting resume will start from the beginning of the third
// sentence.
selectToSpeak.onSelectToSpeakPanelAction_(
chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
assertTrue(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 1);
this.assertEqualsCollapseWhitespace(
this.mockTts.pendingUtterances()[0], 'Third sentence.');
});
});
TEST_F(
'SelectToSpeakNavigationControlTest',
'PauseResumeAtTheBeginningOfParagraph', function() {
const bodyHtml = `
<p id="p1">first sentence.</p>'
`;
this.runWithLoadedTree(
this.generateHtmlWithSelectedElement('p1', bodyHtml), () => {
this.triggerReadSelectedText();
// Speaks until the second word.
this.mockTts.speakUntilCharIndex(6);
assertTrue(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 1);
this.assertEqualsCollapseWhitespace(
this.mockTts.pendingUtterances()[0], 'first sentence.');
// Hitting pause will stop the current TTS.
selectToSpeak.onSelectToSpeakPanelAction_(
chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
assertFalse(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 0);
// Hitting resume will start from the beginning of the paragraph.
selectToSpeak.onSelectToSpeakPanelAction_(
chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
assertTrue(this.mockTts.currentlySpeaking());
assertEquals(this.mockTts.pendingUtterances().length, 1);
this.assertEqualsCollapseWhitespace(
this.mockTts.pendingUtterances()[0], 'first sentence.');
});
});
TEST_F('SelectToSpeakNavigationControlTest', 'NextSentence', function() {
const bodyHtml = `
......
......@@ -9,33 +9,40 @@ class SentenceUtils {
constructor() {}
/**
* Get the next sentence start. When |direction| is set to forward, this
* function will incrementally go over each nodeGroupItem in |nodeGroup|,
* and try to find a sentence start index after the |startCharIndex|. When
* |direction| is set to backward, This function will search for the preivous
* sentence. The |startCharIndex| and the found sentence start index are
* relative to the start of this node group.
* Gets the sentence start from the current position. When |direction| is set
* to forward, this function will incrementally go over each nodeGroupItem in
* |nodeGroup|, and try to find a sentence start index after the
* |startCharIndex|. When |direction| is set to backward, This function will
* search for the sentence start before the current position. The
* |startCharIndex| and the found sentence start index are relative to the
* text content of this node group.
* @param {ParagraphUtils.NodeGroup} nodeGroup The node group this function
* will search.
* @param {number} startCharIndex The char index that we start from. This
* index is relative to the start of this node group and is exclusive: if
* a sentence start at 0 and we search with a 0 |startCharIndex|, this
* function will return the next sentence start after 0.
* @param {constants.Dir} direction Direction for the next sentence.
* |constants.Dir.BACKWARD| will search for the previous sentence while
* |constants.Dir.FORWARD| will search for the next sentence.
* index is relative to the text content of this node group and is
* exclusive: if a sentence start at 0 and we search with a 0
* |startCharIndex|, this function will return the next sentence start
* after 0 if we search forward.
* @param {constants.Dir} direction Direction to search for the next sentence
* start. |constants.Dir.BACKWARD| will search for the sentence start
* before the current position. |constants.Dir.FORWARD| will search for
* the sentence start after the current position.
* @return {?number} the next sentence start after |startCharIndex|, returns
* null if nothing found.
*/
static getSentenceStart(nodeGroup, startCharIndex, direction) {
if (!nodeGroup) {
return null;
}
if (nodeGroup.nodes.length === 0) {
return null;
}
if (direction === constants.Dir.FORWARD) {
for (let i = 0; i < nodeGroup.nodes.length; i++) {
const nodeGroupItem = nodeGroup.nodes[i];
const result = SentenceUtils.getNextSentenceStartInNodeGroupItem(
nodeGroupItem, startCharIndex);
const result = SentenceUtils.getSentenceStartInNodeGroupItem(
nodeGroupItem, startCharIndex, constants.Dir.FORWARD);
if (result !== null) {
return result;
}
......@@ -43,8 +50,8 @@ class SentenceUtils {
} else if (direction === constants.Dir.BACKWARD) {
for (let i = nodeGroup.nodes.length - 1; i >= 0; i--) {
const nodeGroupItem = nodeGroup.nodes[i];
const result = SentenceUtils.getPrevSentenceStartInNodeGroupItem(
nodeGroupItem, startCharIndex);
const result = SentenceUtils.getSentenceStartInNodeGroupItem(
nodeGroupItem, startCharIndex, constants.Dir.BACKWARD);
if (result !== null) {
return result;
}
......@@ -54,82 +61,75 @@ class SentenceUtils {
}
/**
* Get the next sentence start within a node group item.
* Gets the sentence start before or after current position within the input
* |nodeGroupItem|.
* @param {ParagraphUtils.NodeGroupItem} nodeGroupItem The node group item
* this function will search.
* @param {number} startCharIndex The char index that we start from.
* @param {number} startCharIndex The char index that we start from. This is
* relative to the text content of the node group.
* @param {constants.Dir} direction Direction to search for the next sentence
* start. |constants.Dir.BACKWARD| will search for the sentence start
* before the current position. |constants.Dir.FORWARD| will search for
* the sentence start after the current position.
* @return {?number} the next sentence start after |startCharIndex|, returns
* null if nothing found.
*/
static getNextSentenceStartInNodeGroupItem(nodeGroupItem, startCharIndex) {
const nodeGroupItemHasInlineText =
nodeGroupItem && nodeGroupItem.hasInlineText;
if (!nodeGroupItemHasInlineText) {
static getSentenceStartInNodeGroupItem(
nodeGroupItem, startCharIndex, direction) {
if (!nodeGroupItem) {
return null;
}
const nodeGroupItemHasContent = nodeGroupItem.node.children.length > 0 &&
nodeGroupItem.node.name.length > 0;
if (!nodeGroupItemHasContent) {
// Check if this nodeGroupItem has a non-empty static text node.
if (!nodeGroupItem.node.role === RoleType.STATIC_TEXT ||
nodeGroupItem.node.name.length === 0) {
return null;
}
const staticTextStartChar = nodeGroupItem.startChar;
const staticTextNode = nodeGroupItem.node;
// If the corresponding char of |startCharIndex| is after this static text
// node, skip this text node.
if (startCharIndex > staticTextStartChar + staticTextNode.name.length - 1) {
return null;
}
// If the corresponding char of |startCharIndex| is within this static text
// node, we get its relative index in this static text. Otherwise, we search
// from the beginning of the static text.
const searchIndexInStaticText =
Math.max(startCharIndex - staticTextStartChar, 0);
// Find the index of the inline text node corresponding to the
// |searchIndexInStaticText|.
const inlineTextNodeIndex =
ParagraphUtils.findInlineTextNodeIndexByCharacterIndex(
staticTextNode, searchIndexInStaticText);
// findInlineTextNodeIndexByCharacterIndex may return a negative number,
// indicating no children in the staticTextNode.
if (inlineTextNodeIndex < 0) {
return null;
}
// Incrementally iterate over each inline text nodes within this static text
// node from |inlineTextNodeIndex|.
for (let inlineTextNode, startCharInStaticText, i = inlineTextNodeIndex;
i < staticTextNode.children.length; i++) {
// If this is the first node in the for loop, use ParagraphUtils to get
// the start char. Otherwise, update the start char by adding the name
// length of previous node.
if (i === inlineTextNodeIndex) {
inlineTextNode = staticTextNode.children[i];
startCharInStaticText =
ParagraphUtils.getStartCharIndexInParent(inlineTextNode);
} else {
startCharInStaticText += inlineTextNode.name.length;
inlineTextNode = staticTextNode.children[i];
if (direction === constants.Dir.FORWARD) {
// If the corresponding char of |startCharIndex| is after this static text
// node, skip this text node.
if (startCharIndex >
staticTextStartChar + staticTextNode.name.length - 1) {
return null;
}
// If the corresponding char of |startCharIndex| is within this static
// text node, we get its relative index in this static text. Otherwise,
// the start char is before this static text node, and any sentence start
// in this static text node is valid.
const searchIndexInStaticText =
Math.max(startCharIndex - staticTextStartChar, -1);
// Continue to the next one if the end of this inlineTextNode is still
// smaller than startCharIndex.
if (inlineTextNode.name.length - 1 + startCharInStaticText +
staticTextStartChar <=
startCharIndex) {
continue;
// Iterate over all sentenceStarts to find the one that is bigger than
// |searchIndexInStaticText|.
for (let i = 0; i < staticTextNode.sentenceStarts.length; i++) {
if (staticTextNode.sentenceStarts[i] <= searchIndexInStaticText) {
continue;
}
return staticTextNode.sentenceStarts[i] + staticTextStartChar;
}
} else if (direction === constants.Dir.BACKWARD) {
// If the corresponding char of |startCharIndex| is before this static
// text node, skip this text node.
if (startCharIndex < staticTextStartChar) {
return null;
}
// If the corresponding char of |startCharIndex| is within this static
// text node, we get its relative index in this static text. Otherwise,
// the start char is after this static text node, and any sentence start
// in this static text node is valid.
const searchIndexInStaticText = Math.min(
startCharIndex - staticTextStartChar, staticTextNode.name.length);
// Iterate over all sentenceStarts to find the one that is bigger than
// startCharIndex.
for (let j = 0; j < inlineTextNode.sentenceStarts.length; j++) {
const potentialStart = inlineTextNode.sentenceStarts[j] +
startCharInStaticText + staticTextStartChar;
if (potentialStart <= startCharIndex) {
// Iterate over all sentenceStarts to find the one that is smaller than
// |searchIndexInStaticText|.
for (let i = staticTextNode.sentenceStarts.length - 1; i >= 0; i--) {
if (staticTextNode.sentenceStarts[i] >= searchIndexInStaticText) {
continue;
}
return potentialStart;
return staticTextNode.sentenceStarts[i] + staticTextStartChar;
}
}
// We are off the edge of this static text node, return null.
......@@ -137,79 +137,53 @@ class SentenceUtils {
}
/**
* Get the previous sentence start within a node group item.
* @param {ParagraphUtils.NodeGroupItem} nodeGroupItem The node group item
* this function will search.
* @param {number} startCharIndex The char index that we start from.
* @return {?number} the previous sentence start before |startCharIndex|,
* returns null if nothing found.
* Checks if the current position is a sentence start.
* @param {ParagraphUtils.NodeGroup} nodeGroup The node group of the current
* position.
* @param {number} currentCharIndex The char index of the current position.
* This is relative to the text content of the node group.
* @return {boolean} Whether the current position is a start of a sentence.
*/
static getPrevSentenceStartInNodeGroupItem(nodeGroupItem, startCharIndex) {
const nodeGroupItemHasInlineText =
nodeGroupItem && nodeGroupItem.hasInlineText;
if (!nodeGroupItemHasInlineText) {
return null;
}
const nodeGroupItemHasContent = nodeGroupItem.node.children.length > 0 &&
nodeGroupItem.node.name.length > 0;
if (!nodeGroupItemHasContent) {
return null;
}
const staticTextStartChar = nodeGroupItem.startChar;
const staticTextNode = nodeGroupItem.node;
// If the corresponding char of |startCharIndex| is before this static text
// node, skip this text node.
if (startCharIndex < staticTextStartChar) {
return null;
static isSentenceStart(nodeGroup, currentCharIndex) {
if (!nodeGroup) {
return false;
}
// If the corresponding char of |startCharIndex| is within this static text
// node, we get its relative index in this static text. Otherwise, we search
// from the end of the static text.
const searchIndexInStaticText = Math.min(
startCharIndex - staticTextStartChar, staticTextNode.name.length - 1);
// Find the index of the inline text node corresponding to the
// |searchIndexInStaticText|.
const inlineTextNodeIndex =
ParagraphUtils.findInlineTextNodeIndexByCharacterIndex(
staticTextNode, searchIndexInStaticText);
// findInlineTextNodeIndexByCharacterIndex may return a negative number,
// indicating no children in the staticTextNode.
if (inlineTextNodeIndex < 0) {
return null;
if (nodeGroup.nodes.length === 0) {
return false;
}
// Iterate backwards over each inline text nodes within this static text
// node from |inlineTextNodeIndex|.
for (let inlineTextNode, startCharInStaticText, i = inlineTextNodeIndex;
i >= 0; i--) {
inlineTextNode = staticTextNode.children[i];
// If this is the first node in the for loop, use ParagraphUtils to get
// the start char. Otherwise, update the start char by subtracting the
// name length of the current node.
startCharInStaticText = i === inlineTextNodeIndex ?
ParagraphUtils.getStartCharIndexInParent(inlineTextNode) :
startCharInStaticText - inlineTextNode.name.length;
// Iterate over all the nodeGroupItems.
for (let i = 0; i < nodeGroup.nodes.length; i++) {
const nodeGroupItem = nodeGroup.nodes[i];
// Check if this nodeGroupItem has a non-empty static text node.
if (!nodeGroupItem.node.role === RoleType.STATIC_TEXT ||
nodeGroupItem.node.name.length === 0) {
continue;
}
// Continue to the next one if the start of this inlineTextNode is still
// bigger than startCharIndex.
if (startCharInStaticText + staticTextStartChar >= startCharIndex) {
// Check if the corresponding char of |currentCharIndex| is inside this
// static text node.
const staticTextStartChar = nodeGroupItem.startChar;
const staticTextNode = nodeGroupItem.node;
if (currentCharIndex >
staticTextStartChar + staticTextNode.name.length - 1 ||
currentCharIndex < staticTextStartChar) {
continue;
}
// Iterate over all sentenceStarts to find the one that is smaller than
// startCharIndex.
for (let j = inlineTextNode.sentenceStarts.length - 1; j >= 0; j--) {
const potentialStart = inlineTextNode.sentenceStarts[j] +
startCharInStaticText + staticTextStartChar;
if (potentialStart >= startCharIndex) {
continue;
const searchIndexInStaticText = currentCharIndex - staticTextStartChar;
// Iterate over all sentenceStarts in the staticTextNode to see if we
// have |searchIndexInStaticText|.
for (let j = 0; j < staticTextNode.sentenceStarts.length; j++) {
if (staticTextNode.sentenceStarts[j] === searchIndexInStaticText) {
return true;
}
return potentialStart;
}
}
// We are off the edge of this static text node, return not found.
return null;
// We did not find a sentence start equal to |currentCharIndex|.
return false;
}
}
\ No newline at end of file
......@@ -211,79 +211,106 @@ TEST_F(
constants.Dir.BACKWARD /* direction */));
});
TEST_F('SelectToSpeakSentenceUtilsUnitTest', 'isSentenceStart', function() {
// The text of the test node group is "Hello. New. World."
const nodeGroup = getTestNodeGroupWithOneNode();
assertEquals(
true,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 0 /* startCharIndex */));
assertEquals(
false,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 3 /* startCharIndex */));
assertEquals(
true,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 7 /* startCharIndex */));
assertEquals(
false,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 11 /* startCharIndex */));
assertEquals(
true,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 12 /* startCharIndex */));
});
TEST_F(
'SelectToSpeakSentenceUtilsUnitTest', 'isSentenceStartMultiNodes',
function() {
// The text of the test node group is "Hello. New. Beautiful. World." The
// char indexes of four sentence starts are 0, 7, 12, 23.
const nodeGroup = getTestNodeGroupWithMultiNodes();
assertEquals(
true,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 0 /* startCharIndex */));
assertEquals(
false,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 3 /* startCharIndex */));
assertEquals(
true,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 7 /* startCharIndex */));
assertEquals(
false,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 11 /* startCharIndex */));
assertEquals(
true,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 12 /* startCharIndex */));
assertEquals(
false,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 15 /* startCharIndex */));
assertEquals(
true,
SentenceUtils.isSentenceStart(
nodeGroup /* nodeGroup */, 23 /* startCharIndex */));
});
function getTestNodeGroupWithOneNode() {
const inlineText = {sentenceStarts: [0, 7, 12], name: 'Hello. New. World.'};
const staticText = {children: [inlineText], name: 'Hello. New. World.'};
const node = {node: staticText, startChar: 0, hasInlineText: true};
const staticText = {sentenceStarts: [0, 7, 12], name: 'Hello. New. World.'};
const node = {node: staticText, startChar: 0};
return {nodes: [node], text: 'Hello. New. World.'};
}
function getTestNodeGroupWithMultiNodes() {
const staticText1 = {name: 'Hello. New. ', role: 'staticText'};
const inlineText1 = {
sentenceStarts: [0, 7],
const staticText1 = {
name: 'Hello. New. ',
indexInParent: 0,
parent: staticText1
role: 'staticText',
sentenceStarts: [0, 7]
};
staticText1.children = [inlineText1];
const node1 = {node: staticText1, startChar: 0, hasInlineText: true};
const node1 = {node: staticText1, startChar: 0};
const staticText2 = {name: 'Beautiful. World.', role: 'staticText'};
const inlineText2 = {
sentenceStarts: [0],
name: 'Beautiful. ',
indexInParent: 0,
parent: staticText2
};
const inlineText3 = {
sentenceStarts: [0],
name: 'World.',
indexInParent: 1,
parent: staticText2
const staticText2 = {
name: 'Beautiful. World.',
role: 'staticText',
sentenceStarts: [0, 11]
};
staticText2.children = [inlineText2, inlineText3];
const node2 = {node: staticText2, startChar: 12, hasInlineText: true};
const node2 = {node: staticText2, startChar: 12};
return {nodes: [node1, node2], text: 'Hello. New. Beautiful. World.'};
}
function getTestNodeGroupWithSentenceSpanningAcrossMultiNodes() {
const staticText1 = {name: 'Hello', role: 'staticText'};
const inlineText1 = {
sentenceStarts: [0],
name: 'Hello',
indexInParent: 0,
parent: staticText1
};
staticText1.children = [inlineText1];
const node1 = {node: staticText1, startChar: 0, hasInlineText: true};
const staticText1 = {name: 'Hello', role: 'staticText', sentenceStarts: [0]};
const node1 = {node: staticText1, startChar: 0};
const staticText2 = {name: ' world. New', role: 'staticText'};
const inlineText2 = {
sentenceStarts: [],
name: ' world.',
indexInParent: 0,
parent: staticText2
const staticText2 = {
name: ' world. New',
role: 'staticText',
sentenceStarts: [8]
};
const inlineText3 = {
sentenceStarts: [1],
name: ' New',
indexInParent: 1,
parent: staticText2
};
staticText2.children = [inlineText2, inlineText3];
const node2 = {node: staticText2, startChar: 5, hasInlineText: true};
const node2 = {node: staticText2, startChar: 5};
const staticText3 = {name: ' world.', role: 'staticText'};
const inlineText4 = {
sentenceStarts: [],
name: ' world.',
indexInParent: 0,
parent: staticText3
};
staticText3.children = [inlineText4];
const node3 = {node: staticText3, startChar: 16, hasInlineText: true};
const staticText3 = {name: ' world.', role: 'staticText', sentenceStarts: []};
const node3 = {node: staticText3, startChar: 16};
return {nodes: [node1, node2, node3], text: 'Hello world. New world.'};
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment