Skip to content

Commit 01f266a

Browse files
test: increase code coverage pt 2 (#1411)
Co-authored-by: Fadi George <fadii925@gmail.com>
1 parent a3c80a8 commit 01f266a

9 files changed

Lines changed: 498 additions & 10 deletions

File tree

src/page/bell/Bell.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { TestEnvironment } from '__test__/support/environment/TestEnvironment';
2+
import { vi } from 'vitest';
23
import OneSignalEvent from '../../shared/services/OneSignalEvent';
34
import Bell from './Bell';
45
import { BellEvent, BellState } from './constants';
@@ -47,4 +48,78 @@ describe('Bell', () => {
4748
to: BellState._Subscribed,
4849
});
4950
});
51+
52+
test('_updateState sets blocked when permission denied', async () => {
53+
const bell = new Bell({ enable: false });
54+
const permSpy = vi
55+
.spyOn(OneSignal._context._permissionManager, '_getPermissionStatus')
56+
.mockResolvedValue('denied');
57+
const enabledSpy = vi
58+
.spyOn(
59+
OneSignal._context._subscriptionManager,
60+
'_isPushNotificationsEnabled',
61+
)
62+
.mockResolvedValue(false);
63+
bell._updateState();
64+
await Promise.resolve();
65+
await Promise.resolve();
66+
expect(bell._blocked).toBe(true);
67+
expect(permSpy).toHaveBeenCalled();
68+
expect(enabledSpy).toHaveBeenCalled();
69+
});
70+
71+
test('_setCustomColorsIfSpecified applies styles and adds CSS to head', async () => {
72+
const bell = new Bell({ enable: false });
73+
document.body.innerHTML = `
74+
<div class="onesignal-bell-launcher">
75+
<div class="onesignal-bell-launcher-button">
76+
<svg>
77+
<circle class="background"></circle>
78+
<g class="foreground"></g>
79+
<ellipse class="stroke"></ellipse>
80+
</svg>
81+
<div class="pulse-ring"></div>
82+
</div>
83+
<div class="onesignal-bell-launcher-badge"></div>
84+
<div class="onesignal-bell-launcher-dialog">
85+
<div class="onesignal-bell-launcher-dialog-body">
86+
<button class="action">A</button>
87+
</div>
88+
</div>
89+
</div>
90+
`;
91+
bell._options.colors = {
92+
'circle.background': '#111',
93+
'circle.foreground': '#222',
94+
'badge.background': '#333',
95+
'badge.bordercolor': '#444',
96+
'badge.foreground': '#555',
97+
'dialog.button.background': '#666',
98+
'dialog.button.foreground': '#777',
99+
'dialog.button.background.hovering': '#888',
100+
'dialog.button.background.active': '#999',
101+
'pulse.color': '#abc',
102+
};
103+
bell._setCustomColorsIfSpecified();
104+
const background = document.querySelector<HTMLElement>('.background')!;
105+
expect(background.getAttribute('style')).toContain('#111');
106+
107+
const badge = document.querySelector<HTMLElement>(
108+
'.onesignal-bell-launcher-badge',
109+
)!;
110+
expect(badge.getAttribute('style')).toContain('rgb(51, 51, 51)');
111+
112+
const styleHover = document.getElementById(
113+
'onesignal-background-hover-style',
114+
);
115+
expect(styleHover).not.toBeNull();
116+
});
117+
118+
test('_addCssToHead appends once', () => {
119+
const bell = new Bell({ enable: false });
120+
bell._addCssToHead('x', '.a{color:red}');
121+
bell._addCssToHead('x', '.b{color:blue}');
122+
const style = document.getElementById('x')!;
123+
expect(style.textContent).toContain('.a{color:red}');
124+
});
50125
});

