Skip to content

Comments

feat: introduce injectable windowService [WPB-21950]#20443

Open
e-maad wants to merge 1 commit intodevfrom
feat/introduce-window-abstraction-WPB-21950
Open

feat: introduce injectable windowService [WPB-21950]#20443
e-maad wants to merge 1 commit intodevfrom
feat/introduce-window-abstraction-WPB-21950

Conversation

@e-maad
Copy link
Contributor

@e-maad e-maad commented Feb 19, 2026

BugWPB-21950 [Web] App didn't receive any messages but could send them / Also will have No internet banner in some cases

Pull Request

Summary

This PR introduces an injectable windowService abstraction, making the browser window object testable and allowing for dependency injection. This refactoring improves testability by enabling the window to be mocked in unit tests.

Security Checklist (required)

  • External inputs are validated & sanitized on client and/or server where applicable.
  • API responses are validated; unexpected shapes are handled safely (fallbacks or errors).
  • No unsafe HTML is rendered; if unavoidable, sanitization is applied and documented where it happens.
  • Injection risks (XSS/SQL/command) are prevented via safe APIs and/or escaping.

Accessibility (required)

Standards Acknowledgement (required)

Notes for reviewers

  • Trade-offs:
  • Follow-ups (linked issues):
  • Linked PRs (e.g. web-packages):

@sonarqubecloud
Copy link

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a WindowService abstraction intended to make browser-window interactions injectable/mocked in tests, and refactors EventRepository (plus its unit tests) to use that service for event listener wiring.

Changes:

  • Added a singleton WindowService with helper methods for registering/unregistering common window event listeners.
  • Refactored EventRepository.connectWebSocket() to use WindowService for focus/online/offline/visibility listeners.
  • Updated EventRepository tests to mock WindowService via tsyringe and adjusted Electron detection mocking.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
apps/webapp/src/script/window/windowService.ts Introduces injectable service for window-related event listeners.
apps/webapp/src/script/repositories/event/EventRepository.ts Switches event listener setup to use WindowService.
apps/webapp/src/script/repositories/event/EventRepository.test.ts Mocks WindowService and updates expectations around event listener setup.

Comment on lines +40 to +45
const mockService = {
onOnline: jest.fn().mockReturnValue(() => {}),
onOffline: jest.fn().mockReturnValue(() => {}),
onVisibilityChange: jest.fn().mockReturnValue(() => {}),
onFocus: jest.fn().mockReturnValue(() => {}),
} as unknown as WindowService;
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] - mockService is created with jest.fn() at module scope, so its call history persists across tests; jest.restoreAllMocks() does not clear plain jest.fn mocks. This can make assertions less reliable over time.

Suggested fix: add jest.clearAllMocks() in an appropriate beforeEach, or explicitly mockClear() the mockService methods between tests.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +38
window.addEventListener('visibilitychange', callback);
return () => window.removeEventListener('visibilitychange', callback);
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Blocker] - visibilitychange is a document event, but WindowService.onVisibilityChange attaches the listener to window, so the callback may never fire in browsers.

Suggested fix: register/unregister visibilitychange on document (or inject a Document/WindowLike dependency so the correct target is used).

Suggested change
window.addEventListener('visibilitychange', callback);
return () => window.removeEventListener('visibilitychange', callback);
document.addEventListener('visibilitychange', callback);
return () => document.removeEventListener('visibilitychange', callback);

Copilot uses AI. Check for mistakes.
Comment on lines 270 to 282
@@ -277,20 +278,12 @@ export class EventRepository {
}
};

document.addEventListener('visibilitychange', handleVisibilityChange);
cleanupHandlers.push(() => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
});
cleanupHandlers.push(this.windowService.onVisibilityChange(handleVisibilityChange));
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Blocker] - The browser branch uses document.visibilityState but subscribes via windowService.onVisibilityChange(...). Since visibilitychange is dispatched on document, the handler can be missed if the service is wiring it to window.

Suggested fix: ensure the subscription is done on document (either in WindowService.onVisibilityChange or by introducing a dedicated DocumentService).

Copilot uses AI. Check for mistakes.
Comment on lines +314 to 322
const originalIsElectron = Runtime.isElectron;
jest.spyOn(Runtime, 'isElectron').mockReturnValue(false);

await eventRepository.connectWebSocket(mockAccount, false, jest.fn());

expect(document.addEventListener).toHaveBeenCalledWith('visibilitychange', expect.any(Function));
expect(mockService.onVisibilityChange).toHaveBeenCalledWith(expect.any(Function));

(window as any).isElectron = originalIsElectron;
jest.spyOn(Runtime, 'isElectron').mockReturnValue(originalIsElectron());
});
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Blocker] - This test calls jest.spyOn(Runtime, 'isElectron') twice. Spying on the same method again will typically throw and/or leave the spy in an unexpected state.

Suggested fix: keep a reference to the spy and call mockRestore() (or rely on the existing afterEach(jest.restoreAllMocks) and remove the manual restore line entirely).

Copilot uses AI. Check for mistakes.
testFactory.exposeClientActors();
container.registerInstance(WindowService, mockService);
});

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Important] - container.registerInstance(WindowService, mockService) mutates the global tsyringe container but is never undone. This can leak into other test files (module cache is shared) and cause order-dependent failures.

Suggested fix: unregister/reset the container in afterAll (or use a child container / container.clearInstances() pattern) so this registration is scoped to this suite.

Suggested change
afterAll(() => {
container.unregister(WindowService);
});

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Feb 19, 2026

Codecov Report

❌ Patch coverage is 26.66667% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 45.33%. Comparing base (41e872a) to head (a649b65).

Files with missing lines Patch % Lines
apps/webapp/src/script/window/windowService.ts 0.00% 10 Missing ⚠️
...p/src/script/repositories/event/EventRepository.ts 80.00% 1 Missing ⚠️

❌ Your patch check has failed because the patch coverage (26.66%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev   #20443      +/-   ##
==========================================
- Coverage   45.34%   45.33%   -0.01%     
==========================================
  Files        1635     1636       +1     
  Lines       40312    40317       +5     
  Branches     8335     8336       +1     
==========================================
  Hits        18278    18278              
- Misses      20101    20107       +6     
+ Partials     1933     1932       -1     
Flag Coverage Δ
app_webapp 43.53% <26.66%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...p/src/script/repositories/event/EventRepository.ts 48.91% <80.00%> (+0.76%) ⬆️
apps/webapp/src/script/window/windowService.ts 0.00% <0.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants