@playpilot/tpi 3.0.1 → 3.1.0-beta.2
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.
- package/dist/link-injections.js +7 -7
- package/package.json +1 -1
- package/src/lib/api.ts +21 -1
- package/src/lib/enums/TrackingEvent.ts +2 -0
- package/src/lib/linkInjection.ts +8 -8
- package/src/lib/text.ts +24 -0
- package/src/lib/types/config.d.ts +3 -0
- package/src/routes/+page.svelte +18 -3
- package/src/routes/components/Editorial/AIIndicator.svelte +43 -2
- package/src/routes/components/Editorial/DragHandle.svelte +3 -3
- package/src/routes/components/Editorial/Editor.svelte +58 -28
- package/src/routes/components/Editorial/EditorItem.svelte +52 -24
- package/src/routes/components/Editorial/ManualInjection.svelte +69 -16
- package/src/routes/components/Editorial/ResizeHandle.svelte +111 -0
- package/src/routes/components/Editorial/Search/TitleSearch.svelte +17 -90
- package/src/routes/components/Editorial/Search/TitleSearchItem.svelte +107 -0
- package/src/routes/components/Title.svelte +28 -4
- package/src/tests/helpers.js +2 -1
- package/src/tests/lib/api.test.js +30 -1
- package/src/tests/lib/linkInjection.test.js +39 -11
- package/src/tests/lib/text.test.js +23 -1
- package/src/tests/routes/+page.test.js +52 -5
- package/src/tests/routes/components/Editorial/AiIndicator.test.js +5 -2
- package/src/tests/routes/components/Editorial/DragHandle.test.js +5 -5
- package/src/tests/routes/components/Editorial/Editor.test.js +69 -0
- package/src/tests/routes/components/Editorial/EditorItem.test.js +34 -10
- package/src/tests/routes/components/Editorial/ManualInjection.test.js +5 -1
- package/src/tests/routes/components/Editorial/ResizeHandle.test.js +45 -0
- package/src/tests/routes/components/Editorial/Search/TitleSearch.test.js +6 -4
- package/src/tests/routes/components/Editorial/Search/TitleSearchItem.test.js +44 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fireEvent } from '@testing-library/svelte'
|
|
2
2
|
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import { injectLinksInDocument, clearLinkInjections, clearLinkInjection, getLinkInjectionElements, insertAfterArticlePlaylinks, getLinkInjectionsParentElement, sortLinkInjectionsByRange, isAvailableAsManualInjection,
|
|
4
|
+
import { injectLinksInDocument, clearLinkInjections, clearLinkInjection, getLinkInjectionElements, insertAfterArticlePlaylinks, getLinkInjectionsParentElement, sortLinkInjectionsByRange, isAvailableAsManualInjection, filterRemovedAndInactiveInjections, isEquivalentInjection, filterInvalidInTextInjections, filterInvalidAfterArticleInjections, isValidInjection } from '$lib/linkInjection'
|
|
5
5
|
import { mount, unmount } from 'svelte'
|
|
6
6
|
import { generateInjection } from '../helpers'
|
|
7
7
|
|
|
@@ -118,6 +118,24 @@ describe('linkInjection.js', () => {
|
|
|
118
118
|
expect(document.body.innerHTML).toBe(`<p>${sentence}</p>`)
|
|
119
119
|
})
|
|
120
120
|
|
|
121
|
+
it('Should ignore AI injections that are marked as inactive as a matching manual injections', () => {
|
|
122
|
+
const sentence = 'This is a sentence with an injection.'
|
|
123
|
+
const injection = generateInjection(sentence, 'an injection')
|
|
124
|
+
|
|
125
|
+
document.body.innerHTML = `<p>${sentence}</p>`
|
|
126
|
+
|
|
127
|
+
const elements = Array.from(document.body.querySelectorAll('p'))
|
|
128
|
+
|
|
129
|
+
const injections = {
|
|
130
|
+
aiInjections: [injection],
|
|
131
|
+
manualInjections: [{ ...injection, manual: true, inactive: true }],
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
injectLinksInDocument(elements, () => null, injections)
|
|
135
|
+
|
|
136
|
+
expect(document.body.innerHTML).toBe(`<p>${sentence}</p>`)
|
|
137
|
+
})
|
|
138
|
+
|
|
121
139
|
it('Should keep existing HTML elements intact', () => {
|
|
122
140
|
const injection = generateInjection('This is a sentence with an injection.', 'an injection')
|
|
123
141
|
const linkInjections = [injection]
|
|
@@ -216,7 +234,7 @@ describe('linkInjection.js', () => {
|
|
|
216
234
|
|
|
217
235
|
const elements = Array.from(document.body.querySelectorAll('p'))
|
|
218
236
|
|
|
219
|
-
/** @type {LinkInjection[]} */
|
|
237
|
+
/** @type {import('$lib/types/injection').LinkInjection[]} */
|
|
220
238
|
const linkInjections = []
|
|
221
239
|
|
|
222
240
|
injectLinksInDocument(elements, () => null, { aiInjections: linkInjections, manualInjections: [] })
|
|
@@ -228,7 +246,7 @@ describe('linkInjection.js', () => {
|
|
|
228
246
|
/** @type {HTMLElement[]} */
|
|
229
247
|
const elements = []
|
|
230
248
|
|
|
231
|
-
/** @type {LinkInjection[]} */
|
|
249
|
+
/** @type {import('$lib/types/injection').LinkInjection[]} */
|
|
232
250
|
const linkInjections = []
|
|
233
251
|
|
|
234
252
|
expect(() => injectLinksInDocument(elements, () => null, { aiInjections: linkInjections, manualInjections: [] })).not.toThrow()
|
|
@@ -446,8 +464,6 @@ describe('linkInjection.js', () => {
|
|
|
446
464
|
const mock = vi.fn()
|
|
447
465
|
injectLinksInDocument(elements, mock, { aiInjections: [], manualInjections: injections })
|
|
448
466
|
|
|
449
|
-
console.log(document.body.innerHTML)
|
|
450
|
-
|
|
451
467
|
expect(document.querySelectorAll('a')).toHaveLength(4)
|
|
452
468
|
})
|
|
453
469
|
|
|
@@ -469,8 +485,6 @@ describe('linkInjection.js', () => {
|
|
|
469
485
|
const mock = vi.fn()
|
|
470
486
|
injectLinksInDocument(elements, mock, { aiInjections: [], manualInjections: injections })
|
|
471
487
|
|
|
472
|
-
console.log(document.body.innerHTML)
|
|
473
|
-
|
|
474
488
|
expect(document.querySelectorAll('a')).toHaveLength(3)
|
|
475
489
|
})
|
|
476
490
|
})
|
|
@@ -767,26 +781,40 @@ describe('linkInjection.js', () => {
|
|
|
767
781
|
})
|
|
768
782
|
})
|
|
769
783
|
|
|
770
|
-
describe('
|
|
784
|
+
describe('filterRemovedAndInactiveInjections', () => {
|
|
771
785
|
it('Should not return injections that are removed or have an removed manual equivalent', () => {
|
|
772
786
|
const aiInjection = generateInjection('Some sentence', 'Some title')
|
|
773
787
|
const manualInjection = { ...aiInjection, manual: true, removed: true }
|
|
774
788
|
|
|
775
|
-
expect(
|
|
789
|
+
expect(filterRemovedAndInactiveInjections([aiInjection, manualInjection])).toEqual([])
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
it('Should not return injections that are inactive or have an inactive manual equivalent', () => {
|
|
793
|
+
const aiInjection = generateInjection('Some sentence', 'Some title')
|
|
794
|
+
const manualInjection = { ...aiInjection, manual: true, inactive: true }
|
|
795
|
+
|
|
796
|
+
expect(filterRemovedAndInactiveInjections([aiInjection, manualInjection])).toEqual([])
|
|
776
797
|
})
|
|
777
798
|
|
|
778
799
|
it('Should return ai injections that have no matching manual injection', () => {
|
|
779
800
|
const aiInjection = generateInjection('Some sentence', 'Some title')
|
|
780
801
|
const manualInjection = { ...generateInjection('Some other sentence', 'Some other title'), manual: true }
|
|
781
802
|
|
|
782
|
-
expect(
|
|
803
|
+
expect(filterRemovedAndInactiveInjections([aiInjection, manualInjection])).toEqual([aiInjection, manualInjection])
|
|
783
804
|
})
|
|
784
805
|
|
|
785
806
|
it('Should return manual injections that have been removed at one point, but a new injection for the same title was created', () => {
|
|
786
807
|
const manualInjection = { ...generateInjection('Some other sentence', 'Some other title'), manual: true }
|
|
787
808
|
const removedManualInjection = { ...manualInjection, removed: true }
|
|
788
809
|
|
|
789
|
-
expect(
|
|
810
|
+
expect(filterRemovedAndInactiveInjections([manualInjection, removedManualInjection])).toEqual([manualInjection])
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
it('Should return manual injections that have been inactive at one point, but a new injection for the same title was created', () => {
|
|
814
|
+
const manualInjection = { ...generateInjection('Some other sentence', 'Some other title'), manual: true }
|
|
815
|
+
const inactiveManualInjection = { ...manualInjection, inactive: true }
|
|
816
|
+
|
|
817
|
+
expect(filterRemovedAndInactiveInjections([manualInjection, inactiveManualInjection])).toEqual([manualInjection])
|
|
790
818
|
})
|
|
791
819
|
})
|
|
792
820
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
-
import { cleanPhrase, findTextNodeContaining, isNodeInLink, replaceStartingFrom } from '$lib/text'
|
|
2
|
+
import { cleanPhrase, findTextNodeContaining, isNodeInLink, replaceStartingFrom, truncateAroundPhrase } from '$lib/text'
|
|
3
3
|
|
|
4
4
|
describe('text.js', () => {
|
|
5
5
|
beforeEach(() => {
|
|
@@ -105,4 +105,26 @@ describe('text.js', () => {
|
|
|
105
105
|
expect(cleanPhrase('{&}')).toBe('{&}')
|
|
106
106
|
})
|
|
107
107
|
})
|
|
108
|
+
|
|
109
|
+
describe('truncateAroundPhrase', () => {
|
|
110
|
+
it('Should truncate a sentence at the end when the phrase is near the start', () => {
|
|
111
|
+
expect(truncateAroundPhrase('Some sentence with a word', 'Some', 10)).toBe('Some sente…')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('Should truncate a sentence at the start when the phrase is near the end', () => {
|
|
115
|
+
expect(truncateAroundPhrase('Some sentence with a word', 'a word', 10)).toBe('…ith a word')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('Should truncate a sentence at the start end the start when the phrase is near the center', () => {
|
|
119
|
+
expect(truncateAroundPhrase('Some sentence with a word', 'with', 10)).toBe('…ce with a…')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('Should not truncate a sentence if it fits regardless', () => {
|
|
123
|
+
expect(truncateAroundPhrase('Some sentence with a word', 'with', 50)).toBe('Some sentence with a word')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('Should break phrase and truncate if the phrase would not fit in max lengtj', () => {
|
|
127
|
+
expect(truncateAroundPhrase('Some sentence with a word', 'sentence with a', 10)).toBe('…tence with…')
|
|
128
|
+
})
|
|
129
|
+
})
|
|
108
130
|
})
|
|
@@ -2,13 +2,14 @@ import { render, waitFor } from '@testing-library/svelte'
|
|
|
2
2
|
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
|
|
3
3
|
|
|
4
4
|
import page from '../../routes/+page.svelte'
|
|
5
|
-
import { pollLinkInjections } from '$lib/api'
|
|
5
|
+
import { fetchConfig, pollLinkInjections } from '$lib/api'
|
|
6
6
|
import { track } from '$lib/tracking'
|
|
7
7
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
8
8
|
import { authorize } from '$lib/auth'
|
|
9
9
|
|
|
10
10
|
vi.mock('$lib/api', () => ({
|
|
11
11
|
pollLinkInjections: vi.fn(),
|
|
12
|
+
fetchConfig: vi.fn(),
|
|
12
13
|
}))
|
|
13
14
|
|
|
14
15
|
vi.mock('$lib/tracking', () => ({
|
|
@@ -23,6 +24,10 @@ vi.mock(import('$lib/auth'), async (importOriginal) => {
|
|
|
23
24
|
}
|
|
24
25
|
})
|
|
25
26
|
|
|
27
|
+
vi.mock('$lib/url', () => ({
|
|
28
|
+
getFullUrlPath: () => '/test',
|
|
29
|
+
}))
|
|
30
|
+
|
|
26
31
|
describe('$routes/+page.svelte', () => {
|
|
27
32
|
beforeEach(() => {
|
|
28
33
|
vi.resetAllMocks()
|
|
@@ -38,9 +43,11 @@ describe('$routes/+page.svelte', () => {
|
|
|
38
43
|
window.PlayPiloLinkInjections = null
|
|
39
44
|
})
|
|
40
45
|
|
|
41
|
-
it('Should call pollLinkInjections on mount', () => {
|
|
46
|
+
it('Should call pollLinkInjections and fetchConfig on mount', async () => {
|
|
42
47
|
render(page)
|
|
43
|
-
|
|
48
|
+
|
|
49
|
+
expect(fetchConfig).toHaveBeenCalled()
|
|
50
|
+
await waitFor(() => expect(pollLinkInjections).toHaveBeenCalled())
|
|
44
51
|
})
|
|
45
52
|
|
|
46
53
|
it('Should call pollLinkInjections again if ai_running is true while in editorial mode', async () => {
|
|
@@ -58,14 +65,17 @@ describe('$routes/+page.svelte', () => {
|
|
|
58
65
|
})
|
|
59
66
|
})
|
|
60
67
|
|
|
61
|
-
it('Should use the given selector when present', () => {
|
|
68
|
+
it('Should use the given selector when present', async () => {
|
|
62
69
|
document.body.innerHTML = '<div class="some-element"><p>Here</p></div> <p>Not here</p>'
|
|
63
70
|
|
|
64
71
|
// @ts-ignore
|
|
65
72
|
window.PlayPilotLinkInjections = { selector: '.some-element' }
|
|
66
73
|
|
|
67
74
|
render(page)
|
|
68
|
-
|
|
75
|
+
|
|
76
|
+
await waitFor(() => {
|
|
77
|
+
expect(pollLinkInjections).toHaveBeenCalledWith(expect.any(String), '<p>Here</p>', { maxTries: 1 })
|
|
78
|
+
})
|
|
69
79
|
})
|
|
70
80
|
|
|
71
81
|
it('Should render editor if correct url param is given', async () => {
|
|
@@ -121,4 +131,41 @@ describe('$routes/+page.svelte', () => {
|
|
|
121
131
|
|
|
122
132
|
expect(track).toHaveBeenCalledWith(TrackingEvent.ArticlePageView)
|
|
123
133
|
})
|
|
134
|
+
|
|
135
|
+
it('Should not inject if config exclude_urls_pattern matches current url', async () => {
|
|
136
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ exclude_urls_pattern: '/t' })
|
|
137
|
+
|
|
138
|
+
render(page)
|
|
139
|
+
|
|
140
|
+
await new Promise(res => setTimeout(res, 100)) // Await all fetches
|
|
141
|
+
expect(pollLinkInjections).not.toHaveBeenCalled()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('Should not inject if config exclude_urls_pattern matches current url with more complex regex pattern', async () => {
|
|
145
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ exclude_urls_pattern: '^/test$' })
|
|
146
|
+
|
|
147
|
+
render(page)
|
|
148
|
+
|
|
149
|
+
await new Promise(res => setTimeout(res, 100)) // Await all fetches
|
|
150
|
+
expect(pollLinkInjections).not.toHaveBeenCalled()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('Should not inject if config returns an error', async () => {
|
|
154
|
+
vi.mocked(fetchConfig).mockRejectedValueOnce('null')
|
|
155
|
+
|
|
156
|
+
render(page)
|
|
157
|
+
|
|
158
|
+
await new Promise(res => setTimeout(res, 100)) // Await all fetches
|
|
159
|
+
expect(pollLinkInjections).not.toHaveBeenCalled()
|
|
160
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.FetchingConfigFailed)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('Should inject if config exclude_urls_pattern does not match current url', async () => {
|
|
164
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ exclude_urls_pattern: '/something-else' })
|
|
165
|
+
|
|
166
|
+
render(page)
|
|
167
|
+
|
|
168
|
+
await new Promise(res => setTimeout(res, 100)) // Await all fetches
|
|
169
|
+
expect(pollLinkInjections).toHaveBeenCalled()
|
|
170
|
+
})
|
|
124
171
|
})
|
|
@@ -6,18 +6,20 @@ import { generateInjection } from '../../../helpers'
|
|
|
6
6
|
|
|
7
7
|
describe('AIIndicator.svelte', () => {
|
|
8
8
|
it('Should should running state by default', () => {
|
|
9
|
-
const { getByText } = render(AIIndicator, { onadd: () => null })
|
|
9
|
+
const { getByText, getByTestId } = render(AIIndicator, { onadd: () => null })
|
|
10
10
|
|
|
11
11
|
expect(getByText('AI links are currently processing.', { exact: false })).toBeTruthy()
|
|
12
|
+
expect(getByTestId('loading-bar')).toBeTruthy()
|
|
12
13
|
})
|
|
13
14
|
|
|
14
15
|
it('Should show completed state with text depending on length of given array', async () => {
|
|
15
|
-
const { getByText, component } = render(AIIndicator, { onadd: () => null })
|
|
16
|
+
const { getByText, component, queryByTestId } = render(AIIndicator, { onadd: () => null })
|
|
16
17
|
|
|
17
18
|
component.notifyUserOfNewState([generateInjection('Some sentence', 'Some title')])
|
|
18
19
|
await waitFor(() => {
|
|
19
20
|
expect(getByText('AI links are ready.', { exact: false })).toBeTruthy()
|
|
20
21
|
expect(getByText('1 New link was found.')).toBeTruthy()
|
|
22
|
+
expect(queryByTestId('loading-bar')).not.toBeTruthy()
|
|
21
23
|
})
|
|
22
24
|
|
|
23
25
|
component.notifyUserOfNewState([generateInjection('Some sentence', 'Some title'), generateInjection('Some sentence', 'Some title')])
|
|
@@ -29,6 +31,7 @@ describe('AIIndicator.svelte', () => {
|
|
|
29
31
|
component.notifyUserOfNewState([])
|
|
30
32
|
await waitFor(() => {
|
|
31
33
|
expect(getByText('AI links finished running, but no new links were found.')).toBeTruthy()
|
|
34
|
+
expect(queryByTestId('loading-bar')).not.toBeTruthy()
|
|
32
35
|
})
|
|
33
36
|
})
|
|
34
37
|
|
|
@@ -6,7 +6,7 @@ import DragHandle from '../../../../routes/components/Editorial/DragHandle.svelt
|
|
|
6
6
|
describe('DragHandle.svelte', () => {
|
|
7
7
|
it('Should position the given element based on the given position', () => {
|
|
8
8
|
document.body.innerHTML = '<div></div>'
|
|
9
|
-
const element = document.querySelector('div')
|
|
9
|
+
const element = /** @type {HTMLElement} */ (document.querySelector('div'))
|
|
10
10
|
render(DragHandle, { element, position: { x: 50, y: 30 } })
|
|
11
11
|
|
|
12
12
|
expect(element?.style.right).toBe('50px')
|
|
@@ -15,7 +15,7 @@ describe('DragHandle.svelte', () => {
|
|
|
15
15
|
|
|
16
16
|
it('Should limit the position to be visible on the screen', () => {
|
|
17
17
|
document.body.innerHTML = '<div></div>'
|
|
18
|
-
const element = document.querySelector('div')
|
|
18
|
+
const element = /** @type {HTMLElement} */ (document.querySelector('div'))
|
|
19
19
|
render(DragHandle, { element, position: { x: 5000, y: 5000 } })
|
|
20
20
|
|
|
21
21
|
expect(element?.style.right).toBe(window.innerWidth + 'px')
|
|
@@ -24,7 +24,7 @@ describe('DragHandle.svelte', () => {
|
|
|
24
24
|
|
|
25
25
|
it('Should limit the position to the given limit', () => {
|
|
26
26
|
document.body.innerHTML = '<div></div>'
|
|
27
|
-
const element = document.querySelector('div')
|
|
27
|
+
const element = /** @type {HTMLElement} */ (document.querySelector('div'))
|
|
28
28
|
render(DragHandle, { element, position: { x: 0, y: 0 }, limit: { x: 10, y: 10 } })
|
|
29
29
|
|
|
30
30
|
expect(element?.style.right).toBe('10px')
|
|
@@ -33,7 +33,7 @@ describe('DragHandle.svelte', () => {
|
|
|
33
33
|
|
|
34
34
|
it('Should limit the position to both the screen size and the given limit', () => {
|
|
35
35
|
document.body.innerHTML = '<div></div>'
|
|
36
|
-
const element = document.querySelector('div')
|
|
36
|
+
const element = /** @type {HTMLElement} */ (document.querySelector('div'))
|
|
37
37
|
render(DragHandle, { element, position: { x: 5000, y: 5000 }, limit: { x: 10, y: 10 } })
|
|
38
38
|
|
|
39
39
|
expect(element?.style.right).toBe(window.innerWidth - 10 + 'px')
|
|
@@ -44,7 +44,7 @@ describe('DragHandle.svelte', () => {
|
|
|
44
44
|
document.body.innerHTML = '<div></div>'
|
|
45
45
|
|
|
46
46
|
const onchange = vi.fn()
|
|
47
|
-
const element = document.querySelector('div')
|
|
47
|
+
const element = /** @type {HTMLElement} */ (document.querySelector('div'))
|
|
48
48
|
render(DragHandle, { element, position: { x: 5000, y: 5000 }, onchange })
|
|
49
49
|
|
|
50
50
|
expect(onchange).toHaveBeenCalledWith({
|
|
@@ -4,12 +4,19 @@ import { describe, expect, it, vi } from 'vitest'
|
|
|
4
4
|
import Editor from '../../../../routes/components/Editorial/Editor.svelte'
|
|
5
5
|
import { title } from '$lib/fakeData'
|
|
6
6
|
import { generateInjection } from '../../../helpers'
|
|
7
|
+
import { track } from '$lib/tracking'
|
|
8
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
7
9
|
|
|
8
10
|
vi.mock('$lib/api', () => ({
|
|
9
11
|
saveLinkInjections: vi.fn(),
|
|
10
12
|
}))
|
|
11
13
|
|
|
14
|
+
vi.mock('$lib/tracking', () => ({
|
|
15
|
+
track: vi.fn(),
|
|
16
|
+
}))
|
|
17
|
+
|
|
12
18
|
describe('Editor.svelte', () => {
|
|
19
|
+
/** @type {import('$lib/types/injection').LinkInjection[]} */
|
|
13
20
|
const linkInjections = [
|
|
14
21
|
generateInjection('This is a sentence with an injection.', 'an injection'),
|
|
15
22
|
generateInjection('This is a sentence with an injection.', 'a sentence'),
|
|
@@ -21,12 +28,51 @@ describe('Editor.svelte', () => {
|
|
|
21
28
|
expect(getByText(linkInjections.length)).toBeTruthy()
|
|
22
29
|
})
|
|
23
30
|
|
|
31
|
+
it('Should show the number of injections correctly in relation to injections that will be filtered out', () => {
|
|
32
|
+
/** @type {import('$lib/types/injection').LinkInjection[]} */
|
|
33
|
+
const linkInjections = [
|
|
34
|
+
{ ...generateInjection('Sentence', 'word'), failed: true },
|
|
35
|
+
{ ...generateInjection('Sentence', 'word'), duplicate: true },
|
|
36
|
+
{ ...generateInjection('Sentence', 'word'), removed: true },
|
|
37
|
+
{ ...generateInjection('Sentence', 'word'), title_details: undefined },
|
|
38
|
+
{ ...generateInjection('Sentence', 'word'), manual: true, failed: true },
|
|
39
|
+
{ ...generateInjection('Sentence', 'word'), manual: false, failed: true },
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
const { getByText } = render(Editor, { linkInjections })
|
|
43
|
+
|
|
44
|
+
expect(getByText(1)).toBeTruthy()
|
|
45
|
+
})
|
|
46
|
+
|
|
24
47
|
it('Should show injections', async () => {
|
|
25
48
|
const { getAllByText } = render(Editor, { linkInjections })
|
|
26
49
|
|
|
27
50
|
expect(getAllByText(title.title)).toHaveLength(2)
|
|
28
51
|
})
|
|
29
52
|
|
|
53
|
+
it('Should not show injections that are filtered out', () => {
|
|
54
|
+
/** @type {import('$lib/types/injection').LinkInjection[]} */
|
|
55
|
+
const linkInjections = [
|
|
56
|
+
{ ...generateInjection('Sentence', 'word') },
|
|
57
|
+
{ ...generateInjection('Sentence', 'word'), failed: true },
|
|
58
|
+
{ ...generateInjection('Sentence', 'word'), duplicate: true },
|
|
59
|
+
{ ...generateInjection('Sentence', 'word'), removed: true },
|
|
60
|
+
{ ...generateInjection('Sentence', 'word'), title_details: undefined },
|
|
61
|
+
{ ...generateInjection('Sentence', 'word'), manual: true, failed: true },
|
|
62
|
+
{ ...generateInjection('Sentence', 'word'), manual: false, failed: true },
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
const { getAllByText } = render(Editor, { linkInjections })
|
|
66
|
+
|
|
67
|
+
expect(getAllByText(title.title)).toHaveLength(2)
|
|
68
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.TotalInjectionsCount, null, {
|
|
69
|
+
total: '7',
|
|
70
|
+
failed_automatic: '2',
|
|
71
|
+
failed_manual: '1',
|
|
72
|
+
final_injected: '2',
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
30
76
|
it('Should display a loading state when loading is true', async () => {
|
|
31
77
|
const { getByText, container } = render(Editor, { linkInjections: [], loading: true })
|
|
32
78
|
|
|
@@ -65,4 +111,27 @@ describe('Editor.svelte', () => {
|
|
|
65
111
|
|
|
66
112
|
expect(getByText('No links available', { exact: false })).toBeTruthy()
|
|
67
113
|
})
|
|
114
|
+
|
|
115
|
+
it('Should sort injections based on their named and .failed state', () => {
|
|
116
|
+
const linkInjections = [
|
|
117
|
+
{ ...generateInjection('Sentence', 'c'), manual: true, title_details: { ...title, title: 'c' } },
|
|
118
|
+
{ ...generateInjection('Sentence', 'b'), manual: true, title_details: { ...title, title: 'b' } },
|
|
119
|
+
{ ...generateInjection('Sentence', 'a'), manual: true, failed: true, title_details: { ...title, title: 'a' } },
|
|
120
|
+
{ ...generateInjection('Sentence', 'b'), manual: true, title_details: { ...title, title: 'e' } },
|
|
121
|
+
{ ...generateInjection('Sentence', 'b'), manual: true, title_details: { ...title, title: 'd' } },
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
const { container } = render(Editor, { linkInjections })
|
|
125
|
+
|
|
126
|
+
// @ts-ignore
|
|
127
|
+
expect(container.querySelectorAll('.item')[0].querySelector('.title').innerText).toBe('b')
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
expect(container.querySelectorAll('.item')[1].querySelector('.title').innerText).toBe('c')
|
|
130
|
+
// @ts-ignore
|
|
131
|
+
expect(container.querySelectorAll('.item')[2].querySelector('.title').innerText).toBe('d')
|
|
132
|
+
// @ts-ignore
|
|
133
|
+
expect(container.querySelectorAll('.item')[3].querySelector('.title').innerText).toBe('e')
|
|
134
|
+
// @ts-ignore
|
|
135
|
+
expect(container.querySelectorAll('.item')[4].querySelector('.title').innerText).toBe('a')
|
|
136
|
+
})
|
|
68
137
|
})
|
|
@@ -3,7 +3,6 @@ import { describe, expect, it, vi } from 'vitest'
|
|
|
3
3
|
|
|
4
4
|
import EditorItem from '../../../../routes/components/Editorial/EditorItem.svelte'
|
|
5
5
|
import { injectLinksInDocument } from '$lib/linkInjection'
|
|
6
|
-
import { linkInjections, title } from '$lib/fakeData'
|
|
7
6
|
import { track } from '$lib/tracking'
|
|
8
7
|
import { generateInjection } from '../../../helpers'
|
|
9
8
|
|
|
@@ -116,19 +115,29 @@ describe('EditorItem.svelte', () => {
|
|
|
116
115
|
expect(track).toHaveBeenCalled()
|
|
117
116
|
})
|
|
118
117
|
|
|
119
|
-
it('Should show
|
|
120
|
-
const
|
|
121
|
-
|
|
118
|
+
it('Should show sentence of given injection', async () => {
|
|
119
|
+
const { container } = render(EditorItem, { linkInjection })
|
|
120
|
+
|
|
121
|
+
expect(/** @type {HTMLElement} */ (container.querySelector('.sentence')).innerText).toContain(linkInjection.sentence)
|
|
122
|
+
})
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
it('Should not show expand button for sentence if sentence is too short to be expanded', async () => {
|
|
125
|
+
const { queryByText } = render(EditorItem, { linkInjection })
|
|
126
|
+
|
|
127
|
+
expect(queryByText('More')).not.toBeTruthy()
|
|
125
128
|
})
|
|
126
129
|
|
|
127
|
-
it('Should
|
|
128
|
-
const
|
|
129
|
-
const {
|
|
130
|
+
it('Should expand and collapse sentence after clicking button', async () => {
|
|
131
|
+
const longLinkInjection = { ...linkInjection, sentence: 'Some long sentence with a word in it that will be truncated if its long enough to be truncated' }
|
|
132
|
+
const { container, getByText } = render(EditorItem, { linkInjection: longLinkInjection })
|
|
133
|
+
|
|
134
|
+
expect(/** @type {HTMLElement} */ (container.querySelector('.sentence')).innerText).not.toContain(longLinkInjection.sentence)
|
|
135
|
+
|
|
136
|
+
await fireEvent.click(getByText('More'))
|
|
137
|
+
expect(/** @type {HTMLElement} */ (container.querySelector('.sentence')).innerText).toContain(longLinkInjection.sentence)
|
|
130
138
|
|
|
131
|
-
|
|
139
|
+
await fireEvent.click(getByText('Less'))
|
|
140
|
+
expect(/** @type {HTMLElement} */ (container.querySelector('.sentence')).innerText).not.toContain(longLinkInjection.sentence)
|
|
132
141
|
})
|
|
133
142
|
|
|
134
143
|
it('Should call given onremove function when remove button is clicked', async () => {
|
|
@@ -140,4 +149,19 @@ describe('EditorItem.svelte', () => {
|
|
|
140
149
|
|
|
141
150
|
expect(onremove).toHaveBeenCalled()
|
|
142
151
|
})
|
|
152
|
+
|
|
153
|
+
it('Should display an inactive injection as being so with text and classname', async () => {
|
|
154
|
+
const { getByText, container } = render(EditorItem, { linkInjection: { ...linkInjection, inactive: true } })
|
|
155
|
+
|
|
156
|
+
expect(getByText('Inactive')).toBeTruthy()
|
|
157
|
+
expect(container.querySelector('.inactive')).toBeTruthy()
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('Should not display an active injection as being inactive', async () => {
|
|
161
|
+
const { queryByText, container } = render(EditorItem, { linkInjection })
|
|
162
|
+
|
|
163
|
+
expect(queryByText('Inactive')).not.toBeTruthy()
|
|
164
|
+
expect(queryByText('Visible')).toBeTruthy()
|
|
165
|
+
expect(container.querySelector('.inactive')).not.toBeTruthy()
|
|
166
|
+
})
|
|
143
167
|
})
|
|
@@ -27,6 +27,7 @@ describe('ManualInjection.svelte', () => {
|
|
|
27
27
|
endOffset: 0,
|
|
28
28
|
}),
|
|
29
29
|
anchorNode: container,
|
|
30
|
+
focusNode: container,
|
|
30
31
|
}))
|
|
31
32
|
})
|
|
32
33
|
|
|
@@ -82,7 +83,7 @@ describe('ManualInjection.svelte', () => {
|
|
|
82
83
|
it('Should select content outside of element if content is too short', async () => {
|
|
83
84
|
vi.mocked(searchTitles).mockResolvedValueOnce([title])
|
|
84
85
|
|
|
85
|
-
document.body.innerHTML = '<div>Some <strong>text</strong> in a sentence</div>'
|
|
86
|
+
document.body.innerHTML = '<div>Some other sentence. Some <strong><span>text</span></strong> in a sentence</div>'
|
|
86
87
|
|
|
87
88
|
const container = document.querySelector('strong')
|
|
88
89
|
|
|
@@ -96,6 +97,7 @@ describe('ManualInjection.svelte', () => {
|
|
|
96
97
|
endOffset: 0,
|
|
97
98
|
}),
|
|
98
99
|
anchorNode: container,
|
|
100
|
+
focusNode: container,
|
|
99
101
|
}))
|
|
100
102
|
|
|
101
103
|
const onsave = vi.fn()
|
|
@@ -136,6 +138,7 @@ describe('ManualInjection.svelte', () => {
|
|
|
136
138
|
endOffset: 0,
|
|
137
139
|
}),
|
|
138
140
|
anchorNode: container,
|
|
141
|
+
focusNode: container,
|
|
139
142
|
}))
|
|
140
143
|
|
|
141
144
|
await fireEvent.click(window)
|
|
@@ -156,6 +159,7 @@ describe('ManualInjection.svelte', () => {
|
|
|
156
159
|
endOffset: 0,
|
|
157
160
|
}),
|
|
158
161
|
anchorNode: container,
|
|
162
|
+
focusNode: container,
|
|
159
163
|
}))
|
|
160
164
|
|
|
161
165
|
;(render(ManualInjection, { htmlString: document.body.innerHTML, onsave }))
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { render } from '@testing-library/svelte'
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import ResizeHandle from '../../../../routes/components/Editorial/ResizeHandle.svelte'
|
|
5
|
+
|
|
6
|
+
describe('ResizeHandle.svelte', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
window.HTMLElement.prototype.getBoundingClientRect = () => ({ bottom: window.innerHeight })
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('Should set the height of the given element based on the given height', () => {
|
|
13
|
+
document.body.innerHTML = '<div></div>'
|
|
14
|
+
const element = /** @type {HTMLElement} */ (document.querySelector('div'))
|
|
15
|
+
render(ResizeHandle, { element, height: 50 })
|
|
16
|
+
|
|
17
|
+
expect(element?.style.height).toBe('50px')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('Should limit the height to be visible on the screen', () => {
|
|
21
|
+
document.body.innerHTML = '<div></div>'
|
|
22
|
+
const element = /** @type {HTMLElement} */ (document.querySelector('div'))
|
|
23
|
+
render(ResizeHandle, { element, height: 5000 })
|
|
24
|
+
|
|
25
|
+
expect(element?.style.height).toBe(window.innerHeight - 32 + 'px')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('Should limit the height to elements minimum height', () => {
|
|
29
|
+
document.body.innerHTML = '<div style="min-height: 5px"></div>'
|
|
30
|
+
const element = /** @type {HTMLElement} */ (document.querySelector('div'))
|
|
31
|
+
render(ResizeHandle, { element, height: 0 })
|
|
32
|
+
|
|
33
|
+
expect(element?.style.height).toBe('5px')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('Should fire given onchange function when height changes', () => {
|
|
37
|
+
document.body.innerHTML = '<div></div>'
|
|
38
|
+
|
|
39
|
+
const onchange = vi.fn()
|
|
40
|
+
const element = /** @type {HTMLElement} */ (document.querySelector('div'))
|
|
41
|
+
render(ResizeHandle, { element, height: window.innerHeight / 2, onchange })
|
|
42
|
+
|
|
43
|
+
expect(onchange).toHaveBeenCalledWith(window.innerHeight / 2)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -35,24 +35,26 @@ describe('TitleSearch.svelte', () => {
|
|
|
35
35
|
it('Should show results when given', async () => {
|
|
36
36
|
vi.mocked(searchTitles).mockResolvedValueOnce([title])
|
|
37
37
|
|
|
38
|
-
const { getByRole, getByText } = render(TitleSearch)
|
|
38
|
+
const { getByRole, getByText, getByTestId } = render(TitleSearch)
|
|
39
39
|
|
|
40
40
|
await fireEvent.input(getByRole('textbox'), { target: { value: 'test' } })
|
|
41
41
|
|
|
42
|
+
expect(getByTestId('search-results')).toBeTruthy()
|
|
42
43
|
expect(getByText(title.title)).toBeTruthy()
|
|
43
44
|
expect(getByText(title.type)).toBeTruthy()
|
|
44
45
|
})
|
|
45
46
|
|
|
46
|
-
it('Should fire the given onselect function and close the results when a result is clicked', async () => {
|
|
47
|
+
it('Should fire the given onselect function and close the results, and show selected result when a result is clicked', async () => {
|
|
47
48
|
vi.mocked(searchTitles).mockResolvedValueOnce([title])
|
|
48
49
|
|
|
49
50
|
const onselect = vi.fn()
|
|
50
|
-
const { getByRole,
|
|
51
|
+
const { getByRole, getByText, queryByTestId } = render(TitleSearch, { onselect, query: '' })
|
|
51
52
|
|
|
52
53
|
await fireEvent.input(getByRole('textbox'), { target: { value: 'test' } })
|
|
53
54
|
await fireEvent.click(getByRole('button'))
|
|
54
55
|
|
|
55
56
|
expect(onselect).toHaveBeenCalledWith(title)
|
|
56
|
-
expect(
|
|
57
|
+
expect(queryByTestId('search-results')).not.toBeTruthy()
|
|
58
|
+
expect(getByText(title.title)).toBeTruthy()
|
|
57
59
|
})
|
|
58
60
|
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/svelte'
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import TitleSearchItem from '../../../../../routes/components/Editorial/Search/TitleSearchItem.svelte'
|
|
5
|
+
import { title } from '$lib/fakeData'
|
|
6
|
+
|
|
7
|
+
vi.mock('$lib/search', () => ({
|
|
8
|
+
searchTitles: vi.fn(),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
describe('TitleSearchItem.svelte', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('Should fire given onclick function', async () => {
|
|
17
|
+
const onclick = vi.fn()
|
|
18
|
+
const { getByRole } = render(TitleSearchItem, { onclick, title })
|
|
19
|
+
|
|
20
|
+
await fireEvent.click(getByRole('button'))
|
|
21
|
+
|
|
22
|
+
expect(onclick).toHaveBeenCalled()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('Should render title data', () => {
|
|
26
|
+
const { getByText } = render(TitleSearchItem, { title })
|
|
27
|
+
|
|
28
|
+
expect(getByText(title.title)).toBeTruthy()
|
|
29
|
+
expect(getByText(title.year)).toBeTruthy()
|
|
30
|
+
expect(getByText(title.type)).toBeTruthy()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('Should have hoverable class by default', () => {
|
|
34
|
+
const { getByRole } = render(TitleSearchItem, { title })
|
|
35
|
+
|
|
36
|
+
expect(getByRole('button').classList).toContain('hoverable')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Should not have hoverable class when hoverable prop is false', () => {
|
|
40
|
+
const { getByRole } = render(TitleSearchItem, { title, hoverable: false })
|
|
41
|
+
|
|
42
|
+
expect(getByRole('button').classList).not.toContain('hoverable')
|
|
43
|
+
})
|
|
44
|
+
})
|