@saooti/octopus-sdk 41.8.1 → 41.8.3

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.
@@ -9,7 +9,8 @@
9
9
  "Bash(python3:*)",
10
10
  "Bash(node --version:*)",
11
11
  "Bash(npm:*)",
12
- "Bash(npx vitest:*)"
12
+ "Bash(npx vitest:*)",
13
+ "Bash(./node_modules/.bin/vitest run:*)"
13
14
  ]
14
15
  }
15
16
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 41.8.3 (23/03/2026)
4
+
5
+ **Fix**
6
+
7
+ - **14083** - Correction affichage saisons sur page émission s'il y a un creux
8
+ (par exemple saisons 2, 3, et pas de 1)
9
+ - **14373** - Correction taille icone Radio France
10
+
11
+ ## 41.8.2 (23/03/2026)
12
+
13
+ **Fix**
14
+
15
+ - **14083** - Ajustements système de saison
16
+ - Le bon dernier épisode est récupéré sur les pages d'émissions
17
+ - Les saisons sont correctement énumérées sur les pages d'émissions
18
+
3
19
  ## 41.8.1 (20/03/2026)
4
20
 
5
21
  **Fix**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "41.8.1",
3
+ "version": "41.8.3",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -36,8 +36,17 @@ export const useSeasonsManagement = () => {
36
36
  }
37
37
  }
38
38
 
39
+ /**
40
+ * Returns the highest season number for the given emission, or -Infinity if none
41
+ * @param emission The emission to check
42
+ */
43
+ function getMaxSeason(emission: Emission | undefined): number {
44
+ return Math.max(...(emission?.seasons ?? []));
45
+ }
46
+
39
47
  return {
40
48
  areSeasonsEnabled,
41
- formatSeason
49
+ formatSeason,
50
+ getMaxSeason,
42
51
  }
43
52
  }
@@ -39,13 +39,13 @@
39
39
  <ClassicNav
40
40
  v-else
41
41
  v-model:active-tab="activeSeasonTab"
42
- :tab-number="seasons.length"
42
+ :tab-number="emission.seasons.length"
43
43
  >
44
- <template v-for="season in seasons" #[tabNameSlot(season)]>
44
+ <template v-for="(season, i) in emission.seasons" #[tabNameSlot(i)]>
45
45
  {{ $t('Podcast - Season N', { season }) }}
46
46
  </template>
47
47
 
48
- <template v-for="season in seasons" #[tabContentSlot(season)] :key="season">
48
+ <template v-for="(season, i) in emission.seasons" #[tabContentSlot(i)] :key="season">
49
49
  <PodcastList
50
50
  class="flex-grow-1"
51
51
  :first="dfirst"
@@ -123,7 +123,7 @@ const iabId : Ref<number | undefined>= ref(undefined);
123
123
 
124
124
  //Composables
125
125
  const { t } = useI18n();
126
- const { areSeasonsEnabled } = useSeasonsManagement();
126
+ const { areSeasonsEnabled, getMaxSeason } = useSeasonsManagement();
127
127
 
128
128
  //Computed
