DEV: Convert dismiss modals to component-based API (#22262)

This PR converts the following modals:
- `dismiss-new`
- `dismiss-read`
- `dismiss-notification-confirmation`

to make use of the new component-based API

# Additional Changes
## Before
By default we display a warning modal when dismissing a notification however we bypass the warning modal for specific notification types when they are a 'low priority' type of notification (eg. likes). To do this we were overwriting `dismissWarningModal` on a given notification type component

```javascript
dismissWarningModal() {
  return null
}
```

but in the case we wanted to change the text within the modal we were calling `showModal` and then passing in the respective options all over again, putting the logic of rendering the modal in multiple places.

```javascript
dismissWarningModal() {
  const modalController = showModal("dismiss-notification-confirmation");
  modalController.set(
    "confirmationMessage",
    I18n.t("notifications.dismiss_confirmation.body.assigns", {
      count: this._unreadAssignedNotificationsCount,
    })
  );
  return modalController;
}
```
 

## After
I simplified this by adding an extensible `dismissConfirmationText` function that can be updated on a per component basis as that was the only option being overridden. 

eg

```javascript
get dismissConfirmationText() {
  return I18n.t("notifications.dismiss_confirmation.body.bookmarks", {
    count: this.#unreadBookmarkRemindersCount,
});
```

This saves us from importing the entire modal again and keeps the core logic in one place.

Instead of overwriting the `dismissWarningModal` function and returning `null` to bypass the confirmation modal, I added another extension point of `renderDismissConfirmation` (defaults to true) to _toggle_ whether we should display a confirmation when dismissing notifications.

eg

```javascript
get renderDismissConfirmation() {
  return false;
}
```

we utilize this in core for specific _low priority_ notification types. When you need the confirmation modal to be displayed no matter the case you can set `alwaysRenderDismissConfirmation` to `true`

```
get alwaysRenderDismissConfirmation(){
  return true
}
```

This can be useful when you want to render the confirmation modal on a custom notification type that is not deemed as _high priority_, leading to the confirmation modal never being rendered.

You can see this in use in [Discourse Assign](https://github.com/discourse/discourse-assign/pull/481)
This commit is contained in:
Isaac Janzen 2023-07-06 12:14:26 -05:00 committed by GitHub
parent c05e54e461
commit 8b80132f88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 242 additions and 241 deletions

View File

@ -0,0 +1,33 @@
<DModal
@closeModal={{@closeModal}}
@title={{i18n "topics.bulk.dismiss_new_modal.title"}}
>
<:body>
<p>
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.topics"
@checked={{@model.dismissTopics}}
@class="dismiss-topics"
/>
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.posts"
@checked={{@model.dismissPosts}}
@class="dismiss-posts"
/>
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.untrack"
@checked={{@model.untrack}}
@class="untrack"
/>
</p>
</:body>
<:footer>
<DButton
id="dismiss-read-confirm"
@action={{this.dismissed}}
@icon="check"
@label="topics.bulk.dismiss"
class="btn-primary"
/>
</:footer>
</DModal>

View File

@ -0,0 +1,10 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
export default class DismissNew extends Component {
@action
dismissed() {
this.args.model.dismissCallback();
this.args.closeModal();
}
}

View File

@ -0,0 +1,22 @@
<DModal
@headerClass="hidden"
class="dismiss-notification-confirmation"
@closeModal={{@closeModal}}
>
<:body>
{{@model.confirmationMessage}}
</:body>
<:footer>
<DButton
@icon="check"
class="btn-primary"
@action={{this.dismiss}}
@label="notifications.dismiss_confirmation.dismiss"
/>
<DButton
@action={{@closeModal}}
@label="notifications.dismiss_confirmation.cancel"
class="btn-default"
/>
</:footer>
</DModal>

View File

@ -0,0 +1,10 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
export default class DismissNotificationConfirmation extends Component {
@action
dismiss() {
this.args.model?.dismissNotifications?.();
this.args.closeModal();
}
}

View File

@ -0,0 +1,23 @@
<DModal
@closeModal={{@closeModal}}
@title={{i18n @model.title count=@model.count}}
class="dismiss-read-modal"
>
<:body>
<p>
<PreferenceCheckbox
@labelKey="topics.bulk.also_dismiss_topics"
@checked={{this.dismissTopics}}
/>
</p>
</:body>
<:footer>
<DButton
class="btn-primary"
@action={{route-action "dismissReadTopics" this.dismissTopics}}
@icon="check"
id="dismiss-read-confirm"
@label="topics.bulk.dismiss"
/>
</:footer>
</DModal>

View File

@ -1,15 +1,17 @@
import { action } from "@ember/object"; import { action } from "@ember/object";
import showModal from "discourse/lib/show-modal";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n"; import I18n from "I18n";
import Component from "@ember/component"; import Component from "@ember/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import DismissReadModal from "discourse/components/modal/dismiss-read";
export default Component.extend({ export default Component.extend({
tagName: "", tagName: "",
classNames: ["topic-dismiss-buttons"], classNames: ["topic-dismiss-buttons"],
currentUser: service(), currentUser: service(),
modal: service(),
position: null, position: null,
selectedTopics: null, selectedTopics: null,
model: null, model: null,
@ -63,13 +65,14 @@ export default Component.extend({
@action @action
dismissReadPosts() { dismissReadPosts() {
let dismissTitle = "topics.bulk.dismiss_read"; let dismissTitle = "topics.bulk.dismiss_read";
if (this.selectedTopics.length > 0) { if (this.selectedTopics.length) {
dismissTitle = "topics.bulk.dismiss_read_with_selected"; dismissTitle = "topics.bulk.dismiss_read_with_selected";
} }
showModal("dismiss-read", { this.modal.show(DismissReadModal, {
titleTranslated: I18n.t(dismissTitle, { model: {
title: dismissTitle,
count: this.selectedTopics.length, count: this.selectedTopics.length,
}), },
}); });
}, },
}); });

View File

@ -1,7 +1,6 @@
import UserMenuNotificationsList from "discourse/components/user-menu/notifications-list"; import UserMenuNotificationsList from "discourse/components/user-menu/notifications-list";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import Notification from "discourse/models/notification"; import Notification from "discourse/models/notification";
import showModal from "discourse/lib/show-modal";
import I18n from "I18n"; import I18n from "I18n";
import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item"; import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item";
import UserMenuBookmarkItem from "discourse/lib/user-menu/bookmark-item"; import UserMenuBookmarkItem from "discourse/lib/user-menu/bookmark-item";
@ -45,6 +44,12 @@ export default class UserMenuBookmarksList extends UserMenuNotificationsList {
return this.currentUser.get(key) || 0; return this.currentUser.get(key) || 0;
} }
get dismissConfirmationText() {
return I18n.t("notifications.dismiss_confirmation.body.bookmarks", {
count: this.#unreadBookmarkRemindersCount,
});
}
async fetchItems() { async fetchItems() {
const data = await ajax( const data = await ajax(
`/u/${this.currentUser.username}/user-menu-bookmarks` `/u/${this.currentUser.username}/user-menu-bookmarks`
@ -74,15 +79,4 @@ export default class UserMenuBookmarksList extends UserMenuNotificationsList {
return content; return content;
} }
dismissWarningModal() {
const modalController = showModal("dismiss-notification-confirmation");
modalController.set(
"confirmationMessage",
I18n.t("notifications.dismiss_confirmation.body.bookmarks", {
count: this.#unreadBookmarkRemindersCount,
})
);
return modalController;
}
} }

View File

@ -28,6 +28,10 @@ export default class UserMenuItemsList extends Component {
return "user-menu/items-list-empty-state"; return "user-menu/items-list-empty-state";
} }
get renderDismissConfirmation() {
return false;
}
async fetchItems() { async fetchItems() {
throw new Error( throw new Error(
`the fetchItems method must be implemented in ${this.constructor.name}` `the fetchItems method must be implemented in ${this.constructor.name}`
@ -38,10 +42,6 @@ export default class UserMenuItemsList extends Component {
await this.#load(); await this.#load();
} }
dismissWarningModal() {
return null;
}
async #load() { async #load() {
const cached = this.#getCachedItems(); const cached = this.#getCachedItems();
if (cached?.length) { if (cached?.length) {

View File

@ -5,8 +5,8 @@ export default class UserMenuLikesNotificationsList extends UserMenuNotification
return this.filterByTypes; return this.filterByTypes;
} }
dismissWarningModal() { get renderDismissConfirmation() {
return null; return false;
} }
get emptyStateComponent() { get emptyStateComponent() {

View File

@ -1,7 +1,6 @@
import UserMenuNotificationsList from "discourse/components/user-menu/notifications-list"; import UserMenuNotificationsList from "discourse/components/user-menu/notifications-list";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import Notification from "discourse/models/notification"; import Notification from "discourse/models/notification";
import showModal from "discourse/lib/show-modal";
import I18n from "I18n"; import I18n from "I18n";
import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item"; import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item";
import UserMenuMessageItem from "discourse/lib/user-menu/message-item"; import UserMenuMessageItem from "discourse/lib/user-menu/message-item";
@ -49,6 +48,12 @@ export default class UserMenuMessagesList extends UserMenuNotificationsList {
return this.currentUser.get(key) || 0; return this.currentUser.get(key) || 0;
} }
get dismissConfirmationText() {
return I18n.t("notifications.dismiss_confirmation.body.messages", {
count: this.#unreadMessagesNotifications,
});
}
async fetchItems() { async fetchItems() {
const data = await ajax( const data = await ajax(
`/u/${this.currentUser.username}/user-menu-private-messages` `/u/${this.currentUser.username}/user-menu-private-messages`
@ -97,15 +102,4 @@ export default class UserMenuMessagesList extends UserMenuNotificationsList {
return content; return content;
} }
dismissWarningModal() {
const modalController = showModal("dismiss-notification-confirmation");
modalController.set(
"confirmationMessage",
I18n.t("notifications.dismiss_confirmation.body.messages", {
count: this.#unreadMessagesNotifications,
})
);
return modalController;
}
} }

View File

@ -6,12 +6,12 @@ import {
mergeSortedLists, mergeSortedLists,
postRNWebviewMessage, postRNWebviewMessage,
} from "discourse/lib/utilities"; } from "discourse/lib/utilities";
import showModal from "discourse/lib/show-modal";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item"; import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item";
import Notification from "discourse/models/notification"; import Notification from "discourse/models/notification";
import UserMenuReviewable from "discourse/models/user-menu-reviewable"; import UserMenuReviewable from "discourse/models/user-menu-reviewable";
import UserMenuReviewableItem from "discourse/lib/user-menu/reviewable-item"; import UserMenuReviewableItem from "discourse/lib/user-menu/reviewable-item";
import DismissNotificationConfirmationModal from "discourse/components/modal/dismiss-notification-confirmation";
export default class UserMenuNotificationsList extends UserMenuItemsList { export default class UserMenuNotificationsList extends UserMenuItemsList {
@service appEvents; @service appEvents;
@ -19,6 +19,7 @@ export default class UserMenuNotificationsList extends UserMenuItemsList {
@service siteSettings; @service siteSettings;
@service site; @service site;
@service store; @service store;
@service modal;
get filterByTypes() { get filterByTypes() {
return this.args.filterByTypes; return this.args.filterByTypes;
@ -65,6 +66,20 @@ export default class UserMenuNotificationsList extends UserMenuItemsList {
} }
} }
get renderDismissConfirmation() {
return true;
}
get dismissConfirmationText() {
return I18n.t("notifications.dismiss_confirmation.body.default", {
count: this.currentUser.unread_high_priority_notifications,
});
}
get alwaysRenderDismissConfirmation() {
return false;
}
async fetchItems() { async fetchItems() {
const params = { const params = {
limit: 30, limit: 30,
@ -138,29 +153,13 @@ export default class UserMenuNotificationsList extends UserMenuItemsList {
return content; return content;
} }
dismissWarningModal() { async performDismiss() {
if (this.currentUser.unread_high_priority_notifications > 0) {
const modalController = showModal("dismiss-notification-confirmation");
modalController.set(
"confirmationMessage",
I18n.t("notifications.dismiss_confirmation.body.default", {
count: this.currentUser.unread_high_priority_notifications,
})
);
return modalController;
}
}
@action
dismissButtonClick() {
const opts = { type: "PUT" }; const opts = { type: "PUT" };
const dismissTypes = this.dismissTypes; const dismissTypes = this.dismissTypes;
if (dismissTypes?.length > 0) { if (dismissTypes?.length > 0) {
opts.data = { dismiss_types: dismissTypes.join(",") }; opts.data = { dismiss_types: dismissTypes.join(",") };
} }
const modalController = this.dismissWarningModal(); await ajax("/notifications/mark-read", opts);
const modalCallback = () => {
ajax("/notifications/mark-read", opts).then(() => {
if (dismissTypes) { if (dismissTypes) {
const unreadNotificationCountsHash = { const unreadNotificationCountsHash = {
...this.currentUser.grouped_unread_notifications, ...this.currentUser.grouped_unread_notifications,
@ -182,12 +181,36 @@ export default class UserMenuNotificationsList extends UserMenuItemsList {
} }
this.refreshList(); this.refreshList();
postRNWebviewMessage("markRead", "1"); postRNWebviewMessage("markRead", "1");
}
dismissWarningModal() {
this.modal.show(DismissNotificationConfirmationModal, {
model: {
confirmationMessage: this.dismissConfirmationText,
dismissNotifications: () => this.performDismiss(),
},
}); });
}; }
if (modalController) {
modalController.set("dismissNotifications", modalCallback); @action
dismissButtonClick() {
// by default we display a warning modal when dismissing a notification
// however we bypass the warning modal for specific notification types when
// they are a 'low priority' type of notification (eg. likes)
if (
this.renderDismissConfirmation ||
this.alwaysRenderDismissConfirmation
) {
if (
this.currentUser.unread_high_priority_notifications > 0 ||
this.alwaysRenderDismissConfirmation
) {
this.dismissWarningModal();
} else { } else {
modalCallback(); this.performDismiss();
}
} else {
this.performDismiss();
} }
} }
} }

View File

@ -9,7 +9,7 @@ export default class UserMenuOtherNotificationsList extends UserMenuNotification
return "user-menu/other-notifications-list-empty-state"; return "user-menu/other-notifications-list-empty-state";
} }
dismissWarningModal() { get renderDismissConfirmation() {
return null; return false;
} }
} }

View File

@ -5,8 +5,8 @@ export default class UserMenuRepliesNotificationsList extends UserMenuNotificati
return this.filterByTypes; return this.filterByTypes;
} }
dismissWarningModal() { get renderDismissConfirmation() {
return null; return false;
} }
get emptyStateComponent() { get emptyStateComponent() {

View File

@ -1,13 +0,0 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object";
export default class DismissNewController extends Controller.extend(
ModalFunctionality
) {
@action
dismiss() {
this.dismissCallback();
this.send("closeModal");
}
}

View File

@ -1,11 +0,0 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default Controller.extend(ModalFunctionality, {
actions: {
dismiss() {
this.send("closeModal");
this.dismissNotifications();
},
},
});

View File

@ -3,11 +3,13 @@ import getURL from "discourse-common/lib/get-url";
import { iconHTML } from "discourse-common/lib/icon-library"; import { iconHTML } from "discourse-common/lib/icon-library";
import discourseComputed, { observes } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import showModal from "discourse/lib/show-modal";
import I18n from "I18n"; import I18n from "I18n";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { inject as service } from "@ember/service";
import DismissNotificationConfirmationModal from "discourse/components/modal/dismiss-notification-confirmation";
export default Controller.extend({ export default Controller.extend({
modal: service(),
application: controller(), application: controller(),
queryParams: ["filter"], queryParams: ["filter"],
filter: "all", filter: "all",
@ -49,27 +51,24 @@ export default Controller.extend({
); );
}, },
markRead() { async markRead() {
return ajax("/notifications/mark-read", { type: "PUT" }).then(() => { await ajax("/notifications/mark-read", { type: "PUT" });
this.model.forEach((n) => n.set("read", true)); this.model.forEach((n) => n.set("read", true));
});
}, },
actions: { actions: {
async resetNew() { async resetNew() {
const unreadHighPriorityNotifications = this.currentUser.get( if (this.currentUser.unread_high_priority_notifications > 0) {
"unread_high_priority_notifications" this.modal.show(DismissNotificationConfirmationModal, {
); model: {
if (unreadHighPriorityNotifications > 0) {
showModal("dismiss-notification-confirmation").setProperties({
confirmationMessage: I18n.t( confirmationMessage: I18n.t(
"notifications.dismiss_confirmation.body.default", "notifications.dismiss_confirmation.body.default",
{ {
count: unreadHighPriorityNotifications, count: this.currentUser.unread_high_priority_notifications,
} }
), ),
dismissNotifications: () => this.markRead(), dismissNotifications: () => this.markRead(),
},
}); });
} else { } else {
this.markRead(); this.markRead();

View File

@ -1,30 +1,29 @@
import Mixin from "@ember/object/mixin"; import Mixin from "@ember/object/mixin";
import User from "discourse/models/user"; import User from "discourse/models/user";
import showModal from "discourse/lib/show-modal"; import { inject as service } from "@ember/service";
import I18n from "I18n"; import { action } from "@ember/object";
import DismissNewModal from "discourse/components/modal/dismiss-new";
export default Mixin.create({ export default Mixin.create({
actions: { modal: service(),
@action
resetNew() { resetNew() {
const user = User.current(); const user = User.current();
if (!user.new_new_view_enabled) { if (!user.new_new_view_enabled) {
return this.callResetNew(); return this.callResetNew();
} }
const controller = showModal("dismiss-new", { this.modal.show(DismissNewModal, {
model: { model: {
dismissTopics: true, dismissTopics: true,
dismissPosts: true, dismissPosts: true,
}, dismissCallback: () =>
titleTranslated: I18n.t("topics.bulk.dismiss_new_modal.title"),
});
controller.set("dismissCallback", () => {
this.callResetNew( this.callResetNew(
controller.model.dismissPosts, this.model.dismissPosts,
controller.model.dismissTopics, this.model.dismissTopics,
controller.model.untrack this.model.untrack
); ),
}); },
}, });
}, },
}); });

View File

@ -1,29 +0,0 @@
<DModalBody>
<p>
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.topics"
@checked={{this.model.dismissTopics}}
@class="dismiss-topics"
/>
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.posts"
@checked={{this.model.dismissPosts}}
@class="dismiss-posts"
/>
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.untrack"
@checked={{this.model.untrack}}
@class="untrack"
/>
</p>
</DModalBody>
<div class="modal-footer">
<DButton
@class="btn-primary"
@action="dismiss"
@icon="check"
@id="dismiss-read-confirm"
@label="topics.bulk.dismiss"
/>
</div>

View File

@ -1,17 +0,0 @@
<DModalBody @headerClass="hidden" @class="dismiss-notification-confirmation">
{{this.confirmationMessage}}
</DModalBody>
<div class="modal-footer">
<DButton
@icon="check"
@class="btn-primary"
@action={{action "dismiss"}}
@label="notifications.dismiss_confirmation.dismiss"
/>
<DButton
@action={{route-action "closeModal"}}
@label="notifications.dismiss_confirmation.cancel"
@class="btn-default"
/>
</div>

View File

@ -1,18 +0,0 @@
<DModalBody>
<p>
<PreferenceCheckbox
@labelKey="topics.bulk.also_dismiss_topics"
@checked={{this.dismissTopics}}
/>
</p>
</DModalBody>
<div class="modal-footer">
<DButton
@class="btn-primary"
@action={{route-action "dismissReadTopics" this.dismissTopics}}
@icon="check"
@id="dismiss-read-confirm"
@label="topics.bulk.dismiss"
/>
</div>

View File

@ -882,7 +882,9 @@ acceptance("User menu - Dismiss button", function (needs) {
await click(".user-menu .notifications-dismiss"); await click(".user-menu .notifications-dismiss");
assert.strictEqual( assert.strictEqual(
query(".dismiss-notification-confirmation").textContent.trim(), query(
".dismiss-notification-confirmation .modal-body"
).textContent.trim(),
I18n.t("notifications.dismiss_confirmation.body.default", { count: 10 }), I18n.t("notifications.dismiss_confirmation.body.default", { count: 10 }),
"confirmation modal is shown when there are unread high pri notifications" "confirmation modal is shown when there are unread high pri notifications"
); );
@ -918,7 +920,9 @@ acceptance("User menu - Dismiss button", function (needs) {
await click(".user-menu .notifications-dismiss"); await click(".user-menu .notifications-dismiss");
assert.strictEqual( assert.strictEqual(
query(".dismiss-notification-confirmation").textContent.trim(), query(
".dismiss-notification-confirmation .modal-body"
).textContent.trim(),
I18n.t("notifications.dismiss_confirmation.body.bookmarks", { I18n.t("notifications.dismiss_confirmation.body.bookmarks", {
count: 103, count: 103,
}), }),
@ -972,7 +976,9 @@ acceptance("User menu - Dismiss button", function (needs) {
await click(".user-menu .notifications-dismiss"); await click(".user-menu .notifications-dismiss");
assert.strictEqual( assert.strictEqual(
query(".dismiss-notification-confirmation").textContent.trim(), query(
".dismiss-notification-confirmation .modal-body"
).textContent.trim(),
I18n.t("notifications.dismiss_confirmation.body.messages", { I18n.t("notifications.dismiss_confirmation.body.messages", {
count: 89, count: 89,
}), }),

View File

@ -3,9 +3,7 @@ import { setupTest } from "ember-qunit";
import sinon from "sinon"; import sinon from "sinon";
import pretender, { response } from "discourse/tests/helpers/create-pretender"; import pretender, { response } from "discourse/tests/helpers/create-pretender";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import * as showModal from "discourse/lib/show-modal";
import User from "discourse/models/user"; import User from "discourse/models/user";
import I18n from "I18n";
module("Unit | Controller | user-notifications", function (hooks) { module("Unit | Controller | user-notifications", function (hooks) {
setupTest(hooks); setupTest(hooks);
@ -58,29 +56,4 @@ module("Unit | Controller | user-notifications", function (hooks) {
assert.strictEqual(markRead, true); assert.strictEqual(markRead, true);
}); });
test("Shows modal when has high priority notifications", function (assert) {
let capturedProperties;
sinon
.stub(showModal, "default")
.withArgs("dismiss-notification-confirmation")
.returns({
setProperties: (properties) => (capturedProperties = properties),
});
const currentUser = User.create({ unread_high_priority_notifications: 1 });
const controller = this.owner.lookup("controller:user-notifications");
controller.setProperties({ currentUser });
const markReadFake = sinon.fake();
sinon.stub(controller, "markRead").callsFake(markReadFake);
controller.send("resetNew");
assert.strictEqual(
capturedProperties.confirmationMessage,
I18n.t("notifications.dismiss_confirmation.body.default", { count: 1 })
);
capturedProperties.dismissNotifications();
assert.strictEqual(markReadFake.callCount, 1);
});
}); });