@saooti/octopus-sdk 41.5.4 → 41.5.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 41.5.6 (20/02/2026)
4
+
5
+ **Misc**
6
+
7
+ - Ajout d'un props pour ajouter une entrée permettant de sélectionner toutes les
8
+ entrées dans `ClassicMultiselect`
9
+
10
+ ## 41.5.5 (16/02/2026)
11
+
12
+ **Fix**
13
+
14
+ - Correctif pour la lecture de lives/radio sur Organisation sécurisée sous
15
+ chrome
16
+ - Correction affichage critère de filtrage pour épisodes à valider
17
+
18
+ **Misc**
19
+
20
+ - Ajout de `frameborder="0"` dans les iframes miniplayer
21
+ - Ajustements de composants génériques
22
+
3
23
  ## 41.5.4 (12/02/2026)
4
24
 
5
25
  **Misc**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "41.5.4",
3
+ "version": "41.5.6",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -48,7 +48,7 @@
48
48
  "stylelint-config-standard-scss": "^15.0.1",
49
49
  "stylelint-config-standard-vue": "^1.0.0",
50
50
  "stylelint-gamut": "^1.3.4",
51
- "swiper": "^11.2.10",
51
+ "swiper": "^12.1.2",
52
52
  "typescript-eslint": "^8.47.0",
53
53
  "video.js": "^8.23.6",
54
54
  "videojs-quality-selector-hls": "^1.1.1",
@@ -28,19 +28,23 @@ export const usePlayerLive = (hlsReady: Ref<boolean>)=>{
28
28
  return authStore.authParam.accessToken && ("SECURED" === playerStore.playerLive?.organisation?.privacy || playerStore.playerRadio?.secured);
29
29
  });
30
30
 