129
129
  const titleFilter = computed(() => {
@@ -134,10 +134,10 @@ const titleFilter = computed(() => {
134
134
  const query = computed(() => searchPattern.value.length > 3 ? searchPattern.value : "");
135
135
 
136
136
  const showSeasons = computed(() => {
137
- return props.emission !== undefined && areSeasonsEnabled(props.emission) && props.emission.seasonCount > 0;
137
+ return props.emission !== undefined && areSeasonsEnabled(props.emission) && (props.emission.seasons?.length ?? 0) > 0;
138
138
  });
139
139
 
140
- const activeSeasonTab = ref(showSeasons.value ? (props.emission?.seasonCount ?? 1) - 1 : 0);
140
+ const activeSeasonTab = ref(showSeasons.value ? props.emission.seasons?.indexOf(getMaxSeason(props.emission)) ?? 0 : 0);
141
141
 
142
142
  const sort = computed((): PodcastSort => {
143
143
  if(showSeasons.value === true) {
@@ -149,16 +149,6 @@ const sort = computed((): PodcastSort => {
149
149
  }
150
150
  });
151
151
 
152
- const seasons = computed((): Array<number> => {
153
- const ary: Array<number> = [];
154
- if (showSeasons.value === true) {
155
- for (let i = 1; i <= props.emission.seasonCount; i++) {
156
- ary.push(i);
157
- }
158
- }
159
- return ary;
160
- });
161
-
162
152
  //Watch
163
153
  watch(()=>props.reload, () => {
164
154
  reloadList.value = !reloadList.value;
@@ -176,11 +166,11 @@ function fetch(podcasts: Array<Podcast>, season?: number): void {
176
166
  }
177
167
 
178
168
  /** Name of the slot for the tab's title */
179
- function tabNameSlot(season: number): string {
180
- return `${season - 1}`;
169
+ function tabNameSlot(index: number): string {
170
+ return `${index}`;
181
171
  }
182
172
  /** Name of the slot for the tab's content */
183
- function tabContentSlot(season: number): string {
184
- return `tab${season - 1}`;
173
+ function tabContentSlot(index: number): string {
174
+ return `tab${index}`;
185
175
  }
186
176
  </script>
@@ -15,25 +15,25 @@
15
15
  fill-rule="evenodd"
16
16
  clip-rule="evenodd"
17
17
  d="M37.0892 33.8774C37.0892 35.7523 36.5961 37.5174 35.7265 39.0643H50.1862C50.5743 37.3938 50.7809 35.6586 50.7809 33.8774C50.7809 23.6473 44.0269 14.9123 34.5121 11.4499V0.596397H21.9553V23.3202C23.0498 22.9984 24.2131 22.8248 25.419 22.8248C31.8644 22.8248 37.0892 27.7733 37.0892 33.8774Z"
18
- fill="url(#paint0_linear_75_103)"
18
+ :fill="'url(#' + uid + 'p0)'"
19
19
  />
20
20
  <path
21
21
  fill-rule="evenodd"
22
22
  clip-rule="evenodd"
23
23
  d="M29.5724 44.3362C28.2958 44.7861 26.9145 45.0321 25.4717 45.0321C18.9987 45.0321 13.7507 40.0835 13.7507 33.9785C13.7507 32.2762 14.1593 30.664 14.8881 29.2236H0.500687C0.173332 30.7608 0 32.3507 0 33.9785C0 47.2455 11.404 58 25.4717 58C26.8678 58 28.2374 57.8924 29.5724 57.6889V44.3362Z"
24
- fill="url(#paint1_linear_75_103)"
24
+ :fill="'url(#' + uid + 'p1)'"
25
25
  />
26
26
  <path
27
27
  fill-rule="evenodd"
28
28
  clip-rule="evenodd"
29
29
  d="M37.1502 33.909C37.1502 35.7895 36.6555 37.5598 35.7831 39.1113H50.2891C50.6784 37.4358 50.8857 35.6954 50.8857 33.909C50.8857 23.6484 44.1101 14.8873 34.5649 11.4146V0.528656H21.9678V23.3202C23.0658 22.9975 24.2329 22.8234 25.4426 22.8234C31.9087 22.8234 37.1502 27.7867 37.1502 33.909Z"
30
- fill="url(#paint2_linear_75_103)"
30
+ :fill="'url(#' + uid + 'p2)'"
31
31
  />
32
32
  <path
33
33
  fill-rule="evenodd"
34
34
  clip-rule="evenodd"
35
35
  d="M29.5386 44.2967C28.2634 44.7479 26.8837 44.9945 25.4426 44.9945C18.977 44.9945 13.735 40.0316 13.735 33.9089C13.735 32.2017 14.1431 30.5848 14.8711 29.1403H0.500114C0.173133 30.682 0 32.2764 0 33.9089C0 47.2143 11.3909 58 25.4426 58C26.8371 58 28.2051 57.8921 29.5386 57.688V44.2967Z"
36
- fill="url(#paint3_linear_75_103)"
36
+ :fill="'url(#' + uid + 'p3)'"
37
37
  />
38
38
  <path
39
39
  fill-rule="evenodd"
@@ -98,7 +98,7 @@
98
98
  />
99
99
  <defs>
100
100
  <linearGradient
101
- id="paint0_linear_75_103"
101
+ :id="uid + 'p0'"
102
102
  x1="37.483"
103
103
  y1="3.20798"
104
104
  x2="21.3118"
@@ -112,7 +112,7 @@
112
112
  <stop offset="1" stop-color="#FD0323" />
113
113
  </linearGradient>
114
114
  <linearGradient
115
- id="paint1_linear_75_103"
115
+ :id="uid + 'p1'"
116
116
  x1="8.26123"
117
117
  y1="34.1806"
118
118
  x2="25.2003"
@@ -124,7 +124,7 @@
124
124
  <stop offset="1" stop-color="#FF0101" />
125
125
  </linearGradient>
126
126
  <linearGradient
127
- id="paint2_linear_75_103"
127
+ :id="uid + 'p2'"
128
128
  x1="37.5457"
129
129
  y1="3.14803"
130
130
  x2="21.3224"
@@ -138,7 +138,7 @@
138
138
  <stop offset="1" stop-color="#FD0323" />
139
139
  </linearGradient>
140
140
  <linearGradient
141
- id="paint3_linear_75_103"
141
+ :id="uid + 'p3'"
142
142
  x1="8.25178"
143
143
  y1="34.035"
144
144
  x2="25.2587"
@@ -155,8 +155,12 @@
155
155
  </template>
156
156
 
157
157
  <script setup lang="ts">
158
+ import { useId } from 'vue'
159
+
160
+ const uid = useId()
161
+
158
162
  defineProps({
159
- size: { default: 50, type: Number },
163
+ size: { default: 24, type: Number },
160
164
  title: { default: undefined, type: String },
161
165
  })
162
166
  </script>
@@ -204,7 +204,7 @@ const {
204
204
  paginateFirst,
205
205
  isInit
206
206
  } = useSimplePageParam(props, true);
207
- const { areSeasonsEnabled, formatSeason } = useSeasonsManagement();
207
+ const { areSeasonsEnabled, formatSeason, getMaxSeason } = useSeasonsManagement();
208
208
 
209
209
 
210
210
  //Computed
@@ -259,16 +259,22 @@ async function getEmissionDetails(): Promise<void> {
259
259
  initError();
260
260
  }
261
261
  }
262
+
262
263
  function podcastsFetched(podcasts: Array<Podcast>, season: number|undefined) {
263
- if (season !== undefined && season !== emission.value?.seasonCount) {
264
- return;
265
- }
264
+ const isReadyAndVisible = (p: Podcast) => "READY" === p.processingStatus && p.availability.visibility;
266
265
 
267
- for (const podcast of podcasts) {
268
- if ("READY" === podcast.processingStatus && podcast.availability.visibility) {
269
- lastPodcast.value = podcast;
266
+ if (areSeasonsEnabled(emission.value)) {
267
+ // Ignore results that are not from the last season
268
+ const maxSeason = getMaxSeason(emission.value);
269
+ if (season !== undefined && season !== maxSeason) {
270
270
  return;
271
271
  }
272
+ // If seasons are enabled, take last element
273
+ lastPodcast.value = podcasts.findLast(isReadyAndVisible);
274
+ } else {
275
+ // If seasons are disabled, we have a standard date desc sort, so we take
276
+ // first element
277
+ lastPodcast.value = podcasts.find(isReadyAndVisible);
272
278
  }
273
279
  }
274
280
 
@@ -54,8 +54,8 @@ export interface Emission {
54
54
  groupIds?: Array<number>
55
55
  /** Seasons configuration */
56
56
  seasonMode: SeasonMode;
57
- /** Number of seasons on this emission */
58
- seasonCount: number;
57
+ /** Seasons defined on emission */
58
+ seasons?: Array<number>;
59
59
  /** Indicates that the emission has explicit content */
60
60
  explicit?: boolean;
61
61
  }
@@ -72,7 +72,6 @@ export function emptyEmissionData(orga?: Organisation): Emission {
72
72
  rubriqueIds: [],
73
73
  monetisable: "UNDEFINED",
74
74
  limits: {},
75
- seasonMode: SeasonMode.NO_SEASON,
76
- seasonCount: 0
75
+ seasonMode: SeasonMode.NO_SEASON
77
76
  };
78
77
  }
@@ -23,7 +23,7 @@ import { emissionApi } from '@/api/emissionApi';
23
23
  const publicOrga = { id: 'org-1', name: 'Test', imageUrl: '', privacy: 'PUBLIC' };
24
24
 
25
25
  function makeEmission(seasonMode: SeasonMode) {
26
- return { ...emptyEmissionData(), seasonMode, seasonCount: 2, orga: publicOrga };
26
+ return { ...emptyEmissionData(), seasonMode, seasons: [1, 2], orga: publicOrga };
27
27
  }
28
28
 
29
29
  function makeReadyPodcast(seasonMode: SeasonMode = SeasonMode.NO_SEASON) {
@@ -40,8 +40,18 @@ async function mountPage(seasonMode: SeasonMode) {
40
40
  return mount(EmissionPage, { shallow: true, props: { emissionId: 1 } });
41
41
  }
42
42
 
43
- async function triggerFetch(wrapper: VueWrapper, seasonMode: SeasonMode, season?: number) {
44
- await wrapper.findComponent({ name: 'PodcastFilterList' }).vm.$emit('fetch', [makeReadyPodcast(seasonMode)], season);
43
+ async function mountWithSeasons(seasons: number[]) {
44
+ vi.mocked(emissionApi.get).mockResolvedValue({
45
+ ...emptyEmissionData(),
46
+ seasonMode: SeasonMode.SEASON_WITH_PODCAST_NUMBERING,
47
+ seasons,
48
+ orga: publicOrga,
49
+ });
50
+ return mount(EmissionPage, { shallow: true, props: { emissionId: 1 } });
51
+ }
52
+
53
+ async function triggerFetch(wrapper: VueWrapper, seasonMode: SeasonMode, season?: number, podcasts?: ReturnType<typeof makeReadyPodcast>[]) {
54
+ await wrapper.findComponent({ name: 'PodcastFilterList' }).vm.$emit('fetch', podcasts ?? [makeReadyPodcast(seasonMode)], season);
45
55
  await nextTick();
46
56
  }
47
57
 
@@ -70,7 +80,7 @@ describe('EmissionPage', () => {
70
80
  });
71
81
 
72
82
  describe('podcastsFetched season filtering', () => {
73
- // seasonCount is 2 in makeEmission
83
+ // seasons is [1, 2] in makeEmission, so max season is 2
74
84
  it.each([undefined, 2])('shows lastPodcast when season is %s', async (season) => {
75
85
  const wrapper = await mountPage(SeasonMode.SEASON_WITH_PODCAST_NUMBERING);
76
86
  await triggerFetch(wrapper, SeasonMode.SEASON_WITH_PODCAST_NUMBERING, season);
@@ -82,5 +92,49 @@ describe('EmissionPage', () => {
82
92
  await triggerFetch(wrapper, SeasonMode.SEASON_WITH_PODCAST_NUMBERING, 1);
83
93
  expect(wrapper.text()).not.toContain('Listen to the latest episode');
84
94
  });
95
+
96
+ it('accepts the highest season in the seasons array', async () => {
97
+ const wrapper = await mountWithSeasons([1, 2, 3]);
98
+ await triggerFetch(wrapper, SeasonMode.SEASON_WITH_PODCAST_NUMBERING, 3);
99
+ expect(wrapper.text()).toContain('Listen to the latest episode');
100
+ });
101
+
102
+ it('ignores a season below the max in the seasons array', async () => {
103
+ const wrapper = await mountWithSeasons([1, 2, 3]);
104
+ await triggerFetch(wrapper, SeasonMode.SEASON_WITH_PODCAST_NUMBERING, 2);
105
+ expect(wrapper.text()).not.toContain('Listen to the latest episode');
106
+ });
107
+ });
108
+
109
+ describe('podcastsFetched podcast selection', () => {
110
+ function makePodcast(seasonMode: SeasonMode, episodeNumber: number, { ready = true, visible = true } = {}) {
111
+ const podcast = makeReadyPodcast(seasonMode);
112
+ podcast.seasonEpisodeNumber = episodeNumber;
113
+ if (!ready) podcast.processingStatus = PodcastProcessingStatus.Processing;
114
+ if (!visible) podcast.availability.visibility = false;
115
+ return podcast;
116
+ }
117
+
118
+ it('with seasons enabled, selects the last ready visible podcast', async () => {
119
+ const wrapper = await mountPage(SeasonMode.SEASON_WITH_PODCAST_NUMBERING);
120
+ const podcasts = [
121
+ makePodcast(SeasonMode.SEASON_WITH_PODCAST_NUMBERING, 1),
122
+ makePodcast(SeasonMode.SEASON_WITH_PODCAST_NUMBERING, 5),
123
+ ];
124
+ await triggerFetch(wrapper, SeasonMode.SEASON_WITH_PODCAST_NUMBERING, undefined, podcasts);
125
+ expect(wrapper.text()).toContain('S1·E5');
126
+ });
127
+
128
+ it.each([SeasonMode.SEASON_WITH_PODCAST_NUMBERING, SeasonMode.NO_SEASON])('skips non-ready podcasts (%s)', async (seasonMode) => {
129
+ const wrapper = await mountPage(seasonMode);
130
+ await triggerFetch(wrapper, seasonMode, undefined, [makePodcast(seasonMode, 1, { ready: false })]);
131
+ expect(wrapper.text()).not.toContain('Listen to the latest episode');
132
+ });
133
+
134
+ it.each([SeasonMode.SEASON_WITH_PODCAST_NUMBERING, SeasonMode.NO_SEASON])('skips non-visible podcasts (%s)', async (seasonMode) => {
135
+ const wrapper = await mountPage(seasonMode);
136
+ await triggerFetch(wrapper, seasonMode, undefined, [makePodcast(seasonMode, 1, { visible: false })]);
137
+ expect(wrapper.text()).not.toContain('Listen to the latest episode');
138
+ });
85
139
  });
86
140
  });