setSystemTime is not used for cookie expiry when using the JSDom environment
🐛 Bug Report
When using the jsdom environment, if mock out the system time using setSystemTime, cookies that I would expect to have expired are not removed from document.cookie. However, if i create a JSDOM instance manually, the same tests pass.
This fails:
/**
* @jest-environment jsdom
*/
describe('Using the JSDOM environment', () => {
it('should be able to expire mock cookie expiry', () => {
expect(document.cookie).toBe("");
const maxAge = 5;
document.cookie = `KEY=VALUE; max-age=${maxAge}`;
expect(document.cookie).toBe("KEY=VALUE");
jest.useFakeTimers("modern")
jest.setSystemTime(Date.now() + maxAge * 60 * 1000);
expect(document.cookie).toBe("");
jest.useRealTimers();
});
});
While this passes:
const jsdom = require('jsdom')
describe('Creating a JSDOM instance manually', () => {
it('should be able to expire mock cookie expiry', () => {
const dom = new jsdom.JSDOM();
const document = dom.window.document;
expect(document.cookie).toBe("");
const maxAge = 5;
document.cookie = `KEY=VALUE; max-age=${maxAge}`;
expect(document.cookie).toBe("KEY=VALUE");
jest.useFakeTimers("modern")
jest.setSystemTime(Date.now() + maxAge * 60 * 1000);
expect(document.cookie).toBe("");
jest.useRealTimers();
});
});
To Reproduce
Steps to reproduce the behavior: Clone the reproduction repo and run:
npm install
npm run test
Expected behavior
I'd expect both tests to pass. More precisely, I'd expect the jsdom environment to behave the same as a manual jsdom instance and respect the mock system time.
Link to repl or repo (highly encouraged)
reproduction here: https://github.com/adharris/jest-date-mock-issue
envinfo
System:
OS: macOS 10.15.7
CPU: (8) x64 Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
Binaries:
Node: 14.16.1 - /usr/local/bin/node
Yarn: 1.22.4 - /usr/local/bin/yarn
npm: 6.14.12 - /usr/local/bin/npm
npmPackages:
jest: ^27.0.4 => 27.0.4
The cause
This "bug" (or better: oversight) is caused by jsdom rather than jest. Their document.cookie implementation relies on the tough-cookie package that has the ability to work with arbitrary unix timestamps as a time reference to decide upon cookie expiration – jsdom simply doesn't make use of it.
Here's the relevant tough-cookie method:
// RFC6365 S5.4
getCookies(url, options, cb) {
…
const now = options.now || Date.now(); <---
…
And here's jsdom's invocation (it's indirect, going through some proxy calls but that doesn't matter):
…
get cookie() {
return this._cookieJar.getCookieStringSync(this.URL, { http: false }); <---
}
…
Notice the { http: false } at the end? This is the options argument from above, and it's lacking a now property which in turn will let tough-cookie fallback to the current system time. The problem: this is the jest main process's system time; not the one we are able to manipulate inside our test VMs!
The quick fix
As for a workaround we can monkey patch the VM's document.cookie. The patch requires a reference to jsdom, though, which isn't exposed by the built-in jest-environment-jsdom package. Luckily, there's a package that does and it's called jest-environment-jsdom-global. It is a drop-in replacement sitting on top of the built-in environment and all it does is exposing the jsdom reference.
$ npm i -D jest-environment-jsdom-global
Let's first tell jest about our new environment and a custom setup module:
// File: jest.config.mjs
export default {
…
setupFilesAfterEnv: [ "<rootDir>/jest.setup.mjs" ],
…
testEnvironment: "jest-environment-jsdom-global",
…
}
With that installed and out of the way we can finally apply our patch:
// File: jest.setup.mjs
Object.defineProperty(document, 'cookie', {
get() {
return jsdom.cookieJar.getCookieStringSync(this.URL, { http: false, now: Date.now() });
},
set(cookieStr) {
jsdom.cookieJar.setCookieSync(String(cookieStr), this.URL, { http: false, ignoreError: true });
},
});
Thanks for digging @g2guy-aefxx! Do you think this could be patched in JSDOM? So it would pass in its own vm's Date.now() instead of the global one?
@SimenB Yes, that is totally possible. In fact, this is what I did while debugging.
Would you be up for submitting a PR to jsdom doing so?
This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 30 days.
This issue was closed because it has been stalled for 30 days with no activity. Please open a new issue if the issue is still relevant, linking to this one.
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.