Start adding unit tests for menu templating

This commit is contained in:
Griatch 2020-09-27 20:22:40 +02:00
parent 7e58fee171
commit 300429a03f
4 changed files with 619 additions and 210 deletions

View file

@ -42,6 +42,7 @@ not move on until that command has been tried).
"""
import sys
import re
from ast import literal_eval
@ -52,7 +53,7 @@ from fnmatch import fnmatch
from django.utils.translation import gettext as _
_RE_NODE = re.compile(r"#\s*?NODE\s+?(?P<nodename>\S+?)$", re.I + re.M)
_RE_OPTIONS_SEP = re.compile(r"##\s*?OPTIONS\s*?$", re.I + re.M)
_RE_OPTIONS_SEP = re.compile(r"##\s*?OPTIONS*?\s*?$", re.I + re.M)
_RE_CALLABLE = re.compile(r"\S+?\(\)", re.I + re.M)
_RE_CALLABLE = re.compile(
r"(?P<funcname>\S+?)(?:\((?P<kwargs>[\S\s]+?=[\S\s]+?)\)|\(\))", re.I + re.M
@ -133,8 +134,8 @@ def _generated_input_goto_func(caller, raw_string, **kwargs):
return None, {"generated_nodename": current_nodename}
def _generated_node(caller, raw_string, generated_nodename="", **kwargs):
text, options = caller.db._generated_menu_contents[generated_nodename]
def _generated_node(caller, raw_string, **kwargs):
text, options = caller.db._generated_menu_contents[kwargs["_current_nodename"]]
return text, options
@ -249,7 +250,6 @@ def template2menu(
menu_template,
goto_callables=None,
startnode="start",
startnode_input=None,
persistent=False,
**kwargs,
):
@ -266,30 +266,17 @@ def template2menu(
module-global objects to reference by name in the menu-template.
Must be on the form `callable(caller, raw_string, **kwargs)`.
startnode (str, optional): The name of the startnode, if not 'start'.
startnode_input (str or tuple, optional): If a string, the `raw_string`
arg to pass into the starting node. Otherwise should be on form
`(raw_string, {kwargs})`, where `raw_string` and `**kwargs` will be
passed into the start node.
persistent (bool, optional): If the generated menu should be persistent.
**kwargs: Other kwargs will be passed to EvMenu.
**kwargs: All kwargs will be passed into EvMenu.
"""
goto_callables = goto_callables or {}
startnode_raw = ""
startnode_kwargs = {"generated_nodename": startnode}
if isinstance(startnode_input, str):
startnode_raw = startnode_input
elif isinstance(startnode_input, (tuple, list)):
startnode_raw = startnode_input[0]
startnode_kwargs.update(startnode_input[1])
menu_tree = parse_menu_template(caller, menu_template, goto_callables)
EvMenu(
caller,
menu_tree,
startnode_input=(startnode_raw, startnode_kwargs),
persistent=True,
persistent=persistent,
**kwargs,
)
@ -309,6 +296,17 @@ def bar(caller, raw_string, **kwargs):
return "bar"
def customcall(caller, raw_string, **kwargs):
return "start"
def customnode(caller, raw_string, **kwargs):
text = "This is a custom node!"
options = {
"desc": "Go back",
"goto": customcall
}
return text, options
def test_generator(caller):
MENU_TEMPLATE = """
@ -344,6 +342,7 @@ def test_generator(caller):
back: start
to node 2: node2
run foo (rerun node): foo()
customnode: Go to custom node -> customnode
>: return to go back -> start
# node node2
@ -371,7 +370,13 @@ def test_generator(caller):
"""
callables = {"gotonode3": gotonode3, "foo": foo, "bar": bar}
template2menu(caller, MENU_TEMPLATE, callables)
dct = parse_menu_template(caller, MENU_TEMPLATE, callables)
dct["customnode"] = customnode
EvMenu(caller, dct)
# template2menu(caller, MENU_TEMPLATE, callables)
if __name__ == "__main__":

View file

