mirror of
https://github.com/Refactorio/RedMew.git
synced 2024-12-12 10:04:40 +02:00
commit
4b78d9eae3
@ -169,6 +169,10 @@ if _DUMP_ENV then
|
||||
require 'utils.dump_env'
|
||||
end
|
||||
|
||||
if _DEBUG then
|
||||
require('utils.test.main')
|
||||
end
|
||||
|
||||
-- Needs to be at bottom so tokens are registered last.
|
||||
if _DEBUG then
|
||||
require 'features.gui.debug.command'
|
||||
|
@ -56,9 +56,7 @@ local function can_select_landfill_tiles(cursor, surface, area)
|
||||
entity_filters[#entity_filters + 1] = 'character'
|
||||
end
|
||||
|
||||
if surface.count_entities_filtered({area = area, name = entity_filters, invert = invert, limit = 1}) > 0 then
|
||||
return false
|
||||
end
|
||||
return surface.count_entities_filtered({area = area, name = entity_filters, invert = invert, limit = 1}) == 0
|
||||
end
|
||||
|
||||
local function within_reach(tile_position, player_position, radius_squared)
|
||||
|
701
features/landfill_remover_tests.lua
Normal file
701
features/landfill_remover_tests.lua
Normal file
@ -0,0 +1,701 @@
|
||||
local Declare = require 'utils.test.declare'
|
||||
local EventFactory = require 'utils.test.event_factory'
|
||||
local Assert = require 'utils.test.assert'
|
||||
local Helper = require 'utils.test.helper'
|
||||
|
||||
local main_inventory = defines.inventory.character_main
|
||||
local config = global.config.landfill_remover
|
||||
|
||||
local tile_items = {
|
||||
'stone-brick',
|
||||
'concrete',
|
||||
'hazard-concrete',
|
||||
'refined-concrete',
|
||||
'refined-hazard-concrete'
|
||||
}
|
||||
|
||||
Declare.module(
|
||||
{'features', 'landfill remover'},
|
||||
function()
|
||||
local teardown
|
||||
|
||||
Declare.module_startup(
|
||||
function(context)
|
||||
teardown = Helper.startup_test_surface(context)
|
||||
end
|
||||
)
|
||||
|
||||
Declare.module_teardown(
|
||||
function()
|
||||
teardown()
|
||||
end
|
||||
)
|
||||
|
||||
local function setup_player_with_default_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
local function setup_player_with_valid_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
stack.set_tile_filter(1, 'landfill')
|
||||
stack.tile_selection_mode = defines.deconstruction_item.tile_selection_mode.only
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
local function setup_player_with_tile_filter_whitelist_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
stack.set_tile_filter(1, 'landfill')
|
||||
stack.tile_filter_mode = defines.deconstruction_item.tile_filter_mode.whitelist
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
local function setup_player_with_tile_filter_blacklist_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
stack.set_tile_filter(1, 'landfill')
|
||||
stack.tile_filter_mode = defines.deconstruction_item.tile_filter_mode.blacklist
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
local function setup_player_with_normal_selection_mode_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
stack.set_tile_filter(1, 'landfill')
|
||||
stack.tile_selection_mode = defines.deconstruction_item.tile_selection_mode.normal
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
local function setup_player_with_never_selection_mode_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
stack.set_tile_filter(1, 'landfill')
|
||||
stack.tile_selection_mode = defines.deconstruction_item.tile_selection_mode.never
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
local function setup_player_with_always_selection_mode_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
stack.set_tile_filter(1, 'landfill')
|
||||
stack.tile_selection_mode = defines.deconstruction_item.tile_selection_mode.always
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
local function setup_player_with_no_landfill_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
stack.tile_selection_mode = defines.deconstruction_item.tile_selection_mode.only
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
local function setup_player_with_trees_and_rocks_only_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
stack.set_tile_filter(1, 'landfill')
|
||||
stack.trees_and_rocks_only = true
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
local function setup_player_with_entity_filter_whitelist_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
stack.set_tile_filter(1, 'landfill')
|
||||
stack.set_entity_filter(1, 'iron-chest')
|
||||
stack.entity_filter_mode = defines.deconstruction_item.entity_filter_mode.whitelist
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
local function setup_player_with_entity_filter_blacklist_deconstruction_planner(player)
|
||||
local inventory = player.get_inventory(main_inventory)
|
||||
inventory.clear()
|
||||
inventory.insert('deconstruction-planner')
|
||||
local stack = inventory.find_item_stack('deconstruction-planner')
|
||||
stack.set_tile_filter(1, 'landfill')
|
||||
stack.set_entity_filter(1, 'iron-chest')
|
||||
stack.entity_filter_mode = defines.deconstruction_item.entity_filter_mode.blacklist
|
||||
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(stack)
|
||||
|
||||
return cursor
|
||||
end
|
||||
|
||||
Declare.test(
|
||||
'can remove landfill',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local cursor = setup_player_with_valid_deconstruction_planner(player)
|
||||
local position = {2, 2}
|
||||
local area = {{2.1, 2.1}, {2.9, 2.9}}
|
||||
surface.set_tiles({{name = 'landfill', position = position}})
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position[1], position[2])
|
||||
Assert.equal(config.revert_tile, tile.name)
|
||||
end
|
||||
)
|
||||
|
||||
for _, item_name in pairs(tile_items) do
|
||||
Declare.test(
|
||||
'can remove landfill when covered by ' .. item_name,
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local position = {2, 2}
|
||||
local area = {{2.1, 2.1}, {2.9, 2.9}}
|
||||
surface.set_tiles({{name = 'landfill', position = position}})
|
||||
|
||||
-- Place covering tile.
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(item_name)
|
||||
player.build_from_cursor({position = position, terrain_building_size = 1})
|
||||
|
||||
cursor = setup_player_with_valid_deconstruction_planner(player)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position[1], position[2])
|
||||
Assert.equal(config.revert_tile, tile.name)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
Declare.test(
|
||||
'does not remove landfill when entity present',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local position = {2, 2}
|
||||
local area = {{2.1, 2.1}, {2.9, 2.9}}
|
||||
surface.set_tiles({{name = 'landfill', position = position}})
|
||||
|
||||
-- Place entity.
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack('iron-chest')
|
||||
player.build_from_cursor({position = position})
|
||||
|
||||
cursor = setup_player_with_valid_deconstruction_planner(player)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position[1], position[2])
|
||||
Assert.equal('landfill', tile.name)
|
||||
|
||||
local entities = surface.find_entities(area)
|
||||
local entity = entities[1]
|
||||
Assert.is_lua_object_with_name(entity, 'iron-chest', 'iron-chest was not valid.')
|
||||
entity.destroy()
|
||||
end
|
||||
)
|
||||
|
||||
for _, item_name in pairs(tile_items) do
|
||||
Declare.test(
|
||||
'does not remove covered by ' .. item_name .. ' landfill when entity present',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local position = {2, 2}
|
||||
local area = {{2.1, 2.1}, {2.9, 2.9}}
|
||||
surface.set_tiles({{name = 'landfill', position = position}})
|
||||
|
||||
-- Place covering tile.
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(item_name)
|
||||
player.build_from_cursor({position = position, terrain_building_size = 1})
|
||||
|
||||
local before_tile = surface.get_tile(position[1], position[2])
|
||||
|
||||
-- Place entity.
|
||||
cursor.set_stack('iron-chest')
|
||||
player.build_from_cursor({position = position})
|
||||
|
||||
cursor = setup_player_with_valid_deconstruction_planner(player)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position[1], position[2])
|
||||
Assert.equal(before_tile.name, tile.name)
|
||||
|
||||
local entities = surface.find_entities(area)
|
||||
local entity = entities[1]
|
||||
Assert.is_lua_object_with_name(entity, 'iron-chest', 'iron-chest was not valid.')
|
||||
entity.destroy()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
Declare.test(
|
||||
'does not remove landfill when out of reach',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local build_distance = player.build_distance + 5
|
||||
local position = {build_distance, build_distance}
|
||||
local area = {
|
||||
{build_distance + 0.1, build_distance + 0.1},
|
||||
{build_distance + 0.9, build_distance + 0.9}
|
||||
}
|
||||
surface.set_tiles({{name = 'landfill', position = position}})
|
||||
local cursor = setup_player_with_valid_deconstruction_planner(player)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position[1], position[2])
|
||||
Assert.equal('landfill', tile.name)
|
||||
end
|
||||
)
|
||||
|
||||
for _, item_name in pairs(tile_items) do
|
||||
Declare.test(
|
||||
'does not remove landfill when out of reach and covered by ' .. item_name,
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local build_distance = player.build_distance + 5
|
||||
local position = {build_distance, build_distance}
|
||||
local area = {
|
||||
{build_distance + 0.1, build_distance + 0.1},
|
||||
{build_distance + 0.9, build_distance + 0.9}
|
||||
}
|
||||
surface.set_tiles({{name = 'landfill', position = position}})
|
||||
|
||||
-- Place covering tile.
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack(item_name)
|
||||
player.build_from_cursor({position = position, terrain_building_size = 1})
|
||||
|
||||
local before_tile = surface.get_tile(position[1], position[2])
|
||||
|
||||
cursor = setup_player_with_valid_deconstruction_planner(player)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position[1], position[2])
|
||||
Assert.equal(before_tile.name, tile.name)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
Declare.test(
|
||||
'does not remove landfill when trees and rocks only',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local position = {2, 2}
|
||||
local area = {{2.1, 2.1}, {2.9, 2.9}}
|
||||
surface.set_tiles({{name = 'landfill', position = position}})
|
||||
local cursor = setup_player_with_trees_and_rocks_only_deconstruction_planner(player)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position[1], position[2])
|
||||
Assert.equal('landfill', tile.name)
|
||||
end
|
||||
)
|
||||
|
||||
Declare.test(
|
||||
'does not remove landfill when default deconstruction planner',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local position = {2, 2}
|
||||
local area = {{2.1, 2.1}, {2.9, 2.9}}
|
||||
surface.set_tiles({{name = 'landfill', position = position}})
|
||||
local cursor = setup_player_with_default_deconstruction_planner(player)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position[1], position[2])
|
||||
Assert.equal('landfill', tile.name)
|
||||
end
|
||||
)
|
||||
|
||||
local tile_mode_test_cases = {
|
||||
{
|
||||
name = 'only',
|
||||
setup = setup_player_with_valid_deconstruction_planner,
|
||||
should_remove = true
|
||||
},
|
||||
{
|
||||
name = 'normal',
|
||||
setup = setup_player_with_normal_selection_mode_deconstruction_planner,
|
||||
should_remove = true
|
||||
},
|
||||
{
|
||||
name = 'always',
|
||||
setup = setup_player_with_always_selection_mode_deconstruction_planner,
|
||||
should_remove = true
|
||||
},
|
||||
{
|
||||
name = 'never',
|
||||
setup = setup_player_with_never_selection_mode_deconstruction_planner,
|
||||
should_remove = false
|
||||
},
|
||||
{
|
||||
name = 'no landfill',
|
||||
setup = setup_player_with_no_landfill_deconstruction_planner,
|
||||
should_remove = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, test_case in pairs(tile_mode_test_cases) do
|
||||
Declare.test(
|
||||
'tile mode ' ..
|
||||
test_case.name .. ' should ' .. (test_case.should_remove and '' or 'not ') .. 'remove landfill',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local cursor = test_case.setup(player)
|
||||
local position = {2, 2}
|
||||
local area = {{2.1, 2.1}, {2.9, 2.9}}
|
||||
surface.set_tiles({{name = 'landfill', position = position}})
|
||||
local expected_tile = test_case.should_remove and config.revert_tile or 'landfill'
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position[1], position[2])
|
||||
Assert.equal(expected_tile, tile.name)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
local tile_filter_test_cases = {
|
||||
{
|
||||
name = 'whitelist',
|
||||
setup = setup_player_with_tile_filter_whitelist_deconstruction_planner,
|
||||
should_remove = true
|
||||
},
|
||||
{
|
||||
name = 'blacklist',
|
||||
setup = setup_player_with_tile_filter_blacklist_deconstruction_planner,
|
||||
should_remove = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, test_case in pairs(tile_filter_test_cases) do
|
||||
Declare.test(
|
||||
'tile filter ' ..
|
||||
test_case.name .. ' should ' .. (test_case.should_remove and '' or 'not ') .. 'remove landfill',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local cursor = test_case.setup(player)
|
||||
local position = {2, 2}
|
||||
local area = {{2.1, 2.1}, {2.9, 2.9}}
|
||||
surface.set_tiles({{name = 'landfill', position = position}})
|
||||
local expected_tile = test_case.should_remove and config.revert_tile or 'landfill'
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position[1], position[2])
|
||||
Assert.equal(expected_tile, tile.name)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
local tile_mode_with_entity_test_cases = {
|
||||
{
|
||||
name = 'only',
|
||||
setup = setup_player_with_valid_deconstruction_planner,
|
||||
should_remove = true
|
||||
},
|
||||
{
|
||||
name = 'normal',
|
||||
setup = setup_player_with_normal_selection_mode_deconstruction_planner,
|
||||
should_remove = false
|
||||
},
|
||||
{
|
||||
name = 'always',
|
||||
setup = setup_player_with_always_selection_mode_deconstruction_planner,
|
||||
should_remove = true
|
||||
},
|
||||
{
|
||||
name = 'never',
|
||||
setup = setup_player_with_never_selection_mode_deconstruction_planner,
|
||||
should_remove = false
|
||||
},
|
||||
{
|
||||
name = 'no landfill',
|
||||
setup = setup_player_with_no_landfill_deconstruction_planner,
|
||||
should_remove = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, test_case in pairs(tile_mode_with_entity_test_cases) do
|
||||
Declare.test(
|
||||
'tile mode ' ..
|
||||
test_case.name ..
|
||||
' with entity should ' .. (test_case.should_remove and '' or 'not ') .. 'remove landfill',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
|
||||
local position1 = {2, 2}
|
||||
local position2 = {3, 2}
|
||||
local area = {{2.1, 2.1}, {3.9, 2.9}}
|
||||
surface.set_tiles(
|
||||
{{name = 'landfill', position = position1}, {name = 'landfill', position = position2}}
|
||||
)
|
||||
local expected_tile = test_case.should_remove and config.revert_tile or 'landfill'
|
||||
|
||||
-- Place entity.
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack('iron-chest')
|
||||
player.build_from_cursor({position = position1})
|
||||
|
||||
cursor = test_case.setup(player)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position2[1], position2[2])
|
||||
Assert.equal(expected_tile, tile.name)
|
||||
|
||||
local entities = surface.find_entities(area)
|
||||
local entity = entities[1]
|
||||
Assert.is_lua_object_with_name(entity, 'iron-chest', 'iron-chest was not valid.')
|
||||
entity.destroy()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
local tile_filter_with_entity_test_cases = {
|
||||
{
|
||||
name = 'whitelist',
|
||||
setup = setup_player_with_tile_filter_whitelist_deconstruction_planner,
|
||||
should_remove = false
|
||||
},
|
||||
{
|
||||
name = 'blacklist',
|
||||
setup = setup_player_with_tile_filter_blacklist_deconstruction_planner,
|
||||
should_remove = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, test_case in pairs(tile_filter_with_entity_test_cases) do
|
||||
Declare.test(
|
||||
'tile mode ' ..
|
||||
test_case.name ..
|
||||
' with entity should ' .. (test_case.should_remove and '' or 'not ') .. 'remove landfill',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
|
||||
local position1 = {2, 2}
|
||||
local position2 = {3, 2}
|
||||
local area = {{2.1, 2.1}, {3.9, 2.9}}
|
||||
surface.set_tiles(
|
||||
{{name = 'landfill', position = position1}, {name = 'landfill', position = position2}}
|
||||
)
|
||||
local expected_tile = test_case.should_remove and config.revert_tile or 'landfill'
|
||||
|
||||
-- Place entity.
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack('iron-chest')
|
||||
player.build_from_cursor({position = position1})
|
||||
|
||||
cursor = test_case.setup(player)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position2[1], position2[2])
|
||||
Assert.equal(expected_tile, tile.name)
|
||||
|
||||
local entities = surface.find_entities(area)
|
||||
local entity = entities[1]
|
||||
Assert.is_lua_object_with_name(entity, 'iron-chest', 'iron-chest was not valid.')
|
||||
entity.destroy()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
local entity_filter_with_entity_test_cases = {
|
||||
{
|
||||
name = 'whitelist',
|
||||
setup = setup_player_with_entity_filter_whitelist_deconstruction_planner,
|
||||
should_remove = false
|
||||
},
|
||||
{
|
||||
name = 'blacklist',
|
||||
setup = setup_player_with_entity_filter_blacklist_deconstruction_planner,
|
||||
should_remove = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, test_case in pairs(entity_filter_with_entity_test_cases) do
|
||||
Declare.test(
|
||||
'entity filter ' ..
|
||||
test_case.name ..
|
||||
' with entity should ' .. (test_case.should_remove and '' or 'not ') .. 'remove landfill',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
|
||||
local position1 = {2, 2}
|
||||
local position2 = {3, 2}
|
||||
local area = {{2.1, 2.1}, {3.9, 2.9}}
|
||||
surface.set_tiles(
|
||||
{{name = 'landfill', position = position1}, {name = 'landfill', position = position2}}
|
||||
)
|
||||
local expected_tile = test_case.should_remove and config.revert_tile or 'landfill'
|
||||
|
||||
-- Place entity.
|
||||
local cursor = player.cursor_stack
|
||||
cursor.set_stack('iron-chest')
|
||||
player.build_from_cursor({position = position1})
|
||||
|
||||
cursor = test_case.setup(player)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
local tile = surface.get_tile(position2[1], position2[2])
|
||||
Assert.equal(expected_tile, tile.name)
|
||||
|
||||
local entities = surface.find_entities(area)
|
||||
local entity = entities[1]
|
||||
Assert.is_lua_object_with_name(entity, 'iron-chest', 'iron-chest was not valid.')
|
||||
entity.destroy()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
Declare.test(
|
||||
'ignore character when removing landfill',
|
||||
function(context)
|
||||
-- Arrange
|
||||
local player = context.player
|
||||
local surface = player.surface
|
||||
local cursor = setup_player_with_valid_deconstruction_planner(player)
|
||||
local positions = {
|
||||
{-1, -1},
|
||||
{-1, 0},
|
||||
{0, -1},
|
||||
{0, 0}
|
||||
}
|
||||
local area = {{-1.5, -1.5}, {0.5, 0.5}}
|
||||
|
||||
surface.set_tiles(
|
||||
{
|
||||
{name = 'landfill', position = positions[1]},
|
||||
{name = 'landfill', position = positions[2]},
|
||||
{name = 'landfill', position = positions[3]},
|
||||
{name = 'landfill', position = positions[4]}
|
||||
}
|
||||
)
|
||||
|
||||
-- Act
|
||||
EventFactory.do_player_deconstruct_area(cursor, player, area)
|
||||
|
||||
-- Assert
|
||||
for _, pos in pairs(positions) do
|
||||
local tile = surface.get_tile(pos[1], pos[2])
|
||||
Assert.equal(config.revert_tile, tile.name)
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
)
|
@ -135,4 +135,6 @@ function Public.get_on_nth_tick_event_handlers()
|
||||
return on_nth_tick_event_handlers
|
||||
end
|
||||
|
||||
Public.on_event = on_event
|
||||
|
||||
return Public
|
||||
|
@ -23,6 +23,8 @@ local top_elements = {}
|
||||
local on_visible_handlers = {}
|
||||
local on_pre_hidden_handlers = {}
|
||||
|
||||
Gui._top_elements = top_elements
|
||||
|
||||
function Gui.uid_name()
|
||||
return tostring(Token.uid())
|
||||
end
|
||||
|
59
utils/gui_tests.lua
Normal file
59
utils/gui_tests.lua
Normal file
@ -0,0 +1,59 @@
|
||||
local Declare = require 'utils.test.declare'
|
||||
local EventFactory = require 'utils.test.event_factory'
|
||||
local Gui = require 'utils.gui'
|
||||
local Assert = require 'utils.test.assert'
|
||||
|
||||
Declare.module(
|
||||
{'utils', 'Gui'},
|
||||
function()
|
||||
Declare.module(
|
||||
'can toggle top buttons',
|
||||
function()
|
||||
local function count_gui_elements(gui)
|
||||
return #gui.top.children + #gui.left.children + #gui.center.children
|
||||
end
|
||||
|
||||
for _, name in pairs(Gui._top_elements) do
|
||||
Declare.test(
|
||||
Gui.names[name],
|
||||
function(context)
|
||||
local player = context.player
|
||||
local element = player.gui.top[name]
|
||||
|
||||
if not element.enabled then
|
||||
return
|
||||
end
|
||||
|
||||
local event = EventFactory.on_gui_click(element, player.index)
|
||||
local click_action = function()
|
||||
EventFactory.raise(event)
|
||||
end
|
||||
|
||||
local before_count = count_gui_elements(player.gui)
|
||||
|
||||
-- Open
|
||||
click_action()
|
||||
local after_open_count = count_gui_elements(player.gui)
|
||||
Assert.is_true(
|
||||
after_open_count > before_count,
|
||||
'after open count should be greater than before count.'
|
||||
)
|
||||
|
||||
-- Close
|
||||
context:next(click_action):next(
|
||||
function()
|
||||
local after_close_count = count_gui_elements(player.gui)
|
||||
Assert.equal(
|
||||
before_count,
|
||||
after_close_count,
|
||||
'after close count should be equal to before count.'
|
||||
)
|
||||
end
|
||||
)
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
)
|
52
utils/test/assert.lua
Normal file
52
utils/test/assert.lua
Normal file
@ -0,0 +1,52 @@
|
||||
local error = error
|
||||
local concat = table.concat
|
||||
|
||||
local Public = {}
|
||||
|
||||
local function append_optional_message(main_message, optional_message)
|
||||
if optional_message then
|
||||
return concat {main_message, ' - ', optional_message}
|
||||
end
|
||||
return main_message
|
||||
end
|
||||
|
||||
function Public.equal(a, b, optional_message)
|
||||
if a == b then
|
||||
return
|
||||
end
|
||||
|
||||
local message = {tostring(a), ' ~= ', tostring(b)}
|
||||
if optional_message then
|
||||
message[#message + 1] = ' - '
|
||||
message[#message + 1] = optional_message
|
||||
end
|
||||
|
||||
message = concat(message)
|
||||
error(message, 2)
|
||||
end
|
||||
|
||||
function Public.is_true(condition, optional_message)
|
||||
if not condition then
|
||||
error(optional_message or 'condition was not true', 2)
|
||||
end
|
||||
end
|
||||
|
||||
function Public.valid(lua_object, optional_message)
|
||||
if not lua_object then
|
||||
error(append_optional_message('lua_object was nil', optional_message), 2)
|
||||
end
|
||||
|
||||
if not lua_object.valid then
|
||||
error(append_optional_message('lua_object was not valid', optional_message), 2)
|
||||
end
|
||||
end
|
||||
|
||||
function Public.is_lua_object_with_name(lua_object, name, optional_message)
|
||||
Public.valid(lua_object, optional_message)
|
||||
|
||||
if lua_object.name ~= name then
|
||||
error(append_optional_message("lua_object did not have name '" .. tostring(name) .. "'", optional_message), 2)
|
||||
end
|
||||
end
|
||||
|
||||
return Public
|
154
utils/test/builder.lua
Normal file
154
utils/test/builder.lua
Normal file
@ -0,0 +1,154 @@
|
||||
local ModuleStore = require 'utils.test.module_store'
|
||||
local Context = require 'utils.test.context'
|
||||
|
||||
local Public = {}
|
||||
|
||||
local is_init = false
|
||||
local id_count = 0
|
||||
|
||||
local function get_id()
|
||||
id_count = id_count + 1
|
||||
return id_count
|
||||
end
|
||||
|
||||
local function init_inner(module, depth)
|
||||
module.id = get_id()
|
||||
module.depth = depth
|
||||
|
||||
local count = 0
|
||||
|
||||
local tests = {}
|
||||
for name, func in pairs(module.test_funcs) do
|
||||
count = count + 1
|
||||
tests[#tests + 1] = {
|
||||
id = get_id(),
|
||||
name = name,
|
||||
module = module,
|
||||
func = func,
|
||||
context = nil,
|
||||
current_step = nil,
|
||||
passed = nil,
|
||||
error = nil
|
||||
}
|
||||
end
|
||||
module.tests = tests
|
||||
|
||||
for _, child in pairs(module.children) do
|
||||
count = count + init_inner(child, depth + 1)
|
||||
end
|
||||
|
||||
module.count = count
|
||||
return count
|
||||
end
|
||||
|
||||
function Public.init()
|
||||
if is_init then
|
||||
return
|
||||
end
|
||||
|
||||
is_init = true
|
||||
init_inner(ModuleStore.root_module, 0)
|
||||
end
|
||||
|
||||
function Public.get_root_modules()
|
||||
Public.init()
|
||||
return ModuleStore.root_module
|
||||
end
|
||||
|
||||
local function prepare_pre_module_hooks(module, runnables, player)
|
||||
local startup_func = module.startup_func
|
||||
if startup_func then
|
||||
runnables[#runnables + 1] = {
|
||||
is_hook = true,
|
||||
name = 'startup',
|
||||
module = module,
|
||||
func = startup_func,
|
||||
context = Context.new(player),
|
||||
current_step = 0,
|
||||
error = nil
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function build_pre_module_hooks(module, runnables, player)
|
||||
if module == nil then
|
||||
return
|
||||
end
|
||||
|
||||
build_pre_module_hooks(module.parent, runnables, player)
|
||||
prepare_pre_module_hooks(module, runnables, player)
|
||||
end
|
||||
|
||||
local function prepare_post_module_hooks(module, runnables, player)
|
||||
local teardown_func = module.teardown_func
|
||||
if teardown_func then
|
||||
runnables[#runnables + 1] = {
|
||||
is_hook = true,
|
||||
name = 'teardown',
|
||||
module = module,
|
||||
func = teardown_func,
|
||||
context = Context.new(player),
|
||||
current_step = 0,
|
||||
error = nil
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function build_post_module_hooks(module, runnables, player)
|
||||
if module == nil then
|
||||
return
|
||||
end
|
||||
|
||||
prepare_post_module_hooks(module, runnables, player)
|
||||
build_post_module_hooks(module.parent, runnables, player)
|
||||
end
|
||||
|
||||
local function prepare_test(test, player)
|
||||
test.context = Context.new(player)
|
||||
test.current_step = 0
|
||||
test.passed = nil
|
||||
test.error = nil
|
||||
return test
|
||||
end
|
||||
|
||||
local function prepare_module(module, runnables, player)
|
||||
module.passed = nil
|
||||
prepare_pre_module_hooks(module, runnables, player)
|
||||
|
||||
for _, test in pairs(module.tests) do
|
||||
prepare_test(test, player)
|
||||
runnables[#runnables + 1] = test
|
||||
end
|
||||
|
||||
for _, child in pairs(module.children) do
|
||||
prepare_module(child, runnables, player)
|
||||
end
|
||||
|
||||
prepare_post_module_hooks(module, runnables, player)
|
||||
end
|
||||
|
||||
function Public.build_test_for_run(test, player)
|
||||
Public.init()
|
||||
|
||||
local runnables = {}
|
||||
|
||||
build_pre_module_hooks(test.module, runnables, player)
|
||||
runnables[#runnables + 1] = prepare_test(test, player)
|
||||
build_post_module_hooks(test.module, runnables, player)
|
||||
|
||||
return runnables
|
||||
end
|
||||
|
||||
function Public.build_module_for_run(module, player)
|
||||
Public.init()
|
||||
|
||||
local runnables = {}
|
||||
|
||||
build_pre_module_hooks(module.parent, runnables, player)
|
||||
prepare_module(module, runnables, player)
|
||||
build_post_module_hooks(module.parent, runnables, player)
|
||||
|
||||
return runnables
|
||||
end
|
||||
|
||||
return Public
|
21
utils/test/command.lua
Normal file
21
utils/test/command.lua
Normal file
@ -0,0 +1,21 @@
|
||||
local Command = require 'utils.command'
|
||||
local Runner = require 'utils.test.runner'
|
||||
local Viewer = require 'utils.test.viewer'
|
||||
|
||||
Command.add(
|
||||
'test-runner',
|
||||
{
|
||||
description = "Runs tests and opens the test runner, use flag 'open' to skip running tests first.",
|
||||
arguments = {'open'},
|
||||
default_values = {open = false},
|
||||
allowed_by_server = false
|
||||
},
|
||||
function(args, player)
|
||||
local open = args.open
|
||||
if open == 'open' or open == 'o' then
|
||||
Viewer.open(player)
|
||||
else
|
||||
Runner.run_module(nil, player)
|
||||
end
|
||||
end
|
||||
)
|
18
utils/test/context.lua
Normal file
18
utils/test/context.lua
Normal file
@ -0,0 +1,18 @@
|
||||
local Public = {}
|
||||
Public.__index = Public
|
||||
|
||||
function Public.new(player)
|
||||
return setmetatable({player = player, _steps = {}}, Public)
|
||||
end
|
||||
|
||||
function Public.timeout(self, delay, func)
|
||||
local steps = self._steps
|
||||
steps[#steps + 1] = {func = func, delay = delay or 1}
|
||||
return self
|
||||
end
|
||||
|
||||
function Public.next(self, func)
|
||||
return self:timeout(1, func)
|
||||
end
|
||||
|
||||
return Public
|
10
utils/test/declare.lua
Normal file
10
utils/test/declare.lua
Normal file
@ -0,0 +1,10 @@
|
||||
local ModuleStore = require 'utils.test.module_store'
|
||||
|
||||
local Public = {}
|
||||
|
||||
Public.module = ModuleStore.module
|
||||
Public.test = ModuleStore.test
|
||||
Public.module_startup = ModuleStore.module_startup
|
||||
Public.module_teardown = ModuleStore.module_teardown
|
||||
|
||||
return Public
|
5
utils/test/discovery.lua
Normal file
5
utils/test/discovery.lua
Normal file
@ -0,0 +1,5 @@
|
||||
local include = require 'utils.test.include'
|
||||
|
||||
for name in pairs(_G.package.loaded) do
|
||||
include(name .. '_tests')
|
||||
end
|
74
utils/test/event_factory.lua
Normal file
74
utils/test/event_factory.lua
Normal file
@ -0,0 +1,74 @@
|
||||
local EventCore = require 'utils.event_core'
|
||||
|
||||
local Public = {}
|
||||
|
||||
Public.raise = EventCore.on_event
|
||||
|
||||
function Public.position(position)
|
||||
local x = position.x or position[1]
|
||||
local y = position.y or position[2]
|
||||
|
||||
position.x = x
|
||||
position[1] = x
|
||||
position.y = y
|
||||
position[2] = y
|
||||
|
||||
return position
|
||||
end
|
||||
|
||||
function Public.area(area)
|
||||
local left_top = area.left_top or area[1]
|
||||
local right_bottom = area.right_bottom or area[2]
|
||||
|
||||
Public.position(left_top)
|
||||
Public.position(right_bottom)
|
||||
|
||||
area.left_top = left_top
|
||||
area[1] = left_top
|
||||
area.right_bottom = right_bottom
|
||||
area[2] = right_bottom
|
||||
|
||||
return area
|
||||
end
|
||||
|
||||
function Public.on_gui_click(element, player_index)
|
||||
return {
|
||||
name = defines.events.on_gui_click,
|
||||
tick = game.tick,
|
||||
element = element,
|
||||
player_index = player_index,
|
||||
button = defines.mouse_button_type.left,
|
||||
alt = false,
|
||||
control = false,
|
||||
shift = false
|
||||
}
|
||||
end
|
||||
|
||||
function Public.on_player_deconstructed_area(player_index, surface, area, item)
|
||||
return {
|
||||
name = defines.events.on_player_deconstructed_area,
|
||||
tick = game.tick,
|
||||
player_index = player_index,
|
||||
surface = surface,
|
||||
area = Public.area(area),
|
||||
item = item,
|
||||
alt = false
|
||||
}
|
||||
end
|
||||
|
||||
function Public.do_player_deconstruct_area(cursor, player, area, optional_skip_fog_of_war)
|
||||
cursor.deconstruct_area(
|
||||
{
|
||||
surface = player.surface,
|
||||
force = player.force,
|
||||
area = area,
|
||||
by_player = player,
|
||||
skip_fog_of_war = optional_skip_fog_of_war
|
||||
}
|
||||
)
|
||||
|
||||
local event = Public.on_player_deconstructed_area(player.index, player.surface, area, cursor.name)
|
||||
Public.raise(event)
|
||||
end
|
||||
|
||||
return Public
|
128
utils/test/helper.lua
Normal file
128
utils/test/helper.lua
Normal file
@ -0,0 +1,128 @@
|
||||
local Global = require 'utils.global'
|
||||
|
||||
local Public = {}
|
||||
|
||||
local surface_count = 0
|
||||
|
||||
Global.register(
|
||||
{surface_count = surface_count},
|
||||
function(tbl)
|
||||
surface_count = tbl.surface_count
|
||||
end
|
||||
)
|
||||
|
||||
local function get_surface_name()
|
||||
surface_count = surface_count + 1
|
||||
return 'test_surface' .. surface_count
|
||||
end
|
||||
|
||||
local autoplace_settings = {
|
||||
tile = {
|
||||
treat_missing_as_default = false,
|
||||
settings = {
|
||||
['grass-1'] = {frequency = 1, size = 1, richness = 1}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local autoplace_controls = {
|
||||
trees = {
|
||||
frequency = 1,
|
||||
richness = 1,
|
||||
size = 0
|
||||
},
|
||||
['enemy-base'] = {
|
||||
frequency = 1,
|
||||
richness = 1,
|
||||
size = 0
|
||||
},
|
||||
coal = {
|
||||
frequency = 1,
|
||||
richness = 1,
|
||||
size = 0
|
||||
},
|
||||
['copper-ore'] = {
|
||||
frequency = 1,
|
||||
richness = 1,
|
||||
size = 0
|
||||
},
|
||||
['crude-oil'] = {
|
||||
frequency = 1,
|
||||
richness = 1,
|
||||
size = 0
|
||||
},
|
||||
['iron-ore'] = {
|
||||
frequency = 1,
|
||||
richness = 1,
|
||||
size = 0
|
||||
},
|
||||
stone = {
|
||||
frequency = 1,
|
||||
richness = 1,
|
||||
size = 0
|
||||
},
|
||||
['uranium-ore'] = {
|
||||
frequency = 1,
|
||||
richness = 1,
|
||||
size = 0
|
||||
}
|
||||
}
|
||||
|
||||
local cliff_settings = {
|
||||
cliff_elevation_0 = 1024,
|
||||
cliff_elevation_interval = 10,
|
||||
name = 'cliff'
|
||||
}
|
||||
|
||||
function Public.startup_test_surface(context, options)
|
||||
options = options or {}
|
||||
local name = options.name or get_surface_name()
|
||||
local area = options.area or {64, 64}
|
||||
|
||||
local player = context.player
|
||||
local old_surface = player.surface
|
||||
local old_position = player.position
|
||||
local old_character = player.character
|
||||
|
||||
local surface =
|
||||
game.create_surface(
|
||||
name,
|
||||
{
|
||||
width = area.x or area[1],
|
||||
height = area.y or area[2],
|
||||
autoplace_settings = autoplace_settings,
|
||||
autoplace_controls = autoplace_controls,
|
||||
cliff_settings = cliff_settings
|
||||
}
|
||||
)
|
||||
|
||||
surface.request_to_generate_chunks({0, 0}, 32)
|
||||
surface.force_generate_chunk_requests()
|
||||
|
||||
context:next(
|
||||
function()
|
||||
for k, v in pairs(surface.find_entities()) do
|
||||
v.destroy()
|
||||
end
|
||||
|
||||
surface.destroy_decoratives {area = {{-32, -32}, {32, 32}}}
|
||||
|
||||
player.character = nil
|
||||
player.teleport({0, 0}, surface)
|
||||
player.create_character()
|
||||
end
|
||||
)
|
||||
|
||||
return function()
|
||||
player.character = nil
|
||||
player.teleport(old_position, old_surface)
|
||||
|
||||
if old_character and old_character.valid then
|
||||
player.character = old_character
|
||||
end
|
||||
|
||||
game.delete_surface(surface)
|
||||
end
|
||||
end
|
||||
|
||||
return Public
|
14
utils/test/include.lua
Normal file
14
utils/test/include.lua
Normal file
@ -0,0 +1,14 @@
|
||||
local require = require
|
||||
local pcall = pcall
|
||||
local find = string.find
|
||||
|
||||
local function file_is_missing(message)
|
||||
return find(message, 'no such file') or find(message, 'File was removed to decrease save file size')
|
||||
end
|
||||
|
||||
return function(name)
|
||||
local s, e = pcall(require, name)
|
||||
if not s and not file_is_missing(e) then
|
||||
error(e, 2)
|
||||
end
|
||||
end
|
5
utils/test/main.lua
Normal file
5
utils/test/main.lua
Normal file
@ -0,0 +1,5 @@
|
||||
local include = require 'utils.test.include'
|
||||
include 'utils.test.runner'
|
||||
include 'utils.test.viewer'
|
||||
include 'utils.test.command'
|
||||
include 'utils.test.discovery'
|
145
utils/test/module_store.lua
Normal file
145
utils/test/module_store.lua
Normal file
@ -0,0 +1,145 @@
|
||||
local Public = {}
|
||||
|
||||
local function new_module(module_name)
|
||||
return {
|
||||
id = nil,
|
||||
name = module_name,
|
||||
parent = nil,
|
||||
children = {},
|
||||
startup_func = nil,
|
||||
startup_steps = nil,
|
||||
startup_current_step = nil,
|
||||
startup_error = nil,
|
||||
teardown_func = nil,
|
||||
teardown_steps = nil,
|
||||
teardown_current_step = nil,
|
||||
teardown_error = nil,
|
||||
test_funcs = {},
|
||||
tests = nil,
|
||||
is_open = false,
|
||||
depth = nil,
|
||||
count = nil,
|
||||
passed = nil
|
||||
}
|
||||
end
|
||||
|
||||
local root_module = new_module(nil)
|
||||
root_module.is_open = true
|
||||
Public.root_module = root_module
|
||||
|
||||
local parent_module = nil
|
||||
|
||||
local function add_module(module_name, module_func, parent)
|
||||
local parent_children = parent.children
|
||||
local module = parent_children[module_name]
|
||||
|
||||
if not module then
|
||||
module = new_module(module_name)
|
||||
parent_children[module_name] = module
|
||||
module.parent = parent_module
|
||||
end
|
||||
|
||||
parent_module = module
|
||||
module_func()
|
||||
end
|
||||
|
||||
local function no_op()
|
||||
end
|
||||
|
||||
local function add_module_range(modules_names, module_func, parent)
|
||||
for i = 1, #modules_names - 1 do
|
||||
local name = modules_names[i]
|
||||
add_module(name, no_op, parent)
|
||||
parent = parent_module
|
||||
end
|
||||
|
||||
add_module(modules_names[#modules_names], module_func, parent)
|
||||
end
|
||||
|
||||
function Public.module(module_name, module_func)
|
||||
local module_name_type = type(module_name)
|
||||
if module_name_type ~= 'string' and module_name_type ~= 'table' then
|
||||
error('module_name must be of type string or array of strings.', 2)
|
||||
end
|
||||
|
||||
if module_name_type == 'table' and #module_name == 0 then
|
||||
error('when module_name is array must be non empty.', 2)
|
||||
end
|
||||
|
||||
if type(module_func) ~= 'function' then
|
||||
error('module_func must be of type function.', 2)
|
||||
end
|
||||
|
||||
local old_parent = parent_module
|
||||
local parent = parent_module or root_module
|
||||
|
||||
if module_name_type == 'string' then
|
||||
add_module(module_name, module_func, parent)
|
||||
else
|
||||
add_module_range(module_name, module_func, parent)
|
||||
end
|
||||
|
||||
parent_module = old_parent
|
||||
end
|
||||
|
||||
function Public.test(test_name, test_func)
|
||||
if not parent_module then
|
||||
error('test can not be declared outisde of a module.', 2)
|
||||
end
|
||||
|
||||
if type(test_name) ~= 'string' then
|
||||
error('test_name must be of type string.', 2)
|
||||
end
|
||||
|
||||
if type(test_func) ~= 'function' then
|
||||
error('test_func must be of type function.', 2)
|
||||
end
|
||||
|
||||
local test_funcs = parent_module.test_funcs
|
||||
if test_funcs[test_name] then
|
||||
error(
|
||||
table.concat {
|
||||
"test '",
|
||||
test_name,
|
||||
"' already exists, can not have duplicate test names in the same module."
|
||||
},
|
||||
2
|
||||
)
|
||||
end
|
||||
|
||||
test_funcs[test_name] = test_func
|
||||
end
|
||||
|
||||
function Public.module_startup(startup_func)
|
||||
if type(startup_func) ~= 'function' then
|
||||
error('startup_func must be of type function.', 2)
|
||||
end
|
||||
|
||||
if parent_module == nil then
|
||||
error('root module can not have startup_func.', 2)
|
||||
end
|
||||
|
||||
if parent_module.startup_func ~= nil then
|
||||
error('startup_func can not be declared twice for the same module.', 2)
|
||||
end
|
||||
|
||||
parent_module.startup_func = startup_func
|
||||
end
|
||||
|
||||
function Public.module_teardown(teardown_func)
|
||||
if type(teardown_func) ~= 'function' then
|
||||
error('teardown_func must be of type function.', 2)
|
||||
end
|
||||
|
||||
if parent_module == nil then
|
||||
error('root module can not have teardown_func.', 2)
|
||||
end
|
||||
|
||||
if parent_module.teardown_func ~= nil then
|
||||
error('teardown_func can not be declared twice for the same module.', 2)
|
||||
end
|
||||
|
||||
parent_module.teardown_func = teardown_func
|
||||
end
|
||||
|
||||
return Public
|
244
utils/test/runner.lua
Normal file
244
utils/test/runner.lua
Normal file
@ -0,0 +1,244 @@
|
||||
local Token = require 'utils.token'
|
||||
local Task = require 'utils.task'
|
||||
local ModuleStore = require 'utils.test.module_store'
|
||||
local Builder = require 'utils.test.builder'
|
||||
local Event = require 'utils.event'
|
||||
|
||||
local pcall = pcall
|
||||
|
||||
local Public = {}
|
||||
|
||||
Public.events = {
|
||||
tests_run_finished = Event.generate_event_name('test_run_finished')
|
||||
}
|
||||
|
||||
local run_runnables_token
|
||||
|
||||
local function print_summary(data)
|
||||
local pass_count = data.count - data.fail_count
|
||||
data.player.print(table.concat {pass_count, ' of ', data.count, ' tests passed.'})
|
||||
end
|
||||
|
||||
local function mark_module_for_passed(module)
|
||||
local any_fails = false
|
||||
local all_ran = true
|
||||
|
||||
for _, child in pairs(module.children) do
|
||||
local module_any_fails, module_all_ran = mark_module_for_passed(child)
|
||||
any_fails = any_fails or module_any_fails
|
||||
all_ran = all_ran and module_all_ran
|
||||
end
|
||||
|
||||
for _, test in pairs(module.tests) do
|
||||
any_fails = any_fails or (test.passed == false)
|
||||
all_ran = all_ran and (test.passed ~= nil)
|
||||
end
|
||||
|
||||
if any_fails then
|
||||
module.passed = false
|
||||
elseif all_ran then
|
||||
module.passed = true
|
||||
else
|
||||
module.passed = nil
|
||||
end
|
||||
|
||||
return any_fails, all_ran
|
||||
end
|
||||
|
||||
local function mark_modules_for_passed()
|
||||
mark_module_for_passed(ModuleStore.root_module)
|
||||
end
|
||||
|
||||
local function finish_test_run(data)
|
||||
print_summary(data)
|
||||
mark_modules_for_passed()
|
||||
script.raise_event(Public.events.tests_run_finished, {player = data.player})
|
||||
end
|
||||
|
||||
local function print_error(player, test_name, error_message)
|
||||
player.print(table.concat {"Failed - '", test_name, "': ", tostring(error_message)}, {r = 1})
|
||||
end
|
||||
|
||||
local function print_success(player, test_name)
|
||||
player.print(table.concat {"Passed - '", test_name, "'"}, {g = 1})
|
||||
end
|
||||
|
||||
local function print_hook_error(hook)
|
||||
hook.context.player.print(table.concat {'Failed ', hook.name, " hook -':", tostring(hook.error)}, {r = 1})
|
||||
end
|
||||
|
||||
local function record_hook_error_in_module(hook)
|
||||
if hook.name == 'startup' then
|
||||
hook.module.startup_error = hook.error
|
||||
elseif hook.name == 'teardown' then
|
||||
hook.module.teardown_error = hook.error
|
||||
end
|
||||
end
|
||||
|
||||
local function do_termination(data)
|
||||
if not data.stop_on_first_error then
|
||||
return false
|
||||
end
|
||||
|
||||
data.player.print('Test run canceled due to stop on first error policy.')
|
||||
finish_test_run(data)
|
||||
data.index = -1
|
||||
return true
|
||||
end
|
||||
|
||||
local function run_hook(hook)
|
||||
local context = hook.context
|
||||
local steps = context._steps
|
||||
local current_step = hook.current_step
|
||||
|
||||
local func
|
||||
if current_step == 0 then
|
||||
func = hook.func
|
||||
else
|
||||
func = steps[current_step].func
|
||||
end
|
||||
|
||||
local success, return_value = pcall(func, context)
|
||||
|
||||
if not success then
|
||||
hook.error = return_value
|
||||
print_hook_error(hook)
|
||||
record_hook_error_in_module(hook)
|
||||
return false
|
||||
end
|
||||
|
||||
if current_step == #steps then
|
||||
return true
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function do_hook(hook, data)
|
||||
local hook_success = run_hook(hook)
|
||||
|
||||
if hook_success == false and do_termination(data) then
|
||||
return
|
||||
end
|
||||
|
||||
if hook_success == nil then
|
||||
local step_index = hook.current_step + 1
|
||||
local step = hook.context._steps[step_index]
|
||||
hook.current_step = step_index
|
||||
Task.set_timeout_in_ticks(step.delay or 1, run_runnables_token, data)
|
||||
return
|
||||
end
|
||||
|
||||
data.index = data.index + 1
|
||||
Task.set_timeout_in_ticks(1, run_runnables_token, data)
|
||||
return
|
||||
end
|
||||
|
||||
local function run_test(test)
|
||||
local context = test.context
|
||||
local steps = context._steps
|
||||
local current_step = test.current_step
|
||||
|
||||
local func
|
||||
if current_step == 0 then
|
||||
func = test.func
|
||||
else
|
||||
func = steps[current_step].func
|
||||
end
|
||||
|
||||
local success, return_value = pcall(func, context)
|
||||
|
||||
if not success then
|
||||
print_error(context.player, test.name, return_value)
|
||||
test.passed = false
|
||||
test.error = return_value
|
||||
return false
|
||||
end
|
||||
|
||||
if current_step == #steps then
|
||||
print_success(context.player, test.name)
|
||||
test.passed = true
|
||||
return true
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function do_test(test, data)
|
||||
local success = run_test(test)
|
||||
|
||||
if success == false then
|
||||
data.count = data.count + 1
|
||||
data.fail_count = data.fail_count + 1
|
||||
|
||||
if do_termination(data) then
|
||||
return
|
||||
end
|
||||
|
||||
data.index = data.index + 1
|
||||
Task.set_timeout_in_ticks(1, run_runnables_token, data)
|
||||
return
|
||||
end
|
||||
|
||||
if success == true then
|
||||
data.count = data.count + 1
|
||||
data.index = data.index + 1
|
||||
Task.set_timeout_in_ticks(1, run_runnables_token, data)
|
||||
return
|
||||
end
|
||||
|
||||
local step_index = test.current_step + 1
|
||||
test.current_step = step_index
|
||||
local step = test.context._steps[step_index]
|
||||
Task.set_timeout_in_ticks(step.delay or 1, run_runnables_token, data)
|
||||
end
|
||||
|
||||
local function run_runnables(data)
|
||||
local index = data.index
|
||||
local runnable = data.runnables[index]
|
||||
|
||||
if runnable == nil then
|
||||
finish_test_run(data)
|
||||
return
|
||||
end
|
||||
|
||||
if runnable.is_hook then
|
||||
do_hook(runnable, data)
|
||||
else
|
||||
do_test(runnable, data)
|
||||
end
|
||||
end
|
||||
|
||||
run_runnables_token = Token.register(run_runnables)
|
||||
|
||||
local function validate_options(options)
|
||||
options = options or {}
|
||||
options.stop_on_first_error = options.stop_on_first_error or false
|
||||
return options
|
||||
end
|
||||
|
||||
local function run(runnables, player, options)
|
||||
options = validate_options(options)
|
||||
run_runnables(
|
||||
{
|
||||
runnables = runnables,
|
||||
player = player,
|
||||
index = 1,
|
||||
count = 0,
|
||||
fail_count = 0,
|
||||
stop_on_first_error = options.stop_on_first_error
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
function Public.run_module(module, player, options)
|
||||
local runnables = Builder.build_module_for_run(module or ModuleStore.root_module, player)
|
||||
run(runnables, player, options)
|
||||
end
|
||||
|
||||
function Public.run_test(test, player, options)
|
||||
local runnables = Builder.build_test_for_run(test, player)
|
||||
run(runnables, player, options)
|
||||
end
|
||||
|
||||
return Public
|
423
utils/test/viewer.lua
Normal file
423
utils/test/viewer.lua
Normal file
@ -0,0 +1,423 @@
|
||||
local Gui = require 'utils.gui'
|
||||
local Builder = require 'utils.test.builder'
|
||||
local Runner = require 'utils.test.runner'
|
||||
local Color = require 'resources.color_presets'
|
||||
local Token = require 'utils.token'
|
||||
local Task = require 'utils.task'
|
||||
local Event = require 'utils.event'
|
||||
local Global = require 'utils.global'
|
||||
|
||||
local Public = {}
|
||||
|
||||
local info_type_test = {}
|
||||
local info_type_module = {}
|
||||
|
||||
local down_arrow = '▼'
|
||||
local right_arrow = '►'
|
||||
local color_success = {g = 1}
|
||||
local color_failure = {r = 1}
|
||||
local color_selected = Color.orange
|
||||
local color_default = Color.white
|
||||
|
||||
local main_frame_name = Gui.uid_name()
|
||||
local close_main_frame_name = Gui.uid_name()
|
||||
local module_arrow_name = Gui.uid_name()
|
||||
local module_label_name = Gui.uid_name()
|
||||
local test_label_name = Gui.uid_name()
|
||||
local run_all_button_name = Gui.uid_name()
|
||||
local run_selected_button_name = Gui.uid_name()
|
||||
local stop_on_error_checkbox_name = Gui.uid_name()
|
||||
local error_test_box_name = Gui.uid_name()
|
||||
|
||||
local selected_test_info_by_player_index = {}
|
||||
local stop_on_first_error_by_player_index = {}
|
||||
|
||||
Global.register(
|
||||
{
|
||||
selected_test_info_by_player_index = selected_test_info_by_player_index,
|
||||
stop_on_first_error_by_player_index = stop_on_first_error_by_player_index
|
||||
},
|
||||
function(tbl)
|
||||
selected_test_info_by_player_index = tbl.selected_test_info_by_player_index
|
||||
stop_on_first_error_by_player_index = tbl.stop_on_first_error_by_player_index
|
||||
end
|
||||
)
|
||||
|
||||
local function get_module_state(module)
|
||||
local passed = module.passed
|
||||
if passed == false or module.startup_error or module.teardown_error then
|
||||
return false
|
||||
end
|
||||
|
||||
return passed
|
||||
end
|
||||
|
||||
local function get_test_error(test)
|
||||
return test.error or ''
|
||||
end
|
||||
|
||||
local function get_module_error(module)
|
||||
local errors = {}
|
||||
if module.startup_error then
|
||||
errors[#errors + 1] = 'startup error: '
|
||||
errors[#errors + 1] = module.startup_error
|
||||
errors[#errors + 1] = '\n\n'
|
||||
end
|
||||
if module.teardown_error then
|
||||
errors[#errors + 1] = 'teardown error: '
|
||||
errors[#errors + 1] = module.teardown_error
|
||||
end
|
||||
|
||||
return table.concat(errors)
|
||||
end
|
||||
|
||||
local function get_text_box_error(player_index)
|
||||
local test_info = selected_test_info_by_player_index[player_index]
|
||||
if test_info == nil then
|
||||
return ''
|
||||
end
|
||||
|
||||
local info_type = test_info.type
|
||||
|
||||
if info_type == info_type_test then
|
||||
return get_test_error(test_info.test)
|
||||
elseif info_type == info_type_module then
|
||||
return get_module_error(test_info.module)
|
||||
end
|
||||
end
|
||||
|
||||
local function set_selected_style(style, selected)
|
||||
if selected then
|
||||
style.font_color = color_selected
|
||||
else
|
||||
style.font_color = color_default
|
||||
end
|
||||
end
|
||||
|
||||
local function set_passed_style(style, passed)
|
||||
if passed == true then
|
||||
style.font_color = color_success
|
||||
elseif passed == false then
|
||||
style.font_color = color_failure
|
||||
else
|
||||
style.font_color = color_default
|
||||
end
|
||||
end
|
||||
|
||||
local function is_test_selected(test, player_index)
|
||||
local info = selected_test_info_by_player_index[player_index]
|
||||
if not info then
|
||||
return false
|
||||
end
|
||||
|
||||
local info_test = info.test
|
||||
if not info_test then
|
||||
return false
|
||||
end
|
||||
|
||||
return info_test.id == test.id
|
||||
end
|
||||
|
||||
local function is_module_selected(module, player_index)
|
||||
local info = selected_test_info_by_player_index[player_index]
|
||||
if not info then
|
||||
return false
|
||||
end
|
||||
|
||||
local info_module = info.module
|
||||
if not info_module then
|
||||
return false
|
||||
end
|
||||
|
||||
return info_module.id == module.id
|
||||
end
|
||||
|
||||
local function draw_tests_test(container, test, depth)
|
||||
local flow = container.add {type = 'flow'}
|
||||
|
||||
local label = flow.add {type = 'label', name = test_label_name, caption = test.name}
|
||||
local label_style = label.style
|
||||
|
||||
local is_selected = is_test_selected(test, container.player_index)
|
||||
set_selected_style(label_style, is_selected)
|
||||
if not is_selected then
|
||||
set_passed_style(label_style, test.passed)
|
||||
end
|
||||
|
||||
label_style.left_margin = depth * 15 + 10
|
||||
Gui.set_data(label, {test = test, container = container})
|
||||
end
|
||||
|
||||
local function draw_tests_module(container, module)
|
||||
local caption = {module.name or 'All Tests', ' (', module.count, ')'}
|
||||
caption = table.concat(caption)
|
||||
|
||||
local flow = container.add {type = 'flow'}
|
||||
local arrow =
|
||||
flow.add {
|
||||
type = 'label',
|
||||
name = module_arrow_name,
|
||||
caption = module.is_open and down_arrow or right_arrow
|
||||
}
|
||||
arrow.style.left_margin = module.depth * 15
|
||||
Gui.set_data(arrow, {module = module, container = container})
|
||||
|
||||
local label = flow.add {type = 'label', name = module_label_name, caption = caption}
|
||||
|
||||
local label_style = label.style
|
||||
local is_selected = is_module_selected(module, container.player_index)
|
||||
set_selected_style(label_style, is_selected)
|
||||
if not is_selected then
|
||||
set_passed_style(label_style, get_module_state(module))
|
||||
end
|
||||
|
||||
Gui.set_data(label, {module = module, container = container})
|
||||
|
||||
if not module.is_open then
|
||||
return
|
||||
end
|
||||
|
||||
for _, child in pairs(module.children) do
|
||||
draw_tests_module(container, child)
|
||||
end
|
||||
|
||||
for _, test in pairs(module.tests) do
|
||||
draw_tests_test(container, test, module.depth + 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function redraw_tests(container)
|
||||
Gui.clear(container)
|
||||
|
||||
local root_module = Builder.get_root_modules()
|
||||
draw_tests_module(container, root_module)
|
||||
end
|
||||
|
||||
local function draw_tests(container)
|
||||
local scroll_pane =
|
||||
container.add {
|
||||
type = 'scroll-pane',
|
||||
horizontal_scroll_policy = 'auto-and-reserve-space',
|
||||
vertical_scroll_policy = 'auto-and-reserve-space'
|
||||
}
|
||||
local scroll_pane_style = scroll_pane.style
|
||||
scroll_pane_style.horizontally_stretchable = true
|
||||
scroll_pane_style.height = 350
|
||||
|
||||
local list = scroll_pane.add {type = 'flow', direction = 'vertical'}
|
||||
|
||||
redraw_tests(list)
|
||||
end
|
||||
|
||||
local function draw_error_text_box(container)
|
||||
local text = get_text_box_error(container.player_index)
|
||||
|
||||
local text_box = container.add {type = 'text-box', name = error_test_box_name, text = text}
|
||||
local style = text_box.style
|
||||
style.vertically_stretchable = true
|
||||
style.horizontally_stretchable = true
|
||||
style.maximal_width = 800
|
||||
return text_box
|
||||
end
|
||||
|
||||
local function create_main_frame(center)
|
||||
local frame = center.add {type = 'frame', name = main_frame_name, caption = 'Test Runner', direction = 'vertical'}
|
||||
local frame_style = frame.style
|
||||
frame_style.width = 800
|
||||
frame_style.height = 600
|
||||
|
||||
local top_flow = frame.add {type = 'flow', direction = 'horizontal'}
|
||||
top_flow.add {type = 'button', name = run_all_button_name, caption = 'Run All'}
|
||||
top_flow.add {
|
||||
type = 'button',
|
||||
name = run_selected_button_name,
|
||||
caption = 'Run Selected'
|
||||
}
|
||||
top_flow.add {
|
||||
type = 'checkbox',
|
||||
name = stop_on_error_checkbox_name,
|
||||
caption = 'Stop on first error',
|
||||
state = stop_on_first_error_by_player_index[center.player_index] or false
|
||||
}
|
||||
|
||||
draw_tests(frame)
|
||||
|
||||
local error_text_box = draw_error_text_box(frame)
|
||||
Gui.set_data(frame, {error_text_box = error_text_box})
|
||||
|
||||
local close_button = frame.add {type = 'button', name = close_main_frame_name, caption = 'Close'}
|
||||
Gui.set_data(close_button, frame)
|
||||
end
|
||||
|
||||
local function close_main_frame(frame)
|
||||
Gui.destroy(frame)
|
||||
end
|
||||
|
||||
local function get_error_text_box(player)
|
||||
local frame = player.gui.center[main_frame_name]
|
||||
local frame_data = Gui.get_data(frame)
|
||||
return frame_data.error_text_box
|
||||
end
|
||||
|
||||
local function make_options(player_index)
|
||||
return {stop_on_first_error = stop_on_first_error_by_player_index[player_index]}
|
||||
end
|
||||
|
||||
local run_module_token =
|
||||
Token.register(
|
||||
function(data)
|
||||
Runner.run_module(data.module, data.player, data.options)
|
||||
end
|
||||
)
|
||||
|
||||
local run_test_token =
|
||||
Token.register(
|
||||
function(data)
|
||||
Runner.run_test(data.test, data.player, data.options)
|
||||
end
|
||||
)
|
||||
|
||||
Gui.on_click(
|
||||
close_main_frame_name,
|
||||
function(event)
|
||||
local element = event.element
|
||||
local frame = Gui.get_data(element)
|
||||
close_main_frame(frame)
|
||||
end
|
||||
)
|
||||
|
||||
Gui.on_click(
|
||||
module_arrow_name,
|
||||
function(event)
|
||||
local element = event.element
|
||||
local data = Gui.get_data(element)
|
||||
local module = data.module
|
||||
local container = data.container
|
||||
|
||||
module.is_open = not module.is_open
|
||||
redraw_tests(container)
|
||||
end
|
||||
)
|
||||
|
||||
Gui.on_click(
|
||||
module_label_name,
|
||||
function(event)
|
||||
local element = event.element
|
||||
local data = Gui.get_data(element)
|
||||
local module = data.module
|
||||
local container = data.container
|
||||
local player_index = event.player_index
|
||||
|
||||
local is_selected = not is_module_selected(module, player_index)
|
||||
selected_test_info_by_player_index[player_index] = nil
|
||||
|
||||
if is_selected then
|
||||
selected_test_info_by_player_index[player_index] = {type = info_type_module, module = module}
|
||||
end
|
||||
|
||||
local error_text_box = get_error_text_box(event.player)
|
||||
if is_selected then
|
||||
error_text_box.text = get_module_error(module)
|
||||
else
|
||||
error_text_box.text = ''
|
||||
end
|
||||
|
||||
redraw_tests(container)
|
||||
end
|
||||
)
|
||||
|
||||
Gui.on_click(
|
||||
test_label_name,
|
||||
function(event)
|
||||
local element = event.element
|
||||
local data = Gui.get_data(element)
|
||||
local test = data.test
|
||||
local container = data.container
|
||||
local player_index = event.player_index
|
||||
|
||||
local is_selected = not is_test_selected(test, player_index)
|
||||
selected_test_info_by_player_index[player_index] = nil
|
||||
|
||||
if is_selected then
|
||||
selected_test_info_by_player_index[player_index] = {type = info_type_test, test = test}
|
||||
end
|
||||
|
||||
local error_text_box = get_error_text_box(event.player)
|
||||
if is_selected then
|
||||
error_text_box.text = get_test_error(test)
|
||||
else
|
||||
error_text_box.text = ''
|
||||
end
|
||||
|
||||
redraw_tests(container)
|
||||
end
|
||||
)
|
||||
|
||||
Gui.on_click(
|
||||
run_all_button_name,
|
||||
function(event)
|
||||
local frame = event.player.gui.center[main_frame_name]
|
||||
close_main_frame(frame)
|
||||
|
||||
local options = make_options(event.player_index)
|
||||
Task.set_timeout_in_ticks(1, run_module_token, {module = nil, player = event.player, options = options})
|
||||
end
|
||||
)
|
||||
|
||||
Gui.on_click(
|
||||
run_selected_button_name,
|
||||
function(event)
|
||||
local test_info = selected_test_info_by_player_index[event.player_index]
|
||||
if test_info == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local options = make_options(event.player_index)
|
||||
|
||||
local info_type = test_info.type
|
||||
if info_type == info_type_module then
|
||||
Task.set_timeout_in_ticks(
|
||||
1,
|
||||
run_module_token,
|
||||
{module = test_info.module, player = event.player, options = options}
|
||||
)
|
||||
elseif info_type == info_type_test then
|
||||
Task.set_timeout_in_ticks(
|
||||
1,
|
||||
run_test_token,
|
||||
{test = test_info.test, player = event.player, options = options}
|
||||
)
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
local frame = event.player.gui.center[main_frame_name]
|
||||
close_main_frame(frame)
|
||||
end
|
||||
)
|
||||
|
||||
Gui.on_checked_state_changed(
|
||||
stop_on_error_checkbox_name,
|
||||
function(event)
|
||||
stop_on_first_error_by_player_index[event.player_index] = event.element.state or nil
|
||||
end
|
||||
)
|
||||
|
||||
Event.add(
|
||||
Runner.events.tests_run_finished,
|
||||
function(event)
|
||||
Public.open(event.player)
|
||||
end
|
||||
)
|
||||
|
||||
function Public.open(player)
|
||||
local center = player.gui.center
|
||||
local frame = center[main_frame_name]
|
||||
if frame then
|
||||
return
|
||||
end
|
||||
|
||||
create_main_frame(center)
|
||||
end
|
||||
|
||||
return Public
|
Loading…
Reference in New Issue
Block a user