import vm from "vm" ; import type { JestEnvironment } from "@jest/environment" ; import { LegacyFakeTimers , ModernFakeTimers } from "@jest/fake-timers" ; import type { Circus , Config , Global } from "@jest/types" ; import { CachePlugin } from "@miniflare/cache" ; import { BindingsPlugin , CorePlugin , MiniflareCore } from "@miniflare/core" ; import { DurableObjectId , DurableObjectStorage , DurableObjectsPlugin , } from "@miniflare/durable-objects" ; import { HTMLRewriterPlugin } from "@miniflare/html-rewriter" ; import { KVPlugin } from "@miniflare/kv" ; import { VMScriptRunner , defineHasInstances } from "@miniflare/runner-vm" ; import { Context , NoOpLog } from "@miniflare/shared" ; import { SitesPlugin } from "@miniflare/sites" ; import { WebSocketPlugin } from "@miniflare/web-sockets" ; import { ModuleMocker } from "jest-mock" ; import { installCommonGlobals } from "jest-util" ; import { StackedMemoryStorageFactory } from "./storage" ; declare global { function getMiniflareBindings < Bindings = Context > ( ) ; function getMiniflareDurableObjectStorage ( id ) ; } const PLUGINS = { CorePlugin , KVPlugin , DurableObjectsPlugin , CachePlugin , SitesPlugin , HTMLRewriterPlugin , WebSocketPlugin , BindingsPlugin , } ; export type Timer = { id : number ; ref : ( ) => Timer ; unref : ( ) => Timer ; } ; const log = new NoOpLog ( ) ; export default class MiniflareEnvironment implements JestEnvironment { private readonly config : Config . ProjectConfig ; private context : vm . Context | null ; fakeTimers : LegacyFakeTimers < Timer > | null ; fakeTimersModern : ModernFakeTimers | null ; global : Global . Global ; moduleMocker : ModuleMocker | null ; private readonly storageFactory = new StackedMemoryStorageFactory ( ) ; private readonly scriptRunner : VMScriptRunner ; constructor ( config ) { this . config = config ; this . context = vm . createContext ( { } ) ; defineHasInstances ( this . context ) ; this . scriptRunner = new VMScriptRunner ( this . context ) ; const global = ( this . global = vm . runInContext ( "this" , this . context ) ) ; global . global = global ; global . self = global ; global . clearInterval = clearInterval ; global . clearTimeout = clearTimeout ; global . setInterval = setInterval ; global . setTimeout = setTimeout ; global . Buffer = Buffer ; installCommonGlobals ( global , this . config . globals ) ; this . moduleMocker = new ModuleMocker ( global ) ; const timerIdToRef = ( id ) => ( { id , ref ( ) { return this ; } , unref ( ) { return this ; } , } ) ; const timerRefToId = ( timer ) : number | undefined => ( timer && timer . id ) || undefined ; this . fakeTimers = new LegacyFakeTimers ( { config : this . config , global , moduleMocker : this . moduleMocker ! , timerConfig : { idToRef : timerIdToRef , refToId : timerRefToId } , } ) ; this . fakeTimersModern = new ModernFakeTimers ( { config : this . config , global , } ) ; } async setup ( ) { const global = this . global as any ; const mf = new MiniflareCore ( PLUGINS , { log , storageFactory : this . storageFactory , scriptRunner : this . scriptRunner , scriptRunForModuleExports : true , } , { wranglerConfigPath : true , packagePath : true , envPath : true , ... this . config . testEnvironmentOptions , globals : { ... ( this . config . testEnvironmentOptions ?. globals as any ) , console : global . console , setTimeout : global . setTimeout , setInterval : global . setInterval , clearTimeout : global . clearTimeout , clearInterval : global . clearInterval , } , watch : false , kvPersist : false , cachePersist : false , durableObjectsPersist : false , globalAsyncIO : true , globalTimers : true , globalRandom : true , } ) ; const mfGlobalScope = await mf . getGlobalScope ( ) ; mfGlobalScope . global = global ; mfGlobalScope . self = global ; Object . assign ( global , mfGlobalScope ) ; const bindings = await mf . getBindings ( ) ; global . getMiniflareBindings = ( ) => bindings ; global . getMiniflareDurableObjectStorage = async ( id ) => { const plugin = ( await mf . getPlugins ( ) ) . DurableObjectsPlugin ; const storage = mf . getPluginStorage ( "DurableObjectsPlugin" ) ; const state = await plugin . getObject ( storage , id ) ; return state . storage ; } ; } async teardown ( ) { this . fakeTimers ?. dispose ( ) ; this . fakeTimersModern ?. dispose ( ) ; this . context = null ; this . fakeTimers = null ; this . fakeTimersModern = null ; } getVmContext ( ) { return this . context ; } handleTestEvent ( event , _state ) { if ( event . name === "run_describe_start" || event . name === "test_start" ) { this . storageFactory . push ( ) ; } if ( event . name === "run_describe_finish" || event . name === "test_done" ) { this . storageFactory . pop ( ) ; } } }