31
- function onPlay(): void {
31
+ function onPlay(): void {
32
32
  playerStore.playerChangeStatus(PlayerStatus.PAUSED ===playerStore.playerStatus);
33
33
  }
34
34
 
35
35
  function playRadio() {
36
- if (!playerStore.playerRadio) return;
36
+ if (!playerStore.playerRadio) {
37
+ return;
38
+ }
37
39
  handleSessionIdRadio();
38
40
  playerStore.playerUpdatePlayerHlsUrl(playerStore.playerRadio.url+"?origin=octopus&sessionId="+playerStore.playerRadio.sessionId);
39
41
  playHls();
40
42
  }
41
43
 
42
44
  function handleSessionIdRadio(){
43
- if(!playerStore.playerRadio) return;
45
+ if(!playerStore.playerRadio) {
46
+ return;
47
+ }
44
48
  if(playerStore.playerRadio.sessionId && dayjs().diff(dayjs(playerStore.playerRadio.dateSessionId), 'm')<maxMinutesSessionId){
45
49
  return;
46
50
  }
@@ -48,7 +52,9 @@ export const usePlayerLive = (hlsReady: Ref<boolean>)=>{
48
52
  }
49
53
 
50
54
  function playLive() {
51
- if (!playerStore.playerHlsIdentifier) return;
55
+ if (!playerStore.playerHlsIdentifier) {
56
+ return;
57
+ }
52
58
  playerStore.playerUpdatePlayerHlsUrl(`${apiStore.hlsUrl}live/${playerStore.playerHlsIdentifier}/index.m3u8`);
53
59
  playHls();
54
60
  }
@@ -74,11 +80,15 @@ export const usePlayerLive = (hlsReady: Ref<boolean>)=>{
74
80
  !isChrome &&
75
81
  !isAndroid
76
82
  ) {
83
+ let url = playerStore.playerHlsUrl;
77
84
  if(needToAddToken.value) {
78
- audioElement.value.src = playerStore.playerHlsUrl+"?access_token="+authStore.authParam.accessToken;
79
- }else{
80
- audioElement.value.src = playerStore.playerHlsUrl;
85
+ if (url.includes('?')) {
86
+ url += "&access_token="+authStore.authParam.accessToken;
87
+ } else {
88
+ url += "?access_token="+authStore.authParam.accessToken;
89
+ }
81
90
  }
91
+ audioElement.value.src = url;
82
92
  await initLiveDownloadId();
83
93
  hlsReady.value = true;
84
94
  await audioElement.value.play();
@@ -233,7 +233,7 @@ const isSelectValidity = computed(() => {
233
233
  undefined !== organisation.value &&
234
234
  organisationRight.value &&
235
235
  authStore.isRoleContribution &&
236
- !isPodcastmaker &&
236
+ !isPodcastmaker.value &&
237
237
  !props.isEmission &&
238
238
  props.includeHidden
239
239
  );
@@ -20,6 +20,7 @@
20
20
  allowfullscreen="true"
21
21
  allow="clipboard-read; clipboard-write; autoplay"
22
22
  referrerpolicy="no-referrer-when-downgrade"
23
+ frameborder="0"
23
24
  :src="iFrameSrc"
24
25
  width="100%"
25
26
  :height="iFrameHeight"
@@ -273,7 +274,7 @@ const iFrame = computed(() => {
273
274
  const specialDigiteka = props.podcast?.video?.videoId
274
275
  ? 'allowfullscreen="true" referrerpolicy="no-referrer-when-downgrade"'
275
276
  : "";
276
- return `<iframe src="${iFrameSrc.value}" width="100%" height="${iFrameHeight.value}" scrolling="no" ${specialDigiteka} allow="clipboard-read; clipboard-write; autoplay"></iframe>`;
277
+ return `<iframe src="${iFrameSrc.value}" width="100%" height="${iFrameHeight.value}" frameborder="0" scrolling="no" ${specialDigiteka} allow="clipboard-read; clipboard-write; autoplay"></iframe>`;
277
278
  });
278
279
  const dataTitle = computed(() => {
279
280
  if (props.podcast) return props.podcast.podcastId;
@@ -8,6 +8,7 @@
8
8
  id="miniplayerIframeRadio"
9
9
  title="Miniplayer"
10
10
  :src="iFrameSrc"
11
+ frameborder="0"
11
12
  width="100%"
12
13
  height="140px"
13
14
  style="overflow: hidden"
@@ -90,7 +91,7 @@ const iFrameSrc = computed(() => {
90
91
  return url;
91
92
  });
92
93
  const iFrame = computed(() => {
93
- return `<iframe src="${iFrameSrc.value}" width="100%" height="140px" scrolling="no" allow="clipboard-read; clipboard-write; autoplay"></iframe>`;
94
+ return `<iframe src="${iFrameSrc.value}" width="100%" height="140px" frameborder="0" scrolling="no" allow="clipboard-read; clipboard-write; autoplay"></iframe>`;
94
95
  });
95
96
 
96
97
 
@@ -82,7 +82,19 @@ const props = defineProps({
82
82
  })
83
83
 
84
84
  //Emits
85
- const emit = defineEmits(["updateDate", "update:date"]);
85
+ const emit = defineEmits<{
86
+ /**
87
+ * Update the date
88
+ * @param value The new date
89
+ * @deprecated
90
+ */
91
+ (e: "updateDate", value: Date): void;
92
+ /**
93
+ * Update the date
94
+ * @param value The new date
95
+ */
96
+ (e: "update:date", value: Date): void;
97
+ }>();
86
98
 
87
99
  //Data
88
100
  const divContainerRef = useTemplateRef('divContainer');
@@ -147,6 +159,7 @@ function updateValue(date: Date) {
147
159
  (divContainerRef?.value as HTMLElement)?.focus();
148
160
  }
149
161
  emit("updateDate", date);
162
+ emit("update:date", date);
150
163
  }
151
164
 
152
165
  function formatDate(value: Date): string {
@@ -56,21 +56,33 @@
56
56
  @option:selected="onOptionSelected"
57
57
  @option:deselected="onOptionDeselect"
58
58
  >
59
+
59
60
  <template v-if="optionCustomTemplating.length" #option="option">
60
61
  <slot :name="optionCustomTemplating" :option="option" />
61
62
  </template>
63
+ <template v-else-if="withSelectAll" #option="option">
64
+ <strong v-if="option.id === selectAll.id">
65
+ {{ option[optionLabel] }}
66
+ </strong>
67
+ <span v-else>
68
+ {{ option[optionLabel] }}
69
+ </span>
70
+ </template>
71
+
62
72
  <template
63
73
  v-if="optionSelectedCustomTemplating.length"
64
74
  #selected-option="option"
65
75
  >
66
76
  <slot :name="optionSelectedCustomTemplating" :option="option" />
67
77
  </template>
78
+
68
79
  <template #no-options="{ searching }">
69
80
  <span v-if="searching">{{
70
81
  t("No elements found. Consider changing the search query.")
71
82
  }}</span>
72
83
  <span v-else>{{ t("List is empty") }}</span>
73
84
  </template>
85
+
74
86
  <template #list-footer>
75
87
  <div v-if="remainingElements" class="vs__dropdown-option">
76
88
  {{
@@ -81,11 +93,13 @@
81
93
  }}
82
94
  </div>
83
95
  </template>
96
+
84
97
  <template #list-header>
85
98
  <div v-if="maxOptionsSelected" class="vs__dropdown-option">
86
99
  {{ t("Multiselect max options", { max: maxOptions }) }}
87
100
  </div>
88
101
  </template>
102
+
89
103
  <template #open-indicator="{ attributes }">
90
104
  <ChevronDownIcon v-bind="attributes" />
91
105
  </template>
@@ -112,12 +126,14 @@ const {
112
126
  inModal = false, multiple = false, isDisabled = false, width = "100%",
113
127
  maxElement = 50, minSearchLength = 3, noDeselect = true, displayLabel = false,
114
128
  allowEmpty = true, optionChosen, maxOptions = null,
115
- optionCustomTemplating = '', optionSelectedCustomTemplating = ''
129
+ optionCustomTemplating = '', optionSelectedCustomTemplating = '',
130
+ optionLabel,
131
+ withSelectAll = false
116
132
  } = defineProps<{
117
133
  id?: string;
118
134
  label?: string;
119
135
  placeholder?: string;
120
- optionLabel?: string;
136
+ optionLabel?: keyof T;
121
137
  inModal?: boolean;
122
138
  multiple?: boolean;
123
139
  isDisabled?: boolean;
@@ -137,10 +153,24 @@ const {
137
153
  displayRequired?: boolean;
138
154
  popover?: string;
139
155
  popoverRelativeClass?: string;
156
+ /**
157
+ * Add an option to select everything at once
158
+ * If set to a truthy string, the added option will have this value as its
159
+ * label. If set to true, use a generic label.
160
+ */
161
+ withSelectAll?: boolean|string;
140
162
  }>();
141
163
 
142
164
  //Emits
143
- const emit = defineEmits(["onSearch", "selected", "onClose"]);
165
+ const emit = defineEmits<{
166
+ (e: "onSearch", query: string): void;
167
+ (e: "selected", data: Array<T>|T): void;
168
+ (e: "onClose", data: string): void;
169
+ }>();
170
+
171
+ //Composables
172
+ const { t } = useI18n();
173
+
144
174
 
145
175
  //Data
146
176
  const optionSelected : Ref<T|T[]>= ref(undefined);
@@ -148,10 +178,10 @@ const options : Ref<Array<T>>= ref([]);
148
178
  const remainingElements = ref(0);
149
179
  const isLoading = ref(false);
150
180
  const searchInput = ref("");
151
-
152
- //Composables
153
- const { t } = useI18n();
154
-
181
+ const selectAll = {
182
+ id: 'SELECT_ALL',
183
+ [optionLabel]: withSelectAll && typeof withSelectAll === 'string' ? withSelectAll : t('All')
184
+ } as unknown as T;
155
185
 
156
186
  //Computed
157
187
  const maxOptionsSelected = computed(() => {
@@ -178,6 +208,7 @@ watch(optionSelected, () => {
178
208
  function fakeSearch(): Array<unknown> {
179
209
  return options.value;
180
210
  }
211
+
181
212
  function onSearch(search?: string): void {
182
213
  if (search && search.length < minSearchLength) {
183
214
  return;
@@ -187,30 +218,44 @@ function onSearch(search?: string): void {
187
218
  isLoading.value = true;
188
219
  emit("onSearch", search);
189
220
  }
221
+
190
222
  function onClose() {
191
223
  emit("onClose", searchInput.value);
192
224
  searchInput.value = "";
193
225
  }
226
+
194
227
  function afterSearch(optionsFetched: Array<T>, count: number): void {
195
- options.value = optionsFetched;
228
+ if (withSelectAll) {
229
+ options.value = [selectAll, ...optionsFetched];
230
+ count += 1;
231
+ } else {
232
+ options.value = optionsFetched;
233
+ }
196
234
  remainingElements.value = Math.max(0, count - maxElement);
197
235
  isLoading.value = false;
198
236
  }
199
- function onOptionSelected(optionSelected: unknown): void {
200
- emit("selected", optionSelected);
237
+
238
+ function onOptionSelected(newValue: Array<T>|T): void {
239
+ // Check if selectAll is included
240
+ if (withSelectAll && Array.isArray(newValue) && newValue.find(o => o.id === selectAll.id)) {
241
+ emit("selected", options.value.slice(1, -1));
242
+ } else {
243
+ emit("selected", newValue);
244
+ }
201
245
  }
202
- function onOptionDeselect(event: unknown): void {
246
+
247
+ function onOptionDeselect(event: T): void {
203
248
  if (!multiple) {
204
249
  return;
205
250
  }
206
251
  if (
207
252
  !allowEmpty &&
208
- 0 === (optionSelected.value as Array<unknown>).length
253
+ 0 === (optionSelected.value as Array<T>).length
209
254
  ) {
210
- (optionSelected.value as Array<unknown>).push(event);
255
+ (optionSelected.value as Array<T>).push(event);
211
256
  return;
212
257
  }
213
- emit("selected", optionSelected.value);
258
+ emit("selected", (optionSelected.value as Array<T>));
214
259
  }
215
260
 
216
261
  //Expose
@@ -4,8 +4,10 @@
4
4
  Available slots:
5
5
  `label-{option.value}`: Slot to replace the label of the current option.
6
6
  Binding: `option`: the current option
7
+ `selected` : true if the option is selected
7
8
  `after-{option.value}`: Slot after the radio button
8
9
  Binding: `option`: the current option
10
+ `selected` : true if the option is selected
9
11
  -->
10
12
  <template>
11
13
  <div role="radiogroup" class="d-flex" :class="isColumn ? 'flex-column' : ''">
@@ -33,20 +35,14 @@
33
35
  </div>
34
36
  </template>
35
37
 
36
- <script setup lang="ts">
37
-
38
- interface Option {
39
- title: string;
40
- value: string | undefined
41
- };
42
-
38
+ <script setup generic="T extends { title: string; value: string|undefined; }" lang="ts">
43
39
  //Props
44
40
  const { textInit } = defineProps({
45
41
  idRadio: { default: "", type: String },
46
42
  isDisabled: { default: false, type: Boolean },
47
43
  options: {
48
44
  default: () => [],
49
- type: Array as () => Array<Option>,
45
+ type: Array as () => Array<T>,
50
46
  },
51
47
  textInit: { default: undefined, type: String },
52
48
  isColumn: { default: true, type: Boolean },
@@ -60,11 +56,10 @@ function onChange(value:string){
60
56
  emit('update:textInit', value)
61
57
  }
62
58
 
63
- function slotBindings(option: Option): { option: Option; selected: boolean } {
59
+ function slotBindings(option: T): { option: T; selected: boolean } {
64
60
  return {
65
61
  option,
66
62
  selected: textInit === option.value
67
63
  }
68
64
  }
69
-
70
65
  </script>
@@ -415,5 +415,6 @@
415
415
  "Miniplayer - Parameters - Auto height": "Automatische Höhe",
416
416
  "Edit": "Bearbeiten",
417
417
  "Podcast": "Folge",
418
- "Player - Transcription - AI Warning": "Die Transkription basiert auf KI und kann Fehler enthalten, bitte teilen Sie uns dies mit."
418
+ "Player - Transcription - AI Warning": "Die Transkription basiert auf KI und kann Fehler enthalten, bitte teilen Sie uns dies mit.",
419
+ "All organisations": "Alle Organisationen"
419
420
  }
@@ -415,5 +415,6 @@
415
415
  "Miniplayer - Parameters - Auto height": "Auto Height",
416
416
  "Edit": "Edit",
417
417
  "Podcast": "Episode",
418
- "Player - Transcription - AI Warning": "The transcription is based on AI and may contain errors, please let us know."
418
+ "Player - Transcription - AI Warning": "The transcription is based on AI and may contain errors, please let us know.",
419
+ "All organisations": "All organizations"
419
420
  }
@@ -415,5 +415,6 @@
415
415
  "Miniplayer - Parameters - Auto height": "Altura automática",
416
416
  "Edit": "Editar",
417
417
  "Podcast": "Episodio",
418
- "Player - Transcription - AI Warning": "La transcripción se basa en IA y puede contener errores, háganoslo saber."
418
+ "Player - Transcription - AI Warning": "La transcripción se basa en IA y puede contener errores, háganoslo saber.",
419
+ "All organisations": "Todas las organizaciones"
419
420
  }
@@ -24,6 +24,7 @@
24
24
  "Producted by : ": "Produit par : ",
25
25
  "Loading podcasts ...": "Chargement des épisodes…",
26
26
  "All podcasts": "Tous les épisodes",
27
+ "All organisations": "Toutes les organisations",
27
28
  "Error": "Erreur",
28
29
  "Upload": "Téléverser",
29
30
  "Count more elements matched your query, please make a more specific search.":
@@ -416,5 +416,6 @@
416
416
  "Miniplayer - Parameters - Auto height": "Altezza automatica",
417
417
  "Edit": "Modificare",
418
418
  "Podcast": "Episodio",
419
- "Player - Transcription - AI Warning": "La trascrizione si basa sull'intelligenza artificiale e potrebbe contenere errori, faccelo sapere."
419
+ "Player - Transcription - AI Warning": "La trascrizione si basa sull'intelligenza artificiale e potrebbe contenere errori, faccelo sapere.",
420
+ "All organisations": "Tutte le organizzazioni"
420
421
  }
@@ -414,5 +414,6 @@
414
414
  "Miniplayer - Parameters - Auto height": "Samodejna višina",
415
415
  "Edit": "Uredi",
416
416
  "Podcast": "Epizoda",
417
- "Player - Transcription - AI Warning": "Transkripcija temelji na umetni inteligenci in lahko vsebuje napake, zato nas obvestite."
417
+ "Player - Transcription - AI Warning": "Transkripcija temelji na umetni inteligenci in lahko vsebuje napake, zato nas obvestite.",
418
+ "All organisations": "Vse organizacije"
418
419
  }
@@ -0,0 +1,186 @@
1
+ import '@tests/mocks/useRouter';
2
+ import '@tests/mocks/useAdvancedParamInit';
3
+ import '@tests/mocks/i18n';
4
+
5
+ import AdvancedSearch from '@/components/display/filter/AdvancedSearch.vue';
6
+ import { mount } from '@tests/utils';
7
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
8
+ import { useAuthStore } from '@/stores/AuthStore';
9
+ import { state } from '@/stores/ParamSdkStore';
10
+ import { useGeneralStore } from '@/stores/GeneralStore';
11
+
12
+ // Mock the useOrgaComputed composable
13
+ vi.mock('@/components/composable/useOrgaComputed', () => ({
14
+ useOrgaComputed: () => {
15
+ const { computed } = require('vue');
16
+ return {
17
+ isPodcastmaker: computed(() => state.generalParameters.podcastmaker as boolean),
18
+ isEditRights: (orgaId?: string) => {
19
+ const authStore = useAuthStore();
20
+ return authStore.authOrgaId === orgaId || authStore.isRoleAdmin;
21
+ }
22
+ };
23
+ }
24
+ }));
25
+
26
+ // Mock the groups API
27
+ vi.mock('@/api/groupsApi', () => {
28
+ return {
29
+ groupsApi: {
30
+ count: vi.fn().mockResolvedValue(0)
31
+ }
32
+ };
33
+ });
34
+
35
+ describe('AdvancedSearch - isSelectValidity computed (fix for isPodcastmaker.value)', () => {
36
+ beforeEach(() => {
37
+ // Reset the podcastmaker state before each test
38
+ state.generalParameters.podcastmaker = false;
39
+ });
40
+
41
+ it('should correctly evaluate isSelectValidity when isPodcastmaker is false', async () => {
42
+ const wrapper = await mount(AdvancedSearch, {
43
+ props: {
44
+ organisationId: 'test-org-id',
45
+ isEmission: false,
46
+ includeHidden: true
47
+ },
48
+ beforeMount: async () => {
49
+ const authStore = useAuthStore();
50
+ authStore.$patch({
51
+ authOrgaId: 'test-org-id',
52
+ authRole: ['PODCAST_CRUD'],
53
+ authOrganisation: {
54
+ id: 'test-org-id',
55
+ name: 'Test Organisation',
56
+ imageUrl: '',
57
+ attributes: {}
58
+ }
59
+ });
60
+
61
+ const generalStore = useGeneralStore();
62
+ generalStore.$patch({
63
+ platformEducation: false
64
+ });
65
+
66
+ // Ensure isPodcastmaker is false
67
+ state.generalParameters.podcastmaker = false;
68
+ }
69
+ });
70
+
71
+ // Access the component's computed property
72
+ const vm = wrapper.vm as any;
73
+
74
+ // When isPodcastmaker is false and other conditions are met,
75
+ // isSelectValidity should be true
76
+ expect(vm.isSelectValidity).toBe(true);
77
+ });
78
+
79
+ it('should correctly evaluate isSelectValidity when isPodcastmaker is true', async () => {
80
+ const wrapper = await mount(AdvancedSearch, {
81
+ props: {
82
+ organisationId: 'test-org-id',
83
+ isEmission: false,
84
+ includeHidden: true
85
+ },
86
+ beforeMount: async () => {
87
+ const authStore = useAuthStore();
88
+ authStore.$patch({
89
+ authOrgaId: 'test-org-id',
90
+ authRole: ['PODCAST_CRUD'],
91
+ authOrganisation: {
92
+ id: 'test-org-id',
93
+ name: 'Test Organisation',
94
+ imageUrl: '',
95
+ attributes: {}
96
+ }
97
+ });
98
+
99
+ const generalStore = useGeneralStore();
100
+ generalStore.$patch({
101
+ platformEducation: false
102
+ });
103
+
104
+ // Set isPodcastmaker to true
105
+ state.generalParameters.podcastmaker = true;
106
+ }
107
+ });
108
+
109
+ // Access the component's computed property
110
+ const vm = wrapper.vm as any;
111
+
112
+ // When isPodcastmaker is true, isSelectValidity should be false
113
+ // (because the condition includes !isPodcastmaker.value)
114
+ expect(vm.isSelectValidity).toBe(false);
115
+ });
116
+
117
+ it('should correctly evaluate isSelectValidity when isEmission is true', async () => {
118
+ const wrapper = await mount(AdvancedSearch, {
119
+ props: {
120
+ organisationId: 'test-org-id',
121
+ isEmission: true, // This should make isSelectValidity false
122
+ includeHidden: true
123
+ },
124
+ beforeMount: async () => {
125
+ const authStore = useAuthStore();
126
+ authStore.$patch({
127
+ authOrgaId: 'test-org-id',
128
+ authRole: ['PODCAST_CRUD'],
129
+ authOrganisation: {
130
+ id: 'test-org-id',
131
+ name: 'Test Organisation',
132
+ imageUrl: '',
133
+ attributes: {}
134
+ }
135
+ });
136
+
137
+ const generalStore = useGeneralStore();
138
+ generalStore.$patch({
139
+ platformEducation: false
140
+ });
141
+
142
+ state.generalParameters.podcastmaker = false;
143
+ }
144
+ });
145
+
146
+ const vm = wrapper.vm as any;
147
+
148
+ // When isEmission is true, isSelectValidity should be false
149
+ expect(vm.isSelectValidity).toBe(false);
150
+ });
151
+
152
+ it('should correctly evaluate isSelectValidity when includeHidden is false', async () => {
153
+ const wrapper = await mount(AdvancedSearch, {
154
+ props: {
155
+ organisationId: 'test-org-id',
156
+ isEmission: false,
157
+ includeHidden: false // This should make isSelectValidity false
158
+ },
159
+ beforeMount: async () => {
160
+ const authStore = useAuthStore();
161
+ authStore.$patch({
162
+ authOrgaId: 'test-org-id',
163
+ authRole: ['PODCAST_CRUD'],
164
+ authOrganisation: {
165
+ id: 'test-org-id',
166
+ name: 'Test Organisation',
167
+ imageUrl: '',
168
+ attributes: {}
169
+ }
170
+ });
171
+
172
+ const generalStore = useGeneralStore();
173
+ generalStore.$patch({
174
+ platformEducation: false
175
+ });
176
+
177
+ state.generalParameters.podcastmaker = false;
178
+ }
179
+ });
180
+
181
+ const vm = wrapper.vm as any;
182
+
183
+ // When includeHidden is false, isSelectValidity should be false
184
+ expect(vm.isSelectValidity).toBe(false);
185
+ });
186
+ });
@@ -0,0 +1,58 @@
1
+ import '@tests/mocks/useRouter';
2
+ import '@tests/mocks/i18n';
3
+
4
+ import SharePlayer from '@/components/display/sharing/SharePlayer.vue';
5
+ import { mount as testMount } from '@tests/utils';
6
+ import { describe, expect, it, vi } from 'vitest';
7
+ import { useApiStore } from '@/stores/ApiStore';
8
+ import { useSaveFetchStore } from '@/stores/SaveFetchStore';
9
+
10
+ describe('SharePlayer', () => {
11
+ it('renders iframe with frameborder="0"', async () => {
12
+ const wrapper = await testMount(SharePlayer, {
13
+ props: {
14
+ podcast: {
15
+ podcastId: 123,
16
+ organisation: { id: 'org1' },
17
+ availability: { visibility: true }
18
+ },
19
+ organisationId: 'org1',
20
+ notExclusive: true
21
+ },
22
+ beforeMount: async () => {
23
+ const apiStore = useApiStore();
24
+ apiStore.miniplayerUrl = 'https://test.com/';
25
+
26
+ const saveFetchStore = useSaveFetchStore();
27
+ vi.spyOn(saveFetchStore, 'getOrgaAttributes').mockResolvedValue({});
28
+ }
29
+ });
30
+
31
+ const iframe = wrapper.find('#miniplayerIframe');
32
+ expect(iframe.attributes('frameborder')).toBe('0');
33
+ });
34
+
35
+ it('includes frameborder="0" in generated iframe code', async () => {
36
+ const wrapper = await testMount(SharePlayer, {
37
+ props: {
38
+ podcast: {
39
+ podcastId: 123,
40
+ organisation: { id: 'org1' },
41
+ availability: { visibility: true }
42
+ },
43
+ organisationId: 'org1',
44
+ notExclusive: true
45
+ },
46
+ beforeMount: async () => {
47
+ const apiStore = useApiStore();
48
+ apiStore.miniplayerUrl = 'https://test.com/';
49
+
50
+ const saveFetchStore = useSaveFetchStore();
51
+ vi.spyOn(saveFetchStore, 'getOrgaAttributes').mockResolvedValue({});
52
+ }
53
+ });
54
+
55
+ const iFrameCode = (wrapper.vm as any).iFrame;
56
+ expect(iFrameCode).toContain('frameborder="0"');
57
+ });
58
+ });
@@ -0,0 +1,54 @@
1
+ import '@tests/mocks/useRouter';
2
+ import '@tests/mocks/i18n';
3
+
4
+ import SharePlayerRadio from '@/components/display/sharing/SharePlayerRadio.vue';
5
+ import { mount as testMount } from '@tests/utils';
6
+ import { describe, expect, it, vi } from 'vitest';
7
+ import { useApiStore } from '@/stores/ApiStore';
8
+ import { useSaveFetchStore } from '@/stores/SaveFetchStore';
9
+
10
+ describe('SharePlayerRadio', () => {
11
+ it('renders iframe with frameborder="0"', async () => {
12
+ const wrapper = await testMount(SharePlayerRadio, {
13
+ props: {
14
+ canal: {
15
+ id: 'radio1',
16
+ organisationId: 'org1'
17
+ },
18
+ organisationId: 'org1'
19
+ },
20
+ beforeMount: async () => {
21
+ const apiStore = useApiStore();
22
+ apiStore.miniplayerUrl = 'https://test.com/';
23
+
24
+ const saveFetchStore = useSaveFetchStore();
25
+ vi.spyOn(saveFetchStore, 'getOrgaAttributes').mockResolvedValue({});
26
+ }
27
+ });
28
+
29
+ const iframe = wrapper.find('#miniplayerIframeRadio');
30
+ expect(iframe.attributes('frameborder')).toBe('0');
31
+ });
32
+
33
+ it('includes frameborder="0" in generated iframe code', async () => {
34
+ const wrapper = await testMount(SharePlayerRadio, {
35
+ props: {
36
+ canal: {
37
+ id: 'radio1',
38
+ organisationId: 'org1'
39
+ },
40
+ organisationId: 'org1'
41
+ },
42
+ beforeMount: async () => {
43
+ const apiStore = useApiStore();
44
+ apiStore.miniplayerUrl = 'https://test.com/';
45
+
46
+ const saveFetchStore = useSaveFetchStore();
47
+ vi.spyOn(saveFetchStore, 'getOrgaAttributes').mockResolvedValue({});
48
+ }
49
+ });
50
+
51
+ const iFrameCode = (wrapper.vm as any).iFrame;
52
+ expect(iFrameCode).toContain('frameborder="0"');
53
+ });
54
+ });