/** * rbxl-lua-services.test.js — тесты Humanoid, RemoteEvent, DataStore, HttpService. */ import { LuaFactory } from 'wasmoon'; import { registerRobloxApi } from '../src/engine/roblox-shim.js'; import { RobloxScheduler } from '../src/engine/roblox-scheduler.js'; import { installRobloxServices } from '../src/engine/roblox-services.js'; const SCENE = { primitives: {} }; const STORE = new Map(); async function run(luaSource, ticks = []) { const factory = new LuaFactory(); const lua = await factory.createEngine(); const sent = []; const send = (cmd, payload) => sent.push({ cmd, payload }); let playerState = { x: 0, y: 5, z: 0 }; registerRobloxApi(lua, { getSceneSnap: () => SCENE, targetPrimitiveId: null, send }); const sched = new RobloxScheduler(lua); sched.install(); installRobloxServices(lua, { send, getPlayerState: () => playerState, loadSave: (k) => STORE.get(k), saveSave: (k, v) => STORE.set(k, v), removeSave: (k) => STORE.delete(k), }); await sched.spawnMain(luaSource); for (const dt of ticks) await sched.tick(dt); lua.global.close(); return { logs: sent.filter(s => s.cmd === 'log').map(s => s.payload), playerCmds: sent.filter(s => s.cmd === 'playerCmd').map(s => s.payload), broadcasts: sent.filter(s => s.cmd === 'broadcast').map(s => s.payload) }; } const TESTS = [ { name: 'Players.LocalPlayer.Character.Humanoid существует', lua: ` local p = game:GetService("Players").LocalPlayer local h = p.Character:WaitForChild("Humanoid") print("hp:", h.Health, "ws:", h.WalkSpeed) `, expect: [{ level: 'info', text: 'hp:\t100\tws:\t16' }], }, { name: 'Humanoid.WalkSpeed = 50 → playerCmd setWalkSpeed', lua: ` local h = game:GetService("Players").LocalPlayer.Character.Humanoid h.WalkSpeed = 50 `, expectPlayerCmd: { method: 'setWalkSpeed', argsCheck: (a) => a[0] === 50 }, }, { name: 'Humanoid:TakeDamage уменьшает HP', lua: ` local h = game:GetService("Players").LocalPlayer.Character.Humanoid h:TakeDamage(30) print("after damage:", h.Health) `, expect: [{ level: 'info', text: 'after damage:\t70' }], }, { name: 'Humanoid.Health = 0 → Died fires', lua: ` local h = game:GetService("Players").LocalPlayer.Character.Humanoid h.Died:Connect(function() print("DIED") end) h.Health = 0 `, expect: [{ level: 'info', text: 'DIED' }], }, { name: 'DataStoreService GetAsync/SetAsync', lua: ` local DSS = game:GetService("DataStoreService") local store = DSS:GetDataStore("coins") store:SetAsync("player1", 100) print("got:", store:GetAsync("player1")) `, expect: [{ level: 'info', text: 'got:\t100' }], }, { name: 'DataStoreService IncrementAsync', lua: ` local store = game:GetService("DataStoreService"):GetDataStore("score") store:SetAsync("p1", 50) store:IncrementAsync("p1", 25) print("final:", store:GetAsync("p1")) `, expect: [{ level: 'info', text: 'final:\t75' }], }, { name: 'HttpService:JSONEncode/Decode', lua: ` local HS = game:GetService("HttpService") local s = HS:JSONEncode({a=1, b="two"}) print("encoded len:", #s) local d = HS:JSONDecode('{"x":42}') print("decoded x:", d.x) `, expect: [{ level: 'info', text: 'decoded x:\t42' }], }, { name: 'RemoteEvent FireServer + OnServerEvent', lua: ` local re = Instance.new("RemoteEvent", workspace) re.Name = "MyEvent" re.OnServerEvent:Connect(function(player, msg) print("server got:", msg) end) re:FireServer("hello") `, expect: [{ level: 'info', text: 'server got:\thello' }], }, ]; (async () => { let passed = 0, failed = 0; for (const t of TESTS) { try { const r = await run(t.lua, t.ticks); let ok = true; let reason = ''; for (const exp of (t.expect || [])) { const found = r.logs.find(l => l.level === exp.level && l.text === exp.text); if (!found) { ok = false; reason = `missing log: ${exp.text}; got: ${JSON.stringify(r.logs)}`; break; } } if (t.expectPlayerCmd) { const found = r.playerCmds.find(c => c.method === t.expectPlayerCmd.method && (!t.expectPlayerCmd.argsCheck || t.expectPlayerCmd.argsCheck(c.args))); if (!found) { ok = false; reason = `missing playerCmd ${t.expectPlayerCmd.method}; got: ${JSON.stringify(r.playerCmds)}`; } } if (ok) { console.log(`✓ ${t.name}`); passed++; } else { console.log(`✗ ${t.name} — ${reason}`); failed++; } } catch (e) { console.log(`✗ ${t.name} — exception: ${e.message || e}`); failed++; } } console.log(`\n${passed} passed, ${failed} failed`); process.exit(failed > 0 ? 1 : 0); })();