@ -162,11 +162,114 @@ For a menu demo, import CmdTestMenu from this module and add it to
your default cmdset. Run it with this module, like `testmenu
evennia.utils.evmenu`.
## Menu generation from template string
In evmenu.py is a helper function `parse_menu_template` that parses a
template-string and outputs a menu-tree dictionary suitable to pass into
EvMenu:
::
menutree = evmenu.parse_menu_template(caller, menu_template, goto_callables)
EvMenu(caller, menutree)
For maximum flexibility you can inject normally-created nodes in the menu tree
before passing it to EvMenu. If that's not needed, you can also create a menu
in one step with:
::
evmenu.template2menu(caller, menu_template, goto_callables)
The `goto_callables` is a mapping `{"funcname": callable, ...}`, where each
callable must be a module-global function on the form
`funcname(caller, raw_string, **kwargs)` (like any goto-callable). The
`menu_template` is a multi-line string on the following form:
::
## node start
This is the text of the start node.
The text area can have multiple lines, line breaks etc.
Each option below is one of these forms
key: desc -> gotostr_or_func
key: gotostr_or_func
>: gotostr_or_func
> glob/regex: gotostr_or_func
## options
# comments are only allowed from beginning of line.
# Indenting is not necessary, but good for readability
1: Option number 1 -> node1
2: Option number 2 -> node2
next: This steps next -> go_back()
# the -> can be ignored if there is no desc
back: go_back(from_node=start)
abort: abort
## node node1
Text for Node1. Enter a message!
<return> to go back.
## options
# Starting the option-line with >
# allows to perform different actions depending on
# what is inserted.
# this catches everything starting with foo
> foo*: handle_foo_message()
# regex are also allowed (this catches number inputs)
> [0-9]+?: handle_numbers()
# this catches the empty return
>: start
# this catches everything else
> *: handle_message(from_node=node1)
## node node2
Text for Node2. Just go back.
## options
>: start
# node abort
This exits the menu since there is no `## options` section.
Each menu node is defined by a `# node <name>` containing the text of the node,
followed by `## options` Also `## NODE` and `## OPTIONS` work. No python code
logics is allowed in the template, this code is not evaluated but parsed. More
advanced dynamic usage requires a full node-function (which can be added to the
generated dict, as said).
Adding `(..)` to a goto treats it as a callable and it must then be included in
the `goto_callable` mapping. Only named keywords (or no args at all) are
allowed, these will be added to the `**kwargs` going into the callable. Quoting
strings is only needed if wanting to pass strippable spaces, otherwise the
key:values will be converted to strings/numbers with literal_eval before passed
into the callable.
The `> ` option takes a glob or regex to perform different actions depending on user
input. Make sure to sort these in increasing order of generality since they
will be tested in sequence.
"""
import random
import re
import inspect
from ast import literal_eval
from fnmatch import fnmatch
from inspect import isfunction, getargspec
from django.conf import settings
from evennia import Command, CmdSet
@ -176,6 +279,9 @@ from evennia.utils.ansi import strip_ansi
from evennia.utils.utils import mod_import, make_iter, pad, to_str, m_len, is_iter, dedent, crop
from evennia.commands import cmdhandler
# i18n
from django.utils.translation import gettext as _
# read from protocol NAWS later?
_MAX_TEXT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
@ -186,8 +292,6 @@ _CMD_NOINPUT = cmdhandler.CMD_NOINPUT
# Return messages
# i18n
from django.utils.translation import gettext as _
_ERR_NOT_IMPLEMENTED = _(
"Menu node '{nodename}' is either not implemented or caused an error. "
@ -668,6 +772,7 @@ class EvMenu:
self.caller.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename), session=self._session)
raise EvMenuError
try:
kwargs["_current_nodename"] = nodename
ret = self._safe_call(node, raw_string, **kwargs)
if isinstance(ret, (tuple, list)) and len(ret) > 1:
nodetext, options = ret[:2]
@ -1475,219 +1580,232 @@ def get_input(caller, prompt, callback, session=None, *args, **kwargs):
# -------------------------------------------------------------
#
# test menu strucure and testing command
# Menu generation from menu template string
#
# -------------------------------------------------------------
_RE_NODE = re.compile(r"##\s*?NODE\s+?(?P<nodename>\S+?)$", re.I + re.M)
_RE_OPTIONS_SEP = re.compile(r"##\s*?OPTIONS\s*?$", re.I + re.M)
_RE_CALLABLE = re.compile(r"\S+?\(\)", re.I + re.M)
_RE_CALLABLE = re.compile(
r"(?P<funcname>\S+?)(?:\((?P<kwargs>[\S\s]+?=[\S\s]+?)\)|\(\))", re.I + re.M
)
def _generate_goto(caller, **kwargs):
return kwargs.get("name", "test_dynamic_node"), {"name": "replaced!"}
_HELP_NO_OPTION_MATCH = _("Choose an option or try 'help'.")
_OPTION_INPUT_MARKER = ">"
_OPTION_ALIAS_MARKER = ";"
_OPTION_SEP_MARKER = ":"
_OPTION_CALL_MARKER = "->"
_OPTION_COMMENT_START = "#"
def test_start_node(caller):
menu = caller.ndb._menutree
text = """
This is an example menu.
# Input/option/goto handler functions that allows for dynamically generated
# nodes read from the menu template.
If you enter anything except the valid options, your input will be
recorded and you will be brought to a menu entry showing your
input.
Select options or use 'quit' to exit the menu.
def _generated_goto_func(caller, raw_string, **kwargs):
goto = kwargs["goto"]
goto_callables = kwargs["goto_callables"]
current_nodename = kwargs["current_nodename"]
The menu was initialized with two variables: %s and %s.
""" % (
menu.testval,
menu.testval2,
)
if _RE_CALLABLE.match(goto):
gotofunc = goto.strip()[:-2]
if gotofunc in goto_callables:
goto = goto_callables[gotofunc](caller, raw_string, **kwargs)
if goto is None:
return goto, {"generated_nodename": current_nodename}
caller.msg(_HELP_NO_OPTION_MATCH)
return goto, {"generated_nodename": goto}
options = (
{
"key": ("|yS|net", "s"),
"desc": "Set an attribute on yourself.",
"exec": lambda caller: caller.attributes.add("menuattrtest", "Test value"),
"goto": "test_set_node",
},
{
"key": ("|yL|nook", "l"),
"desc": "Look and see a custom message.",
"goto": "test_look_node",
},
{"key": ("|yV|niew", "v"), "desc": "View your own name", "goto": "test_view_node"},
{
"key": ("|yD|nynamic", "d"),
"desc": "Dynamic node",
"goto": (_generate_goto, {"name": "test_dynamic_node"}),
},
{
"key": ("|yQ|nuit", "quit", "q", "Q"),
"desc": "Quit this menu example.",
"goto": "test_end_node",
},
{"key": "_default", "goto": "test_displayinput_node"},
)
def _generated_input_goto_func(caller, raw_string, **kwargs):
gotomap = kwargs["gotomap"]
goto_callables = kwargs["goto_callables"]
current_nodename = kwargs["current_nodename"]
# start with glob patterns
for pattern, goto in gotomap.items():
if fnmatch(raw_string.lower(), pattern):
match = _RE_CALLABLE.match(goto)
if match:
gotofunc = match.group("funcname")
gotokwargs = match.group("kwargs") or ""
if gotofunc in goto_callables:
for kwarg in gotokwargs.split(","):
if kwarg and "=" in kwarg:
key, value = [part.strip() for part in kwarg.split("=", 1)]
try:
key = literal_eval(key)
except ValueError:
pass
try:
value = literal_eval(value)
except ValueError:
pass
kwargs[key] = value
goto = goto_callables[gotofunc](caller, raw_string, **kwargs)
if goto is None:
return goto, {"generated_nodename": current_nodename}
return goto, {"generated_nodename": goto}
# no glob pattern match; try regex
for pattern, goto in gotomap.items():
if re.match(pattern, raw_string.lower(), flags=re.I + re.M):
if _RE_CALLABLE.match(goto):
gotofunc = goto.strip()[:-2]
if gotofunc in goto_callables:
goto = goto_callables[gotofunc](caller, raw_string, **kwargs)
if goto is None:
return goto, {"generated_nodename": current_nodename}
return goto, {"generated_nodename": goto}
# no match, rerun current node
caller.msg(_HELP_NO_OPTION_MATCH)
return None, {"generated_nodename": current_nodename}
def _generated_node(caller, raw_string, **kwargs):
text, options = caller.db._generated_menu_contents[kwargs["_current_nodename"]]
return text, options
def test_look_node(caller):
text = "This is a custom look location!"
options = {
"key": ("|yL|nook", "l"),
"desc": "Go back to the previous menu.",
"goto": "test_start_node",
}
return text, options
def parse_menu_template(caller, menu_template, goto_callables=None):
"""
Parse menu-template string
Args:
caller (Object or Account): Entity using the menu.
menu_template (str): Menu described using the templating format.
goto_callables (dict, optional): Mapping between call-names and callables
on the form `callable(caller, raw_string, **kwargs)`. These are what is
available to use in the `menu_template` string.
def test_set_node(caller):
text = (
"""
def _parse_options(nodename, optiontxt, goto_callables):
"""
The attribute 'menuattrtest' was set to
|w%s|n
(check it with examine after quitting the menu).
This node's has only one option, and one of its key aliases is the
string "_default", meaning it will catch any input, in this case
to return to the main menu. So you can e.g. press <return> to go
back now.
"""
% caller.db.menuattrtest, # optional help text for this node
Parse option section into option dict.
"""
This is the help entry for this node. It is created by returning
the node text as a tuple - the second string in that tuple will be
used as the help text.
""",
)
options = []
optiontxt = optiontxt[0].strip() if optiontxt else ""
optionlist = [optline.strip() for optline in optiontxt.split("\n")]
inputparsemap = {}
options = {"key": ("back (default)", "_default"), "goto": "test_start_node"}
return text, options
for inum, optline in enumerate(optionlist):
if optline.startswith(_OPTION_COMMENT_START) or _OPTION_SEP_MARKER not in optline:
# skip comments or invalid syntax
continue
key = ""
desc = ""
pattern = None
key, goto = [part.strip() for part in optline.split(_OPTION_SEP_MARKER, 1)]
def test_view_node(caller, **kwargs):
text = (
# desc -> goto
if _OPTION_CALL_MARKER in goto:
desc, goto = [part.strip() for part in goto.split(_OPTION_CALL_MARKER, 1)]
# parse key [;aliases|pattern]
key = [part.strip() for part in key.split(_OPTION_ALIAS_MARKER)]
if not key:
# fall back to this being the Nth option
key = [f"{inum + 1}"]
main_key = key[0]
if main_key.startswith(_OPTION_INPUT_MARKER):
# if we have a pattern, build the arguments for _default later
pattern = main_key[len(_OPTION_INPUT_MARKER):].strip()
inputparsemap[pattern] = goto
else:
# a regular goto string/callable target
option = {
"key": key,
"goto": (
_generated_goto_func,
{
"goto": goto,
"current_nodename": nodename,
"goto_callables": goto_callables,
},
),
}
if desc:
option["desc"] = desc
options.append(option)
if inputparsemap:
# if this exists we must create a _default entry too
options.append(
{
"key": "_default",
"goto": (
_generated_input_goto_func,
{
"gotomap": inputparsemap,
"current_nodename": nodename,
"goto_callables": goto_callables,
},
),
}
)
return options
def _parse(caller, menu_template, goto_callables):
"""
Your name is |g%s|n!
click |lclook|lthere|le to trigger a look command under MXP.
This node's option has no explicit key (nor the "_default" key
set), and so gets assigned a number automatically. You can infact
-always- use numbers (1...N) to refer to listed options also if you
don't see a string option key (try it!).
"""
% caller.key
)
if kwargs.get("executed_from_dynamic_node", False):
# we are calling this node as a exec, skip return values
caller.msg("|gCalled from dynamic node:|n \n {}".format(text))
return
else:
options = {"desc": "back to main", "goto": "test_start_node"}
return text, options
def test_displayinput_node(caller, raw_string):
text = (
Parse the menu string format into a node tree.
"""
You entered the text:
nodetree = {}
splits = _RE_NODE.split(menu_template)
splits = splits[1:] if splits else []
"|w%s|n"
# from evennia import set_trace;set_trace(term_size=(140,120))
content_map = {}
for node_ind in range(0, len(splits), 2):
nodename, nodetxt = splits[node_ind], splits[node_ind + 1]
text, *optiontxt = _RE_OPTIONS_SEP.split(nodetxt, maxsplit=2)
options = _parse_options(nodename, optiontxt, goto_callables)
content_map[nodename] = (text, options)
nodetree[nodename] = _generated_node
caller.db._generated_menu_contents = content_map
... which could now be handled or stored here in some way if this
was not just an example.
return nodetree
This node has an option with a single alias "_default", which
makes it hidden from view. It catches all input (except the
in-menu help/quit commands) and will, in this case, bring you back
to the start node.
return _parse(caller, menu_template, goto_callables)
def template2menu(
caller,
menu_template,
goto_callables=None,
startnode="start",
persistent=False,
**kwargs,
):
"""
% raw_string.rstrip()
)
options = {"key": "_default", "goto": "test_start_node"}
return text, options
Helper function to generate and start an EvMenu based on a menu template
string.
Args:
caller (Object or Account): The entity using the menu.
menu_template (str): The menu-template string describing the content
and structure of the menu. It can also be the python-path to, or a module
containing a `MENU_TEMPLATE` global variable with the template.
goto_callables (dict, optional): Mapping of callable-names to
module-global objects to reference by name in the menu-template.
Must be on the form `callable(caller, raw_string, **kwargs)`.
startnode (str, optional): The name of the startnode, if not 'start'.
persistent (bool, optional): If the generated menu should be persistent.
**kwargs: All kwargs will be passed into EvMenu.
def _test_call(caller, raw_input, **kwargs):
mode = kwargs.get("mode", "exec")
caller.msg(
"\n|y'{}' |n_test_call|y function called with\n "
'caller: |n{}\n |yraw_input: "|n{}|y" \n kwargs: |n{}\n'.format(
mode, caller, raw_input.rstrip(), kwargs
)
)
if mode == "exec":
kwargs = {"random": random.random()}
caller.msg("function modify kwargs to {}".format(kwargs))
else:
caller.msg("|ypassing function kwargs without modification.|n")
return "test_dynamic_node", kwargs
def test_dynamic_node(caller, **kwargs):
text = """
This is a dynamic node with input:
{}
""".format(
kwargs
)
options = (
{
"desc": "pass a new random number to this node",
"goto": ("test_dynamic_node", {"random": random.random()}),
},
{
"desc": "execute a func with kwargs",
"exec": (_test_call, {"mode": "exec", "test_random": random.random()}),
},
{"desc": "dynamic_goto", "goto": (_test_call, {"mode": "goto", "goto_input": "test"})},
{
"desc": "exec test_view_node with kwargs",
"exec": ("test_view_node", {"executed_from_dynamic_node": True}),
"goto": "test_dynamic_node",
},
{"desc": "back to main", "goto": "test_start_node"},
)
return text, options
def test_end_node(caller):
text = """
This is the end of the menu and since it has no options the menu
will exit here, followed by a call of the "look" command.
"""
return text, None
class CmdTestMenu(Command):
"""
Test menu
Usage:
testmenu <menumodule>
Starts a demo menu from a menu node definition module.
Returns:
EvMenu: The generated EvMenu.
"""
key = "testmenu"
def func(self):
if not self.args:
self.caller.msg("Usage: testmenu menumodule")
return
# start menu
EvMenu(
self.caller,
self.args.strip(),
startnode="test_start_node",
persistent=True,
cmdset_mergetype="Replace",
testval="val",
testval2="val2",
)
goto_callables = goto_callables or {}
menu_tree = parse_menu_template(caller, menu_template, goto_callables)
return EvMenu(
caller,
menu_tree,
persistent=persistent,
**kwargs,
)

View file

@ -0,0 +1,221 @@
# -------------------------------------------------------------
#
# test menu strucure and testing command
#
# -------------------------------------------------------------
import random
def _generate_goto(caller, **kwargs):
return kwargs.get("name", "test_dynamic_node"), {"name": "replaced!"}
def test_start_node(caller):
menu = caller.ndb._menutree
text = """
This is an example menu.
If you enter anything except the valid options, your input will be
recorded and you will be brought to a menu entry showing your
input.
Select options or use 'quit' to exit the menu.
The menu was initialized with two variables: %s and %s.
""" % (
menu.testval,
menu.testval2,
)
options = (
{
"key": ("|yS|net", "s"),
"desc": "Set an attribute on yourself.",
"exec": lambda caller: caller.attributes.add("menuattrtest", "Test value"),
"goto": "test_set_node",
},
{
"key": ("|yL|nook", "l"),
"desc": "Look and see a custom message.",
"goto": "test_look_node",
},
{"key": ("|yV|niew", "v"), "desc": "View your own name", "goto": "test_view_node"},
{
"key": ("|yD|nynamic", "d"),
"desc": "Dynamic node",
"goto": (_generate_goto, {"name": "test_dynamic_node"}),
},
{
"key": ("|yQ|nuit", "quit", "q", "Q"),
"desc": "Quit this menu example.",
"goto": "test_end_node",
},
{"key": "_default", "goto": "test_displayinput_node"},
)
return text, options
def test_look_node(caller):
text = "This is a custom look location!"
options = {
"key": ("|yL|nook", "l"),
"desc": "Go back to the previous menu.",
"goto": "test_start_node",
}
return text, options
def test_set_node(caller):
text = (
"""
The attribute 'menuattrtest' was set to
|w%s|n
(check it with examine after quitting the menu).
This node's has only one option, and one of its key aliases is the
string "_default", meaning it will catch any input, in this case
to return to the main menu. So you can e.g. press <return> to go
back now.
"""
% caller.db.menuattrtest, # optional help text for this node
"""
This is the help entry for this node. It is created by returning
the node text as a tuple - the second string in that tuple will be
used as the help text.
""",
)
options = {"key": ("back (default)", "_default"), "goto": "test_start_node"}
return text, options
def test_view_node(caller, **kwargs):
text = (
"""
Your name is |g%s|n!
click |lclook|lthere|le to trigger a look command under MXP.
This node's option has no explicit key (nor the "_default" key
set), and so gets assigned a number automatically. You can infact
-always- use numbers (1...N) to refer to listed options also if you
don't see a string option key (try it!).
"""
% caller.key
)
if kwargs.get("executed_from_dynamic_node", False):
# we are calling this node as a exec, skip return values
caller.msg("|gCalled from dynamic node:|n \n {}".format(text))
return
else:
options = {"desc": "back to main", "goto": "test_start_node"}
return text, options
def test_displayinput_node(caller, raw_string):
text = (
"""
You entered the text:
"|w%s|n"
... which could now be handled or stored here in some way if this
was not just an example.
This node has an option with a single alias "_default", which
makes it hidden from view. It catches all input (except the
in-menu help/quit commands) and will, in this case, bring you back
to the start node.
"""
% raw_string.rstrip()
)
options = {"key": "_default", "goto": "test_start_node"}
return text, options
def _test_call(caller, raw_input, **kwargs):
mode = kwargs.get("mode", "exec")
caller.msg(
"\n|y'{}' |n_test_call|y function called with\n "
'caller: |n{}\n |yraw_input: "|n{}|y" \n kwargs: |n{}\n'.format(
mode, caller, raw_input.rstrip(), kwargs
)
)
if mode == "exec":
kwargs = {"random": random.random()}
caller.msg("function modify kwargs to {}".format(kwargs))
else:
caller.msg("|ypassing function kwargs without modification.|n")
return "test_dynamic_node", kwargs
def test_dynamic_node(caller, **kwargs):
text = """
This is a dynamic node with input:
{}
""".format(
kwargs
)
options = (
{
"desc": "pass a new random number to this node",
"goto": ("test_dynamic_node", {"random": random.random()}),
},
{
"desc": "execute a func with kwargs",
"exec": (_test_call, {"mode": "exec", "test_random": random.random()}),
},
{"desc": "dynamic_goto", "goto": (_test_call, {"mode": "goto", "goto_input": "test"})},
{
"desc": "exec test_view_node with kwargs",
"exec": ("test_view_node", {"executed_from_dynamic_node": True}),
"goto": "test_dynamic_node",
},
{"desc": "back to main", "goto": "test_start_node"},
)
return text, options
def test_end_node(caller):
text = """
This is the end of the menu and since it has no options the menu
will exit here, followed by a call of the "look" command.
"""
return text, None
# class CmdTestMenu(Command):
# """
# Test menu
#
# Usage:
# testmenu <menumodule>
#
# Starts a demo menu from a menu node definition module.
#
# """
#
# key = "testmenu"
#
# def func(self):
#
# if not self.args:
# self.caller.msg("Usage: testmenu menumodule")
# return
# # start menu
# EvMenu(
# self.caller,
# self.args.strip(),
# startnode="test_start_node",
# persistent=True,
# cmdset_mergetype="Replace",
# testval="val",
# testval2="val2",
# )
#

