Skip to content

Commit

Permalink
feat(lsx): lsx is jsx but in lua
Browse files Browse the repository at this point in the history
 - Refactored tests into a scenario (easier to automatically launch)
 - Added thumbnail.png
  • Loading branch information
maxpowa committed Aug 27, 2024
1 parent 34f3473 commit de7601b
Show file tree
Hide file tree
Showing 18 changed files with 158 additions and 47 deletions.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,22 @@ end)
- Save/load event handler restoration
- Context API support (`createContext`/`useContext`)

### Examples

### Running Tests

Run Factorio and launch the "React for Factorio/Unit Testing" scenario. This can be automated by adding a launch argument to your Factorio configuration. As of 1.1.109, the argument is `--load-scenario react/react-testing`.

If you're using FMTK/VSCode, the below launch configuration should work.
```json
{
"type": "factoriomod",
"request": "launch",
"name": "Factorio Unit Test (React)",
"factorioArgs": [
"--load-scenario",
"react/react-testing"
]
},
```

### Credits

Expand Down
6 changes: 0 additions & 6 deletions control.lua

This file was deleted.

2 changes: 1 addition & 1 deletion info.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react",
"version": "1.2.1",
"title": "Reactorio (React for Factorio)",
"title": "React for Factorio",
"author": "maxpowa",
"contact": "",
"homepage": "https://github.com/maxpowa/reactorio",
Expand Down
82 changes: 82 additions & 0 deletions lib/lsx.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
local createElement = require("__react__.lib.core").createElement

-- static vars
local MODE_TEXT = 0
local MODE_OPEN_TAG = 1
local MODE_CLOSE_TAG = 2

local function assert(condition, msg)
if not condition then
log(msg)
end
end

local function lsx(s, fields)
-- Stack of nested tags. Start with a fake top node. The actual top virtual
-- node would become the first child of this node. We know this element is
-- going to be invalid, so we ignore the param type mismatch.
---@diagnostic disable-next-line: param-type-mismatch
local stack = { createElement(nil) }

-- current parsing mode
local mode = MODE_TEXT

-- Read and return the next word from the string, starting at position i. If
-- the string is empty - return the corresponding placeholder field.
local function readToken(str, i, pattern, field)
str = str:sub(i)
if #str == 0 then
return str, field
end
local _, stop, capture = str:find(pattern)
return str:sub(stop + 1), capture
end

