Mocking `loadstring` doesn't work with ui labs with the global mock loadstring

//test.story.ts
import fixLoadModule from "common/shared/utils/fix-load-module";
import { runTests } from "common/shared/utils/run-tests";
import { FunctionStory } from "@rbxts/ui-labs";

fixLoadModule();

export = (() => {
runTests();
return () => {};
}) satisfies FunctionStory;
//test.story.ts
import fixLoadModule from "common/shared/utils/fix-load-module";
import { runTests } from "common/shared/utils/run-tests";
import { FunctionStory } from "@rbxts/ui-labs";

fixLoadModule();

export = (() => {
runTests();
return () => {};
}) satisfies FunctionStory;
function fixLoadModule()
local fenv = getfenv()
local metaenv = getmetatable(fenv)
local original = loadstring

metaenv.__index.loadstring = function(str, ...)
local virtual = table.pack(original(str, ...))
setfenv(
virtual[1],
setmetatable(
{
_G = fenv._G,
require = fenv.require,
script = str,
} :: any,
metaenv
)
)
return unpack(virtual)
end
end

return fixLoadModule
function fixLoadModule()
local fenv = getfenv()
local metaenv = getmetatable(fenv)
local original = loadstring

metaenv.__index.loadstring = function(str, ...)
local virtual = table.pack(original(str, ...))
setfenv(
virtual[1],
setmetatable(
{
_G = fenv._G,
require = fenv.require,
script = str,
} :: any,
metaenv
)
)
return unpack(virtual)
end
end

return fixLoadModule
No description
78 Replies
duck
duckOP2w ago
cc @PepeElToro41 this is my setup
declare global {
interface _G {
NOCOLOR: boolean;
}
}
_G.NOCOLOR = true;
import { ServerScriptService, ReplicatedStorage, StarterPlayer } from "@rbxts/services";
import { runCLI } from "@rbxts/jest";

const setupFileTree = $getModuleTree("./setup");

// TODO - extract this into a utility function if other scripts need it
function FindFirstChildRecursive(parent: Instance, tree: Array<string>) {
const childName = tree.shift();
if (!childName) return parent;
const childInstance = parent.FindFirstChild(childName);
assert(childInstance, "cannot find child instance");
FindFirstChildRecursive(childInstance, tree);
}

const setupFileInstance = FindFirstChildRecursive(setupFileTree[0], setupFileTree[1]) as ModuleScript;

export function runTests() {
runCLI(script, { setupFiles: [setupFileInstance], verbose: false, ci: false }, [
ReplicatedStorage.WaitForChild("TS").WaitForChild("__tests__"),
ServerScriptService.WaitForChild("TS").WaitForChild("__tests__"),
StarterPlayer.WaitForChild("StarterPlayerScripts").WaitForChild("TS").WaitForChild("__tests__"),
ReplicatedStorage.WaitForChild("common").WaitForChild("__tests__"),
ServerScriptService.WaitForChild("common").WaitForChild("__tests__"),
StarterPlayer.WaitForChild("StarterPlayerScripts").WaitForChild("common").WaitForChild("__tests__"),
]);
}
declare global {
interface _G {
NOCOLOR: boolean;
}
}
_G.NOCOLOR = true;
import { ServerScriptService, ReplicatedStorage, StarterPlayer } from "@rbxts/services";
import { runCLI } from "@rbxts/jest";

const setupFileTree = $getModuleTree("./setup");

// TODO - extract this into a utility function if other scripts need it
function FindFirstChildRecursive(parent: Instance, tree: Array<string>) {
const childName = tree.shift();
if (!childName) return parent;
const childInstance = parent.FindFirstChild(childName);
assert(childInstance, "cannot find child instance");
FindFirstChildRecursive(childInstance, tree);
}

const setupFileInstance = FindFirstChildRecursive(setupFileTree[0], setupFileTree[1]) as ModuleScript;

