diff --git a/client/src/components/Notifications/Broadcasts/BroadcastsOverlay.test.ts b/client/src/components/Notifications/Broadcasts/BroadcastsOverlay.test.ts index 9c423edb6dda..c13a1d896741 100644 --- a/client/src/components/Notifications/Broadcasts/BroadcastsOverlay.test.ts +++ b/client/src/components/Notifications/Broadcasts/BroadcastsOverlay.test.ts @@ -9,15 +9,26 @@ import { type BroadcastNotification, useBroadcastsStore } from "@/stores/broadca const localVue = getLocalVue(true); const now = new Date(); -const inTwoMonths = new Date(now.setMonth(now.getMonth() + 2)); +const inTwoMonths = new Date(new Date(now).setMonth(now.getMonth() + 2)); -function generateBroadcastNotification(id: string): BroadcastNotification { +/** API date-time does not have timezone indicator and it's always UTC. */ +function toApiDate(date: Date): string { + return date.toISOString().replace("Z", ""); +} + +function generateBroadcastNotification( + id: string, + publicationTime?: Date, + expirationTime?: Date +): BroadcastNotification { + const publication_time = publicationTime ? toApiDate(publicationTime) : toApiDate(now); + const expiration_time = expirationTime ? toApiDate(expirationTime) : toApiDate(inTwoMonths); return { id: id, - create_time: now.toISOString(), - update_time: now.toISOString(), - publication_time: now.toISOString(), - expiration_time: inTwoMonths.toISOString(), + create_time: toApiDate(now), + update_time: toApiDate(now), + publication_time, + expiration_time, source: "testing", variant: "info", content: { @@ -78,4 +89,20 @@ describe("BroadcastsOverlay.vue", () => { expect(wrapper.findAll(".broadcast-message")).toHaveLength(1); expect(wrapper.find(".broadcast-message").text()).toContain("Test message 2"); }); + + it("should not render the broadcast when it has expired", async () => { + const expiredBroadcast = generateBroadcastNotification("expired", undefined, new Date(now)); + const wrapper = await mountBroadcastsOverlayWith([expiredBroadcast]); + + expect(wrapper.exists()).toBe(true); + expect(wrapper.html()).toBe(""); + }); + + it("should not render the broadcast when it has not been published yet", async () => { + const unpublishedBroadcast = generateBroadcastNotification("unpublished", new Date(inTwoMonths)); + const wrapper = await mountBroadcastsOverlayWith([unpublishedBroadcast]); + + expect(wrapper.exists()).toBe(true); + expect(wrapper.html()).toBe(""); + }); }); diff --git a/client/src/stores/broadcastsStore.ts b/client/src/stores/broadcastsStore.ts index 8395b5915218..d465aeb81f8f 100644 --- a/client/src/stores/broadcastsStore.ts +++ b/client/src/stores/broadcastsStore.ts @@ -16,7 +16,7 @@ export const useBroadcastsStore = defineStore( const dismissedBroadcasts = ref<{ [key: string]: Expirable }>({}); const activeBroadcasts = computed(() => { - return broadcasts.value.filter((b) => !dismissedBroadcasts.value[b.id]); + return broadcasts.value.filter(isActive); }); async function loadBroadcasts() { @@ -40,6 +40,14 @@ export const useBroadcastsStore = defineStore( Vue.set(dismissedBroadcasts.value, broadcast.id, { expiration_time: broadcast.expiration_time }); } + function isActive(broadcast: BroadcastNotification) { + return ( + !dismissedBroadcasts.value[broadcast.id] && + !hasExpired(broadcast.expiration_time) && + hasBeenPublished(broadcast) + ); + } + function hasExpired(expirationTimeStr?: string) { if (!expirationTimeStr) { return false; @@ -49,6 +57,12 @@ export const useBroadcastsStore = defineStore( return now > expirationTime; } + function hasBeenPublished(broadcast: BroadcastNotification) { + const publicationTime = new Date(`${broadcast.publication_time}Z`); + const now = new Date(); + return now >= publicationTime; + } + function clearExpiredDismissedBroadcasts() { for (const key in dismissedBroadcasts.value) { if (hasExpired(dismissedBroadcasts.value[key]?.expiration_time)) {