View file

@ -18,7 +18,9 @@ To help debug the menu, turn on `debug_output`, which will print the traversal p
"""
import copy
from anything import Anything
from django.test import TestCase
from evennia.utils.test_resources import EvenniaTest
from evennia.utils import evmenu
from evennia.utils import ansi
from mock import MagicMock
@ -229,7 +231,7 @@ class TestEvMenu(TestCase):
class TestEvMenuExample(TestEvMenu):
menutree = "evennia.utils.evmenu"
menutree = "evennia.utils.tests.data.evmenu_example"
startnode = "test_start_node"
kwargs = {"testval": "val", "testval2": "val2"}
debug_output = False
@ -262,3 +264,66 @@ class TestEvMenuExample(TestEvMenu):
def test_kwargsave(self):
self.assertTrue(hasattr(self.menu, "testval"))
self.assertTrue(hasattr(self.menu, "testval2"))
def _callnode1(caller, raw_string, **kwargs):
return "node1"
def _callnode2(caller, raw_string, **kwargs):
return "node2"
class TestMenuTemplateParse(EvenniaTest):
"""Test menu templating helpers"""
def setUp(self):
super().setUp()
self.menu_template = """
## node start
Neque ea alias perferendis molestiae eligendi. Debitis exercitationem
exercitationem quas blanditiis quisquam officia ut. Fugit aut fugit enim quia
non. Earum et excepturi animi ex esse accusantium et. Id adipisci eos enim
ratione.
## options
1: first option -> node1
2: second option -> node2
next: node1
## node node1
Node 1
## options
fwd: node2
call1: callnode1()
call2: callnode2(foo=bar, bar=22, goo="another test")
>: start
## node node2
Text of node 2
## options
> foo*: node1
> [0-9]+?: node2
> back: start
"""
self.goto_callables = {"callnode1": _callnode1, "callnode2": _callnode2}
def test_parse_menu_template(self):
"""EvMenu template testing"""
menutree = evmenu.parse_menu_template(self.char1, self.menu_template,
self.goto_callables)
self.assertEqual(menutree, {"start": Anything, "node1": Anything, "node2": Anything})
def test_template2menu(self):
evmenu.template2menu(self.char1, self.menu_template, self.goto_callables)