From 206e8cff82f3157d9f2ac1f3b038ba42a9af6dc3 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Jun 2026 16:10:23 +0900 Subject: [PATCH] fix!: allow mutating happy-dom/jsdom window object (#10373) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Codex --- docs/guide/migration.md | 4 +++ packages/vitest/src/integrations/env/utils.ts | 4 +++ test/unit/test/environments/happy-dom.spec.ts | 25 +++++++++++++++++++ test/unit/test/environments/jsdom.spec.ts | 13 ++++++++++ 4 files changed, 46 insertions(+) diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 1bc18c249..ea3892bcb 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -134,6 +134,10 @@ $ cd subdir && vitest # [!code --] $ cd subdir && vitest --config ../vitest.config.ts # [!code ++] ``` +### DOM Environment Global Assignments Now Update the Underlying Window + +Assignments to properties on `globalThis` or `window` in `jsdom` and `happy-dom` environments are now propagated to the underlying DOM implementation. Mutable properties such as `innerWidth` can affect APIs implemented by the DOM environment, for example `happy-dom`'s `matchMedia`. + ## Migrating to Vitest 4.0 {#vitest-4} ::: warning Prerequisites diff --git a/packages/vitest/src/integrations/env/utils.ts b/packages/vitest/src/integrations/env/utils.ts index bbbeee5fb..f8c4a75ef 100644 --- a/packages/vitest/src/integrations/env/utils.ts +++ b/packages/vitest/src/integrations/env/utils.ts @@ -78,6 +78,10 @@ export function populateGlobal( }, set(v) { overrideObject.set(key, v) + // propagate changes to underlying window implementation, + // which can affect other window API behavior internally, e.g. + // updating `innerWidth` affects `matchMedia("(max-width: *)")` on happy-dom. + win[key] = v }, configurable: true, }) diff --git a/test/unit/test/environments/happy-dom.spec.ts b/test/unit/test/environments/happy-dom.spec.ts index 47bea78d8..6d99b3219 100644 --- a/test/unit/test/environments/happy-dom.spec.ts +++ b/test/unit/test/environments/happy-dom.spec.ts @@ -39,3 +39,28 @@ test('can pass down a simple form data', async () => { await req.formData() })()).resolves.not.toThrow() }) + +test('innerWidth and matchMedia', () => { + expect(window.innerWidth).toBe(1024) + expect(window.matchMedia('(max-width: 100px)').matches).toBe(false) + window.innerWidth = 50 + expect(window.matchMedia('(max-width: 100px)').matches).toBe(true) +}) + +test('readonly window assignment throws', ({ task }) => { + // happy-dom's vmThreads setup returns Window as a Node VM context directly. + // Node contextification reports this getter-only assignment as successful, + // unlike the populateGlobal facade used by threads/forks. + if (task.file.pool === 'vmThreads') { + expect(() => { + Object.assign(window, { navigator: {} }) + }).not.toThrow() + return + } + + expect(() => { + Object.assign(window, { navigator: {} }) + }).toThrowErrorMatchingInlineSnapshot( + `[TypeError: Cannot set property navigator of # which has only a getter]`, + ) +}) diff --git a/test/unit/test/environments/jsdom.spec.ts b/test/unit/test/environments/jsdom.spec.ts index c2d8ab6c5..bac68eea1 100644 --- a/test/unit/test/environments/jsdom.spec.ts +++ b/test/unit/test/environments/jsdom.spec.ts @@ -335,3 +335,16 @@ test('jsdom global is exposed', () => { test('ssr is disabled', () => { expect(import.meta.env.SSR).toBe(false) }) + +test('innerWidth and matchMedia', () => { + expect(window.innerWidth).toBe(1024) + expect(window.matchMedia).toBe(undefined) +}) + +test('readonly window assignment throws', () => { + expect(() => { + Object.assign(window, { navigator: {} }) + }).toThrowErrorMatchingInlineSnapshot( + `[TypeError: Cannot set property navigator of [object Window] which has only a getter]`, + ) +}) -- 2.51.2