mirror of
https://github.com/evennia/evennia.git
synced 2026-04-02 22:17:17 +02:00
Unit testing/debugging olc menu
This commit is contained in:
parent
9360dc71f1
commit
194eb8e42f
4 changed files with 171 additions and 31 deletions
|
|
@ -29,7 +29,7 @@ _MENU_ATTR_LITERAL_EVAL_ERROR = (
|
|||
|
||||
|
||||
def _get_menu_prototype(caller):
|
||||
|
||||
"""Return currently active menu prototype."""
|
||||
prototype = None
|
||||
if hasattr(caller.ndb._menutree, "olc_prototype"):
|
||||
prototype = caller.ndb._menutree.olc_prototype
|
||||
|
|
@ -40,11 +40,23 @@ def _get_menu_prototype(caller):
|
|||
|
||||
|
||||
def _is_new_prototype(caller):
|
||||
"""Check if prototype is marked as new or was loaded from a saved one."""
|
||||
return hasattr(caller.ndb._menutree, "olc_new")
|
||||
|
||||
|
||||
def _format_property(prop, required=False, prototype=None, cropper=None):
|
||||
def _format_option_value(prop, required=False, prototype=None, cropper=None):
|
||||
"""
|
||||
Format wizard option values.
|
||||
|
||||
Args:
|
||||
prop (str): Name or value to format.
|
||||
required (bool, optional): The option is required.
|
||||
prototype (dict, optional): If given, `prop` will be considered a key in this prototype.
|
||||
cropper (callable, optional): A function to crop the value to a certain width.
|
||||
|
||||
Returns:
|
||||
value (str): The formatted value.
|
||||
"""
|
||||
if prototype is not None:
|
||||
prop = prototype.get(prop, '')
|
||||
|
||||
|
|
@ -61,7 +73,8 @@ def _format_property(prop, required=False, prototype=None, cropper=None):
|
|||
return " ({}|n)".format(cropper(out) if cropper else utils.crop(out, _MENU_CROP_WIDTH))
|
||||
|
||||
|
||||
def _set_prototype_value(caller, field, value):
|
||||
def _set_prototype_value(caller, field, value, parse=True):
|
||||
"""Set prototype's field in a safe way."""
|
||||
prototype = _get_menu_prototype(caller)
|
||||
prototype[field] = value
|
||||
caller.ndb._menutree.olc_prototype = prototype
|
||||
|
|
@ -70,15 +83,21 @@ def _set_prototype_value(caller, field, value):
|
|||
|
||||
def _set_property(caller, raw_string, **kwargs):
|
||||
"""
|
||||
Update a property. To be called by the 'goto' option variable.
|
||||
Add or update a property. To be called by the 'goto' option variable.
|
||||
|
||||
Args:
|
||||
caller (Object, Account): The user of the wizard.
|
||||
raw_string (str): Input from user on given node - the new value to set.
|
||||
|
||||
Kwargs:
|
||||
test_parse (bool): If set (default True), parse raw_string for protfuncs and obj-refs and
|
||||
try to run result through literal_eval. The parser will be run in 'testing' mode and any
|
||||
parsing errors will shown to the user. Note that this is just for testing, the original
|
||||
given string will be what is inserted.
|
||||
prop (str): Property name to edit with `raw_string`.
|
||||
processor (callable): Converts `raw_string` to a form suitable for saving.
|
||||
next_node (str): Where to redirect to after this has run.
|
||||
|
||||
Returns:
|
||||
next_node (str): Next node to go to.
|
||||
|
||||
|
|
@ -103,7 +122,7 @@ def _set_property(caller, raw_string, **kwargs):
|
|||
if not value:
|
||||
return next_node
|
||||
|
||||
prototype = _set_prototype_value(caller, "prototype_key", value)
|
||||
prototype = _set_prototype_value(caller, prop, value)
|
||||
|
||||
# typeclass and prototype_parent can't co-exist
|
||||
if propname_low == "typeclass":
|
||||
|
|
@ -113,16 +132,26 @@ def _set_property(caller, raw_string, **kwargs):
|
|||
|
||||
caller.ndb._menutree.olc_prototype = prototype
|
||||
|
||||
caller.msg("Set {prop} to '{value}'.".format(prop=prop, value=str(value)))
|
||||
out = [" Set {prop} to {value} ({typ}).".format(prop=prop, value=value, typ=type(value))]
|
||||
|
||||
if kwargs.get("test_parse", True):
|
||||
out.append(" Simulating parsing ...")
|
||||
err, parsed_value = protlib.protfunc_parser(value, testing=True)
|
||||
if err:
|
||||
out.append(" |yPython `literal_eval` warning: {}|n".format(err))
|
||||
if parsed_value != value:
|
||||
out.append(" |g(Example-)value when parsed ({}):|n {}".format(
|
||||
type(parsed_value), parsed_value))
|
||||
else:
|
||||
out.append(" |gNo change.")
|
||||
|
||||
caller.msg("\n".join(out))
|
||||
|
||||
return next_node
|
||||
|
||||
|
||||
def _wizard_options(curr_node, prev_node, next_node, color="|W"):
|
||||
"""
|
||||
Creates default navigation options available in the wizard.
|
||||
|
||||
"""
|
||||
"""Creates default navigation options available in the wizard."""
|
||||
options = []
|
||||
if prev_node:
|
||||
options.append({"key": ("|wb|Wack", "b"),
|
||||
|
|
@ -166,7 +195,7 @@ def node_index(caller):
|
|||
|
||||
options = []
|
||||
options.append(
|
||||
{"desc": "|WPrototype-Key|n|n{}".format(_format_property("Key", True, prototype, None)),
|
||||
{"desc": "|WPrototype-Key|n|n{}".format(_format_option_value("Key", True, prototype, None)),
|
||||
"goto": "node_prototype_key"})
|
||||
for key in ('Prototype', 'Typeclass', 'Key', 'Aliases', 'Attrs', 'Tags', 'Locks',
|
||||
'Permissions', 'Location', 'Home', 'Destination'):
|
||||
|
|
@ -178,13 +207,13 @@ def node_index(caller):
|
|||
cropper = _path_cropper
|
||||
options.append(
|
||||
{"desc": "|w{}|n{}".format(
|
||||
key, _format_property(key, required, prototype, cropper=cropper)),
|
||||
key, _format_option_value(key, required, prototype, cropper=cropper)),
|
||||
"goto": "node_{}".format(key.lower())})
|
||||
required = False
|
||||
for key in ('Desc', 'Tags', 'Locks'):
|
||||
options.append(
|
||||
{"desc": "|WPrototype-{}|n|n{}".format(
|
||||
key, _format_property(key, required, prototype, None)),
|
||||
key, _format_option_value(key, required, prototype, None)),
|
||||
"goto": "node_prototype_{}".format(key.lower())})
|
||||
|
||||
return text, options
|
||||
|
|
@ -215,6 +244,7 @@ def _check_prototype_key(caller, key):
|
|||
olc_new = _is_new_prototype(caller)
|
||||
key = key.strip().lower()
|
||||
if old_prototype:
|
||||
old_prototype = old_prototype[0]
|
||||
# we are starting a new prototype that matches an existing
|
||||
if not caller.locks.check_lockstring(
|
||||
caller, old_prototype['prototype_locks'], access_type='edit'):
|
||||
|
|
@ -229,7 +259,7 @@ def _check_prototype_key(caller, key):
|
|||
caller.msg("Prototype already exists. Reloading.")
|
||||
return "node_index"
|
||||
|
||||
return _set_property(caller, key, prop='prototype_key', next_node="node_prototype")
|
||||
return _set_property(caller, key, prop='prototype_key', next_node="node_prototype_parent")
|
||||
|
||||
|
||||
def node_prototype_key(caller):
|
||||
|
|
@ -250,27 +280,32 @@ def node_prototype_key(caller):
|
|||
return text, options
|
||||
|
||||
|
||||
def _all_prototypes(caller):
|
||||
def _all_prototype_parents(caller):
|
||||
"""Return prototype_key of all available prototypes for listing in menu"""
|
||||
return [prototype["prototype_key"]
|
||||
for prototype in protlib.search_prototype() if "prototype_key" in prototype]
|
||||
|
||||
|
||||
def _prototype_examine(caller, prototype_name):
|
||||
def _prototype_parent_examine(caller, prototype_name):
|
||||
"""Convert prototype to a string representation for closer inspection"""
|
||||
prototypes = protlib.search_prototype(key=prototype_name)
|
||||
if prototypes:
|
||||
caller.msg(protlib.prototype_to_str(prototypes[0]))
|
||||
caller.msg("Prototype not registered.")
|
||||
return None
|
||||
ret = protlib.prototype_to_str(prototypes[0])
|
||||
caller.msg(ret)
|
||||
return ret
|
||||
else:
|
||||
caller.msg("Prototype not registered.")
|
||||
|
||||
|
||||
def _prototype_select(caller, prototype):
|
||||
ret = _set_property(caller, prototype, prop="prototype", processor=str, next_node="node_key")
|
||||
def _prototype_parent_select(caller, prototype):
|
||||
ret = _set_property(caller, prototype['prototype_key'],
|
||||
prop="prototype_parent", processor=str, next_node="node_key")
|
||||
caller.msg("Selected prototype |y{}|n. Removed any set typeclass parent.".format(prototype))
|
||||
return ret
|
||||
|
||||
|
||||
@list_node(_all_prototypes, _prototype_select)
|
||||
def node_prototype(caller):
|
||||
@list_node(_all_prototype_parents, _prototype_parent_select)
|
||||
def node_prototype_parent(caller):
|
||||
prototype = _get_menu_prototype(caller)
|
||||
|
||||
prot_parent_key = prototype.get('prototype')
|
||||
|
|
@ -289,18 +324,20 @@ def node_prototype(caller):
|
|||
text = "\n\n".join(text)
|
||||
options = _wizard_options("prototype", "prototype_key", "typeclass", color="|W")
|
||||
options.append({"key": "_default",
|
||||
"goto": _prototype_examine})
|
||||
"goto": _prototype_parent_examine})
|
||||
|
||||
return text, options
|
||||
|
||||
|
||||
def _all_typeclasses(caller):
|
||||
"""Get name of available typeclasses."""
|
||||
return list(name for name in
|
||||
sorted(utils.get_all_typeclasses("evennia.objects.models.ObjectDB").keys())
|
||||
if name != "evennia.objects.models.ObjectDB")
|
||||
|
||||
|
||||
def _typeclass_examine(caller, typeclass_path):
|
||||
"""Show info (docstring) about given typeclass."""
|
||||
if typeclass_path is None:
|
||||
# this means we are exiting the listing
|
||||
return "node_key"
|
||||
|
|
@ -319,10 +356,11 @@ def _typeclass_examine(caller, typeclass_path):
|
|||
else:
|
||||
txt = "This is typeclass |y{}|n.".format(typeclass)
|
||||
caller.msg(txt)
|
||||
return None
|
||||
return txt
|
||||
|
||||
|
||||
def _typeclass_select(caller, typeclass):
|
||||
"""Select typeclass from list and add it to prototype. Return next node to go to."""
|
||||
ret = _set_property(caller, typeclass, prop='typeclass', processor=str, next_node="node_key")
|
||||
caller.msg("Selected typeclass |y{}|n. Removed any set prototype parent.".format(typeclass))
|
||||
return ret
|
||||
|
|
@ -350,7 +388,7 @@ def node_key(caller):
|
|||
prototype = _get_menu_prototype(caller)
|
||||
key = prototype.get("key")
|
||||
|
||||
text = ["Set the prototype's |yKey|n. This will retain case sensitivity."]
|
||||
text = ["Set the prototype's name (|yKey|n.) This will retain case sensitivity."]
|
||||
if key:
|
||||
text.append("Current key value is '|y{key}|n'.".format(key=key))
|
||||
else:
|
||||
|
|
@ -370,7 +408,7 @@ def node_aliases(caller):
|
|||
aliases = prototype.get("aliases")
|
||||
|
||||
text = ["Set the prototype's |yAliases|n. Separate multiple aliases with commas. "
|
||||
"ill retain case sensitivity."]
|
||||
"they'll retain case sensitivity."]
|
||||
if aliases:
|
||||
text.append("Current aliases are '|y{aliases}|n'.".format(aliases=aliases))
|
||||
else:
|
||||
|
|
@ -714,7 +752,7 @@ def start_olc(caller, session=None, prototype=None):
|
|||
menudata = {"node_index": node_index,
|
||||
"node_validate_prototype": node_validate_prototype,
|
||||
"node_prototype_key": node_prototype_key,
|
||||
"node_prototype": node_prototype,
|
||||
"node_prototype_parent": node_prototype_parent,
|
||||
"node_typeclass": node_typeclass,
|
||||
"node_key": node_key,
|
||||
"node_aliases": node_aliases,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from evennia.objects.models import ObjectDB
|
|||
from evennia.utils.create import create_script
|
||||
from evennia.utils.utils import (
|
||||
all_from_module, make_iter, is_iter, dbid_to_obj, callables_from_module,
|
||||
get_all_typeclasses)
|
||||
get_all_typeclasses, to_str)
|
||||
from evennia.locks.lockhandler import validate_lockstring, check_lockstring
|
||||
from evennia.utils import logger
|
||||
from evennia.utils import inlinefuncs
|
||||
|
|
@ -64,6 +64,7 @@ def protfunc_parser(value, available_functions=None, testing=False, stacktrace=F
|
|||
value (any): The value to test for a parseable protfunc. Only strings will be parsed for
|
||||
protfuncs, all other types are returned as-is.
|
||||
available_functions (dict, optional): Mapping of name:protfunction to use for this parsing.
|
||||
If not set, use default sources.
|
||||
testing (bool, optional): Passed to protfunc. If in a testing mode, some protfuncs may
|
||||
behave differently.
|
||||
stacktrace (bool, optional): If set, print the stack parsing process of the protfunc-parser.
|
||||
|
|
@ -86,7 +87,8 @@ def protfunc_parser(value, available_functions=None, testing=False, stacktrace=F
|
|||
|
||||
"""
|
||||
if not isinstance(value, basestring):
|
||||
return value
|
||||
value = to_str(value, force_string=True)
|
||||
|
||||
available_functions = _PROT_FUNCS if available_functions is None else available_functions
|
||||
|
||||
# insert $obj(#dbref) for #dbref
|
||||
|
|
|
|||
|
|
@ -308,6 +308,91 @@ class TestPrototypeStorage(EvenniaTest):
|
|||
self.assertTrue(str(unicode(protlib.list_prototypes(self.char1))))
|
||||
|
||||
|
||||
class _MockMenu(object):
|
||||
pass
|
||||
|
||||
|
||||
class TestMenuModule(EvenniaTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestMenuModule, self).setUp()
|
||||
|
||||
# set up fake store
|
||||
self.caller = self.char1
|
||||
menutree = _MockMenu()
|
||||
self.caller.ndb._menutree = menutree
|
||||
|
||||
self.test_prot = {"prototype_key": "test_prot",
|
||||
"prototype_locks": "edit:all();spawn:all()"}
|
||||
|
||||
def test_helpers(self):
|
||||
|
||||
caller = self.caller
|
||||
|
||||
# general helpers
|
||||
|
||||
self.assertEqual(olc_menus._get_menu_prototype(caller), {})
|
||||
self.assertEqual(olc_menus._is_new_prototype(caller), True)
|
||||
|
||||
self.assertEqual(
|
||||
olc_menus._set_prototype_value(caller, "key", "TestKey"), {"key": "TestKey"})
|
||||
self.assertEqual(olc_menus._get_menu_prototype(caller), {"key": "TestKey"})
|
||||
|
||||
self.assertEqual(olc_menus._format_option_value(
|
||||
"key", required=True, prototype=olc_menus._get_menu_prototype(caller)), " (TestKey|n)")
|
||||
self.assertEqual(olc_menus._format_option_value(
|
||||
[1, 2, 3, "foo"], required=True), ' (1, 2, 3, foo|n)')
|
||||
|
||||
self.assertEqual(olc_menus._set_property(
|
||||
caller, "ChangedKey", prop="key", processor=str, next_node="foo"), "foo")
|
||||
self.assertEqual(olc_menus._get_menu_prototype(caller), {"key": "ChangedKey"})
|
||||
|
||||
self.assertEqual(olc_menus._wizard_options(
|
||||
"ThisNode", "PrevNode", "NextNode"),
|
||||
[{'goto': 'node_PrevNode', 'key': ('|wb|Wack', 'b'), 'desc': '|W(PrevNode)|n'},
|
||||
{'goto': 'node_NextNode', 'key': ('|wf|Worward', 'f'), 'desc': '|W(NextNode)|n'},
|
||||
{'goto': 'node_index', 'key': ('|wi|Wndex', 'i')},
|
||||
{'goto': ('node_validate_prototype', {'back': 'ThisNode'}),
|
||||
'key': ('|wv|Walidate prototype', 'v')}])
|
||||
|
||||
def test_node_helpers(self):
|
||||
|
||||
caller = self.caller
|
||||
|
||||
with mock.patch("evennia.prototypes.menus.protlib.search_prototype",
|
||||
new=mock.MagicMock(return_value=[self.test_prot])):
|
||||
self.assertEqual(olc_menus._check_prototype_key(caller, "test_prot"),
|
||||
"node_prototype_parent")
|
||||
caller.ndb._menutree.olc_new = True
|
||||
self.assertEqual(olc_menus._check_prototype_key(caller, "test_prot"),
|
||||
"node_index")
|
||||
|
||||
self.assertEqual(olc_menus._all_prototype_parents(caller), ['test_prot'])
|
||||
self.assertEqual(olc_menus._prototype_parent_examine(
|
||||
caller, 'test_prot'),
|
||||
'|cprototype key:|n test_prot, |ctags:|n None, |clocks:|n edit:all();spawn:all() '
|
||||
'\n|cdesc:|n None \n|cprototype:|n {\n \n}')
|
||||
self.assertEqual(olc_menus._prototype_parent_select(caller, self.test_prot), "node_key")
|
||||
self.assertEqual(olc_menus._get_menu_prototype(caller),
|
||||
{'prototype_key': 'test_prot',
|
||||
'prototype_locks': 'edit:all();spawn:all()',
|
||||
'prototype_parent': "test_prot"})
|
||||
|
||||
with mock.patch("evennia.utils.utils.get_all_typeclasses",
|
||||
new=mock.MagicMock(return_value={"foo": None, "bar": None})):
|
||||
self.assertEqual(olc_menus._all_typeclasses(caller), ["bar", "foo"])
|
||||
self.assertTrue(olc_menus._typeclass_examine(
|
||||
caller, "evennia.objects.objects.DefaultObject").startswith("Typeclass |y"))
|
||||
|
||||
self.assertEqual(olc_menus._typeclass_select(
|
||||
caller, "evennia.objects.objects.DefaultObject"), "node_key")
|
||||
# prototype_parent should be popped off here
|
||||
self.assertEqual(olc_menus._get_menu_prototype(caller),
|
||||
{'prototype_key': 'test_prot',
|
||||
'prototype_locks': 'edit:all();spawn:all()',
|
||||
'typeclass': 'evennia.objects.objects.DefaultObject'})
|
||||
|
||||
|
||||
@mock.patch("evennia.prototypes.menus.protlib.search_prototype", new=mock.MagicMock(
|
||||
return_value=[{"prototype_key": "TestPrototype",
|
||||
"typeclass": "TypeClassTest", "key": "TestObj"}]))
|
||||
|
|
@ -320,6 +405,7 @@ class TestOLCMenu(TestEvMenu):
|
|||
startnode = "node_index"
|
||||
|
||||
debug_output = True
|
||||
expect_all_nodes = True
|
||||
|
||||
expected_node_texts = {
|
||||
"node_index": "|c --- Prototype wizard --- |n"
|
||||
|
|
|
|||
|
|
@ -162,6 +162,20 @@ def clr(*args, **kwargs):
|
|||
def null(*args, **kwargs):
|
||||
return args[0] if args else ''
|
||||
|
||||
|
||||
def nomatch(name, *args, **kwargs):
|
||||
"""
|
||||
Default implementation of nomatch returns the function as-is as a string.
|
||||
|
||||
"""
|
||||
kwargs.pop("inlinefunc_stack_depth", None)
|
||||
kwargs.pop("session")
|
||||
|
||||
return "${name}({args}{kwargs})".format(
|
||||
name=name,
|
||||
args=",".join(args),
|
||||
kwargs=",".join("{}={}".format(key, val) for key, val in kwargs.items()))
|
||||
|
||||
_INLINE_FUNCS = {}
|
||||
|
||||
# we specify a default nomatch function to use if no matching func was
|
||||
|
|
@ -284,7 +298,6 @@ def parse_inlinefunc(string, strip=False, available_funcs=None, stacktrace=False
|
|||
|
||||
"""
|
||||
global _PARSING_CACHE
|
||||
|
||||
usecache = False
|
||||
if not available_funcs:
|
||||
available_funcs = _INLINE_FUNCS
|
||||
|
|
@ -357,6 +370,7 @@ def parse_inlinefunc(string, strip=False, available_funcs=None, stacktrace=False
|
|||
except KeyError:
|
||||
stack.append(available_funcs["nomatch"])
|
||||
stack.append(funcname)
|
||||
stack.append(None)
|
||||
ncallable += 1
|
||||
elif gdict["escaped"]:
|
||||
# escaped tokens
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue