@playpilot/tpi 1.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.
- package/.github/workflows/tests.yml +22 -0
- package/.prettierignore +4 -0
- package/.prettierrc +16 -0
- package/README.md +38 -0
- package/dist/link-injections.js +7 -0
- package/eslint.config.js +33 -0
- package/index.html +11 -0
- package/jsconfig.json +19 -0
- package/package.json +35 -0
- package/src/app.d.ts +13 -0
- package/src/app.html +12 -0
- package/src/demo.spec.js +7 -0
- package/src/lib/api.js +160 -0
- package/src/lib/array.js +15 -0
- package/src/lib/auth.js +84 -0
- package/src/lib/constants.js +2 -0
- package/src/lib/enums/TrackingEvent.js +15 -0
- package/src/lib/fakeData.js +140 -0
- package/src/lib/genres.json +420 -0
- package/src/lib/global.css +37 -0
- package/src/lib/hash.js +15 -0
- package/src/lib/html.js +21 -0
- package/src/lib/index.js +1 -0
- package/src/lib/linkInjection.js +275 -0
- package/src/lib/search.js +24 -0
- package/src/lib/text.js +61 -0
- package/src/lib/tracking.js +32 -0
- package/src/lib/variables.css +16 -0
- package/src/main.js +45 -0
- package/src/routes/+layout.svelte +54 -0
- package/src/routes/+page.svelte +96 -0
- package/src/routes/components/AfterArticlePlaylinks.svelte +90 -0
- package/src/routes/components/ContextMenu.svelte +67 -0
- package/src/routes/components/Description.svelte +47 -0
- package/src/routes/components/Editorial/Alert.svelte +18 -0
- package/src/routes/components/Editorial/DragHandle.svelte +134 -0
- package/src/routes/components/Editorial/Editor.svelte +277 -0
- package/src/routes/components/Editorial/EditorItem.svelte +260 -0
- package/src/routes/components/Editorial/ManualInjection.svelte +192 -0
- package/src/routes/components/Editorial/PlaylinkTypeSelect.svelte +132 -0
- package/src/routes/components/Editorial/Search/TitleSearch.svelte +176 -0
- package/src/routes/components/Editorial/Switch.svelte +76 -0
- package/src/routes/components/Editorial/TextInput.svelte +29 -0
- package/src/routes/components/Genres.svelte +41 -0
- package/src/routes/components/Icons/IconAlign.svelte +12 -0
- package/src/routes/components/Icons/IconBack.svelte +3 -0
- package/src/routes/components/Icons/IconBookmark.svelte +3 -0
- package/src/routes/components/Icons/IconChevron.svelte +18 -0
- package/src/routes/components/Icons/IconClose.svelte +3 -0
- package/src/routes/components/Icons/IconContinue.svelte +3 -0
- package/src/routes/components/Icons/IconDots.svelte +5 -0
- package/src/routes/components/Icons/IconEnlarge.svelte +12 -0
- package/src/routes/components/Icons/IconIMDb.svelte +3 -0
- package/src/routes/components/Icons/IconNewTab.svelte +3 -0
- package/src/routes/components/Modal.svelte +106 -0
- package/src/routes/components/Participants.svelte +44 -0
- package/src/routes/components/Playlinks.svelte +155 -0
- package/src/routes/components/Popover.svelte +95 -0
- package/src/routes/components/RoundButton.svelte +38 -0
- package/src/routes/components/SkeletonText.svelte +33 -0
- package/src/routes/components/Title.svelte +180 -0
- package/src/routes/components/TitleModal.svelte +24 -0
- package/src/routes/components/TitlePopover.svelte +17 -0
- package/src/tests/helpers.js +18 -0
- package/src/tests/lib/api.test.js +162 -0
- package/src/tests/lib/array.test.js +14 -0
- package/src/tests/lib/auth.test.js +115 -0
- package/src/tests/lib/hash.test.js +28 -0
- package/src/tests/lib/html.test.js +16 -0
- package/src/tests/lib/linkInjection.test.js +754 -0
- package/src/tests/lib/search.test.js +42 -0
- package/src/tests/lib/text.test.js +94 -0
- package/src/tests/lib/tracking.test.js +71 -0
- package/src/tests/routes/+page.test.js +109 -0
- package/src/tests/routes/components/AfterArticlePlaylinks.test.js +115 -0
- package/src/tests/routes/components/ContextMenu.test.js +37 -0
- package/src/tests/routes/components/Description.test.js +58 -0
- package/src/tests/routes/components/Editorial/Alert.test.js +17 -0
- package/src/tests/routes/components/Editorial/DragHandle.test.js +55 -0
- package/src/tests/routes/components/Editorial/Editor.test.js +64 -0
- package/src/tests/routes/components/Editorial/EditorItem.test.js +142 -0
- package/src/tests/routes/components/Editorial/ManualInjection.test.js +114 -0
- package/src/tests/routes/components/Editorial/PlaylinkTypeSelect.test.js +63 -0
- package/src/tests/routes/components/Editorial/Search/TitleSearch.test.js +58 -0
- package/src/tests/routes/components/Editorial/Switch.test.js +60 -0
- package/src/tests/routes/components/Editorial/TextInput.test.js +30 -0
- package/src/tests/routes/components/Genres.test.js +37 -0
- package/src/tests/routes/components/Modal.test.js +84 -0
- package/src/tests/routes/components/Participants.test.js +33 -0
- package/src/tests/routes/components/Playlinks.test.js +101 -0
- package/src/tests/routes/components/Popover.test.js +66 -0
- package/src/tests/routes/components/RoundButton.test.js +35 -0
- package/src/tests/routes/components/SkeletonText.test.js +12 -0
- package/src/tests/routes/components/Title.test.js +82 -0
- package/src/tests/routes/components/TitleModal.test.js +33 -0
- package/src/tests/routes/components/TitlePopover.test.js +23 -0
- package/src/tests/setup.js +53 -0
- package/src/typedefs.js +72 -0
- package/static/favicon.png +0 -0
- package/svelte.config.js +13 -0
- package/vite.config.js +61 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import Playlinks from '../../../routes/components/Playlinks.svelte'
|
|
5
|
+
import { title } from '$lib/fakeData'
|
|
6
|
+
import { track } from '$lib/tracking'
|
|
7
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
8
|
+
import { getContext } from 'svelte'
|
|
9
|
+
|
|
10
|
+
vi.mock('$lib/tracking', () => ({
|
|
11
|
+
track: vi.fn(),
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
vi.mock('svelte', async (importActual) => ({
|
|
15
|
+
...(await importActual()),
|
|
16
|
+
getContext: vi.fn(),
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
describe('Playlinks.svelte', () => {
|
|
20
|
+
it('Should render each given playlink', () => {
|
|
21
|
+
const playlinks = [
|
|
22
|
+
{ name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
|
|
23
|
+
{ name: 'Some other playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
|
|
24
|
+
]
|
|
25
|
+
const { getByText } = render(Playlinks, { playlinks, title })
|
|
26
|
+
|
|
27
|
+
expect(getByText('Some playlink')).toBeTruthy()
|
|
28
|
+
expect(getByText('Some other playlink')).toBeTruthy()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('Should show empty state when no playlinks were given', () => {
|
|
32
|
+
/** @type {PlaylinkData[]} */
|
|
33
|
+
const playlinks = []
|
|
34
|
+
const { container } = render(Playlinks, { playlinks, title })
|
|
35
|
+
|
|
36
|
+
expect(container.querySelector('.empty')).toBeTruthy()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Should render category as words', () => {
|
|
40
|
+
const playlinks = [
|
|
41
|
+
{ name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
|
|
42
|
+
{ name: 'Some buy playlink', logo_url: 'logo', extra_info: { category: 'BUY' } },
|
|
43
|
+
{ name: 'Some rent playlink', logo_url: 'logo', extra_info: { category: 'RENT' } },
|
|
44
|
+
{ name: 'Some other playlink', logo_url: 'logo', extra_info: { category: 'other' } },
|
|
45
|
+
]
|
|
46
|
+
const { getByText, getAllByText } = render(Playlinks, { playlinks, title })
|
|
47
|
+
|
|
48
|
+
expect(getAllByText('Stream')).toHaveLength(2)
|
|
49
|
+
expect(getByText('Buy')).toBeTruthy()
|
|
50
|
+
expect(getByText('Rent')).toBeTruthy()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('Should call track function when clicked', async () => {
|
|
54
|
+
const playlinks = [
|
|
55
|
+
{ name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
|
|
56
|
+
{ name: 'Some other playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
|
|
57
|
+
]
|
|
58
|
+
const { getByText } = render(Playlinks, { playlinks, title })
|
|
59
|
+
|
|
60
|
+
await fireEvent.click(getByText(playlinks[0].name))
|
|
61
|
+
|
|
62
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.TitlePopoverPlaylinkClick, title, { playlink: playlinks[0].name })
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('Should call track function for modal when clicked inside of modal scope', async () => {
|
|
66
|
+
/** @type {import('vitest').Mock} */ (getContext).mockReturnValueOnce('modal')
|
|
67
|
+
|
|
68
|
+
const playlinks = [
|
|
69
|
+
{ name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
|
|
70
|
+
]
|
|
71
|
+
const { getByText } = render(Playlinks, { playlinks, title })
|
|
72
|
+
|
|
73
|
+
await fireEvent.click(getByText(playlinks[0].name))
|
|
74
|
+
|
|
75
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.TitleModalPlaylinkClick, title, { playlink: playlinks[0].name })
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('Should exclude playlinks without logo', () => {
|
|
79
|
+
const playlinks = [
|
|
80
|
+
{ name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
|
|
81
|
+
{ name: 'Some playlink', extra_info: { category: 'BUY' } },
|
|
82
|
+
{ name: 'Some playlink', logo_url: '', extra_info: { category: 'RENT' } },
|
|
83
|
+
{ name: 'Some playlink', logo_url: null, extra_info: { category: 'other' } },
|
|
84
|
+
]
|
|
85
|
+
const { getAllByText } = render(Playlinks, { playlinks, title })
|
|
86
|
+
|
|
87
|
+
expect(getAllByText('Some playlink')).toHaveLength(1)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('Should not have list class by default', async () => {
|
|
91
|
+
const { container } = render(Playlinks, { playlinks: [], title })
|
|
92
|
+
|
|
93
|
+
expect(container.querySelector('.list')).not.toBeTruthy()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('Should have list class when prop is given', async () => {
|
|
97
|
+
const { container } = render(Playlinks, { playlinks: [], title, list: true })
|
|
98
|
+
|
|
99
|
+
expect(container.querySelector('.list')).toBeTruthy()
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { render, waitFor } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import Popover from '../../../routes/components/Popover.svelte'
|
|
5
|
+
import { createRawSnippet } from 'svelte'
|
|
6
|
+
|
|
7
|
+
describe('Popover.svelte', () => {
|
|
8
|
+
const children = createRawSnippet(() => ({ render: () => 'Some snippet' }))
|
|
9
|
+
|
|
10
|
+
it('Should render given snippet', () => {
|
|
11
|
+
const { getByText } = render(Popover, { children })
|
|
12
|
+
|
|
13
|
+
expect(getByText('Some snippet')).toBeTruthy()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('Should flip its position when near the top of the screen', async () => {
|
|
17
|
+
const { container } = render(Popover, { children })
|
|
18
|
+
|
|
19
|
+
const element = /** @type {HTMLElement} */ (container.querySelector('.popover'))
|
|
20
|
+
|
|
21
|
+
await waitFor(() => {
|
|
22
|
+
expect(element?.classList).toContain('flip')
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('Should not flip its position when not near the top of the screen', async () => {
|
|
27
|
+
document.body.innerHTML = '<div style="height: 1000px"></div>'
|
|
28
|
+
|
|
29
|
+
const { container } = render(Popover, { children })
|
|
30
|
+
const element = /** @type {HTMLElement} */ (container.querySelector('.popover'))
|
|
31
|
+
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
window.HTMLElement.prototype.getBoundingClientRect = () => ({ top: 500 })
|
|
34
|
+
|
|
35
|
+
await new Promise(res => setTimeout(res, 500)) // Wait for element to be positioned
|
|
36
|
+
|
|
37
|
+
expect(element?.classList).not.toContain('flip')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('Should position itself from the right side of the screen if the element is offscreen', async () => {
|
|
41
|
+
document.body.innerHTML = '<div style="height: 1000px"></div>'
|
|
42
|
+
|
|
43
|
+
const { container } = render(Popover, { children })
|
|
44
|
+
const element = /** @type {HTMLElement} */ (container.querySelector('.popover'))
|
|
45
|
+
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
window.HTMLElement.prototype.getBoundingClientRect = () => ({ right: 2000 })
|
|
48
|
+
|
|
49
|
+
await waitFor(() => {
|
|
50
|
+
expect(element?.style).toContain('left')
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('Should position itself from the right side of the screen if the element is offscreen', async () => {
|
|
55
|
+
document.body.innerHTML = '<div style="height: 1000px"></div>'
|
|
56
|
+
|
|
57
|
+
const { container } = render(Popover, { children })
|
|
58
|
+
const element = /** @type {HTMLElement} */ (container.querySelector('.popover'))
|
|
59
|
+
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
window.HTMLElement.prototype.getBoundingClientRect = () => ({ right: 0 })
|
|
62
|
+
|
|
63
|
+
await new Promise(res => setTimeout(res, 500)) // Wait for element to be positioned
|
|
64
|
+
expect(element?.style).not.toContain('left: -992px;')
|
|
65
|
+
})
|
|
66
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import RoundButton from '../../../routes/components/RoundButton.svelte'
|
|
5
|
+
|
|
6
|
+
describe('RoundButton.svelte', () => {
|
|
7
|
+
it('Should render as a button by default', () => {
|
|
8
|
+
const { getByRole } = render(RoundButton)
|
|
9
|
+
|
|
10
|
+
expect(getByRole('button')).toBeTruthy()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('Should render as a link when an href is passed', () => {
|
|
14
|
+
const { getByRole } = render(RoundButton, { href: 'https://some-link.com' })
|
|
15
|
+
|
|
16
|
+
expect(getByRole('link')).toBeTruthy()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('Should include any rest props on the element', () => {
|
|
20
|
+
const { getByRole } = render(RoundButton, { href: 'https://some-link.com', 'aria-label': 'Some label', id: 'some-id' })
|
|
21
|
+
|
|
22
|
+
expect(getByRole('link').getAttribute('href')).toBe('https://some-link.com')
|
|
23
|
+
expect(getByRole('link').getAttribute('aria-label')).toBe('Some label')
|
|
24
|
+
expect(getByRole('link').getAttribute('id')).toBe('some-id')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('Should fire given onclick function on click', async () => {
|
|
28
|
+
const onclick = vi.fn()
|
|
29
|
+
const { getByRole } = render(RoundButton, { onclick })
|
|
30
|
+
|
|
31
|
+
await fireEvent.click(getByRole('button'))
|
|
32
|
+
|
|
33
|
+
expect(onclick).toHaveBeenCalled()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { render } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import SkeletonText from '../../../routes/components/SkeletonText.svelte'
|
|
5
|
+
|
|
6
|
+
describe('SkeletonText.svelte', () => {
|
|
7
|
+
it('Should render the given amount of lines', () => {
|
|
8
|
+
const { container } = render(SkeletonText, { lines: 3 })
|
|
9
|
+
|
|
10
|
+
expect(container.querySelectorAll('.skeleton').length).toBe(3)
|
|
11
|
+
})
|
|
12
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { render } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import Title from '../../../routes/components/Title.svelte'
|
|
5
|
+
import { title } from '$lib/fakeData'
|
|
6
|
+
|
|
7
|
+
vi.mock('$lib/tracking', () => ({
|
|
8
|
+
track: vi.fn(),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
vi.mock('svelte', async (importActual) => ({
|
|
12
|
+
...(await importActual()),
|
|
13
|
+
getContext: vi.fn(),
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
describe('Title.svelte', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.resetAllMocks()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('Should render the given playlinks', () => {
|
|
22
|
+
const { getByText } = render(Title, { title })
|
|
23
|
+
|
|
24
|
+
expect(getByText(title.providers[0].name)).toBeTruthy()
|
|
25
|
+
expect(getByText(title.providers[1].name)).toBeTruthy()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('Should render the given image', () => {
|
|
29
|
+
const { getByAltText } = render(Title, { title })
|
|
30
|
+
|
|
31
|
+
expect(getByAltText(`Movie poster for '${title.title}'`)).toBeTruthy()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('Should render the given info', () => {
|
|
35
|
+
const { getByText } = render(Title, { title })
|
|
36
|
+
|
|
37
|
+
expect(getByText(title.title)).toBeTruthy()
|
|
38
|
+
expect(getByText(title.imdb_score)).toBeTruthy()
|
|
39
|
+
expect(getByText(title.year)).toBeTruthy()
|
|
40
|
+
})
|
|
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
|
+
it('Should not have small class by default', () => {
|
|
52
|
+
const { container } = render(Title, { title })
|
|
53
|
+
|
|
54
|
+
expect(container.querySelector('.small')).not.toBeTruthy()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('Should have small class when prop is given', () => {
|
|
58
|
+
const { container } = render(Title, { title, small: true })
|
|
59
|
+
|
|
60
|
+
expect(container.querySelector('.small')).toBeTruthy()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('Should render given description', () => {
|
|
64
|
+
const { getByText } = render(Title, { title: { ...title, description: 'Some description' } })
|
|
65
|
+
|
|
66
|
+
expect(getByText('Some description')).toBeTruthy()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('Should not render description when small prop is given', () => {
|
|
70
|
+
const { queryByText } = render(Title, { title: { ...title, description: 'Some description' }, small: true })
|
|
71
|
+
|
|
72
|
+
expect(queryByText('Some description')).not.toBeTruthy()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('Should render as compacter version when compact prop is true', () => {
|
|
76
|
+
const { queryByAltText, container } = render(Title, { title: { ...title, description: 'Some description' }, compact: true })
|
|
77
|
+
|
|
78
|
+
expect(queryByAltText(`Movie poster for '${title.title}'`)).not.toBeTruthy()
|
|
79
|
+
expect(container.querySelector('.compact')).toBeTruthy()
|
|
80
|
+
expect(container.querySelector('.faded')).toBeTruthy()
|
|
81
|
+
})
|
|
82
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import TitleModal from '../../../routes/components/TitleModal.svelte'
|
|
5
|
+
import { title } from '$lib/fakeData'
|
|
6
|
+
import { track } from '$lib/tracking'
|
|
7
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
8
|
+
|
|
9
|
+
vi.mock('$lib/tracking', () => ({
|
|
10
|
+
track: vi.fn(),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
describe('TitleModal.svelte', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.resetAllMocks()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const onclose = vi.fn()
|
|
19
|
+
|
|
20
|
+
it('Should call track function when rendered', () => {
|
|
21
|
+
render(TitleModal, { onclose, title })
|
|
22
|
+
|
|
23
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.TitleModalView, title)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('Should call track function when scrolled', async () => {
|
|
27
|
+
const { getByRole } = render(TitleModal, { onclose, title })
|
|
28
|
+
|
|
29
|
+
await fireEvent.scroll(getByRole('dialog'))
|
|
30
|
+
|
|
31
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.TitleModalScroll, title)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { render } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import TitlePopover from '../../../routes/components/TitlePopover.svelte'
|
|
5
|
+
import { title } from '$lib/fakeData'
|
|
6
|
+
import { track } from '$lib/tracking'
|
|
7
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
8
|
+
|
|
9
|
+
vi.mock('$lib/tracking', () => ({
|
|
10
|
+
track: vi.fn(),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
describe('TitlePopover.svelte', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.resetAllMocks()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('Should call track function when rendered', () => {
|
|
19
|
+
render(TitlePopover, { title })
|
|
20
|
+
|
|
21
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.TitlePopoverView, title)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { cleanup } from '@testing-library/svelte'
|
|
2
|
+
import { afterEach, beforeAll, beforeEach, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
// https://github.com/jsdom/jsdom/issues/3429#issuecomment-1936128876
|
|
5
|
+
const mockAnimations = () => {
|
|
6
|
+
Element.prototype.animate ??= vi.fn().mockReturnValue({
|
|
7
|
+
finished: Promise.resolve(),
|
|
8
|
+
cancel: vi.fn(),
|
|
9
|
+
startTime: null,
|
|
10
|
+
currentTime: null,
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const mockLocalStorage = () => {
|
|
15
|
+
/** @type {Record<string, string>} */
|
|
16
|
+
let store = {}
|
|
17
|
+
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
;(window).localStorage = {
|
|
20
|
+
getItem: function (key) {
|
|
21
|
+
return store[key] || null
|
|
22
|
+
},
|
|
23
|
+
setItem: function (key, value) {
|
|
24
|
+
store[key] = value.toString()
|
|
25
|
+
},
|
|
26
|
+
removeItem: function (key) {
|
|
27
|
+
delete store[key]
|
|
28
|
+
},
|
|
29
|
+
clear: function () {
|
|
30
|
+
store = {}
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
beforeAll(() => {
|
|
36
|
+
mockAnimations()
|
|
37
|
+
mockLocalStorage()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
window.PlayPilotLinkInjections = { token: 'some-token' }
|
|
43
|
+
|
|
44
|
+
// Reset cookies
|
|
45
|
+
document.cookie.split(';').forEach((cookie) => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, '=;'))
|
|
46
|
+
|
|
47
|
+
window.HTMLElement.prototype.scrollIntoView = vi.fn()
|
|
48
|
+
localStorage.clear()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
cleanup()
|
|
53
|
+
})
|
package/src/typedefs.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* sid: string,
|
|
4
|
+
* slug: string,
|
|
5
|
+
* poster_uuid: string,
|
|
6
|
+
* standing_poster_uuid: string,
|
|
7
|
+
* genres: string[],
|
|
8
|
+
* year: number,
|
|
9
|
+
* imdb_score: number,
|
|
10
|
+
* type: string,
|
|
11
|
+
* providers: PlaylinkData[],
|
|
12
|
+
* description: string,
|
|
13
|
+
* small_poster: string,
|
|
14
|
+
* medium_poster: string,
|
|
15
|
+
* standing_poster: string,
|
|
16
|
+
* title: string,
|
|
17
|
+
* original_title: string,
|
|
18
|
+
* length?: number,
|
|
19
|
+
* participants?: Participant[],
|
|
20
|
+
* blurb?: string,
|
|
21
|
+
* }} TitleData
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {{ sid: string, name: string, url: string, logo_url: string, extra_info: { category: PlaylinkCategory } }} PlaylinkData
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {'SVOD' | 'BUY' | 'RENT'} PlaylinkCategory
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {{ username: string, profile: { display_name: string, profile_photo: string } }} User
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {{ sid: string, name: string, birth_date: string, death_date: string | null, jobs: Job[], image: string | null, image_uuid: string | null, gender: string, character: string | null }} Participant
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {'actor' | 'writer' | 'director'} Job
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {{
|
|
46
|
+
* sid: string,
|
|
47
|
+
* title: string,
|
|
48
|
+
* sentence: string,
|
|
49
|
+
* playpilot_url: string,
|
|
50
|
+
* key: string,
|
|
51
|
+
* title_details?: TitleData,
|
|
52
|
+
* inactive?: boolean,
|
|
53
|
+
* failed?: boolean,
|
|
54
|
+
* in_text?: boolean,
|
|
55
|
+
* after_article?: boolean
|
|
56
|
+
* after_article_style?: 'modal_button' | 'playlinks' | null
|
|
57
|
+
* }} LinkInjection
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/** @typedef {Record<string, { elementIndex: number, from: number, to: number }>} LinkInjectionRanges */
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @typedef {'bottom' | 'center'} Alignment
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {{ injections_ready: boolean, injections_enabled: boolean, link_injections: LinkInjection[] | null, automation_enabled: boolean }} LinkInjectionResponse
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {{ x: number, y: number }} Position
|
|
72
|
+
*/
|
|
Binary file
|
package/svelte.config.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import adapter from '@sveltejs/adapter-auto'
|
|
2
|
+
|
|
3
|
+
/** @type {import('@sveltejs/kit').Config} */
|
|
4
|
+
const config = {
|
|
5
|
+
kit: {
|
|
6
|
+
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
|
7
|
+
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
|
8
|
+
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
|
9
|
+
adapter: adapter(),
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default config
|
package/vite.config.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { defineConfig } from 'vitest/config'
|
|
4
|
+
import { sveltekit } from '@sveltejs/kit/vite'
|
|
5
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
6
|
+
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
|
7
|
+
|
|
8
|
+
export default defineConfig(({ command }) => ({
|
|
9
|
+
plugins: [
|
|
10
|
+
command === 'build' ? svelte() : sveltekit(),
|
|
11
|
+
command === 'build' && cssInjectedByJsPlugin(),
|
|
12
|
+
{
|
|
13
|
+
// The build is output to /dist/index.html and /dist/assets/index-[random-hash].js
|
|
14
|
+
// We only care for the .js file. We remove the .html file, and move and remove the .js to the root .dist folder.
|
|
15
|
+
|
|
16
|
+
name: 'move-build-files',
|
|
17
|
+
writeBundle() {
|
|
18
|
+
const distPath = path.resolve(__dirname, './dist')
|
|
19
|
+
const assetsPath = path.resolve(__dirname, './dist/assets')
|
|
20
|
+
|
|
21
|
+
// 1. Remove index.html
|
|
22
|
+
const indexHtml = path.join(distPath, 'index.html')
|
|
23
|
+
if (fs.existsSync(indexHtml)) fs.unlinkSync(indexHtml)
|
|
24
|
+
|
|
25
|
+
// 2. Find the index-[hash].js file
|
|
26
|
+
const files = fs.readdirSync(assetsPath)
|
|
27
|
+
const jsFile = files.find(f => /^index-.*\.js$/.test(f))
|
|
28
|
+
|
|
29
|
+
if (!jsFile) return
|
|
30
|
+
|
|
31
|
+
const sourcePath = path.join(assetsPath, jsFile)
|
|
32
|
+
const targetPath = path.join(distPath, 'link-injections.js')
|
|
33
|
+
|
|
34
|
+
// 3. Move and rename the file
|
|
35
|
+
fs.renameSync(sourcePath, targetPath)
|
|
36
|
+
console.log(`Moved and renamed ${jsFile} to link-injections.js`)
|
|
37
|
+
|
|
38
|
+
// 4. Remove the now-empty /assets directory
|
|
39
|
+
const remaining = fs.readdirSync(assetsPath)
|
|
40
|
+
if (remaining.length === 0) fs.rmdirSync(assetsPath)
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
|
|
45
|
+
server: {
|
|
46
|
+
port: 3000,
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
test: {
|
|
50
|
+
environment: 'happy-dom',
|
|
51
|
+
include: ['src/**/*.{test,spec}.{js,ts}'],
|
|
52
|
+
setupFiles: ['src/tests/setup.js'],
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
resolve: {
|
|
56
|
+
conditions: process.env.VITEST ? ['browser'] : [],
|
|
57
|
+
alias: {
|
|
58
|
+
'$lib': path.resolve(__dirname, './src/lib'),
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}))
|