Detect monkey patching and emit a deprecation warning.

This isn't comprehensive but it should be broad enough to ensure that most
people who would be affected by blocking monkey patching see a warning.
Covers the jasmine namespace as well as classes that are monkey patched by
zone.js.

Replacing globals (describe/it/etc) doesn't trigger a warning because they
belong to the user and are expected to be replaced.
This commit is contained in:
Steve Gravrock
2025-11-25 16:54:08 -08:00
parent 32168be6c7
commit 23894c1a0a
15 changed files with 315 additions and 47 deletions

View File

@@ -13,7 +13,72 @@ describe('The jasmine namespace', function() {
expect(setDifference(actualKeys, expectedKeys())).toEqual(new Set());
});
function expectedKeys() {
describe('Warning about monkey patching', function() {
beforeEach(function() {
spyOn(console, 'error');
});
for (const key of expectedKeys(false)) {
if (!key.startsWith('MAX_') && key !== 'private' && key !== 'getEnv') {
describe(`jasmine.${key}`, function() {
let orig;
beforeEach(function() {
orig = jasmineUnderTest[key];
});
afterEach(function() {
jasmineUnderTest[key] = orig;
});
it('warns if monkey patched', function() {
const patch = {};
jasmineUnderTest[key] = patch;
verifyDeprecation();
expect(jasmineUnderTest[key]).toBe(patch);
});
});
}
}
// These specs rely on jasmineRequire being exposed. That only happens
// in browsers.
if (typeof document !== 'undefined') {
const statics = ['addMatchers', 'clock', 'createSpyObj'];
for (const name of statics) {
describe(`jasmine.${name}`, function() {
let bootedCore, env, orig;
beforeEach(function() {
bootedCore = jasmineRequire.core(jasmineRequire);
env = bootedCore.getEnv();
jasmineRequire.interface(bootedCore, env);
orig = bootedCore[name];
});
afterEach(function() {
bootedCore[name] = orig;
env.cleanup_();
});
it(`warns if jasmine.${name} is monkey patched`, function() {
const patch = {};
bootedCore[name] = patch;
verifyDeprecation();
expect(bootedCore[name]).toBe(patch);
});
});
}
}
});
function expectedKeys(includeHtml) {
if (includeHtml === undefined) {
includeHtml = typeof window !== 'undefined';
}
// Does not include properties added by requireInterface(), since that isn't
// called by defineJasmineUnderTest.js/nodeDefineJasmineUnderTest.js.
const result = new Set([
@@ -51,7 +116,7 @@ describe('The jasmine namespace', function() {
'getGlobal'
]);
if (typeof window !== 'undefined') {
if (includeHtml) {
// jasmine-html.js
result.add('HtmlReporter');
result.add('HtmlReporterV2');
@@ -76,4 +141,15 @@ describe('The jasmine namespace', function() {
return result;
}
function verifyDeprecation() {
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledOnceWith(
jasmine.stringContaining('DEPRECATION: Monkey patching detected.')
);
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledOnceWith(
jasmine.stringContaining('jasmineNamespaceSpec.js')
);
}
});