src/page/managers/PromptsManager.test.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { TestEnvironment } from '__test__/support/environment/TestEnvironment';
22
import { setupLoadStylesheet } from '__test__/support/helpers/setup';
3+
import { DelayedPromptType } from 'src/shared/prompts/constants';
4+
import type { DelayedPromptOptions } from 'src/shared/prompts/types';
35
import { Browser } from 'src/shared/useragent/constants';
46
import * as detect from 'src/shared/useragent/detect';
57
import { PromptsManager } from './PromptsManager';
6-
78
const getBrowserNameSpy = vi.spyOn(detect, 'getBrowserName');
89
const getBrowserVersionSpy = vi.spyOn(detect, 'getBrowserVersion');
910
const isMobileBrowserSpy = vi.spyOn(detect, 'isMobileBrowser');
@@ -52,4 +53,49 @@ describe('PromptsManager', () => {
5253
await pm['_internalShowSlidedownPrompt']();
5354
expect(installSpy).toHaveBeenCalledTimes(1);
5455
});
56+
57+
test('_internalShowDelayedPrompt forces slidedown when interaction required', async () => {
58+
requiresUserInteractionSpy.mockReturnValue(true);
59+
const pm = new PromptsManager(OneSignal._context);
60+
const nativeSpy = vi
61+
.spyOn(pm, '_internalShowNativePrompt')
62+
.mockResolvedValue(true);
63+
const slidedownSpy = vi
64+
.spyOn(pm, '_internalShowSlidedownPrompt')
65+
.mockResolvedValue(undefined);
66+
await pm._internalShowDelayedPrompt(DelayedPromptType._Native, 0);
67+
expect(nativeSpy).not.toHaveBeenCalled();
68+
69+
expect(slidedownSpy).toHaveBeenCalled();
70+
});
71+
72+
test('_spawnAutoPrompts triggers native when condition met and not forced', async () => {
73+
const pm = new PromptsManager(OneSignal._context);
74+
const getOptsSpy = vi
75+
.spyOn(pm, '_getDelayedPromptOptions' as keyof PromptsManager)
76+
.mockImplementation(
77+
(): DelayedPromptOptions => ({
78+
enabled: true,
79+
autoPrompt: true,
80+
timeDelay: 0,
81+
pageViews: 0,
82+
}),
83+
);
84+
const condSpy = vi
85+
.spyOn(pm, '_isPageViewConditionMet' as keyof PromptsManager)
86+
.mockResolvedValue(true);
87+
88+
const delayedSpy = vi
89+
.spyOn(pm, '_internalShowDelayedPrompt')
90+
.mockResolvedValue(undefined);
91+
requiresUserInteractionSpy.mockReturnValue(false);
92+
getBrowserNameSpy.mockReturnValue(Browser._Chrome);
93+
getBrowserVersionSpy.mockReturnValue(62);
94+
95+
await pm._spawnAutoPrompts();
96+
97+
expect(getOptsSpy).toHaveBeenCalled();
98+
expect(condSpy).toHaveBeenCalled();
99+
expect(delayedSpy).toHaveBeenCalledWith(DelayedPromptType._Native, 0);
100+
});
55101
});

src/page/managers/PromptsManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export class PromptsManager {
192192
return success;
193193
}
194194

