Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ export namespace Gleap {
function attachNetworkLogs(networkLogs: string): void;
function clearIdentity(): void;
function setTags(tags: string[]): void;
/**
* Hide notification cards, including cached or already-visible cards, without
* hiding the support button or messenger. Reenable to show eligible cards again.
*/
function setDisableInAppNotifications(
disableInAppNotifications: boolean
): void;
Expand Down
5 changes: 5 additions & 0 deletions src/Gleap.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,12 @@ class Gleap {
*/
static setDisableInAppNotifications(disableInAppNotifications) {
const instance = this.getInstance();
if (instance.disableInAppNotifications === disableInAppNotifications) {
return;
}
instance.disableInAppNotifications = disableInAppNotifications;
// Apply route changes to cards already on screen, without marking them read.
GleapNotificationManager.getInstance().renderNotifications();
}

/**
Expand Down
10 changes: 9 additions & 1 deletion src/GleapNotificationManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ export default class GleapNotificationManager {
* rendered on an allowed page followed users onto excluded ones.
*/
getVisibleNotifications() {
// Cached cards and direct deliveries bypass performActions' arrival guard.
if (Gleap.getInstance().disableInAppNotifications) {
return [];
}
return this.notifications.filter((notification) => this.notificationPassesPageRules(notification));
}

Expand Down Expand Up @@ -199,7 +203,11 @@ export default class GleapNotificationManager {
// Play sound only if it's a new one and its page rules allow the
// current page (it may have arrived on an excluded page and only
// become visible later).
if (notification.sound && this.notificationPassesPageRules(notification)) {
if (
notification.sound &&
!Gleap.getInstance().disableInAppNotifications &&
this.notificationPassesPageRules(notification)
) {
GleapAudioManager.ping();
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/GleapNotificationManager.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GleapTabCommunication, GleapSession, GleapAudioManager } from './Gleap';
import Gleap, { GleapTabCommunication, GleapSession, GleapAudioManager } from './Gleap';
import { loadFromGleapCache, saveToGleapCache } from './GleapHelper';
import GleapNotificationManager from './GleapNotificationManager';

Expand All @@ -7,7 +7,7 @@ import GleapNotificationManager from './GleapNotificationManager';
// GleapTabCommunication, GleapSession and saveToGleapCache.
jest.mock('./Gleap', () => ({
__esModule: true,
default: {},
default: { getInstance: jest.fn() },
GleapFeedbackButtonManager: { getInstance: jest.fn(() => ({ updateNotificationBadge: jest.fn() })) },
GleapConfigManager: { getInstance: jest.fn(() => ({ getFlowConfig: () => ({}) })) },
GleapSession: { getInstance: jest.fn() },
Expand Down Expand Up @@ -44,6 +44,7 @@ const withContainer = (nm) => {
};

beforeEach(() => {
Gleap.getInstance.mockReturnValue({ disableInAppNotifications: false });
sendMessageMock = jest.fn();
GleapTabCommunication.getInstance.mockReturnValue({ sendMessage: sendMessageMock });
GleapSession.getInstance.mockReturnValue({ session: { gleapId: 'user-1' } });
Expand Down
125 changes: 125 additions & 0 deletions src/GleapNotifications.disabled.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* @jest-environment jsdom
*/
import Gleap, {
GleapAudioManager,
GleapConfigManager,
GleapFeedbackButtonManager,
GleapFrameManager,
GleapNotificationManager,
GleapSession,
} from './Gleap';
import * as cache from './GleapHelper';

const news = (extra = {}) => ({
outbound: 'product-announcement',
createdAt: new Date().toISOString(),
cachedForGleapId: 'signup-visitor',
data: { news: { id: 'news-1' }, text: 'Product announcement' },
...extra,
});

const mountNotifications = () => {
const manager = GleapNotificationManager.getInstance();
manager.notificationContainer = document.createElement('div');
document.body.appendChild(manager.notificationContainer);
return manager;
};

const visibleCards = (manager) => manager.notificationContainer.querySelectorAll('.gleap-notification-card');

beforeEach(() => {
// Exercise the real public setter and notification renderer, without SDK startup.
jest.spyOn(Gleap, 'getInstance').mockReturnValue({ disableInAppNotifications: false });
jest.spyOn(GleapSession, 'getInstance').mockReturnValue({
session: { gleapId: 'signup-visitor' },
getName: () => 'Visitor',
});
jest.spyOn(GleapConfigManager, 'getInstance').mockReturnValue({ getFlowConfig: () => ({}) });
jest.spyOn(GleapAudioManager, 'ping').mockImplementation(() => {});
jest.spyOn(cache, 'loadFromGleapCache').mockReturnValue(null);
jest.spyOn(cache, 'saveToGleapCache').mockImplementation(() => {});
GleapNotificationManager.instance = undefined;
});

afterEach(() => {
jest.restoreAllMocks();
GleapNotificationManager.instance = undefined;
document.body.innerHTML = '';
});

test('disabling before startup keeps cached announcements hidden without deleting them', () => {
Gleap.setDisableInAppNotifications(true);
const manager = mountNotifications();
const cached = news();
cache.loadFromGleapCache.mockReturnValue([cached]);

manager.reloadNotificationsFromCache();

expect(visibleCards(manager)).toHaveLength(0);
expect(manager.notifications).toEqual([cached]);
expect(cache.saveToGleapCache).not.toHaveBeenCalled();

Gleap.setDisableInAppNotifications(false);
expect(visibleCards(manager)).toHaveLength(1);
});

test('disabling immediately hides an existing card and keeps it hidden on cache reload', () => {
const manager = mountNotifications();
const cached = news();
cache.loadFromGleapCache.mockReturnValue([cached]);
manager.reloadNotificationsFromCache();
expect(visibleCards(manager)).toHaveLength(1);

Gleap.setDisableInAppNotifications(true);
expect(manager.notificationContainer.children).toHaveLength(0);

// Closing the messenger reloads cached notifications through the same path.
manager.reloadNotificationsFromCache();
expect(manager.notificationContainer.children).toHaveLength(0);
expect(manager.notifications).toEqual([cached]);

Gleap.setDisableInAppNotifications(false);
expect(visibleCards(manager)).toHaveLength(1);
expect(cache.saveToGleapCache).not.toHaveBeenCalled();
});

test('a directly delivered notification stays silent and hidden while disabled', () => {
const manager = mountNotifications();
Gleap.setDisableInAppNotifications(true);

Gleap.showNotification(news({ sound: true }));

expect(visibleCards(manager)).toHaveLength(0);
expect(GleapAudioManager.ping).not.toHaveBeenCalled();
expect(manager.notifications).toHaveLength(1);

Gleap.setDisableInAppNotifications(false);
expect(visibleCards(manager)).toHaveLength(1);
expect(GleapAudioManager.ping).not.toHaveBeenCalled();
});

test('reenabling notifications still respects their page exclusions', () => {
const manager = mountNotifications();
manager.notifications = [news({ pageRules: [{ pageFilter: '/', pageFilterType: 'notcontains' }] })];

Gleap.setDisableInAppNotifications(true);
Gleap.setDisableInAppNotifications(false);

expect(visibleCards(manager)).toHaveLength(0);
expect(manager.notifications).toHaveLength(1);
});

test('support chat can still open while notification cards are disabled', () => {
const frame = { setAppMode: jest.fn(), showWidget: jest.fn(), hideWidget: jest.fn() };
jest.spyOn(GleapFrameManager, 'getInstance').mockReturnValue(frame);
const buttonManager = jest.spyOn(GleapFeedbackButtonManager, 'getInstance');

Gleap.setDisableInAppNotifications(true);
Gleap.open();

expect(frame.setAppMode).toHaveBeenCalledWith('widget');
expect(frame.showWidget).toHaveBeenCalledTimes(1);
expect(frame.hideWidget).not.toHaveBeenCalled();
expect(buttonManager).not.toHaveBeenCalled();
});
Loading