@playpilot/tpi 5.15.0 → 5.16.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 (40) hide show
  1. package/dist/link-injections.js +11 -10
  2. package/package.json +2 -1
  3. package/release.js +3 -1
  4. package/src/lib/enums/SplitTest.ts +4 -0
  5. package/src/lib/fakeData.ts +70 -0
  6. package/src/lib/linkInjection.ts +11 -29
  7. package/src/lib/modal.ts +97 -0
  8. package/src/lib/playlink.ts +4 -1
  9. package/src/lib/types/participant.d.ts +14 -0
  10. package/src/lib/types/title.d.ts +3 -1
  11. package/src/routes/components/Description.svelte +1 -0
  12. package/src/routes/components/Icons/IconArrow.svelte +22 -0
  13. package/src/routes/components/Icons/IconClose.svelte +1 -1
  14. package/src/routes/components/Icons/IconIMDb.svelte +9 -1
  15. package/src/routes/components/ListTitle.svelte +204 -0
  16. package/src/routes/components/Modal.svelte +63 -13
  17. package/src/routes/components/Participant.svelte +92 -0
  18. package/src/routes/components/ParticipantModal.svelte +31 -0
  19. package/src/routes/components/PlaylinkIcon.svelte +41 -0
  20. package/src/routes/components/PlaylinkLabel.svelte +37 -0
  21. package/src/routes/components/Playlinks.svelte +1 -3
  22. package/src/routes/components/Rails/ParticipantsRail.svelte +56 -0
  23. package/src/routes/components/Rails/Rail.svelte +91 -0
  24. package/src/routes/components/Rails/SimilarRail.svelte +16 -0
  25. package/src/routes/components/Rails/TitlesRail.svelte +95 -0
  26. package/src/routes/components/Tabs.svelte +47 -0
  27. package/src/routes/components/Title.svelte +20 -17
  28. package/src/routes/components/TitleModal.svelte +3 -3
  29. package/src/routes/components/TitlePoster.svelte +30 -0
  30. package/src/tests/lib/linkInjection.test.js +10 -22
  31. package/src/tests/lib/modal.test.js +148 -0
  32. package/src/tests/lib/playlink.test.js +25 -10
  33. package/src/tests/routes/components/ListTitle.test.js +84 -0
  34. package/src/tests/routes/components/Modal.test.js +51 -19
  35. package/src/tests/routes/components/PlaylinkIcon.test.js +27 -0
  36. package/src/tests/routes/components/PlaylinkLabel.test.js +19 -0
  37. package/src/tests/routes/components/Rails/ParticipantsRail.test.js +41 -0
  38. package/src/tests/routes/components/Rails/TitleRail.test.js +38 -0
  39. package/src/tests/routes/components/Title.test.js +6 -0
  40. package/src/tests/routes/components/TitlePoster.test.js +20 -0
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ interface Option {
3
+ label: string
4
+ value: string
5
+ }
6
+
7
+ interface Props {
8
+ options: Option[]
9
+ }
10
+
11
+ const { options }: Props = $props()
12
+
13
+ let active = $state(options[0]?.value)
14
+ </script>
15
+
16
+ <div class="tabs">
17
+ {#each options as { label, value }}
18
+ <button class="tab" class:active={value === active} onclick={() => active = value}>{label}</button>
19
+ {/each}
20
+ </div>
21
+
22
+ <style lang="scss">
23
+ .tabs {
24
+ display: flex;
25
+ gap: margin(0.5);
26
+ }
27
+
28
+ .tab {
29
+ cursor: pointer;
30
+ appearance: none;
31
+ background: transparent;
32
+ padding: margin(0.25) margin(0.5);
33
+ border: 1px solid currentColor;
34
+ border-radius: var(--playpilot-tabs-border-radius, margin(0.25));
35
+ transition: opacity 100ms;
36
+ font-family: inherit;
37
+ font-weight: var(--playpilot-tabs-font-weight, 500);
38
+ color: var(--playpilot-tabs-text-color, var(--playpilot-detail-text-color, var(--playpilot-text-color-alt)));
39
+ font-size: inherit;
40
+
41
+ &.active,
42
+ &:hover {
43
+ background: var(--playpilot-tabs-background-active, var(--playpilot-content));
44
+ color: var(--playpilot-tabs-text-color-active, var(--playpilot-detail-text-color, var(--playpilot-text-color)));
45
+ }
46
+ }
47
+ </style>
@@ -3,10 +3,14 @@
3
3
  import Playlinks from './Playlinks.svelte'
4
4
  import Description from './Description.svelte'
5
5
  import IconIMDb from './Icons/IconIMDb.svelte'
6
+ import ParticipantsRail from './Rails/ParticipantsRail.svelte'
7
+ import SimilarRail from './Rails/SimilarRail.svelte'
8
+ import TitlePoster from './TitlePoster.svelte'
6
9
  import { t } from '$lib/localization'
7
10
  import type { TitleData } from '$lib/types/title'
8
11
  import { heading } from '$lib/actions/heading'
9
12
  import { removeImageUrlPrefix } from '$lib/image'
13
+ import { participants } from '$lib/fakeData'
10
14
 
11
15
  interface Props {
12
16
  title: TitleData
@@ -22,13 +26,8 @@
22
26
 
23
27
  <div class="content" class:small data-playpilot-link-injections-title>
24
28
  <div class="header">
25
- <div class="top">
26
- <img
27
- class="poster"
28
- class:loaded={posterLoaded}
29
- src={removeImageUrlPrefix(title.standing_poster)}
30
- alt="Movie poster for '{title.title}'"
31
- onload={() => posterLoaded = true} />
29
+ <div class="poster" class:loaded={posterLoaded}>
30
+ <TitlePoster {title} onload={() => posterLoaded = true} />
32
31
  </div>
33
32
 
34
33
  <div class="heading" use:heading={2} class:truncate={small} id="title">{title.title}</div>
@@ -53,9 +52,18 @@
53
52
  <div class="main">
54
53
  <Playlinks playlinks={title.providers} {title} />
55
54
 
56
- {#if !small}
55
+ {#if !small && title.description}
57
56
  <Description text={title.description} blurb={title.blurb} />
58
57
  {/if}
58
+
59
+ <!-- Temporarily not available on production as there is not yet an API endpoint for either -->
60
+ {#if process.env.NODE_ENV !== 'production'}
61
+ {#if true || title.participants?.length}
62
+ <ParticipantsRail participants={participants} />
63
+ {/if}
64
+
65
+ <SimilarRail />
66
+ {/if}
59
67
  </div>
60
68
  </div>
61
69
 
@@ -103,14 +111,14 @@
103
111
  }
104
112
 
105
113
  &.small {
106
- font-size: var(--playpilot-detail-font-size, 12px);
114
+ font-size: var(--playpilot-detail-font-size-small, 12px);
107
115
  line-height: 1.45;
108
- padding-bottom: margin(0.5);
116
+ padding-bottom: margin(1);
109
117
  }
110
118
  }
111
119
 
112
120
  .header {
113
- padding: margin(2) 0 margin(1);
121
+ padding: margin(3) 0 margin(1);
114
122
  background: transparent;
115
123
 
116
124
  .small & {
@@ -118,12 +126,6 @@
118
126
  }
119
127
  }
120
128
 
121
- .top {
122
- display: flex;
123
- justify-content: space-between;
124
- align-items: flex-end;
125
- }
126
-
127
129
  .poster {
128
130
  display: block;
129
131
  width: margin(4.5);
@@ -132,6 +134,7 @@
132
134
  border-radius: var(--playpilot-detail-image-border-radius, margin(0.5));
133
135
  background: var(--playpilot-detail-image-background, var(--playpilot-content));
134
136
  box-shadow: var(--playpilot-detail-image-shadow, var(--playpilot-shadow));
137
+ overflow: hidden;
135
138
  opacity: 0;
136
139
 
137
140
  &.loaded {
@@ -10,11 +10,11 @@
10
10
  import { getFirstAdOfType } from '$lib/ads'
11
11
 
12
12
  interface Props {
13
- onclose: () => void,
14
13
  title: TitleData
14
+ initialScrollPosition?: number
15
15
  }
16
16
 
17
- const { onclose, title }: Props = $props()
17
+ const { title, initialScrollPosition = 0 }: Props = $props()
18
18
 
19
19
  const topScrollAd = getFirstAdOfType('top_scroll')
20
20
  const displayAd = getFirstAdOfType('card')
@@ -49,6 +49,6 @@
49
49
  {/if}
50
50
  {/snippet}
51
51
 
52
- <Modal {onclose} {onscroll} prepend={displayAd ? prepend : null} bubble={topScrollAd ? bubble : null} tall>
52
+ <Modal {onscroll} {initialScrollPosition} prepend={displayAd ? prepend : null} bubble={topScrollAd ? bubble : null} tall>
53
53
  <Title {title} />
54
54
  </Modal>
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import { imagePlaceholderDataUrl } from '$lib/constants'
3
+ import { removeImageUrlPrefix } from '$lib/image'
4
+ import type { TitleData } from '$lib/types/title'
5
+
6
+ interface Props {
7
+ title: TitleData
8
+ width?: number
9
+ height?: number
10
+ onload?: () => void
11
+ }
12
+
13
+ const { title, width = 96, height = 144, onload = () => null }: Props = $props()
14
+ </script>
15
+
16
+ <img
17
+ src={removeImageUrlPrefix(title.standing_poster)}
18
+ alt="Movie poster for '{title.title}'"
19
+ {width}
20
+ {height}
21
+ onerror={({ target }) => (target as HTMLImageElement).src = imagePlaceholderDataUrl}
22
+ {onload} />
23
+
24
+ <style lang="scss">
25
+ img {
26
+ display: block;
27
+ width: 100%;
28
+ height: auto;
29
+ }
30
+ </style>
@@ -4,6 +4,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
4
4
  import { injectLinksInDocument, clearLinkInjections, clearLinkInjection, getLinkInjectionElements, insertAfterArticlePlaylinks, getLinkInjectionsParentElement, isAvailableAsManualInjection, filterRemovedAndInactiveInjections, isEquivalentInjection, filterInvalidInTextInjections, filterInvalidAfterArticleInjections, isValidInjection, isValidPlaylinkType, removePlayPilotTitleLinks, trackLinkIntersection } from '$lib/linkInjection'
5
5
  import { mount, unmount } from 'svelte'
6
6
  import { fakeFetch, generateInjection } from '../helpers'
7
+ import { openModal } from '$lib/modal'
7
8
  import { track } from '$lib/tracking'
8
9
 
9
10
  vi.mock('svelte', () => ({
@@ -11,6 +12,13 @@ vi.mock('svelte', () => ({
11
12
  unmount: vi.fn(),
12
13
  }))
13
14
 
15
+ vi.mock('$lib/modal', () => ({
16
+ openModal: vi.fn(),
17
+ destroyAllModals: vi.fn(),
18
+ getPreviousModal: vi.fn(),
19
+ goBackToPreviousModal: vi.fn(),
20
+ }))
21
+
14
22
  vi.mock('$lib/tracking', () => ({
15
23
  track: vi.fn(),
16
24
  }))
@@ -311,7 +319,7 @@ describe('linkInjection.js', () => {
311
319
  expect(() => injectLinksInDocument(elements, { aiInjections: linkInjections, manualInjections: [] })).not.toThrow()
312
320
  })
313
321
 
314
- it('Should mount modal when clicked', async () => {
322
+ it('Should open modal when link is clicked', async () => {
315
323
  document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
316
324
 
317
325
  const elements = Array.from(document.body.querySelectorAll('p'))
@@ -322,27 +330,7 @@ describe('linkInjection.js', () => {
322
330
  const link = /** @type {HTMLAnchorElement} */ (document.querySelector('a'))
323
331
  await fireEvent.click(link)
324
332
 
325
- expect(mount).toHaveBeenCalled()
326
- })
327
-
328
- it('Should not mount modal multiple times if modal is already open', async () => {
329
- const sentence = 'This is a sentence with an injection.'
330
- document.body.innerHTML = `<p>${sentence}</p>`
331
-
332
- const elements = Array.from(document.body.querySelectorAll('p'))
333
-
334
- const linkInjections = [
335
- generateInjection(sentence, 'a sentence'),
336
- generateInjection(sentence, 'an injection'),
337
- ]
338
-
339
- injectLinksInDocument(elements, { aiInjections: linkInjections, manualInjections: [] })
340
-
341
- const links = document.querySelectorAll('a')
342
- await fireEvent.click(links[0])
343
- await fireEvent.click(links[1])
344
-
345
- expect(mount).toHaveBeenCalledTimes(1)
333
+ expect(openModal).toHaveBeenCalled()
346
334
  })
347
335
 
348
336
  it('Should not fire given onclick function when clicked with modifier keys or not left click', async () => {
@@ -0,0 +1,148 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { closeCurrentModal, destroyAllModals, destroyCurrentModal, getAllModals, getPreviousModal, goBackToPreviousModal, openModal } from '$lib/modal'
3
+ import { linkInjections, title } from '$lib/fakeData'
4
+ import { mount, unmount } from 'svelte'
5
+ import ParticipantModal from '../../routes/components/ParticipantModal.svelte'
6
+ import TitleModal from '../../routes/components/TitleModal.svelte'
7
+
8
+ vi.mock('svelte', () => ({
9
+ mount: vi.fn(),
10
+ unmount: vi.fn(),
11
+ }))
12
+
13
+ const modal = {
14
+ injection: linkInjections[0],
15
+ data: title,
16
+ type: 'title',
17
+ }
18
+
19
+ describe('modal.js', () => {
20
+ beforeEach(() => {
21
+ destroyAllModals()
22
+ vi.resetAllMocks()
23
+ })
24
+
25
+ describe('openModal', () => {
26
+ it('Should mount modal', () => {
27
+ // @ts-ignore
28
+ openModal(modal)
29
+
30
+ expect(mount).toHaveBeenCalledWith(TitleModal, expect.any(Object))
31
+ })
32
+
33
+ it('Should mount modal for participants', () => {
34
+ // @ts-ignore
35
+ openModal({ ...modal, type: 'participant' })
36
+
37
+ expect(mount).toHaveBeenCalledWith(ParticipantModal, expect.any(Object))
38
+ })
39
+
40
+ it('Should not mount modal if modifier key was present in event', () => {
41
+ // @ts-ignore
42
+ openModal({ ...modal, event: { ctrlKey: true } })
43
+
44
+ expect(mount).not.toHaveBeenCalled()
45
+ })
46
+ })
47
+
48
+ describe('closeCurrentModal', () => {
49
+ it('Should unmount modal without removing it from list', () => {
50
+ // @ts-ignore
51
+ openModal(modal)
52
+ closeCurrentModal()
53
+
54
+ expect(unmount).toHaveBeenCalled()
55
+ expect(getAllModals()).toHaveLength(1)
56
+ })
57
+ })
58
+
59
+ describe('destroyCurrentModal', () => {
60
+ it('Should unmount modal and remove lastest entry from list', () => {
61
+ // @ts-ignore
62
+ openModal(modal)
63
+ destroyCurrentModal()
64
+
65
+ expect(unmount).toHaveBeenCalled()
66
+ expect(getAllModals()).toHaveLength(0)
67
+ })
68
+ })
69
+
70
+ describe('destroyAllModals', () => {
71
+ it('Should unmount current modal and remove all modals from list', () => {
72
+ // @ts-ignore
73
+ openModal(modal)
74
+ // @ts-ignore
75
+ openModal(modal)
76
+ // @ts-ignore
77
+ openModal(modal)
78
+ // @ts-ignore
79
+ openModal(modal)
80
+
81
+ destroyAllModals()
82
+
83
+ expect(unmount).toHaveBeenCalled()
84
+ expect(getAllModals()).toHaveLength(0)
85
+ })
86
+ })
87
+
88
+ describe('getPreviousModal', () => {
89
+ it('Should return the modal before the last', () => {
90
+ // @ts-ignore
91
+ openModal({ ...modal, type: 'participant' })
92
+ // @ts-ignore
93
+ openModal({ ...modal, type: 'title' })
94
+
95
+ // @ts-ignore
96
+ expect(getPreviousModal().type).toBe('participant')
97
+ })
98
+
99
+ it('Should return null if there is no previous modal', () => {
100
+ // @ts-ignore
101
+ openModal({ ...modal, type: 'participant' })
102
+
103
+ // @ts-ignore
104
+ expect(getPreviousModal()).toBeNull()
105
+ })
106
+
107
+ it('Should return null if there are no modals at all', () => {
108
+ // @ts-ignore
109
+ expect(getPreviousModal()).toBeNull()
110
+ })
111
+ })
112
+
113
+ describe('goBackToPreviousModal', () => {
114
+ it('Should destroy the current modal and mount the previous modal', () => {
115
+ // @ts-ignore
116
+ openModal({ ...modal, type: 'participant', data: { ...title, title: 'Previous modal' } })
117
+ // @ts-ignore
118
+ openModal({ ...modal, type: 'title' })
119
+
120
+ vi.resetAllMocks()
121
+
122
+ goBackToPreviousModal()
123
+
124
+ expect(unmount).toHaveBeenCalled()
125
+ expect(mount).toHaveBeenCalledWith(
126
+ ParticipantModal,
127
+ expect.objectContaining({
128
+ props: expect.objectContaining({
129
+ participant: expect.objectContaining({ title: 'Previous modal' }),
130
+ }),
131
+ }),
132
+ )
133
+ })
134
+ })
135
+
136
+ describe('getAllModals', () => {
137
+ it('Should return all current modals', () => {
138
+ // @ts-ignore
139
+ openModal(modal)
140
+ // @ts-ignore
141
+ openModal(modal)
142
+ // @ts-ignore
143
+ openModal(modal)
144
+
145
+ expect(getAllModals()).toHaveLength(3)
146
+ })
147
+ })
148
+ })
@@ -4,12 +4,12 @@ import { mergePlaylinks } from '$lib/playlink'
4
4
  describe('$lib/playlink', () => {
5
5
  describe('mergePlaylinks', () => {
6
6
  it('Should return an array with RENT and BUY categories of the same provider as one entry', () => {
7
- /** @type {PlaylinkData[]} */
7
+ /** @type {import('$lib/types/playlink').PlaylinkData[]} */
8
8
  const playlinks = [{
9
9
  sid: '1',
10
10
  name: 'Netflix',
11
11
  url: '',
12
- logo_url: '',
12
+ logo_url: 'a',
13
13
  extra_info: {
14
14
  category: 'RENT',
15
15
  },
@@ -17,7 +17,7 @@ describe('$lib/playlink', () => {
17
17
  sid: '2',
18
18
  name: 'Netflix',
19
19
  url: '',
20
- logo_url: '',
20
+ logo_url: 'a',
21
21
  extra_info: {
22
22
  category: 'BUY',
23
23
  },
@@ -28,12 +28,12 @@ describe('$lib/playlink', () => {
28
28
  })
29
29
 
30
30
  it('Should not merge playlinks if they do not share the same name', () => {
31
- /** @type {PlaylinkData[]} */
31
+ /** @type {import('$lib/types/playlink').PlaylinkData[]} */
32
32
  const playlinks = [{
33
33
  sid: '1',
34
34
  name: 'Netflix',
35
35
  url: '',
36
- logo_url: '',
36
+ logo_url: 'a',
37
37
  extra_info: {
38
38
  category: 'RENT',
39
39
  },
@@ -41,7 +41,7 @@ describe('$lib/playlink', () => {
41
41
  sid: '2',
42
42
  name: 'Apple',
43
43
  url: '',
44
- logo_url: '',
44
+ logo_url: 'a',
45
45
  extra_info: {
46
46
  category: 'BUY',
47
47
  },
@@ -51,12 +51,12 @@ describe('$lib/playlink', () => {
51
51
  })
52
52
 
53
53
  it('Should not merge playlinks if one of the categories is not RENT or BUY', () => {
54
- /** @type {PlaylinkData[]} */
54
+ /** @type {import('$lib/types/playlink').PlaylinkData[]} */
55
55
  const playlinks = [{
56
56
  sid: '1',
57
57
  name: 'Netflix',
58
58
  url: '',
59
- logo_url: '',
59
+ logo_url: 'a',
60
60
  extra_info: {
61
61
  category: 'RENT',
62
62
  },
@@ -64,7 +64,7 @@ describe('$lib/playlink', () => {
64
64
  sid: '2',
65
65
  name: 'Netflix',
66
66
  url: '',
67
- logo_url: '',
67
+ logo_url: 'a',
68
68
  extra_info: {
69
69
  category: 'SVOD',
70
70
  },
@@ -73,8 +73,23 @@ describe('$lib/playlink', () => {
73
73
  expect(mergePlaylinks(playlinks)).toHaveLength(2)
74
74
  })
75
75
 
76
+ it('Should filter out playlinks without logos', () => {
77
+ /** @type {import('$lib/types/playlink').PlaylinkData[]} */
78
+ const playlinks = [{
79
+ sid: '1',
80
+ name: 'Netflix',
81
+ url: '',
82
+ logo_url: '',
83
+ extra_info: {
84
+ category: 'RENT',
85
+ },
86
+ }]
87
+
88
+ expect(mergePlaylinks(playlinks)).toHaveLength(0)
89
+ })
90
+
76
91
  it('Should return an empty array if given', () => {
77
- /** @type {PlaylinkData[]} */
92
+ /** @type {import('$lib/types/playlink').PlaylinkData[]} */
78
93
  const playlinks = []
79
94
  expect(mergePlaylinks(playlinks)).toEqual([])
80
95
  })
@@ -0,0 +1,84 @@
1
+ import { fireEvent, render } from '@testing-library/svelte'
2
+ import { describe, expect, it, vi, beforeEach } from 'vitest'
3
+
4
+ import ListTitle from '../../../routes/components/ListTitle.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('ListTitle.svelte', () => {
17
+ beforeEach(() => {
18
+ vi.resetAllMocks()
19
+ })
20
+
21
+ it('Should render the given image', () => {
22
+ const { getByAltText } = render(ListTitle, { title })
23
+
24
+ expect(getByAltText(`Movie poster for '${title.title}'`)).toBeTruthy()
25
+ })
26
+
27
+ it('Should render the given info', () => {
28
+ const { getByText } = render(ListTitle, { title })
29
+
30
+ expect(getByText(title.title)).toBeTruthy()
31
+ expect(getByText(title.imdb_score)).toBeTruthy()
32
+ expect(getByText(title.year)).toBeTruthy()
33
+ expect(getByText(title.description)).toBeTruthy()
34
+ })
35
+
36
+ it('Should not have small class by default', () => {
37
+ const { container } = render(ListTitle, { title })
38
+
39
+ expect(container.querySelector('.small')).not.toBeTruthy()
40
+ })
41
+
42
+ it('Should fire the given onclick function when clicked', async () => {
43
+ const onclick = vi.fn()
44
+ const { getByText } = render(ListTitle, { title, onclick })
45
+
46
+ await fireEvent.click(getByText(title.title))
47
+
48
+ expect(onclick).toHaveBeenCalled()
49
+ })
50
+
51
+ describe('Playlinks', () => {
52
+ const playlinks = [
53
+ { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
54
+ { name: 'Some other playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
55
+ ]
56
+
57
+ it('Should render playlinks', () => {
58
+ // @ts-ignore
59
+ const { queryByAltText, queryByText } = render(ListTitle, { title: { ...title, description: 'Some description', providers: playlinks } })
60
+
61
+ expect(queryByAltText('Some playlink') || queryByText('Some playlink')).toBeTruthy()
62
+ expect(queryByAltText('Some other playlink') || queryByText('Some other playlink')).toBeTruthy()
63
+ })
64
+
65
+ it('Should limit the number of shown playlinks', () => {
66
+ // @ts-ignore
67
+ const { container, getByText } = render(ListTitle, { title: { ...title, description: 'Some description', providers: [...playlinks, ...playlinks] } })
68
+
69
+ expect(container.querySelectorAll('.playlink')).toHaveLength(3)
70
+ expect(getByText('+1')).toBeTruthy()
71
+ })
72
+
73
+ it('Should not fire the given onclick function when playlink is clicked', async () => {
74
+ const onclick = vi.fn()
75
+ // @ts-ignore
76
+ const { container } = render(ListTitle, { title: { ...title, providers: playlinks }, onclick })
77
+
78
+ // @ts-ignore
79
+ await fireEvent.click(container.querySelector('.playlink'))
80
+
81
+ expect(onclick).not.toHaveBeenCalled()
82
+ })
83
+ })
84
+ })
@@ -1,57 +1,63 @@
1
1
  import { render, fireEvent } from '@testing-library/svelte'
2
- import { describe, expect, it, vi } from 'vitest'
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
3
3
 
4
4
  import Modal from '../../../routes/components/Modal.svelte'
5
5
  import { createRawSnippet } from 'svelte'
6
+ import { destroyAllModals, getPreviousModal, goBackToPreviousModal } from '$lib/modal'
7
+
8
+ vi.mock('$lib/modal', () => ({
9
+ destroyAllModals: vi.fn(),
10
+ getPreviousModal: vi.fn(),
11
+ goBackToPreviousModal: vi.fn(),
12
+ }))
6
13
 
7
14
  describe('Modal.svelte', () => {
15
+ beforeEach(() => {
16
+ vi.resetAllMocks()
17
+ })
18
+
8
19
  const children = createRawSnippet(() => ({ render: () => '<div></div>' }))
9
20
 
10
- it('Should fire given onclose function when backdrop is clicked', async () => {
11
- const onclose = vi.fn()
12
- const { container } = render(Modal, { children, onclose })
21
+ it('Should fire given destroyAllModals function when backdrop is clicked', async () => {
22
+ const { container } = render(Modal, { children })
13
23
 
14
24
  await fireEvent.click(/** @type {Node} */ (container.querySelector('.backdrop')))
15
25
 
16
- expect(onclose).toHaveBeenCalled()
26
+ expect(destroyAllModals).toHaveBeenCalled()
17
27
  })
18
28
 
19
- it('Should fire given onclose function when close button is clicked', async () => {
20
- const onclose = vi.fn()
21
- const { container } = render(Modal, { children, onclose })
29
+ it('Should fire destroyAllModals function when close button is clicked', async () => {
30
+ const { container } = render(Modal, { children })
22
31
 
23
32
  await fireEvent.click(/** @type {Node} */ (container.querySelector('button')))
24
33
 
25
- expect(onclose).toHaveBeenCalled()
34
+ expect(destroyAllModals).toHaveBeenCalled()
26
35
  })
27
36
 
28
37
  it('Should not fire given onclose function when dialog is clicked', async () => {
29
- const onclose = vi.fn()
30
- const { container } = render(Modal, { children, onclose })
38
+ const { container } = render(Modal, { children })
31
39
 
32
40
  await fireEvent.click(/** @type {Node} */ (container.querySelector('.dialog')))
33
41
 
34
- expect(onclose).not.toHaveBeenCalled()
42
+ expect(destroyAllModals).not.toHaveBeenCalled()
35
43
  })
36
44
 
37
- it('Should fire given onclose function when escape key is pressed', async () => {
38
- const onclose = vi.fn()
39
- render(Modal, { children, onclose })
45
+ it('Should fire given destroyAllModals function when escape key is pressed', async () => {
46
+ render(Modal, { children })
40
47
 
41
48
  fireEvent.keyDown(window, { key: 'Escape' })
42
49
 
43
- expect(onclose).toHaveBeenCalled()
50
+ expect(destroyAllModals).toHaveBeenCalled()
44
51
  })
45
52
 
46
53
  it('Should not fire given onclose function when key other than escape key is pressed', async () => {
47
- const onclose = vi.fn()
48
- render(Modal, { children, onclose })
54
+ render(Modal, { children })
49
55
 
50
56
  fireEvent.keyDown(window, { key: 'a' })
51
57
  fireEvent.keyDown(window, { key: 'Enter' })
52
58
  fireEvent.keyDown(window, { key: 'Space' })
53
59
 
54
- expect(onclose).not.toHaveBeenCalled()
60
+ expect(destroyAllModals).not.toHaveBeenCalled()
55
61
  })
56
62
 
57
63
  it('Should fire given onscroll function when dialog is scrolled', async () => {
@@ -96,4 +102,30 @@ describe('Modal.svelte', () => {
96
102
 
97
103
  expect(getByRole('dialog').classList).toContain('tall')
98
104
  })
105
+
106
+ it('Should not show back button by default', () => {
107
+ const { queryByLabelText } = render(Modal, { children })
108
+
109
+ expect(queryByLabelText('Back')).not.toBeTruthy()
110
+ })
111
+
112
+ it('Should show back button if previous modal was opened', () => {
113
+ // @ts-ignore
114
+ vi.mocked(getPreviousModal).mockReturnValueOnce(true)
115
+
116
+ const { getByLabelText } = render(Modal, { children })
117
+
118
+ expect(getByLabelText('Back')).toBeTruthy()
119
+ })
120
+
121
+ it('Should fire goBackToPreviousModal when back button is clicked', async () => {
122
+ // @ts-ignore
123
+ vi.mocked(getPreviousModal).mockReturnValueOnce(true)
124
+
125
+ const { getByLabelText } = render(Modal, { children })
126
+
127
+ await fireEvent.click(getByLabelText('Back'))
128
+
129
+ expect(goBackToPreviousModal).toHaveBeenCalled()
130
+ })
99
131
  })