Deprecate access to non-public Spec properties in spec filters

This commit is contained in:
Steve Gravrock
2025-09-14 11:34:06 -07:00
parent 6d77f3e7f0
commit 3cbf4dc27b
6 changed files with 125 additions and 10 deletions

View File

@@ -66,6 +66,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.getClearStack = jRequire.clearStack(j$); j$.getClearStack = jRequire.clearStack(j$);
j$.Clock = jRequire.Clock(); j$.Clock = jRequire.Clock();
j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
j$.deprecatingSpecProxy = jRequire.deprecatingSpecProxy(j$);
j$.Deprecator = jRequire.Deprecator(j$); j$.Deprecator = jRequire.Deprecator(j$);
j$.Configuration = jRequire.Configuration(j$); j$.Configuration = jRequire.Configuration(j$);
j$.Env = jRequire.Env(j$); j$.Env = jRequire.Env(j$);
@@ -1498,7 +1499,8 @@ getJasmineRequireObj().Env = function(j$) {
runQueue, runQueue,
TreeProcessor: j$.TreeProcessor, TreeProcessor: j$.TreeProcessor,
globalErrors, globalErrors,
getConfig: () => config getConfig: () => config,
deprecated: this.deprecated
}); });
this.setParallelLoadingState = function(state) { this.setParallelLoadingState = function(state) {
@@ -3711,6 +3713,43 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
return DelayedFunctionScheduler; return DelayedFunctionScheduler;
}; };
// TODO: Remove this in the next major release.
getJasmineRequireObj().deprecatingSpecProxy = function(j$) {
const allowedMembers = ['id', 'description', 'getFullName', 'getPath'];
function isMember(target, prop) {
return (
Object.keys(target).indexOf(prop) !== -1 ||
Object.keys(j$.Spec.prototype).indexOf(prop) !== -1
);
}
function msg(member) {
const memberName = member.toString().replace(/^Symbol\((.+)\)$/, '$1');
return (
'Access to private Spec members (in this case `' +
memberName +
'`) via spec filters is not supported and will break in ' +
'a future release. See <https://jasmine.github.io/api/edge/Spec.html> ' +
'for correct usage.'
);
}
function deprecatingSpecProxy(spec, deprecated) {
return new Proxy(spec, {
get(target, prop, receiver) {
if (isMember(target, prop) && !allowedMembers.includes(prop)) {
deprecated(msg(prop));
}
return target[prop];
}
});
}
return deprecatingSpecProxy;
};
getJasmineRequireObj().Deprecator = function(j$) { getJasmineRequireObj().Deprecator = function(j$) {
function Deprecator(topSuite) { function Deprecator(topSuite) {
this.topSuite_ = topSuite; this.topSuite_ = topSuite;
@@ -9440,6 +9479,7 @@ getJasmineRequireObj().Runner = function(j$) {
#globalErrors; #globalErrors;
#reportDispatcher; #reportDispatcher;
#getConfig; #getConfig;
#deprecated;
#executedBefore; #executedBefore;
#currentRunableTracker; #currentRunableTracker;
@@ -9453,6 +9493,7 @@ getJasmineRequireObj().Runner = function(j$) {
this.#globalErrors = options.globalErrors; this.#globalErrors = options.globalErrors;
this.#reportDispatcher = options.reportDispatcher; this.#reportDispatcher = options.reportDispatcher;
this.#getConfig = options.getConfig; this.#getConfig = options.getConfig;
this.#deprecated = options.deprecated;
this.#executedBefore = false; this.#executedBefore = false;
this.#currentRunableTracker = new j$.CurrentRunableTracker(); this.#currentRunableTracker = new j$.CurrentRunableTracker();
} }
@@ -9505,8 +9546,9 @@ getJasmineRequireObj().Runner = function(j$) {
orderChildren: function(node) { orderChildren: function(node) {
return order.sort(node.children); return order.sort(node.children);
}, },
excludeNode: function(spec) { excludeNode: spec => {
return !config.specFilter(spec); const proxy = j$.deprecatingSpecProxy(spec, this.#deprecated);
return !config.specFilter(proxy);
} }
}); });
this.#executionTree = treeProcessor.processTree(); this.#executionTree = treeProcessor.processTree();

View File

@@ -913,6 +913,7 @@ describe('Env integration', function() {
}); });
env.configure({ env.configure({
random: false,
specFilter: function(spec) { specFilter: function(spec) {
return /^first suite/.test(spec.getFullName()); return /^first suite/.test(spec.getFullName());
} }
@@ -920,13 +921,44 @@ describe('Env integration', function() {
await env.execute(); await env.execute();
expect(calls.length).toEqual(2); expect(calls).toEqual(['first spec', 'second spec']);
expect(calls).toEqual(
jasmine.arrayContaining(['first spec', 'second spec'])
);
expect(suiteCallback).toHaveBeenCalled(); expect(suiteCallback).toHaveBeenCalled();
}); });
it('reports a deprecation warning when a spec filter accesses private properties', async function() {
env.it('a spec', function() {});
const reporter = jasmine.createSpyObj('reporter', ['jasmineDone']);
env.addReporter(reporter);
env.configure({
random: false,
specFilter: function(spec) {
spec.result; // deprecated
spec.id; // not deprecated
spec.description; // not deprecated
spec.getPath(); // not deprecated
spec.getFullName(); // not deprecated
return true;
}
});
spyOn(console, 'error');
await env.execute();
expect(reporter.jasmineDone).toHaveBeenCalledWith(
jasmine.objectContaining({
deprecationWarnings: [
jasmine.objectContaining({
message: jasmine.stringContaining(
'Access to private Spec members (in this case `result`)'
)
})
]
})
);
});
it('Functions can be spied on and have their calls tracked', async function() { it('Functions can be spied on and have their calls tracked', async function() {
let originalFunctionWasCalled = false; let originalFunctionWasCalled = false;
const subject = { const subject = {

View File

@@ -371,7 +371,8 @@ getJasmineRequireObj().Env = function(j$) {
runQueue, runQueue,
TreeProcessor: j$.TreeProcessor, TreeProcessor: j$.TreeProcessor,
globalErrors, globalErrors,
getConfig: () => config getConfig: () => config,
deprecated: this.deprecated
}); });
this.setParallelLoadingState = function(state) { this.setParallelLoadingState = function(state) {

View File

@@ -10,6 +10,7 @@ getJasmineRequireObj().Runner = function(j$) {
#globalErrors; #globalErrors;
#reportDispatcher; #reportDispatcher;
#getConfig; #getConfig;
#deprecated;
#executedBefore; #executedBefore;
#currentRunableTracker; #currentRunableTracker;
@@ -23,6 +24,7 @@ getJasmineRequireObj().Runner = function(j$) {
this.#globalErrors = options.globalErrors; this.#globalErrors = options.globalErrors;
this.#reportDispatcher = options.reportDispatcher; this.#reportDispatcher = options.reportDispatcher;
this.#getConfig = options.getConfig; this.#getConfig = options.getConfig;
this.#deprecated = options.deprecated;
this.#executedBefore = false; this.#executedBefore = false;
this.#currentRunableTracker = new j$.CurrentRunableTracker(); this.#currentRunableTracker = new j$.CurrentRunableTracker();
} }
@@ -75,8 +77,9 @@ getJasmineRequireObj().Runner = function(j$) {
orderChildren: function(node) { orderChildren: function(node) {
return order.sort(node.children); return order.sort(node.children);
}, },
excludeNode: function(spec) { excludeNode: spec => {
return !config.specFilter(spec); const proxy = j$.deprecatingSpecProxy(spec, this.#deprecated);
return !config.specFilter(proxy);
} }
}); });
this.#executionTree = treeProcessor.processTree(); this.#executionTree = treeProcessor.processTree();

View File

@@ -0,0 +1,36 @@
// TODO: Remove this in the next major release.
getJasmineRequireObj().deprecatingSpecProxy = function(j$) {
const allowedMembers = ['id', 'description', 'getFullName', 'getPath'];
function isMember(target, prop) {
return (
Object.keys(target).indexOf(prop) !== -1 ||
Object.keys(j$.Spec.prototype).indexOf(prop) !== -1
);
}
function msg(member) {
const memberName = member.toString().replace(/^Symbol\((.+)\)$/, '$1');
return (
'Access to private Spec members (in this case `' +
memberName +
'`) via spec filters is not supported and will break in ' +
'a future release. See <https://jasmine.github.io/api/edge/Spec.html> ' +
'for correct usage.'
);
}
function deprecatingSpecProxy(spec, deprecated) {
return new Proxy(spec, {
get(target, prop, receiver) {
if (isMember(target, prop) && !allowedMembers.includes(prop)) {
deprecated(msg(prop));
}
return target[prop];
}
});
}
return deprecatingSpecProxy;
};

View File

@@ -42,6 +42,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.getClearStack = jRequire.clearStack(j$); j$.getClearStack = jRequire.clearStack(j$);
j$.Clock = jRequire.Clock(); j$.Clock = jRequire.Clock();
j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
j$.deprecatingSpecProxy = jRequire.deprecatingSpecProxy(j$);
j$.Deprecator = jRequire.Deprecator(j$); j$.Deprecator = jRequire.Deprecator(j$);
j$.Configuration = jRequire.Configuration(j$); j$.Configuration = jRequire.Configuration(j$);
j$.Env = jRequire.Env(j$); j$.Env = jRequire.Env(j$);