195-
private async _internalShowSlidedownPrompt(
195+
public async _internalShowSlidedownPrompt(
196196
options: AutoPromptOptions = { force: false },
197197
): Promise<void> {
198198
logMethodCall('internalShowSlidedownPrompt');
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { TestEnvironment } from '__test__/support/environment/TestEnvironment';
2+
import { setupLoadStylesheet } from '__test__/support/helpers/setup';
3+
import {
4+
CUSTOM_LINK_CSS_CLASSES,
5+
CUSTOM_LINK_CSS_SELECTORS,
6+
} from 'src/shared/slidedown/constants';
7+
import { vi, type MockInstance } from 'vitest';
8+
import { ResourceLoadState } from '../../page/services/DynamicResourceLoader';
9+
import { CustomLinkManager } from './CustomLinkManager';
10+
11+
describe('CustomLinkManager', () => {
12+
let isPushEnabledSpy: MockInstance;
13+
beforeEach(() => {
14+
TestEnvironment.initialize();
15+
document.body.innerHTML = `
16+
<div class="${CUSTOM_LINK_CSS_SELECTORS._ContainerSelector.replace('.', '')}"></div>
17+
`;
18+
isPushEnabledSpy = vi.spyOn(
19+
OneSignal._context._subscriptionManager,
20+
'_isPushNotificationsEnabled',
21+
);
22+
});
23+
24+
test('_initialize returns when disabled or stylesheet fails', async () => {
25+
const mgrDisabled = new CustomLinkManager({ enabled: false });
26+
await expect(mgrDisabled._initialize()).resolves.toBeUndefined();
27+
28+
// Stylesheet not loaded
29+
const mgr = new CustomLinkManager({
30+
enabled: true,
31+
text: { explanation: 'x', subscribe: 'Sub' },
32+
});
33+
vi.spyOn(
34+
OneSignal._context._dynamicResourceLoader,
35+
'_loadSdkStylesheet',
36+
).mockResolvedValue(ResourceLoadState._Failed);
37+
await mgr._initialize();
38+
39+
// nothing injected
40+
const containers = document.querySelectorAll(
41+
CUSTOM_LINK_CSS_SELECTORS._ContainerSelector,
42+
);
43+
expect(containers.length).toBe(1);
44+
45+
expect(containers[0].children.length).toBe(0);
46+
});
47+
48+
test('_initialize hides containers when subscribed and unsubscribe disabled', async () => {
49+
await setupLoadStylesheet();
50+
isPushEnabledSpy.mockResolvedValue(true);
51+
const mgr = new CustomLinkManager({
52+
enabled: true,
53+
unsubscribeEnabled: false,
54+
text: {
55+
explanation: 'hello',
56+
subscribe: 'Subscribe',
57+
unsubscribe: 'Unsubscribe',
58+
},
59+
});
60+
await mgr._initialize();
61+
const containers = document.querySelectorAll<HTMLElement>(
62+
CUSTOM_LINK_CSS_SELECTORS._ContainerSelector,
63+
);
64+
expect(
65+
containers[0].classList.contains(CUSTOM_LINK_CSS_CLASSES._Hide),
66+
).toBe(true);
67+
});
68+
69+
test('_initialize injects markup and click toggles subscription', async () => {
70+
await setupLoadStylesheet();
71+
isPushEnabledSpy.mockResolvedValue(false);
72+
const optInSpy = vi
73+
.spyOn(OneSignal.User.PushSubscription, 'optIn')
74+
.mockResolvedValue();
75+
const optOutSpy = vi
76+
.spyOn(OneSignal.User.PushSubscription, 'optOut')
77+
.mockResolvedValue();
78+
const mgr = new CustomLinkManager({
79+
enabled: true,
80+
unsubscribeEnabled: true,
81+
text: {
82+
explanation: 'hello',
83+
subscribe: 'Subscribe',
84+
unsubscribe: 'Unsubscribe',
85+
},
86+
style: 'button',
87+
size: 'medium',
88+
color: { text: '#fff', button: '#000' },
89+
});
90+
await mgr._initialize();
91+
const button = document.querySelector<HTMLButtonElement>(
92+
`.${CUSTOM_LINK_CSS_CLASSES._SubscribeClass}`,
93+
);
94+
expect(button).not.toBeNull();
95+
96+
expect(button?.textContent).toBe('Subscribe');
97+
98+
await button?.click();
99+
expect(optInSpy).toHaveBeenCalled();
100+
101+
// simulate subscribed now (set optedIn getter)
102+
vi.spyOn(OneSignal.User.PushSubscription, 'optedIn', 'get').mockReturnValue(
103+
true,
104+
);
105+
await button?.click();
106+
expect(optOutSpy).toHaveBeenCalled();
107+
});
108+
});

0 commit comments

Comments
 (0)