@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.
- package/.claude/settings.local.json +2 -1
- package/CHANGELOG.md +16 -0
- package/package.json +1 -1
- package/src/components/composable/useSeasonsManagement.ts +10 -1
- package/src/components/display/podcasts/PodcastFilterList.vue +10 -20
- package/src/components/icons/RadioFranceIcon.vue +13 -9
- package/src/components/pages/EmissionPage.vue +13 -7
- package/src/stores/class/general/emission.ts +3 -4
- package/tests/components/pages/EmissionPage.spec.ts +58 -4
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
|
@@ -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(
|
|
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(
|
|
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.
|
|
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
|
|
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(
|
|
180
|
-
return `${
|
|
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(
|
|
184
|
-
return `tab${
|
|
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(#
|
|
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(#
|
|
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(#
|
|
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(#
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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:
|
|
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
|
-
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
264
|
+
const isReadyAndVisible = (p: Podcast) => "READY" === p.processingStatus && p.availability.visibility;
|
|
266
265
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
/**
|
|
58
|
-
|
|
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,
|
|
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
|
|
44
|
-
|
|
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
|
-
//
|
|
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
|
});
|