Compare commits

...

13 Commits

Author SHA1 Message Date
e50959dd9c remove components folder 2023-11-22 16:40:23 -08:00
a63f4175bc Merge branch 'master' into 6820 2023-11-22 11:41:07 -08:00
b3d2995019 WIP fixing e2e test 2023-10-04 17:17:22 -07:00
22af8e2ac2 update severity before emitting 2023-09-28 16:26:01 -07:00
5402a40888 notification event is only for the active noti 2023-09-28 11:32:23 -07:00
7a0bc7a15b WIP refactoring tests 2023-09-27 08:43:40 -07:00
ddf704ec6c fix modal visibility assertions 2023-09-26 10:58:19 -07:00
eb0e0a5316 Merge branch 'master' into 6820 2023-09-25 16:33:35 -07:00
753b2b2f4c fix vue key preventing last noti being removed 2023-09-20 10:23:35 -07:00
1854ca4b6f react to all noti adds not just active noti add 2023-09-20 10:14:03 -07:00
64763f5c24 refactor indicator, list, message
pass destroyed up from message

let list close itself, not message

update indicator on message destroy
2023-09-19 16:39:47 -07:00
85969e048a general code cleanup 2023-09-19 14:58:45 -07:00
ff48074143 make severity responsive 2023-09-19 14:08:11 -07:00
5 changed files with 122 additions and 62 deletions

View File

