Skip to content

Commit 0a5b0b9

Browse files
committed
fix render has-links toast detail across payload shapes
1 parent 611be1c commit 0a5b0b9

3 files changed

Lines changed: 110 additions & 27 deletions

File tree

frontend/src/app.vue

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,32 +39,27 @@
3939

4040
<div class="p-toast-detail">
4141
<div>
42-
{{ (slotProps.message.detail as ToastHasLinksGroupDetail).message }}
42+
{{ getToastHasLinksDetailMessage(slotProps.message.detail) }}
4343
</div>
4444
<div
45-
v-if="
46-
(slotProps.message.detail as ToastHasLinksGroupDetail).button
47-
?.action
48-
"
45+
v-if="hasToastHasLinksButtonAction(slotProps.message.detail)"
4946
class="mt-2"
5047
>
5148
<Button
5249
size="small"
5350
:label="
54-
(slotProps.message.detail as ToastHasLinksGroupDetail).button
51+
asToastHasLinksGroupDetail(slotProps.message.detail)?.button
5552
?.text
5653
"
5754
@click="
58-
(
59-
slotProps.message.detail as ToastHasLinksGroupDetail
60-
).button?.action?.()
55+
asToastHasLinksGroupDetail(
56+
slotProps.message.detail,
57+
)?.button?.action?.()
6158
"
6259
></Button>
6360
</div>
6461
<div
65-
v-else-if="
66-
(slotProps.message.detail as ToastHasLinksGroupDetail).link
67-
"
62+
v-else-if="hasToastHasLinksLink(slotProps.message.detail)"
6863
class="mt-2"
6964
>
7065
<Button
@@ -73,11 +68,10 @@
7368
as="a"
7469
variant="link"
7570
:label="
76-
(slotProps.message.detail as ToastHasLinksGroupDetail).link
77-
?.text
71+
asToastHasLinksGroupDetail(slotProps.message.detail)?.link?.text
7872
"
7973
:href="
80-
(slotProps.message.detail as ToastHasLinksGroupDetail).link?.url
74+
asToastHasLinksGroupDetail(slotProps.message.detail)?.link?.url
8175
"
8276
target="_blank"
8377
rel="noopener noreferrer"
@@ -98,21 +92,15 @@
9892
</template>
9993

10094
<script setup lang="ts">
101-
type ToastHasLinksGroupDetail = {
102-
message: string;
103-
link?: {
104-
text: string;
105-
url: string;
106-
};
107-
button?: {
108-
text: string;
109-
action?: () => void;
110-
};
111-
};
112-
11395
import AddSourceImap from '@/components/mining/stepper-panels/source/AddSourceImap.vue';
11496
import PassiveMiningDialog from '@/components/mining/PassiveMiningDialog.vue';
11597
import type { MiningSource } from '@/types/mining';
98+
import {
99+
asToastHasLinksGroupDetail,
100+
getToastHasLinksDetailMessage,
101+
hasToastHasLinksButtonAction,
102+
hasToastHasLinksLink,
103+
} from '@/utils/toast';
116104
import { useIdle } from '@vueuse/core';
117105
import { reloadNuxtApp } from 'nuxt/app';
118106
import Normalizer from '~/utils/normalizer';

frontend/src/tests/unit/mining-toast.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { describe, expect, it } from 'vitest';
22

33
import { createStartMiningToastPayload } from '@/utils/extras';
4+
import {
5+
getToastHasLinksDetailMessage,
6+
hasToastHasLinksButtonAction,
7+
hasToastHasLinksLink,
8+
} from '@/utils/toast';
49

510
describe('createStartMiningToastPayload', () => {
611
const translations: Record<string, string> = {
@@ -48,3 +53,40 @@ describe('createStartMiningToastPayload', () => {
4853
});
4954
});
5055
});
56+
57+
describe('toast has-links detail helpers', () => {
58+
it('returns message when detail is a string', () => {
59+
expect(getToastHasLinksDetailMessage('Simple detail')).toBe(
60+
'Simple detail',
61+
);
62+
});
63+
64+
it('returns message when detail is an object', () => {
65+
expect(
66+
getToastHasLinksDetailMessage({
67+
message: 'Object detail',
68+
}),
69+
).toBe('Object detail');
70+
});
71+
72+
it('returns empty string for unsupported detail values', () => {
73+
expect(getToastHasLinksDetailMessage(undefined)).toBe('');
74+
expect(getToastHasLinksDetailMessage(null)).toBe('');
75+
});
76+
77+
it('detects object link and button action safely', () => {
78+
const withAction = {
79+
message: 'x',
80+
button: { text: 'Retry', action: () => true },
81+
};
82+
const withLink = {
83+
message: 'x',
84+
link: { text: 'More', url: 'https://example.com' },
85+
};
86+
87+
expect(hasToastHasLinksButtonAction(withAction)).toBe(true);
88+
expect(hasToastHasLinksButtonAction(withLink)).toBe(false);
89+
expect(hasToastHasLinksLink(withLink)).toBe(true);
90+
expect(hasToastHasLinksLink('plain detail')).toBe(false);
91+
});
92+
});

frontend/src/utils/toast.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export type ToastHasLinksGroupDetail = {
2+
message: string;
3+
link?: {
4+
text: string;
5+
url: string;
6+
};
7+
button?: {
8+
text: string;
9+
action?: () => void;
10+
};
11+
};
12+
13+
function isToastHasLinksGroupDetail(
14+
detail: unknown,
15+
): detail is ToastHasLinksGroupDetail {
16+
return (
17+
typeof detail === 'object' &&
18+
detail !== null &&
19+
'message' in detail &&
20+
typeof detail.message === 'string'
21+
);
22+
}
23+
24+
export function getToastHasLinksDetailMessage(detail: unknown) {
25+
if (typeof detail === 'string') {
26+
return detail;
27+
}
28+
29+
if (isToastHasLinksGroupDetail(detail)) {
30+
return detail.message;
31+
}
32+
33+
return '';
34+
}
35+
36+
export function hasToastHasLinksButtonAction(detail: unknown) {
37+
return Boolean(
38+
isToastHasLinksGroupDetail(detail) &&
39+
typeof detail.button?.action === 'function',
40+
);
41+
}
42+
43+
export function hasToastHasLinksLink(detail: unknown) {
44+
return Boolean(isToastHasLinksGroupDetail(detail) && detail.link?.url);
45+
}
46+
47+
export function asToastHasLinksGroupDetail(detail: unknown) {
48+
if (isToastHasLinksGroupDetail(detail)) {
49+
return detail;
50+
}
51+
52+
return undefined;
53+
}

0 commit comments

Comments
 (0)