Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Close the release announcement when a dialog is opened (#12559)
Browse files Browse the repository at this point in the history
* Fire `ModalManagerEvent.Closed` when a dialog is closed

* Listen to modal events in the RA

* Fix first RA test
  • Loading branch information
florianduros authored May 29, 2024
1 parent 17ab522 commit 679b170
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 5 deletions.
11 changes: 11 additions & 0 deletions src/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ interface IOptions<C extends ComponentType> {

export enum ModalManagerEvent {
Opened = "opened",
Closed = "closed",
}

type HandlerMap = {
[ModalManagerEvent.Opened]: () => void;
[ModalManagerEvent.Closed]: () => void;
};

export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMap> {
Expand Down Expand Up @@ -232,6 +234,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
}

this.reRender();
this.emitClosed();
},
deferred.promise,
];
Expand Down Expand Up @@ -328,6 +331,14 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
}
}

/**
* Emit the closed event
* @private
*/
private emitClosed(): void {
this.emit(ModalManagerEvent.Closed);
}

private onBackgroundClick = (): void => {
const modal = this.getCurrentModal();
if (!modal) {
Expand Down
21 changes: 19 additions & 2 deletions src/hooks/useIsReleaseAnnouncementOpen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,34 @@
* /
*/

import { useTypedEventEmitterState } from "./useEventEmitter";
import { useState } from "react";

import { useTypedEventEmitter, useTypedEventEmitterState } from "./useEventEmitter";
import { Feature, ReleaseAnnouncementStore } from "../stores/ReleaseAnnouncementStore";
import Modal, { ModalManagerEvent } from "../Modal";

/**
* Hook to return true if a modal is opened
*/
function useModalOpened(): boolean {
const [opened, setOpened] = useState(false);
useTypedEventEmitter(Modal, ModalManagerEvent.Opened, () => setOpened(true));
// Modal can be stacked, we need to check if all dialogs are closed
useTypedEventEmitter(Modal, ModalManagerEvent.Closed, () => !Modal.hasDialogs() && setOpened(false));
return opened;
}

/**
* Return true if the release announcement of the given feature is enabled
* @param feature
*/
export function useIsReleaseAnnouncementOpen(feature: Feature): boolean {
return useTypedEventEmitterState(
const modalOpened = useModalOpened();
const releaseAnnouncementOpened = useTypedEventEmitterState(
ReleaseAnnouncementStore.instance,
"releaseAnnouncementChanged",
() => ReleaseAnnouncementStore.instance.getReleaseAnnouncement() === feature,
);

return !modalOpened && releaseAnnouncementOpened;
}
4 changes: 3 additions & 1 deletion src/stores/ReleaseAnnouncementStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { cloneDeep } from "lodash";

import SettingsStore from "../settings/SettingsStore";
import { SettingLevel } from "../settings/SettingLevel";
Expand Down Expand Up @@ -90,7 +91,8 @@ export class ReleaseAnnouncementStore extends TypedEventEmitter<ReleaseAnnouncem
* @private
*/
private getViewedReleaseAnnouncements(): StoredSettings {
return SettingsStore.getValue<StoredSettings>("releaseAnnouncementData");
// Clone the settings to avoid to mutate the internal stored value in the SettingsStore
return cloneDeep(SettingsStore.getValue<StoredSettings>("releaseAnnouncementData"));
}

/**
Expand Down
32 changes: 30 additions & 2 deletions test/components/structures/ReleaseAnnouncement-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@
*/

import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import { act, render, screen, waitFor } from "@testing-library/react";

import { ReleaseAnnouncement } from "../../../src/components/structures/ReleaseAnnouncement";
import Modal, { ModalManagerEvent } from "../../../src/Modal";
import { ReleaseAnnouncementStore } from "../../../src/stores/ReleaseAnnouncementStore";

describe("ReleaseAnnouncement", () => {
beforeEach(async () => {
// Reset the singleton instance of the ReleaseAnnouncementStore
// @ts-ignore
ReleaseAnnouncementStore.internalInstance = new ReleaseAnnouncementStore();
});

function renderReleaseAnnouncement() {
return render(
<ReleaseAnnouncement
Expand All @@ -39,10 +47,30 @@ describe("ReleaseAnnouncement", () => {
renderReleaseAnnouncement();

// The release announcement is displayed
expect(screen.queryByRole("dialog", { name: "header" })).toBeDefined();
expect(screen.queryByRole("dialog", { name: "header" })).toBeVisible();
// Click on the close button in the release announcement
screen.getByRole("button", { name: "close" }).click();
// The release announcement should be hidden after the close button is clicked
await waitFor(() => expect(screen.queryByRole("dialog", { name: "header" })).toBeNull());
});

test("when a dialog is opened, the release announcement should not be displayed", async () => {
renderReleaseAnnouncement();
// The release announcement is displayed
expect(screen.queryByRole("dialog", { name: "header" })).toBeVisible();

// Open a dialog
act(() => {
Modal.emit(ModalManagerEvent.Opened);
});
// The release announcement should be hidden after the dialog is opened
expect(screen.queryByRole("dialog", { name: "header" })).toBeNull();

// Close the dialog
act(() => {
Modal.emit(ModalManagerEvent.Closed);
});
// The release announcement should be displayed after the dialog is closed
expect(screen.queryByRole("dialog", { name: "header" })).toBeVisible();
});
});

0 comments on commit 679b170

Please sign in to comment.