export function runTests() {
runCLI(script, { setupFiles: [setupFileInstance], verbose: false, ci: false }, [
ReplicatedStorage.WaitForChild("TS").WaitForChild("__tests__"),
ServerScriptService.WaitForChild("TS").WaitForChild("__tests__"),
StarterPlayer.WaitForChild("StarterPlayerScripts").WaitForChild("TS").WaitForChild("__tests__"),
ReplicatedStorage.WaitForChild("common").WaitForChild("__tests__"),
ServerScriptService.WaitForChild("common").WaitForChild("__tests__"),
StarterPlayer.WaitForChild("StarterPlayerScripts").WaitForChild("common").WaitForChild("__tests__"),
]);
}
PepeElToro41
PepeElToro412w ago
still the same problem? can you check if your custom loadstring does print to see if it's actually using it
duck
duckOP2w ago
printn what?
PepeElToro41
PepeElToro412w ago
here the fixLoadModule also, loadmodule asked for a script, but in loadstring it just asks for a string, so, you can remove the script part, I'd imagine jest would replace it anyway
duck
duckOP2w ago
No description
duck
duckOP2w ago
No description
duck
duckOP2w ago
fenv.require?
PepeElToro41
PepeElToro412w ago
that is fine, jest would probably replace that one too the problem is just the _G table let me see how jest loads it
duck
duckOP2w ago
No description
duck
duckOP2w ago
OH why does this happen though?
PepeElToro41
PepeElToro412w ago
loadstring resets the current environment
PepeElToro41
PepeElToro412w ago
here is the part that loads it
duck
duckOP2w ago
I did this instead? _G = { NOCOLOR = true }, don't really understannd so I would need to clear it?
PepeElToro41
PepeElToro412w ago
can you print(_G) before the fixLoadModule and inside your tests and check if it's the same _G
PepeElToro41
PepeElToro412w ago
No description
PepeElToro41
PepeElToro412w ago
use this to check that it is the same address
duck
duckOP2w ago
No description
duck
duckOP2w ago
No description
PepeElToro41
PepeElToro412w ago
no address in the first one
duck
duckOP2w ago
same thing the first one I forgot to turn on
PepeElToro41
PepeElToro412w ago
so this is in the story, and inside the test, it prints the same table? if it does, they you should be fine now
duck
duckOP2w ago
import fixLoadModule from "common/shared/utils/fix-load-module"; import { runTests } from "common/shared/utils/run-tests"; import { FunctionStory } from "@rbxts/ui-labs"; print(_G); fixLoadModule(); export = (() => { runTests(); return () => {}; }) satisfies FunctionStory; no, this is the start
PepeElToro41
PepeElToro412w ago
yeah, now in your .spec files, print _G too
duck
duckOP2w ago
pirror to fix it doesn't print??
duck
duckOP2w ago
No description
PepeElToro41
PepeElToro412w ago
_G doesnt exist?
duck
duckOP2w ago
No description
PepeElToro41
PepeElToro412w ago
oh yeah I mean it makes sense, try to print it outside of the test in fact, you probably want to print it before the roblox-ts imports so maybe modify the luau files directly because that's where the error happens
duck
duckOP2w ago
nah it don't work
duck
duckOP2w ago
No description
PepeElToro41
PepeElToro412w ago
try modifying the luau transpiled files I think roblox-ts will always put the imports on top
duck
duckOP2w ago
No description
duck
duckOP2w ago
true but only for TS
duck
duckOP2w ago
No description
duck
duckOP2w ago
No description
duck
duckOP2w ago
do not know why it leads to here
PepeElToro41
PepeElToro412w ago
do you import use-hook-state somewhere in your code by any chance? like outside of the .spec tests
duck
duckOP2w ago
no
duck
duckOP2w ago
No description
duck
duckOP2w ago
its my custom useHookState and I only resetHookStorage
PepeElToro41
PepeElToro412w ago
remove the services import try doing GetService manually
duck
duckOP2w ago
replace it with game:GetService?
PepeElToro41
PepeElToro412w ago
yes
duck
duckOP2w ago
No description
duck
duckOP2w ago
nah lets see if the transformer is doing its job.. yeah
duck
duckOP2w ago
No description
duck
duckOP2w ago
it is
PepeElToro41
PepeElToro412w ago
Im running out of ideas, try just printing _G right before doing runCLI to see what has been imported by roblox-ts before that
duck
duckOP2w ago
same memory address
PepeElToro41
PepeElToro412w ago
but what entries does it have
duck
duckOP2w ago
so the idea is to make different memory addresses?
PepeElToro41
PepeElToro412w ago
no, the idea is to have the same address try commenting the fixLoadModule, the _G table should be different
duck
duckOP2w ago
No description
duck
duckOP2w ago
No description
duck
duckOP2w ago
actually the addresses are differennt
PepeElToro41
PepeElToro412w ago
ya I think I see what could be happening
duck
duckOP2w ago
_G for use-change.spec.ts
PepeElToro41
PepeElToro412w ago
because the first one always passes right?
duck
duckOP2w ago
no tests pass
PepeElToro41
PepeElToro412w ago
here
duck
duckOP2w ago
ya you gotta call it every time??
PepeElToro41
PepeElToro412w ago
yeah, so, seems like jest might call loadstring everytime or something try printing the _G table in every test file and probably do it by editing the luau files at least the first two that run
duck
duckOP2w ago
No description
duck
duckOP2w ago
dupes dupes of src. which is jest?
PepeElToro41
PepeElToro412w ago
yeah so I think jest is trying to hot-reload the script everytime I have an idea, instead of doing fenv._G, do just a new table {} here
duck
duckOP2w ago
all tests fail
PepeElToro41
PepeElToro412w ago
what is the error?
duck
duckOP2w ago
the same thing none passes
PepeElToro41
PepeElToro412w ago
and how the _G tables are looking?
duck
duckOP2w ago
nvalid module access! Do you have multiple TS runtimes trying to import this? ReplicatedStorage.common.ecs.hooks.use-hook-state I will go to sleep gn I think its probably a duplicate reference of jest src so a script that deletes the same instance referennce perchance but at the same time its not the same reference (different addresses for the instances) so the src got duped?
PepeElToro41
PepeElToro412w ago
do you only have one src? that's weird yeah I'll try to check it later myself, jest is doing some weird stuff with the globals and require
duck
duckOP2w ago
it seems like src is duped right before tests pass
No description
duck
duckOP2w ago
maybe I should make a custom plugin that runs unit tests since loadstring needs plugin security?
PepeElToro41
PepeElToro412w ago
yeah thats weird, I dont see how this would work, because, you need plugin security to use loadstring but it also doesnt work with plugins
duck
duckOP2w ago
duplicate version of src? but why though?
PepeElToro41
PepeElToro412w ago
no like, jest in general only with run-in-roblox it might be different packages src's honestly, I need to check this myself to find a fix
duck
duckOP2w ago
lmk when you fix it and ping me

Did you find this page helpful?