@playpilot/tpi 3.10.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest'
2
- import { cleanPhrase, findTextNodeContaining, isNodeInLink, replaceStartingFrom, truncateAroundPhrase } from '$lib/text'
2
+ import { cleanPhrase, findAllMatchesBetweenPhrases, findShortestMatchBetweenPhrases, findSurroundingPhrases, findTextNodeContaining, getFirstNumberOfWordsInString, getNumberOfLeadingAndTrailingSpaces, isNodeInLink, replaceStartingFrom, reverseString, truncateAroundPhrase } from '$lib/text'
3
3
 
4
4
  describe('text.js', () => {
5
5
  beforeEach(() => {
@@ -127,4 +127,223 @@ describe('text.js', () => {
127
127
  expect(truncateAroundPhrase('Some sentence with a word', 'sentence with a', 10)).toBe('…tence with…')
128
128
  })
129
129
  })
130
+
131
+ describe('getNumberOfLeadingAndTrailingSpaces', () => {
132
+ it('Should return the number of leading spaces and trailing spaces', () => {
133
+ expect(getNumberOfLeadingAndTrailingSpaces(' Some text ')).toEqual({ leadingSpaces: 2, trailingSpaces: 1 })
134
+ expect(getNumberOfLeadingAndTrailingSpaces(' Some text ')).toEqual({ leadingSpaces: 1, trailingSpaces: 3 })
135
+ })
136
+
137
+ it('Should return 0 for no leading or trailing spaces were given', () => {
138
+ expect(getNumberOfLeadingAndTrailingSpaces('Some text ')).toEqual({ leadingSpaces: 0, trailingSpaces: 1 })
139
+ expect(getNumberOfLeadingAndTrailingSpaces(' Some text')).toEqual({ leadingSpaces: 1, trailingSpaces: 0 })
140
+ expect(getNumberOfLeadingAndTrailingSpaces('Some text')).toEqual({ leadingSpaces: 0, trailingSpaces: 0 })
141
+ })
142
+
143
+ it('Should return 0 when given text is empty', () => {
144
+ expect(getNumberOfLeadingAndTrailingSpaces('')).toEqual({ leadingSpaces: 0, trailingSpaces: 0 })
145
+ })
146
+ })
147
+
148
+ describe('findAllMatchesBetweenPhrases', () => {
149
+ it('Should find a match between the given delimiters', () => {
150
+ expect(findAllMatchesBetweenPhrases('Some text with a match', 'text', 'match')).toEqual([
151
+ { match: ' with a ', index: 9 },
152
+ ])
153
+ })
154
+
155
+ it('Should find multiple matches between the given delimiters when multiple are present', () => {
156
+ expect(findAllMatchesBetweenPhrases('text Some match text with a match', 'text', 'match')).toEqual([
157
+ { match: ' Some ', index: 4 },
158
+ { match: ' with a ', index: 20 },
159
+ ])
160
+ })
161
+
162
+ it('Should match from start of the string when no start string is given', () => {
163
+ expect(findAllMatchesBetweenPhrases('Some text with a match', '', 'with')).toEqual([
164
+ { match: 'Some text ', index: 0 },
165
+ ])
166
+ })
167
+
168
+ it('Should match to end of the string when no end string is given', () => {
169
+ expect(findAllMatchesBetweenPhrases('Some text with a match', 'with', '')).toEqual([
170
+ { match: ' a match', index: 14 },
171
+ ])
172
+ })
173
+
174
+ it('Should match from start to end of the string when no start and end string is given', () => {
175
+ expect(findAllMatchesBetweenPhrases('Some text with a match', '', '')).toEqual([
176
+ { match: 'Some text with a match', index: 0 },
177
+ ])
178
+ })
179
+ })
180
+
181
+ describe('findShortestMatchBetweenPhrases', () => {
182
+ it('Should return the shortest match', () => {
183
+ expect(findShortestMatchBetweenPhrases('A start with end and another start with another end', 'with', 'start', 'end')).toEqual({
184
+ match: ' with ',
185
+ index: 7,
186
+ })
187
+ })
188
+
189
+ it('Should correctly account for html elements', () => {
190
+ expect(findShortestMatchBetweenPhrases('A start <strong>with</strong> end and another start and end', 'with', 'start', 'end')).toEqual({
191
+ match: ' <strong>with</strong> ',
192
+ index: 7,
193
+ })
194
+ })
195
+
196
+ it('Should match phrase even if broken up by html', () => {
197
+ expect(findShortestMatchBetweenPhrases('A start wi<strong>th</strong> end and another start and end', 'with', 'start', 'end')).toEqual({
198
+ match: ' wi<strong>th</strong> ',
199
+ index: 7,
200
+ })
201
+ })
202
+
203
+ it('Should match phrase even if text contains incomplete html', () => {
204
+ expect(findShortestMatchBetweenPhrases('<a>A start with</strong> end <strong>and another start and end', 'with', 'start', 'end')).toEqual({
205
+ match: ' with</strong> ',
206
+ index: 10,
207
+ })
208
+ })
209
+
210
+ it('Should return null when no matches are found', () => {
211
+ expect(findShortestMatchBetweenPhrases('A string without matches', 'with', 'start', 'end')).toEqual(null)
212
+ })
213
+ })
214
+
215
+ describe('getFirstNumberOfWordsInString', () => {
216
+ it('Should return the first word in a given phrase, separated by spaces', () => {
217
+ expect(getFirstNumberOfWordsInString('Some sentence with words')).toBe('Some')
218
+ })
219
+
220
+ it('Should return the given number of words in a given phrase', () => {
221
+ expect(getFirstNumberOfWordsInString('Some sentence with words', 3)).toBe('Some sentence with')
222
+ })
223
+
224
+ it('Should return nothing if given string is empty', () => {
225
+ expect(getFirstNumberOfWordsInString('')).toBe('')
226
+ })
227
+
228
+ it('Should return word including special characters', () => {
229
+ expect(getFirstNumberOfWordsInString('("Some") sentence with words')).toBe('("Some")')
230
+ })
231
+
232
+ it('Should include special characters as words where relevant', () => {
233
+ expect(getFirstNumberOfWordsInString('Some & sentence with words', 2)).toBe('Some &')
234
+ })
235
+ })
236
+
237
+ describe('findSurroundingPhrases', () => {
238
+ it('Should return the first 2 words around the given indexes inside of a text node', () => {
239
+ document.body.innerHTML = 'A text with a phrase that can be matched'
240
+ const node = /** @type {Element} */ (document.querySelector('body')).childNodes[0]
241
+
242
+ expect(findSurroundingPhrases(node, 11, 20)).toEqual({
243
+ before: 'text with',
244
+ after: 'that can',
245
+ })
246
+ })
247
+
248
+ it('Should return the first 2 words around the given indexes inside of a html element', () => {
249
+ document.body.innerHTML = '<span>A text with a phrase that can be matched</span>'
250
+ const node = /** @type {Node} */ (document.querySelector('span'))
251
+
252
+ expect(findSurroundingPhrases(node, 11, 20)).toEqual({
253
+ before: 'text with',
254
+ after: 'that can',
255
+ })
256
+ })
257
+
258
+ it('Should return ignore html elements', () => {
259
+ document.body.innerHTML = '<span>A text <strong>with</strong> a <em>phrase</em> that can be matched</span>'
260
+ const node = /** @type {Node} */ (document.querySelector('span'))
261
+
262
+ expect(findSurroundingPhrases(node, 11, 20)).toEqual({
263
+ before: 'text with',
264
+ after: 'that can',
265
+ })
266
+ })
267
+
268
+ it('Should return ignore html elements on partial words', () => {
269
+ document.body.innerHTML = '<span>A text <strong>wi</strong>th a <em>phr</em><em>ase</em> that can be matched</span>'
270
+ const node = /** @type {Node} */ (document.querySelector('span'))
271
+
272
+ expect(findSurroundingPhrases(node, 11, 20)).toEqual({
273
+ before: 'text with',
274
+ after: 'that can',
275
+ })
276
+ })
277
+
278
+ it('Should return 1 word for before if start only contains 1 word', () => {
279
+ document.body.innerHTML = 'Text with a phrase that can be matched'
280
+ const node = /** @type {Element} */ (document.querySelector('body')).childNodes[0]
281
+
282
+ expect(findSurroundingPhrases(node, 4, 9)).toEqual({
283
+ before: 'Text',
284
+ after: 'a phrase',
285
+ })
286
+ })
287
+
288
+ it('Should return no words for before when starting at 0', () => {
289
+ document.body.innerHTML = 'A text with a phrase that can be matched'
290
+ const node = /** @type {Element} */ (document.querySelector('body')).childNodes[0]
291
+
292
+ expect(findSurroundingPhrases(node, 0, 6)).toEqual({
293
+ before: '',
294
+ after: 'with a',
295
+ })
296
+ })
297
+
298
+ it('Should return no words for before when no starting text is found', () => {
299
+ document.body.innerHTML = ' A text with a phrase that can be matched'
300
+ const node = /** @type {Element} */ (document.querySelector('body')).childNodes[0]
301
+
302
+ expect(findSurroundingPhrases(node, 3, 9)).toEqual({
303
+ before: '',
304
+ after: 'with a',
305
+ })
306
+ })
307
+
308
+ it('Should return no words for after when end is at the end of the string', () => {
309
+ document.body.innerHTML = 'A text with a phrase that can be matched'
310
+ const node = /** @type {Element} */ (document.querySelector('body')).childNodes[0]
311
+
312
+ expect(findSurroundingPhrases(node, 30, /** @type {string} */ (node.textContent).length)).toEqual({
313
+ before: 'that can',
314
+ after: '',
315
+ })
316
+ })
317
+
318
+ it('Should return no words for after when no after text is found', () => {
319
+ document.body.innerHTML = 'A text with a phrase that can be matched '
320
+ const node = /** @type {Element} */ (document.querySelector('body')).childNodes[0]
321
+
322
+ expect(findSurroundingPhrases(node, 30, /** @type {string} */ (node.textContent).length - 3)).toEqual({
323
+ before: 'that can',
324
+ after: '',
325
+ })
326
+ })
327
+
328
+ it('Should return 1 word for after if start only contains 1 word', () => {
329
+ document.body.innerHTML = 'Text with a phrase that can be matched'
330
+ const node = /** @type {Element} */ (document.querySelector('body')).childNodes[0]
331
+
332
+ expect(findSurroundingPhrases(node, 19, 30)).toEqual({
333
+ before: 'a phrase',
334
+ after: 'matched',
335
+ })
336
+ })
337
+ })
338
+
339
+ describe('reverseString', () => {
340
+ it('Should reverse a given string', () => {
341
+ expect(reverseString('abc')).toBe('cba')
342
+ expect(reverseString('abc 123')).toBe('321 cba')
343
+ })
344
+
345
+ it('Should handle empty strings by returning an empty string', () => {
346
+ expect(reverseString('')).toBe('')
347
+ })
348
+ })
130
349
  })