while #s > 0 do
local val
s = s:gsub("^%s+", "")
if mode == MODE_TEXT then
if s:sub(1,1) == "<" then
if s:sub(2,2) == "/" then
s = readToken(s, 3, "(%w+)", fields)
mode = MODE_CLOSE_TAG
else
s, val = readToken(s, 2, "(%w+)", fields)
stack[#stack + 1] = createElement(val, {})
mode = MODE_OPEN_TAG
end
else
s, val = readToken(s, 1, "([^<]+)", "")
table.insert(stack[#stack].children, val)
end
elseif mode == MODE_OPEN_TAG then
if s:sub(1,1) == "/" and s:sub(2,2) == ">" then
table.insert(stack[#stack - 1].children, table.remove(stack))
mode = MODE_TEXT
s = s:sub(3)
elseif s:sub(1,1) == ">" then
mode = MODE_TEXT
s = s:sub(2)
else
s, val = readToken(s, 1, "([%w_-]+)=", "")
assert(val ~= nil, "Invalid tag name")
local propName = val
s, val = readToken(s, 1, '"([^"]*)"', fields)
stack[#stack].props[propName] = val
end
elseif mode == MODE_CLOSE_TAG then
table.insert(stack[#stack - 1].children, table.remove(stack))
s = s:sub(2)
mode = MODE_TEXT
end
end
if mode == MODE_TEXT then
table.insert(stack[#stack].children, fields)
end
return stack[1].children[1]
end


return {
lsx = lsx
}
4 changes: 4 additions & 0 deletions react.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ end

local core = require("__react__.lib.core")
local hooks = require("__react__.lib.hooks")
local lsx = require("__react__.lib.lsx")

return {
-- Core
Expand All @@ -18,4 +19,7 @@ return {
useMemo = hooks.useMemo,
useCallback = hooks.useCallback,
useRef = hooks.useRef,

-- LSX
lsx = lsx.lsx
}
5 changes: 5 additions & 0 deletions scenarios/react-testing/control.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- https://github.com/justarandomgeek/vscode-factoriomod-debug/blob/aa61fe5e3782ed0b8914caa1ac9986f985000fb0/doc/debugapi.md#detecting-debugging
if script.active_mods["gvv"] then require("__gvv__.gvv")() end

-- Load the test suite
require("init")
4 changes: 4 additions & 0 deletions scenarios/react-testing/description.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"multiplayer-compatible": true,
"order": "f"
}
1 change: 1 addition & 0 deletions scenarios/react-testing/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
36 changes: 31 additions & 5 deletions test/harness.lua → scenarios/react-testing/init.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
local React = require("__react__.react")
local tests = {
require("tests.01-basic"),
require("tests.02-typical"),
require("tests.03-basic-with-hooks"),
require("tests.04-useRef"),
require("tests.05-lsx"),
require("tests.99-all-elements"),
}

local RUN_ALL_TESTS_DELAY = 60 * 3 -- 3 seconds
local RUN_ALL_TESTS_DELAY = 60 * 2 -- 3 seconds

local timeoutHandlers = {}
-- This isn't perfect - in editor mode game.tick is not updated, but it's good enough for testing.
-- In editor mode you can manually advance the tick by pressing the "Advance tick" button.
-- The tests should always be run in game mode anyway.
script.on_event(defines.events.on_tick, function()
for i, handler in pairs(timeoutHandlers) do
if (game.tick - handler.start > 0) and ((game.tick - handler.start) % handler.delay == 0) then
Expand All @@ -27,12 +35,12 @@ local function clearTimeout(index)
timeoutHandlers[index] = nil
end

-- Test harness component, provides the ability for the user to navigate through the tests
local function TestHarness(props)
local tests = props.tests
local player = props.player

local current_test_index, setState = React.useState(1)
local autoRun, setAutoRun = React.useState(false)
local autoRun, setAutoRun = React.useState(true)

local current_test = tests[current_test_index]
local on_gui_click = function()
Expand Down Expand Up @@ -86,4 +94,22 @@ local function TestHarness(props)
)
end

return TestHarness
function run_tests(player)
player.print("Factorio React Testing Apparatus loaded")

if player.gui.screen["react_root"] then
player.gui.screen["react_root"].destroy()
end
local root = player.gui.screen.add({ type = "frame", name = "react_root", caption = "React Testing Apparatus", direction = "vertical" })
root.auto_center = true

-- Dogfood the tests :)
global.hook_storage = {}
React.render(React.createElement(TestHarness, { player = player }), root, global.hook_storage)
end

script.on_event(defines.events.on_player_created, function(event)
local player = game.players[event.player_index]
player.character.destroy() -- remove player character to prevent them from moving around
run_tests(player)
end)
2 changes: 2 additions & 0 deletions scenarios/react-testing/locale/en/react-testing.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
scenario-name=Unit Tests
description=Your task is to test the React library for Factorio.\n\nThis is [font=default-bold]definitely not[/font] the intended way of playing Factorio.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 12 additions & 0 deletions scenarios/react-testing/tests/05-lsx.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
local React = require("__react__.react")

local function Test(props)
local player = props.player

return React.lsx([[<frame>
<label caption="This was rendered by LSX" />
<label caption="Hello, ${name}!" />
</frame>]], { name = player.name })
end

return Test
File renamed without changes.
33 changes: 0 additions & 33 deletions test/init.lua

This file was deleted.

Binary file added thumbnail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit de7601b

Please sign in to comment.