@ -27,8 +27,71 @@ This test suite is dedicated to tests which verify Open MCT's Notification funct
const { createDomainObjectWithDefaults, createNotification } = require('../../appActions');
const { test, expect } = require('../../pluginFixtures');
test.describe('Notifications List', () => {
test.fixme('Notifications can be dismissed individually', async ({ page }) => {
const infoConfig = {
severity: 'info',
message: 'Info message'
};
const alertConfig = {
severity: 'alert',
message: 'Alert message'
};
const errorConfig = {
severity: 'error',
message: 'Error message'
};
test.describe('Notifications list', () => {
/** @type {import('@playwright/test').Locator} */
let notificationsList;
test.beforeEach(async ({ page }) => {
notificationsList = page.locator('div[role="dialog"]');
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Notifications can be dismissed individually from the list', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6820'
});
// Create an alert notification with the message "Alert message"
await createNotification(page, alertConfig);
// Create an alert notification with the message "Alert message"
await createNotification(page, alertConfig);
// Create an error notification with the message "Error message"
await createNotification(page, errorConfig);
// Create an alert notification with the message "Alert message"
await createNotification(page, alertConfig);
// Create an alert notification with the message "Alert message"
await createNotification(page, alertConfig);
// Verify that there is a button with aria-label "Review 2 Notifications"
expect(await page.locator('button[aria-label="Review 5 Notifications"]').count()).toBe(1);
// Click on button with aria-label "Review 1 Notification"
await page.click('button[aria-label="Review 5 Notifications"]');
// Notifications list dialog is open
await expect(notificationsList).toBeVisible();
expect(await notificationsList.locator('div[role="listitem"]').count()).toBe(5);
// Verify there is still a notification (listitem) with the text "Alert message"
expect(await notificationsList.locator('div[role="listitem"]').nth(2).innerText()).toContain(
'Error message'
);
// Click on button with aria-label="Dismiss notification of Alert message"
await page.click('button[aria-label="Dismiss notification of Error message"]');
expect(await notificationsList.locator('div[role="listitem"]').count()).toBe(4);
});
test('When last notification dismissed, list closes automatically', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6820'
@ -38,16 +101,10 @@ test.describe('Notifications List', () => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create an error notification with the message "Error message"
await createNotification(page, {
severity: 'error',
message: 'Error message'
});
await createNotification(page, errorConfig);
// Create an alert notification with the message "Alert message"
await createNotification(page, {
severity: 'alert',
message: 'Alert message'
});
await createNotification(page, alertConfig);
// Verify that there is a button with aria-label "Review 2 Notifications"
expect(await page.locator('button[aria-label="Review 2 Notifications"]').count()).toBe(1);
@ -55,24 +112,23 @@ test.describe('Notifications List', () => {
// Click on button with aria-label "Review 2 Notifications"
await page.click('button[aria-label="Review 2 Notifications"]');
// Notifications list dialog is open
await expect(notificationsList).toBeVisible();
// Click on button with aria-label="Dismiss notification of Error message"
await page.click('button[aria-label="Dismiss notification of Error message"]');
await page.getByRole('button', { name: 'Dismiss notification of Error message' }).click();
// Verify there is no a notification (listitem) with the text "Error message" since it was dismissed
expect(await page.locator('div[role="dialog"] div[role="listitem"]').innerText()).not.toContain(
'Error message'
);
// expect(await page.getByRole('dialog').getByRole('listitem')).not.toContain('Error message');
// Verify there is still a notification (listitem) with the text "Alert message"
expect(await page.locator('div[role="dialog"] div[role="listitem"]').innerText()).toContain(
'Alert message'
);
// expect(await page.getByRole('dialog').getByRole('listitem')).toContain('Alert message');
// Click on button with aria-label="Dismiss notification of Alert message"
await page.click('button[aria-label="Dismiss notification of Alert message"]');
await page.getByRole('button', { name: 'Dismiss notification of Alert message' }).click();
// Verify that there is no dialog since the notification overlay was closed automatically after all notifications were dismissed
expect(await page.locator('div[role="dialog"]').count()).toBe(0);
await expect(notificationsList).toBeHidden();
});
});

View File

@ -265,7 +265,7 @@ export default class NotificationAPI extends EventEmitter {
this._setActiveNotification(this._selectNextNotification());
this._setHighestSeverity();
notification.emit('destroy');
notification.emit('destroy', notification);
}
/**
@ -324,23 +324,24 @@ export default class NotificationAPI extends EventEmitter {
this.notifications.push(notification);
this._setHighestSeverity();
this.emit('add', notification);
/*
Check if there is already an active (ie. visible) notification
*/
Check if there is already an active (ie. visible) notification
*/
if (!this.activeNotification && !notification?.model?.options?.minimized) {
this._setActiveNotification(notification);
} else if (!this.activeTimeout) {
/*
If there is already an active notification, time it out. If it's
already got a timeout in progress (either because it has had
timeout forced because of a queue of messages, or it had an
autodismiss specified), leave it to run. Otherwise force a
timeout.
If there is already an active notification, time it out. If it's
already got a timeout in progress (either because it has had
timeout forced because of a queue of messages, or it had an
autodismiss specified), leave it to run. Otherwise force a
timeout.
This notification has been added to queue and will be
serviced as soon as possible.
*/
This notification has been added to queue and will be
serviced as soon as possible.
*/
this.activeTimeout = setTimeout(() => {
this._dismissOrMinimize(activeNotification);
}, DEFAULT_AUTO_DISMISS_TIMEOUT);

View File

@ -43,6 +43,7 @@
:notifications="notifications"
@close="toggleNotificationsList"
@clear-all="dismissAllNotifications"
@dismissed="updateNotifications"
/>
</div>
</template>
@ -58,22 +59,22 @@ export default {
data() {
return {
notifications: this.openmct.notifications.notifications,
highest: this.openmct.notifications.highest,
highestSeverity: this.openmct.notifications.highest.severity,
showNotificationsOverlay: false
};
},
computed: {
severityClass() {
return `s-status-${this.highest.severity}`;
return `s-status-${this.highestSeverity}`;
}
},
mounted() {
this.openmct.notifications.on('notification', this.updateNotifications);
this.openmct.notifications.on('add', this.updateNotifications);
this.openmct.notifications.on('dismiss-all', this.updateNotifications);
},
unmounted() {
this.openmct.notifications.of('notification', this.updateNotifications);
this.openmct.notifications.of('dismiss-all', this.updateNotifications);
this.openmct.notifications.off('add', this.updateNotifications);
this.openmct.notifications.off('dismiss-all', this.updateNotifications);
},
methods: {
dismissAllNotifications() {
@ -84,7 +85,7 @@ export default {
},
updateNotifications() {
this.notifications = [...this.openmct.notifications.notifications];
this.highest = this.openmct.notifications.highest;
this.highestSeverity = this.openmct.notifications.highest.severity;
},
notificationsCountMessage(count) {
if (count > 1) {

View File

@ -72,16 +72,9 @@ export default {
notification: {
type: Object,
required: true
},
closeOverlay: {
type: Function,
required: true
},
notificationsCount: {
type: Number,
required: true
}
},
emits: ['dismissed'],
data() {
return {
isProgressNotification: false,
@ -98,6 +91,8 @@ export default {
}
},
mounted() {
this.notification.once('destroy', this.dismissNotification);
if (this.notification.model.progressPerc) {
this.isProgressNotification = true;
this.notification.on('progress', this.updateProgressBar);
@ -110,9 +105,13 @@ export default {
},
dismiss() {
this.notification.dismiss();
if (this.notificationsCount === 1) {
this.closeOverlay();
},
dismissNotification() {
if (this.isProgressNotification) {
this.notification.off('progress', this.updateProgressBar);
}
this.$emit('dismissed');
}
}
};

View File

@ -24,16 +24,15 @@
<div class="c-overlay__top-bar">
<div class="c-overlay__dialog-title">Notifications</div>
<div class="c-overlay__dialog-hint">
{{ notificationsCountDisplayMessage(notifications.length) }}
{{ notificationsCountDisplayMessage }}
</div>
</div>
<div role="list" class="w-messages c-overlay__messages">
<notification-message
v-for="(notification, notificationIndex) in notifications"
:key="notificationIndex"
:close-overlay="closeOverlay"
v-for="notification in notifications"
:key="notification.model.timestamp"
:notification="notification"
:notifications-count="notifications.length"
@dismissed="notificationDismissed"
/>
</div>
</div>
@ -53,9 +52,16 @@ export default {
required: true
}
},
emits: ['close', 'clear-all'],
data() {
return {};
emits: ['clear-all', 'close', 'dismissed'],
computed: {
notificationsCount() {
return this.notifications.length;
},
notificationsCountDisplayMessage() {
return this.notificationsCount > 1 || this.notificationsCount === 0
? `Displaying ${this.notificationsCount} notifications`
: `Displaying ${this.notificationsCount} notification`;
}
},
mounted() {
this.openOverlay();
@ -81,15 +87,12 @@ export default {
}
});
},
closeOverlay() {
this.overlay.dismiss();
},
notificationsCountDisplayMessage(count) {
if (count > 1 || count === 0) {
return `Displaying ${count} notifications`;
} else {
return `Displaying ${count} notification`;
notificationDismissed() {
if (this.notificationsCount === 1) {
this.overlay.dismiss();
}
this.$emit('dismissed');
}
}
};