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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Clean up BroadcastChannel resources to fix hanging tests #8045",
"packageName": "@azure/msal-browser",
"email": "kshabelko@microsoft.com",
"dependentChangeType": "patch"
}
2 changes: 1 addition & 1 deletion lib/msal-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"clean:coverage": "rimraf ../../.nyc_output/*",
"lint": "eslint src --ext .ts",
"lint:fix": "npm run lint -- --fix",
"test": "jest --forceExit",
"test": "jest",
"test:coverage": "jest --coverage",
"test:coverage:only": "npm run clean:coverage && npm run test:coverage",
"build:all": "cd ../.. && npm run build --workspace=@azure/msal-common --workspace=@azure/msal-browser",
Expand Down
19 changes: 18 additions & 1 deletion lib/msal-browser/test/utils/BridgeSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,21 @@ beforeAll(() => {
window.nestedAppAuthBridge = new MockBridge();
}
});
afterAll(() => {});

afterEach(() => {
// Clean up BroadcastChannels after each test
if ((global as any).forceCleanupMsalBroadcastChannels) {
(global as any).forceCleanupMsalBroadcastChannels();
} else if ((global as any).cleanupMsalBroadcastChannels) {
(global as any).cleanupMsalBroadcastChannels();
}
});

afterAll(() => {
// Final aggressive cleanup
if ((global as any).forceCleanupMsalBroadcastChannels) {
(global as any).forceCleanupMsalBroadcastChannels();
} else if ((global as any).cleanupMsalBroadcastChannels) {
(global as any).cleanupMsalBroadcastChannels();
}
});
94 changes: 94 additions & 0 deletions shared-configs/jest-config/BroadcastChannelTracker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

/**
* Shared BroadcastChannel tracking utility for test environments.
* This module provides consistent tracking and cleanup functionality
* across different test environments (browser/jsdom and Node.js).
*/

/**
* Creates a BroadcastChannel tracker for a given environment
* @param {Function} getBroadcastChannel - Function that returns the BroadcastChannel constructor for the environment
* @returns {Object} Object containing TrackedBroadcastChannel class and cleanup functions
*/
function createBroadcastChannelTracker(getBroadcastChannel) {
const activeBroadcastChannels = new Set();
const OriginalBroadcastChannel = getBroadcastChannel();

class TrackedBroadcastChannel extends OriginalBroadcastChannel {
constructor(name) {
super(name);
activeBroadcastChannels.add(this);
}

close() {
super.close();
activeBroadcastChannels.delete(this);
}
}

// Basic cleanup function
function cleanupBroadcastChannels() {
activeBroadcastChannels.forEach((channel) => {
try {
channel.close();
} catch (error) {
// Ignore cleanup errors
}
});
activeBroadcastChannels.clear();
}

// Enhanced cleanup function with additional reset logic
function forceCleanupBroadcastChannels() {
// First try graceful cleanup
cleanupBroadcastChannels();

// Add more aggressive cleanup if needed for browser environments
if (typeof window !== "undefined" && window.BroadcastChannel) {
// Reset to original BroadcastChannel to prevent further leaks
window.BroadcastChannel = OriginalBroadcastChannel;
// Then restore tracked version
window.BroadcastChannel = TrackedBroadcastChannel;
}

// Clear any remaining instances in case some weren't tracked
activeBroadcastChannels.clear();
}

return {
TrackedBroadcastChannel,
cleanupBroadcastChannels,
forceCleanupBroadcastChannels,
OriginalBroadcastChannel
};
}

// Node.js-specific setup for @jest-environment node tests
// This automatically sets up BroadcastChannel tracking when this module is imported
if (typeof window === "undefined" && typeof global !== "undefined") {
// Create BroadcastChannel tracker for Node.js environment
const {
TrackedBroadcastChannel,
cleanupBroadcastChannels
} = createBroadcastChannelTracker(() => {
const { BroadcastChannel } = require('worker_threads');
return BroadcastChannel;
});

// Replace global BroadcastChannel with tracked version
global.BroadcastChannel = TrackedBroadcastChannel;

// Add cleanup hooks for Node.js test environment
if (typeof afterEach !== "undefined" && typeof afterAll !== "undefined") {
afterEach(cleanupBroadcastChannels);
afterAll(cleanupBroadcastChannels);
}
}

module.exports = {
createBroadcastChannelTracker
};
34 changes: 23 additions & 11 deletions shared-configs/jest-config/setupGlobals.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@
const crypto = require("crypto");
const { TextDecoder, TextEncoder } = require("util");
const { BroadcastChannel, MessageChannel } = require("worker_threads");
const { createBroadcastChannelTracker } = require("./BroadcastChannelTracker");

// Create BroadcastChannel tracker for browser/jsdom environment
const {
TrackedBroadcastChannel,
cleanupBroadcastChannels,
forceCleanupBroadcastChannels
} = createBroadcastChannelTracker(() => BroadcastChannel);

// Add cleanup functions to global for use in test setup files
global.cleanupMsalBroadcastChannels = cleanupBroadcastChannels;
global.forceCleanupMsalBroadcastChannels = forceCleanupBroadcastChannels;

try {
Object?.defineProperties(global.self, {
"crypto": {
crypto: {
value: {
subtle: crypto.webcrypto.subtle,
getRandomValues(dataBuffer) {
Expand All @@ -18,20 +30,20 @@ try {
randomUUID() {
return crypto.randomUUID();
},
}
},
},
TextDecoder: {
value: TextDecoder,
},
"TextDecoder": {
value: TextDecoder
TextEncoder: {
value: TextEncoder,
},
"TextEncoder": {
value: TextEncoder
BroadcastChannel: {
value: TrackedBroadcastChannel,
},
"BroadcastChannel": {
value: BroadcastChannel
MessageChannel: {
value: MessageChannel,
},
"MessageChannel": {
value: MessageChannel
}
});
} catch (e) {
// catch silently for non-browser tests
Expand Down