@playpilot/tpi 3.1.0 → 3.2.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.
Files changed (42) hide show
  1. package/dist/link-injections.js +8 -7
  2. package/package.json +1 -1
  3. package/src/lib/actions/heading.ts +11 -0
  4. package/src/lib/api.ts +1 -3
  5. package/src/lib/auth.ts +13 -1
  6. package/src/lib/constants.ts +2 -0
  7. package/src/lib/enums/TrackingEvent.ts +1 -0
  8. package/src/lib/linkInjection.ts +43 -32
  9. package/src/lib/scss/_mixins.scss +8 -0
  10. package/src/lib/scss/global.scss +6 -6
  11. package/src/lib/scss/variables.scss +2 -0
  12. package/src/lib/stores/organization.ts +4 -0
  13. package/src/lib/tracking.ts +14 -1
  14. package/src/lib/types/injection.d.ts +5 -0
  15. package/src/routes/+layout.svelte +6 -2
  16. package/src/routes/+page.svelte +31 -13
  17. package/src/routes/components/Description.svelte +2 -3
  18. package/src/routes/components/Editorial/AIIndicator.svelte +85 -91
  19. package/src/routes/components/Editorial/Alert.svelte +12 -2
  20. package/src/routes/components/Editorial/Editor.svelte +82 -61
  21. package/src/routes/components/Editorial/EditorItem.svelte +32 -7
  22. package/src/routes/components/Editorial/ManualInjection.svelte +10 -9
  23. package/src/routes/components/Editorial/PlaylinkTypeSelect.svelte +14 -0
  24. package/src/routes/components/Editorial/ResizeHandle.svelte +1 -1
  25. package/src/routes/components/Editorial/Search/TitleSearchItem.svelte +7 -5
  26. package/src/routes/components/Icons/IconWarning.svelte +5 -0
  27. package/src/routes/components/Modal.svelte +2 -0
  28. package/src/routes/components/Playlinks.svelte +12 -11
  29. package/src/routes/components/Popover.svelte +2 -0
  30. package/src/routes/components/Title.svelte +3 -2
  31. package/src/routes/components/TitlePopover.svelte +1 -1
  32. package/src/tests/lib/auth.test.js +31 -1
  33. package/src/tests/lib/linkInjection.test.js +87 -48
  34. package/src/tests/lib/tracking.test.js +61 -1
  35. package/src/tests/routes/+page.test.js +94 -4
  36. package/src/tests/routes/components/Editorial/AiIndicator.test.js +28 -42
  37. package/src/tests/routes/components/Editorial/Alert.test.js +10 -3
  38. package/src/tests/routes/components/Editorial/Editor.test.js +15 -0
  39. package/src/tests/routes/components/Editorial/EditorItem.test.js +32 -7
  40. package/src/tests/routes/components/Editorial/PlaylinkTypeSelect.test.js +13 -1
  41. package/src/tests/routes/components/Title.test.js +2 -2
  42. package/svelte.config.js +1 -0
@@ -1,61 +1,47 @@
1
- import { fireEvent, render, waitFor } from '@testing-library/svelte'
2
- import { describe, expect, it, vi } from 'vitest'
1
+ import { render } from '@testing-library/svelte'
2
+ import { describe, expect, it } from 'vitest'
3
3
 
4
4
  import AIIndicator from '../../../../routes/components/Editorial/AIIndicator.svelte'
5
- import { generateInjection } from '../../../helpers'
6
5
 