@@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
4
4
  import ManualInjection from '../../../../routes/components/Editorial/ManualInjection.svelte'
5
5
  import { searchTitles } from '$lib/search'
6
6
  import { title } from '$lib/fakeData'
7
+ import { getIndexOfSelection } from '$lib/selection'
7
8
 
8
9
  function createSelectionMock() {
9
10
  const container = document.querySelector('div')
@@ -27,14 +28,20 @@ vi.mock('$lib/search', () => ({
27
28
  searchTitles: vi.fn(),
28
29
  }))
29
30
 
31
+ vi.mock('$lib/selection', () => ({
32
+ getIndexOfSelection: vi.fn(),
33
+ }))
34
+
30
35
  describe('ManualInjection.svelte', () => {
31
36
  beforeEach(() => {
32
37
  vi.resetAllMocks()
38
+ vi.mocked(getIndexOfSelection).mockReturnValueOnce({ start: 0, end: 0 })
33
39
 
34
40
  document.body.innerHTML = '<div>Some text in a sentence</div>'
35
41
  })
36
42
 
37
43
  it('Should input selected text in selected text input and search input', async () => {
44
+ vi.mocked(getIndexOfSelection).mockReturnValueOnce({ start: 0, end: 0 })
38
45
  createSelectionMock()
39
46
 
40
47
  const { getByLabelText } = render(ManualInjection, { pageText: document.body.innerText, onsave: () => null })
@@ -58,6 +65,7 @@ describe('ManualInjection.svelte', () => {
58
65
  createSelectionMock()
59
66
 
60
67
  vi.mocked(searchTitles).mockResolvedValueOnce([title])
68
+ vi.mocked(getIndexOfSelection).mockReturnValueOnce({ start: 0, end: 0 })
61
69
 
62
70
  const { getByText } = render(ManualInjection, { pageText: document.body.innerText, onsave: () => null })
63
71
 
@@ -72,6 +80,7 @@ describe('ManualInjection.svelte', () => {
72
80
  createSelectionMock()
73
81
 
74
82
  vi.mocked(searchTitles).mockResolvedValueOnce([title])
83
+ vi.mocked(getIndexOfSelection).mockReturnValueOnce({ start: 5, end: 10 })
75
84
 
76
85
  const onsave = vi.fn()
77
86
  const { getByText } = render(ManualInjection, { pageText: document.body.innerText, onsave })
@@ -88,11 +97,14 @@ describe('ManualInjection.svelte', () => {
88
97
  key: expect.any(String),
89
98
  title_details: title,
90
99
  manual: true,
100
+ phrase_before: 'Some',
101
+ phrase_after: 'in a',
91
102
  })
92
103
  })
93
104
 
94
105
  it('Should select content outside of element if content is too short', async () => {
95
106
  vi.mocked(searchTitles).mockResolvedValueOnce([title])
107
+ vi.mocked(getIndexOfSelection).mockReturnValueOnce({ start: 0, end: 0 })
96
108
 
97
109
  document.body.innerHTML = '<div>Some other sentence. Some <strong><span>text</span></strong> in a sentence</div>'
98
110
 
@@ -127,11 +139,14 @@ describe('ManualInjection.svelte', () => {
127
139
  key: expect.any(String),
128
140
  title_details: title,
129
141
  manual: true,
142
+ phrase_before: '',
143
+ phrase_after: '',
130
144
  })
131
145
  })
132
146
 
133
147
  it('Should not select content if it is outside of given parent', async () => {
134
148
  vi.mocked(searchTitles).mockResolvedValueOnce([title])
149
+ vi.mocked(getIndexOfSelection).mockReturnValueOnce({ start: 0, end: 0 })
135
150
 
136
151
  document.body.innerHTML = '<div>Some text <main><p>in a sentence</p></main></div>'
137
152
 
@@ -182,34 +197,4 @@ describe('ManualInjection.svelte', () => {
182
197
 
183
198
  await waitFor(() => expect(searchTitles).toHaveBeenCalled())
184
199
  })
185
-
186
- it('Should not select content if it contains multiple child nodes with content', async () => {
187
- document.body.innerHTML = '<main><div>Some text in a sentence</div></main>'
188
-
189
- const container = document.querySelector('div')
190
- const onsave = vi.fn()
191
- const { getByText } = render(ManualInjection, { pageText: document.body.innerText, onsave })
192
-
193
- // @ts-ignore
194
- window.getSelection = vi.fn(() => ({
195
- toString: () => 'Some text',
196
- getRangeAt: () => ({
197
- commonAncestorContainer: container,
198
- startContainer: container,
199
- startOffset: 0,
200
- endOffset: 0,
201
- cloneContents: () => ({ childNodes: [{ textContent: 'a' }, { textContent: 'b' }] }),
202
- }),
203
- anchorNode: container,
204
- focusNode: container,
205
- }))
206
-
207
- await fireEvent.mouseUp(window)
208
-
209
- expect(searchTitles).not.toHaveBeenCalled()
210
-
211
- await waitFor(() => {
212
- expect(getByText('Selection contains multiple items. Selection may not contain a mix of styled and non styled text. Please select the text more directly.')).toBeTruthy()
213
- })
214
- })
215
200
  })
@@ -39,15 +39,6 @@ describe('Title.svelte', () => {
39
39
  expect(getByText(title.year)).toBeTruthy()
40
40
  })
41
41
 
42
- it('Should include data attributes', () => {
43
- const { container } = render(Title, { title })
44
-
45
- const element = /** @type {HTMLElement} */ (container.querySelector('[data-playpilot-link-injections-title]'))
46
-
47
- expect(element).toBeTruthy()
48
- expect(element?.dataset.playpilotOriginalTitle).toBe(title.original_title)
49
- })
50
-
51
42
  it('Should not have small class by default', () => {
52
43
  const { container } = render(Title, { title })
53
44