player/tests/rbxl-lua-mvp.test.js
min a5e1558c2d
All checks were successful
CI / Lint (push) Successful in 54s
CI / Build (push) Successful in 1m30s
CI / Secret scan (push) Successful in 20s
CI / PR size check (push) Has been skipped
CI / Deploy to S1 + S2 (push) Successful in 2m56s
feat(player): ������������� �� ������� (Lua + JS-API + Roblox-������ + LoadingOverlay)
2026-06-09 22:01:51 +00:00

188 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* rbxl-lua-mvp.test.js — headless smoke-тест Roblox Lua API shim.
*
* НЕ запускает Worker (это требует браузерного Worker API). Вместо этого
* напрямую импортирует roblox-shim.js и инициализирует Lua в текущем потоке.
*
* Запуск: node --experimental-vm-modules tests/rbxl-lua-mvp.test.js
*/
import { LuaFactory } from 'wasmoon';
import { registerRobloxApi } from '../src/engine/roblox-shim.js';
const FAKE_SCENE_SNAP = {
primitives: {
1: { id: 1, type: 'cube', name: 'Floor', x: 0, y: 0, z: 0, sx: 10, sy: 1, sz: 10,
color: '#888888', material: 'glossy', anchored: true, canCollide: true, opacity: 1 },
2: { id: 2, type: 'cube', name: 'KillBrick', x: 5, y: 1, z: 0, sx: 2, sy: 1, sz: 2,
color: '#ff0000', material: 'neon', anchored: true, canCollide: true, opacity: 1 },
},
};
const SNIPPETS = [
{
name: 'print hello',
lua: `print("Hello from Lua!")`,
expectLogs: [{ level: 'info', text: 'Hello from Lua!' }],
},
{
name: 'Vector3 math',
lua: `
local v = Vector3.new(3, 4, 0)
print("magnitude:", v.Magnitude)
local u = v.Unit
print("unit:", u.X, u.Y, u.Z)
`,
expectLogs: [
{ level: 'info', text: 'magnitude:\t5' },
],
},
{
name: 'workspace iteration',
lua: `
local children = workspace:GetChildren()
print("count:", #children)
for i, c in ipairs(children) do
print("child:", c.Name, "class:", c.ClassName)
end
`,
expectLogs: [
{ level: 'info', text: 'count:\t2' },
],
},
{
name: 'FindFirstChild',
lua: `
local kb = workspace:FindFirstChild("KillBrick")
if kb then print("found:", kb.Name)
else print("not found") end
`,
expectLogs: [{ level: 'info', text: 'found:\tKillBrick' }],
},
{
name: 'Part.Position get',
lua: `
local kb = workspace:FindFirstChild("KillBrick")
print("position:", kb.Position.X, kb.Position.Y, kb.Position.Z)
`,
expectLogs: [{ level: 'info', text: 'position:\t5\t1\t0' }],
},
{
name: 'Part.Color set',
lua: `
local kb = workspace:FindFirstChild("KillBrick")
kb.Color = Color3.new(0, 1, 0)
print("new color hex (via Position):", kb.Color.R, kb.Color.G, kb.Color.B)
`,
expectPartSet: { primId: 2, prop: 'color' },
},
{
name: 'CFrame.Angles',
lua: `
local cf = CFrame.Angles(0, math.pi/2, 0)
print("lookvector:", cf.LookVector.X, cf.LookVector.Y, cf.LookVector.Z)
`,
expectLogs: [],
},
{
name: 'Instance.new + Parent',
lua: `
local f = Instance.new("Folder", workspace)
f.Name = "MyFolder"
print("folder name:", f.Name, "parent:", f.Parent.Name)
`,
expectLogs: [{ level: 'info', text: 'folder name:\tMyFolder\tparent:\tWorkspace' }],
},
{
name: 'IsA hierarchy',
lua: `
local kb = workspace:FindFirstChild("KillBrick")
print("isa Part:", kb:IsA("Part"))
print("isa BasePart:", kb:IsA("BasePart"))
print("isa Instance:", kb:IsA("Instance"))
print("isa Sound:", kb:IsA("Sound"))
`,
expectLogs: [
{ level: 'info', text: 'isa Part:\ttrue' },
{ level: 'info', text: 'isa BasePart:\ttrue' },
{ level: 'info', text: 'isa Instance:\ttrue' },
{ level: 'info', text: 'isa Sound:\tfalse' },
],
},
];
async function runSnippet(snippet) {
const factory = new LuaFactory();
const lua = await factory.createEngine();
const logs = [];
const sent = [];
const send = (cmd, payload) => sent.push({ cmd, payload });
registerRobloxApi(lua, {
getSceneSnap: () => FAKE_SCENE_SNAP,
targetPrimitiveId: 2, // как будто скрипт прикреплён к KillBrick
send,
});
// Перехват print через send('log', ...)
let errMsg = null;
try {
await lua.doString(snippet.lua);
} catch (e) {
errMsg = e && e.message ? e.message : String(e);
}
lua.global.close();
const captured = sent.filter(s => s.cmd === 'log');
return { logs: captured.map(s => s.payload), partSets: sent.filter(s => s.cmd === 'partSet'), error: errMsg };
}
(async () => {
let passed = 0;
let failed = 0;
for (const s of SNIPPETS) {
const result = await runSnippet(s);
const ok = checkExpectations(s, result);
if (ok.success) {
console.log(`${s.name}`);
passed++;
} else {
console.log(`${s.name}`);
console.log(` error: ${result.error || 'none'}`);
console.log(` logs received:`);
for (const l of result.logs) console.log(` [${l.level}] ${JSON.stringify(l.text)}`);
if (result.partSets.length) {
console.log(` partSets:`, JSON.stringify(result.partSets));
}
console.log(` reason: ${ok.reason}`);
failed++;
}
}
console.log(`\n${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);
})();
function checkExpectations(snippet, result) {
if (result.error) {
return { success: false, reason: `lua error: ${result.error}` };
}
if (snippet.expectLogs) {
for (const exp of snippet.expectLogs) {
const found = result.logs.find(l => l.level === exp.level && l.text === exp.text);
if (!found) {
return { success: false, reason: `missing log: [${exp.level}] ${JSON.stringify(exp.text)}` };
}
}
}
if (snippet.expectPartSet) {
const found = result.partSets.find(s =>
s.payload.primId === snippet.expectPartSet.primId &&
s.payload.prop === snippet.expectPartSet.prop
);
if (!found) {
return { success: false, reason: `missing partSet ${JSON.stringify(snippet.expectPartSet)}` };
}
}
return { success: true };
}