7
6
  describe('AIIndicator.svelte', () => {
8
- it('Should should running state by default', () => {
9
- const { getByText, getByTestId } = render(AIIndicator, { onadd: () => null })
7
+ it('Should show disabled state by default', () => {
8
+ const { getByText, queryByTestId } = render(AIIndicator)
9
+
10
+ expect(getByText('AI processing is disabled', { exact: false })).toBeTruthy()
11
+ expect(queryByTestId('loading-bar')).not.toBeTruthy()
12
+ })
13
+
14
+ it('Should show running when aiRunning and automationEnabled are true', () => {
15
+ const { getByText, getByTestId } = render(AIIndicator, { aiRunning: true, automationEnabled: true })
10
16
 
11
17
  expect(getByText('AI links are currently processing.', { exact: false })).toBeTruthy()
12
18
  expect(getByTestId('loading-bar')).toBeTruthy()
13
19
  })
14
20
 
15
- it('Should show completed state with text depending on length of given array', async () => {
16
- const { getByText, component, queryByTestId } = render(AIIndicator, { onadd: () => null })
17
-
18
- component.notifyUserOfNewState([generateInjection('Some sentence', 'Some title')])
19
- await waitFor(() => {
20
- expect(getByText('AI links are ready.', { exact: false })).toBeTruthy()
21
- expect(getByText('1 New link was found.')).toBeTruthy()
22
- expect(queryByTestId('loading-bar')).not.toBeTruthy()
23
- })
24
-
25
- component.notifyUserOfNewState([generateInjection('Some sentence', 'Some title'), generateInjection('Some sentence', 'Some title')])
26
- await waitFor(() => {
27
- expect(getByText('AI links are ready.', { exact: false })).toBeTruthy()
28
- expect(getByText('2 New links were found.')).toBeTruthy()
29
- })
30
-
31
- component.notifyUserOfNewState([])
32
- await waitFor(() => {
33
- expect(getByText('AI links finished running, but no new links were found.')).toBeTruthy()
34
- expect(queryByTestId('loading-bar')).not.toBeTruthy()
35
- })
36
- })
21
+ it('Should not show animator or loading bar if aiRunning is false', () => {
22
+ const { queryByTestId } = render(AIIndicator, { aiRunning: false })
37
23
 
38
- it('Should close element when dismiss button is clicked', async () => {
39
- const { getByText, component, container } = render(AIIndicator, { onadd: () => null })
24
+ expect(queryByTestId('loading-bar')).not.toBeTruthy()
25
+ expect(queryByTestId('animator')).not.toBeTruthy()
26
+ })
40
27
 
41
- component.notifyUserOfNewState([])
42
- await waitFor(() => getByText('Dismiss'))
28
+ it('Should show given message if aiRunning is true', () => {
29
+ const { getByText } = render(AIIndicator, { aiRunning: true, automationEnabled: true, message: 'Some message' })
43
30
 
44
- await fireEvent.click(getByText('Dismiss'))
45
- expect(container.querySelector('.ai-indicator')).not.toBeTruthy()
31
+ expect(getByText('Some message')).toBeTruthy()
46
32
  })
47
33
 
48
- it('Should close element and call onadd when Add AI links button is clicked', async () => {
49
- const onadd = vi.fn()
50
- const { getByText, component, container } = render(AIIndicator, { onadd })
34
+ it('Should set progress bar to given progress percentage', () => {
35
+ const { getByText, getByTestId } = render(AIIndicator, { aiRunning: true, automationEnabled: true, percentage: 25 })
51
36
 
52
- const injection = generateInjection('Some sentence', 'Some title')
53
- component.notifyUserOfNewState([injection])
54
- await waitFor(() => getByText('Add AI links'))
37
+ expect(getByTestId('loading-bar').style.width).toBe('25%')
38
+ expect(getByText('25%')).toBeTruthy()
39
+ })
55
40
 
56
- await fireEvent.click(getByText('Add AI links'))
41
+ it('Should limit progress to minimum value in loading bar style only', () => {
42
+ const { getByText, getByTestId } = render(AIIndicator, { aiRunning: true, automationEnabled: true, percentage: 0 })
57
43
 
58
- expect(onadd).toHaveBeenCalledWith([injection])
59
- expect(container.querySelector('.ai-indicator')).not.toBeTruthy()
44
+ expect(getByTestId('loading-bar').style.width).toBe('3%')
45
+ expect(getByText('0%')).toBeTruthy()
60
46
  })
61
47
  })
@@ -5,13 +5,20 @@ import Alert from '../../../../routes/components/Editorial/Alert.svelte'
5
5
  import { createRawSnippet } from 'svelte'
6
6
 
7
7
  describe('Alert.svelte', () => {
8
+ const children = createRawSnippet(() => ({
9
+ render: () => '<p>Some snippet</p>',
10
+ }))
11
+
8
12
  it('Should render the given snippet', () => {
9
- const children = createRawSnippet(() => ({
10
- render: () => '<p>Some snippet</p>',
11
- }))
12
13
 
13
14
  const { getByText } = render(Alert, { children })
14
15
 
15
16
  expect(getByText('Some snippet')).toBeTruthy()
16
17
  })
18
+
19
+ it('Should render with given type as class', () => {
20
+ const { container } = render(Alert, { children, type: 'warning' })
21
+
22
+ expect(container.querySelector('.alert')?.classList).toContain('warning')
23
+ })
17
24
  })
@@ -89,18 +89,21 @@ describe('Editor.svelte', () => {
89
89
  it('Should not show save button if no injections are present', async () => {
90
90
  const { queryByText } = render(Editor, { linkInjections: [], loading: false })
91
91
 
92
+ await new Promise(res => setTimeout(res))
92
93
  expect(queryByText('Save links')).not.toBeTruthy()
93
94
  })
94
95
 
95
96
  it('Should not show save button if injections are present but no change is made', async () => {
96
97
  const { queryByText } = render(Editor, { linkInjections, loading: false })
97
98
 
99
+ await new Promise(res => setTimeout(res))
98
100
  expect(queryByText('Save links')).not.toBeTruthy()
99
101
  })
100
102
 
101
103
  it('Should show save button if injections are present and a change has been made', async () => {
102
104
  const { getByText, getAllByText } = render(Editor, { linkInjections, loading: false })
103
105
 
106
+ await new Promise(res => setTimeout(res))
104
107
  await fireEvent.click(getAllByText('Visible')[0])
105
108
 
106
109
  expect(getByText('Save links')).toBeTruthy()
@@ -112,6 +115,18 @@ describe('Editor.svelte', () => {
112
115
  expect(getByText('No links available', { exact: false })).toBeTruthy()
113
116
  })
114
117
 
118
+ it('Should show message if injections are not enabled', async () => {
119
+ const { getByText } = render(Editor, { linkInjections: [], loading: false, injectionsEnabled: false })
120
+
121
+ expect(getByText('Playlinks are currently not published.')).toBeTruthy()
122
+ })
123
+
124
+ it('Should not show message if injections are enabled', async () => {
125
+ const { queryByText } = render(Editor, { linkInjections: [], loading: false, injectionsEnabled: true })
126
+
127
+ expect(queryByText('Playlinks are currently not published.')).not.toBeTruthy()
128
+ })
129
+
115
130
  it('Should sort injections based on their named and .failed state', () => {
116
131
  const linkInjections = [
117
132
  { ...generateInjection('Sentence', 'c'), manual: true, title_details: { ...title, title: 'c' } },
@@ -29,10 +29,10 @@ describe('EditorItem.svelte', () => {
29
29
  const { getAllByText, container } = render(EditorItem, { linkInjection })
30
30
 
31
31
  await fireEvent.mouseEnter(/** @type {HTMLElement} */ (container.querySelector('.item')))
32
- expect(/** @type {HTMLElement} */ (getAllByText(linkInjection.title)[0].closest('span')).classList).toContain('injection-highlight')
32
+ expect(/** @type {HTMLElement} */ (getAllByText(linkInjection.title)[0].closest('span')).classList).toContain('playpilot-injection-highlight')
33
33
 
34
34
  await fireEvent.mouseLeave(/** @type {HTMLElement} */ (container.querySelector('.item')))
35
- expect(/** @type {HTMLElement} */ (getAllByText(linkInjection.title)[0].closest('span')).classList).not.toContain('injection-highlight')
35
+ expect(/** @type {HTMLElement} */ (getAllByText(linkInjection.title)[0].closest('span')).classList).not.toContain('playpilot-injection-highlight')
36
36
  })
37
37
 
38
38
  it('Should highlight multiple elements if multiple are present', async () => {
@@ -44,7 +44,20 @@ describe('EditorItem.svelte', () => {
44
44
  const { container } = render(EditorItem, { linkInjection })
45
45
 
46
46
  await fireEvent.mouseEnter(/** @type {HTMLElement} */ (container.querySelector('.item')))
47
- expect(document.querySelectorAll('.injection-highlight')).toHaveLength(2)
47
+ expect(document.querySelectorAll('.playpilot-injection-highlight')).toHaveLength(2)
48
+ })
49
+
50
+ it('Should highlight the sentence the injection is in if an injection failed but a matching sentence exists.', async () => {
51
+ document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
52
+
53
+ const failedInjection = generateInjection('This is a sentence', 'fail')
54
+
55
+ injectLinksInDocument(Array.from(document.querySelectorAll('p')), () => null, { aiInjections: [], manualInjections: [failedInjection] })
56
+
57
+ const { container } = render(EditorItem, { linkInjection })
58
+
59
+ await fireEvent.mouseEnter(/** @type {HTMLElement} */ (container.querySelector('.item')))
60
+ expect(/** @type {HTMLElement} */ (document.querySelector('p')).classList).toContain('playpilot-injection-highlight')
48
61
  })
49
62
 
50
63
  it('Should scroll matching link into view when component is clicked', async () => {
@@ -59,9 +72,9 @@ describe('EditorItem.svelte', () => {
59
72
  })
60
73
 
61
74
  it('Should not scroll matching link into view when component is clicked but there is no matching injection', async () => {
62
- document.body.innerHTML = '<p>This has no matching injections.</p>'
75
+ document.body.innerHTML = '<main><p>This has no matching injections.</p></main>'
63
76
 
64
- injectLinksInDocument(Array.from(document.querySelectorAll('p')), () => null, linkInjections)
77
+ injectLinksInDocument(Array.from(document.querySelectorAll('main p')), () => null, linkInjections)
65
78
 
66
79
  const { container } = render(EditorItem, { linkInjection })
67
80
 
@@ -108,9 +121,9 @@ describe('EditorItem.svelte', () => {
108
121
  })
109
122
 
110
123
  it('Should show different state when injection failed', () => {
111
- const { getByText, queryByLabelText } = render(EditorItem, { linkInjection: { ...linkInjection, failed: true } })
124
+ const { getByText, queryByLabelText } = render(EditorItem, { linkInjection: { ...linkInjection, failed: true, failed_message: 'Some message' } })
112
125
 
113
- expect(getByText('the link could not be injected', { exact: false })).toBeTruthy()
126
+ expect(getByText('Some message')).toBeTruthy()
114
127
  expect(queryByLabelText('Expand')).not.toBeTruthy()
115
128
  expect(track).toHaveBeenCalled()
116
129
  })
@@ -164,4 +177,16 @@ describe('EditorItem.svelte', () => {
164
177
  expect(queryByText('Visible')).toBeTruthy()
165
178
  expect(container.querySelector('.inactive')).not.toBeTruthy()
166
179
  })
180
+
181
+ it('Should display an icon when playlink types are invalid', async () => {
182
+ const { getByLabelText } = render(EditorItem, { linkInjection: { ...linkInjection, in_text: false } })
183
+
184
+ expect(getByLabelText('Invalid playlink settings')).toBeTruthy()
185
+ })
186
+
187
+ it('Should not display an icon when playlink types are valid', async () => {
188
+ const { queryByLabelText } = render(EditorItem, { linkInjection: { ...linkInjection, in_text: true } })
189
+
190
+ expect(queryByLabelText('Invalid playlink settings')).not.toBeTruthy()
191
+ })
167
192
  })
@@ -4,7 +4,7 @@ import { beforeEach, describe, expect, it } from 'vitest'
4
4
  import PlaylinkTypeSelect from '../../../../routes/components/Editorial/PlaylinkTypeSelect.svelte'
5
5
 
6
6
  describe('PlaylinkTypeSelect.svelte', () => {
7
- /** @type {LinkInjection} */
7
+ /** @type {import('$lib/types/injection').LinkInjection} */
8
8
  const linkInjection = {
9
9
  sid: '1',
10
10
  title: 'test',
@@ -60,4 +60,16 @@ describe('PlaylinkTypeSelect.svelte', () => {
60
60
  expect(getByText('Modal button').classList).toContain('active')
61
61
  expect(/** @type {HTMLElement} */(document.querySelector('.active-marker')).classList).toContain('right')
62
62
  })
63
+
64
+ it('Should display a message when playlink types are invalid', async () => {
65
+ const { getByText } = render(PlaylinkTypeSelect, { linkInjection: { ...linkInjection, in_text: false } })
66
+
67
+ expect(getByText('At least one layout option must be selected for the playlink to be visible.')).toBeTruthy()
68
+ })
69
+
70
+ it('Should not display a message when playlink types are valid', async () => {
71
+ const { queryByText } = render(PlaylinkTypeSelect, { linkInjection: { ...linkInjection, in_text: true } })
72
+
73
+ expect(queryByText('At least one layout option must be selected for the playlink to be visible.')).not.toBeTruthy()
74
+ })
63
75
  })
@@ -61,9 +61,9 @@ describe('Title.svelte', () => {
61
61
  })
62
62
 
63
63
  it('Should truncate title when small prop is given', () => {
64
- const { container } = render(Title, { title, small: true })
64
+ const { getAllByRole } = render(Title, { title, small: true })
65
65
 
66
- expect(container.querySelector('h1.truncate')).toBeTruthy()
66
+ expect(getAllByRole('heading')[0].classList).toContain('truncate')
67
67
  })
68
68
 
69
69
  it('Should render given description', () => {
package/svelte.config.js CHANGED
@@ -8,6 +8,7 @@ const config = {
8
8
  includePaths: ['src'],
9
9
  prependData: `
10
10
  @use "src/lib/scss/_functions.scss" as *;
11
+ @use "src/lib/scss/_mixins.scss" as *;
11
12
  `,
12
13
  },
13
14
  }),