Make match & describe handle empty quotes ‘correctly’

What constitutes correct behaviour could be debated, but it seems
reasonable to match at every possible position.
diff --git a/packages/dom/src/text-quote/describe.ts b/packages/dom/src/text-quote/describe.ts
index 572e218..784881c 100644
--- a/packages/dom/src/text-quote/describe.ts
+++ b/packages/dom/src/text-quote/describe.ts
@@ -52,7 +52,7 @@
   // Find all matches of the text in the scope.
   const stringMatches: number[] = [];
   let fromIndex = 0;
-  while (fromIndex < scopeText.length) {
+  while (fromIndex <= scopeText.length) {
     const matchIndex = scopeText.indexOf(exactText, fromIndex);
     if (matchIndex === -1) break;
     stringMatches.push(matchIndex);
diff --git a/packages/dom/src/text-quote/match.ts b/packages/dom/src/text-quote/match.ts
index f32afce..18b077e 100644
--- a/packages/dom/src/text-quote/match.ts
+++ b/packages/dom/src/text-quote/match.ts
@@ -54,7 +54,7 @@
       : 0;
 
     let fromIndex = 0;
-    while (fromIndex < scopeText.length) {
+    while (fromIndex <= scopeText.length) {
       // Find the quote with its prefix and suffix in the string.
       const patternStartIndex = scopeText.indexOf(searchPattern, fromIndex);
       if (patternStartIndex === -1) return;
diff --git a/packages/dom/test/text-quote-describe-cases.ts b/packages/dom/test/text-quote-describe-cases.ts
index cfc1435..4f34c92 100644
--- a/packages/dom/test/text-quote-describe-cases.ts
+++ b/packages/dom/test/text-quote-describe-cases.ts
@@ -83,9 +83,23 @@
       suffix: '',
     },
   },
+  'empty quote': {
+    html: '<b>To annotate or not to annotate</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 11,
+      endContainerXPath: '//b/text()',
+      endOffset: 11,
+    },
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: '',
+      prefix: 'e',
+      suffix: ' ',
+    },
+  },
 
   // TODO test for:
-  // emtpy range
   // empty scope
   // custom scope
   // element edges, across elements, etc.
diff --git a/packages/dom/test/text-quote-match-cases.ts b/packages/dom/test/text-quote-match-cases.ts
index 33d66de..0fd757a 100644
--- a/packages/dom/test/text-quote-match-cases.ts
+++ b/packages/dom/test/text-quote-match-cases.ts
@@ -264,6 +264,72 @@
       },
     ],
   },
+  'empty quote': {
+    html: '<b>lorem</b>',
+    selector: {
+      type: 'TextQuoteSelector',
+      exact: '',
+    },
+    // A five character string contains six spots to find an empty string
+    expected: Array(6).fill(null).map((_, i) => ({
+      startContainerXPath: '//b/text()',
+      startOffset: i,
+      endContainerXPath: '//b/text()',
+      endOffset: i,
+    }))
+  },
+  'empty quote, with prefix': {
+    html: '<b>lorem ipsum dolor amet yada yada</b>',
+    selector: {
+      type: 'TextQuoteSelector',
+      exact: '',
+      prefix: 'dolor',
+    },
+    expected: [{
+      startContainerXPath: '//b/text()',
+      startOffset: 17,
+      endContainerXPath: '//b/text()',
+      endOffset: 17,
+    }]
+  },
+  'empty quote, with suffix': {
+    html: '<b>lorem ipsum dolor amet yada yada</b>',
+    selector: {
+      type: 'TextQuoteSelector',
+      exact: '',
+      suffix: 'i',
+    },
+    expected: [{
+      startContainerXPath: '//b/text()',
+      startOffset: 6,
+      endContainerXPath: '//b/text()',
+      endOffset: 6,
+    }]
+  },
+  'empty quote, with prefix and suffix': {
+    html: '<b>lorem ipsum dolor amet yada yada</b>',
+    selector: {
+      type: 'TextQuoteSelector',
+      exact: '',
+      prefix: 'lorem ',
+      suffix: 'ipsum',
+    },
+    expected: [{
+      startContainerXPath: '//b/text()',
+      startOffset: 6,
+      endContainerXPath: '//b/text()',
+      endOffset: 6,
+    }]
+  },
+  'empty quote, no matches': {
+    html: '<b>lorem ipsum dolor amet yada yada</b>',
+    selector: {
+      type: 'TextQuoteSelector',
+      exact: '',
+      prefix: 'X',
+    },
+    expected: [],
+  }
 };
 
 export default testCases;