From 582ab138e10cae4551e30c65c4471fececd4ebe0 Mon Sep 17 00:00:00 2001 From: Natalie Rose Date: Fri, 18 Sep 2026 11:20:21 +1000 Subject: [PATCH] Fix hmr thing that broke the jest tests --- app/__mocks__/hmr.ts | 7 +++++++ app/hmr.ts | 13 +++++++++++++ app/lacuna.ts | 14 ++++++-------- app/stores/session.ts | 10 ++++------ jest.config.js | 4 ++++ 5 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 app/__mocks__/hmr.ts create mode 100644 app/hmr.ts diff --git a/app/__mocks__/hmr.ts b/app/__mocks__/hmr.ts new file mode 100644 index 00000000..28ca1e4a --- /dev/null +++ b/app/__mocks__/hmr.ts @@ -0,0 +1,7 @@ +// import.meta.hot doesn't exist outside Vite, so under Jest this is just a no-op stand-in +// for app/hmr.ts. +export function getHmrPreserved(_key: string): T | undefined { + return undefined; +} + +export function preserveOnHmr(_key: string, _value: T): void {} diff --git a/app/hmr.ts b/app/hmr.ts new file mode 100644 index 00000000..9caea519 --- /dev/null +++ b/app/hmr.ts @@ -0,0 +1,13 @@ +// Isolated so the only `import.meta.hot` references in the app live in one file: Jest +// forces ts-jest to compile to CommonJS, and TypeScript refuses `import.meta` syntax under +// that module target (TS1343). jest.config.js maps this module to a no-op mock instead. +export function getHmrPreserved(key: string): T | undefined { + return import.meta.hot?.data[key]; +} + +export function preserveOnHmr(key: string, value: T): void { + if (import.meta.hot) { + import.meta.hot.data[key] = value; + import.meta.hot.accept(); + } +} diff --git a/app/lacuna.ts b/app/lacuna.ts index 0c450651..639e777b 100644 --- a/app/lacuna.ts +++ b/app/lacuna.ts @@ -2,18 +2,19 @@ import { CAPTCHA_ERROR_CODE, Lacuna, SESSION_EXPIRED_ERROR_CODE } from '@tlecomm import * as vex from 'app/vex'; import WindowsStore from 'app/stores/windows'; import * as util from 'app/util'; +import { getHmrPreserved, preserveOnHmr } from 'app/hmr'; import environment from './environment'; import { splitStatus } from './server'; import game from 'app/game'; // Vite HMR can re-execute this module (e.g. when an update propagates up from an edited -// component) without a full page reload. Reusing the instance via import.meta.hot.data keeps +// component) without a full page reload. Reusing the instance preserved via app/hmr.ts keeps // the in-memory session id intact instead of resetting it, and guarding the setup below stops // onResponse from accumulating a duplicate handler on every such reload. -const lacuna: Lacuna = - import.meta.hot?.data.lacuna ?? new Lacuna({ serverUrl: environment.getServerUrl() }); +const preservedLacuna = getHmrPreserved('lacuna'); +const lacuna: Lacuna = preservedLacuna ?? new Lacuna({ serverUrl: environment.getServerUrl() }); -if (!import.meta.hot?.data.lacuna) { +if (!preservedLacuna) { lacuna.log.setLogLevel('info'); lacuna.onResponse(({ request, response, retry }) => { @@ -63,9 +64,6 @@ if (!import.meta.hot?.data.lacuna) { }); } -if (import.meta.hot) { - import.meta.hot.data.lacuna = lacuna; - import.meta.hot.accept(); -} +preserveOnHmr('lacuna', lacuna); export default lacuna; diff --git a/app/stores/session.ts b/app/stores/session.ts index feeb6eec..b4f0c954 100644 --- a/app/stores/session.ts +++ b/app/stores/session.ts @@ -1,4 +1,5 @@ import { makeAutoObservable } from 'mobx'; +import { getHmrPreserved, preserveOnHmr } from 'app/hmr'; class SessionStore { session = ''; @@ -13,13 +14,10 @@ class SessionStore { } // Vite HMR can re-execute this module (e.g. when an update propagates up from an edited -// component) without a full page reload. Reusing the instance via import.meta.hot.data keeps +// component) without a full page reload. Reusing the instance preserved via app/hmr.ts keeps // the in-memory session id intact instead of resetting to '' on every such reload. -const store: SessionStore = import.meta.hot?.data.sessionStore ?? new SessionStore(); +const store: SessionStore = getHmrPreserved('sessionStore') ?? new SessionStore(); -if (import.meta.hot) { - import.meta.hot.data.sessionStore = store; - import.meta.hot.accept(); -} +preserveOnHmr('sessionStore', store); export default store; diff --git a/jest.config.js b/jest.config.js index a3cb54e5..0d6082a3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -31,6 +31,10 @@ const config = { // react-markdown is ESM-only and cannot be required by Jest's CommonJS runtime '^react-markdown$': '/app/__mocks__/reactMarkdown.tsx', + // app/hmr.ts uses import.meta.hot, which TypeScript refuses to compile to the CommonJS + // module ts-jest forces for Jest (TS1343); swap in the no-op mock instead + '^app/hmr$': '/app/__mocks__/hmr.ts', + // app/ absolute module alias 'app/(.*)': '/app/$1', }, -- 2.51.2