Tests: add tests for sub-classing & calling operators from Python

Add two tests, one to check that sub-classing works as expected,
another for the operators callable types.

Written to validate fixes for #155928 & #155996 and ensure
there are no further regressions.

Ref !155994
This commit is contained in:
Campbell Barton 2026-03-20 17:15:36 +11:00 • committed by Thomas Dinges
parent c1878785d6
commit 2edae0c5e0
3 changed files with 839 additions and 0 deletions

View file

@ -320,6 +320,16 @@ add_blender_test(
--python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_bmesh.py
)
add_blender_test(
operator_function_py_api
--python ${CMAKE_CURRENT_LIST_DIR}/bl_operator_function_py_api.py
)
add_blender_test(
operator_wrap_py_api
--python ${CMAKE_CURRENT_LIST_DIR}/bl_operator_wrap_py_api.py
)
add_blender_test(
script_pyapi_grease_pencil
--python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_grease_pencil.py

View file

@ -0,0 +1,140 @@
# SPDX-FileCopyrightText: 2026 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Tests for `source/blender/python/intern/bpy_operator_function.cc`
# Python calling into the operator system (using a callable function like type).
#
# ./blender.bin --background --python tests/python/bl_operator_function_py_api.py -- --verbose
__all__ = (
"main",
)
import unittest
import bpy
class TestOperatorFunctionAccess(unittest.TestCase):
def test_access_operator(self):
op = bpy.ops.object.select_all
self.assertTrue(callable(op))
class TestOperatorCall(unittest.TestCase):
def test_call_clear_recent_files(self):
self.assertEqual(bpy.ops.wm.clear_recent_files(), {'FINISHED'})
class TestOperatorCallPositionalArgs(unittest.TestCase):
def test_exec_default(self):
self.assertEqual(bpy.ops.wm.clear_recent_files('EXEC_DEFAULT'), {'FINISHED'})
def test_invoke_default(self):
self.assertEqual(bpy.ops.wm.clear_recent_files('INVOKE_DEFAULT'), {'FINISHED'})
def test_context_and_undo(self):
self.assertEqual(bpy.ops.wm.clear_recent_files('EXEC_DEFAULT', True), {'FINISHED'})
def test_context_and_undo_int(self):
self.assertEqual(bpy.ops.wm.clear_recent_files('EXEC_DEFAULT', 1), {'FINISHED'})
class TestOperatorCallKeywordArgs(unittest.TestCase):
def test_enum_keyword(self):
self.assertEqual(bpy.ops.object.select_all(action='TOGGLE'), {'FINISHED'})
def test_keyword_with_context(self):
self.assertEqual(
bpy.ops.object.select_all('EXEC_DEFAULT', action='TOGGLE'), {'FINISHED'})
def test_keyword_with_context_and_undo(self):
self.assertEqual(
bpy.ops.object.select_all('EXEC_DEFAULT', True, action='TOGGLE'), {'FINISHED'})
def test_multiple_keywords(self):
self.assertEqual(
bpy.ops.mesh.primitive_cube_add(size=1.0, location=(0, 0, 0)), {'FINISHED'})
class TestOperatorCallKeywordArgsInvalid(unittest.TestCase):
def test_invalid_enum_value(self):
with self.assertRaises(TypeError):
bpy.ops.object.select_all(action='INVALID')
def test_wrong_type_for_enum(self):
with self.assertRaises(TypeError):
bpy.ops.object.select_all(action=123)
def test_wrong_type_for_float(self):
with self.assertRaises(TypeError):
bpy.ops.mesh.primitive_cube_add(size='big')
def test_wrong_type_for_bool(self):
with self.assertRaises(TypeError):
bpy.ops.mesh.primitive_cube_add(align='WORLD', calc_uvs='yes')
def test_none_for_enum(self):
with self.assertRaises(TypeError):
bpy.ops.object.select_all(action=None)
def test_none_for_float(self):
with self.assertRaises(TypeError):
bpy.ops.mesh.primitive_cube_add(size=None)
def test_list_for_float(self):
with self.assertRaises(TypeError):
bpy.ops.mesh.primitive_cube_add(size=[1.0])
def test_unknown_keyword(self):
with self.assertRaises(TypeError):
bpy.ops.object.select_all(nonexistent=True)
def test_multiple_unknown_keywords(self):
with self.assertRaises(TypeError):
bpy.ops.object.select_all(foo=1, bar=2)
def test_valid_and_unknown_keyword(self):
with self.assertRaises(TypeError):
bpy.ops.object.select_all(action='TOGGLE', nonexistent=True)
def test_empty_string_for_enum(self):
with self.assertRaises(TypeError):
bpy.ops.object.select_all(action='')
def test_non_string_keywords(self):
for key in (1, None, (1, 2)):
with self.assertRaises(TypeError):
bpy.ops.object.select_all(**{key: 'TOGGLE'})
class TestOperatorCallInvalid(unittest.TestCase):
def test_invalid_context_string(self):
with self.assertRaises(TypeError):
bpy.ops.wm.clear_recent_files('INVALID_CONTEXT')
def test_invalid_keyword_argument(self):
with self.assertRaises(TypeError):
bpy.ops.wm.clear_recent_files(nonexistent_prop=True)
def test_poll_failure(self):
# Edit-mode operator cannot run without an active mesh in edit-mode.
with self.assertRaises(RuntimeError):
bpy.ops.mesh.subdivide()
def main():
import sys
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
unittest.main()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,689 @@
# SPDX-FileCopyrightText: 2026 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Tests for `source/blender/python/intern/bpy_operator_wrap.cc`
# Python wrapping the operator system (using subclasses).
#
# ./blender.bin --background --python tests/python/bl_operator_wrap_py_api.py -- --verbose
__all__ = (
"main",
)
import contextlib
import unittest
import bpy
@contextlib.contextmanager
def registered_operator(cls):
bpy.utils.register_class(cls)
try:
yield cls
finally:
bpy.utils.unregister_class(cls)
class TestOperatorRegister(unittest.TestCase):
def test_register_and_unregister(self):
class TESTING_OT_stub(bpy.types.Operator):
bl_idname = "testing.stub"
bl_label = "Stub"
def execute(self, context):
return {'FINISHED'}
bpy.utils.register_class(TESTING_OT_stub)
self.assertTrue(bpy.ops.testing.stub.poll())
bpy.utils.unregister_class(TESTING_OT_stub)
with self.assertRaises(AttributeError):
bpy.ops.testing.stub.poll()
def test_execute(self):
class TESTING_OT_exec(bpy.types.Operator):
bl_idname = "testing.exec"
bl_label = "Exec"
def execute(self, context):
return {'FINISHED'}
with registered_operator(TESTING_OT_exec):
self.assertEqual(bpy.ops.testing.exec(), {'FINISHED'})
def test_execute_cancelled(self):
class TESTING_OT_cancel(bpy.types.Operator):
bl_idname = "testing.cancel"
bl_label = "Cancel"
def execute(self, context):
return {'CANCELLED'}
with registered_operator(TESTING_OT_cancel):
self.assertEqual(bpy.ops.testing.cancel(), {'CANCELLED'})
def test_poll(self):
class TESTING_OT_poll_pass(bpy.types.Operator):
bl_idname = "testing.poll_pass"
bl_label = "Poll Pass"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
return {'FINISHED'}
with registered_operator(TESTING_OT_poll_pass):
self.assertTrue(bpy.ops.testing.poll_pass.poll())
self.assertEqual(bpy.ops.testing.poll_pass(), {'FINISHED'})
def test_poll_fails(self):
class TESTING_OT_poll_fail(bpy.types.Operator):
bl_idname = "testing.poll_fail"
bl_label = "Poll Fail"
@classmethod
def poll(cls, context):
return False
def execute(self, context):
return {'FINISHED'}
with registered_operator(TESTING_OT_poll_fail):
self.assertFalse(bpy.ops.testing.poll_fail.poll())
with self.assertRaises(RuntimeError):
bpy.ops.testing.poll_fail()
class TestOperatorRegisterInvalid(unittest.TestCase):
def test_missing_bl_idname(self):
class TESTING_OT_no_id(bpy.types.Operator):
bl_label = "No ID"
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(RuntimeError) as ex:
bpy.utils.register_class(TESTING_OT_no_id)
self.assertEqual(
str(ex.exception),
"Error: Registering operator class 'TESTING_OT_no_id', "
"invalid bl_idname 'TESTING_OT_no_id', Invalid character at position 0\n",
)
def test_missing_bl_label(self):
class TESTING_OT_no_label(bpy.types.Operator):
bl_idname = "testing.no_label"
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(AttributeError) as ex:
bpy.utils.register_class(TESTING_OT_no_label)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_no_label class to have an "bl_label" attribute',
)
def test_double_register(self):
class TESTING_OT_double(bpy.types.Operator):
bl_idname = "testing.double"
bl_label = "Double"
def execute(self, context):
return {'FINISHED'}
bpy.utils.register_class(TESTING_OT_double)
self.addCleanup(bpy.utils.unregister_class, TESTING_OT_double)
with self.assertRaises(ValueError) as ex:
bpy.utils.register_class(TESTING_OT_double)
self.assertEqual(
str(ex.exception),
"register_class(...): already registered as a subclass 'TESTING_OT_double'",
)
def test_unregister_not_registered(self):
class TESTING_OT_not_reg(bpy.types.Operator):
bl_idname = "testing.not_reg"
bl_label = "Not Reg"
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(RuntimeError) as ex:
bpy.utils.unregister_class(TESTING_OT_not_reg)
self.assertEqual(
str(ex.exception),
"unregister_class(...):, missing bl_rna attribute from '_RNAMeta' instance (may not be registered)",
)
def test_bl_idname_no_dot(self):
class TESTING_OT_nodot(bpy.types.Operator):
bl_idname = "testingnodot"
bl_label = "No Dot"
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(RuntimeError) as ex:
bpy.utils.register_class(TESTING_OT_nodot)
self.assertEqual(
str(ex.exception),
"Error: Registering operator class 'TESTING_OT_nodot', "
"invalid bl_idname 'testingnodot', Must contain 1 '.' character\n",
)
def test_bl_idname_empty(self):
class TESTING_OT_empty(bpy.types.Operator):
bl_idname = ""
bl_label = "Empty"
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(RuntimeError) as ex:
bpy.utils.register_class(TESTING_OT_empty)
self.assertEqual(
str(ex.exception),
"Error: Registering operator class 'TESTING_OT_empty', invalid bl_idname '', Must contain 1 '.' character\n",
)
def test_bl_idname_uppercase(self):
class TESTING_OT_upper(bpy.types.Operator):
bl_idname = "testing.Upper"
bl_label = "Upper"
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(RuntimeError) as ex:
bpy.utils.register_class(TESTING_OT_upper)
self.assertEqual(
str(ex.exception),
"Error: Registering operator class 'TESTING_OT_upper', "
"invalid bl_idname 'testing.Upper', Invalid character at position 8\n",
)
def test_bl_idname_spaces(self):
class TESTING_OT_spaces(bpy.types.Operator):
bl_idname = "testing.has spaces"
bl_label = "Spaces"
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(RuntimeError) as ex:
bpy.utils.register_class(TESTING_OT_spaces)
self.assertEqual(
str(ex.exception),
"Error: Registering operator class 'TESTING_OT_spaces', "
"invalid bl_idname 'testing.has spaces', Invalid character at position 11\n",
)
def test_bl_label_wrong_type(self):
class TESTING_OT_intlabel(bpy.types.Operator):
bl_idname = "testing.intlabel"
bl_label = 123
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(TypeError) as ex:
bpy.utils.register_class(TESTING_OT_intlabel)
self.assertEqual(
str(ex.exception),
"validating class: Operator.bl_label expected a string type, not int",
)
class TestOperatorRegisterCallbackSignature(unittest.TestCase):
def test_execute_missing_arg(self):
class TESTING_OT_exec_missing(bpy.types.Operator):
bl_idname = "testing.exec_missing"
bl_label = "Exec Missing"
def execute(self):
return {'FINISHED'}
with self.assertRaises(ValueError) as ex:
bpy.utils.register_class(TESTING_OT_exec_missing)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_exec_missing class "execute" function to have 2 args, found 1',
)
def test_execute_extra_arg(self):
class TESTING_OT_exec_extra(bpy.types.Operator):
bl_idname = "testing.exec_extra"
bl_label = "Exec Extra"
def execute(self, context, extra):
return {'FINISHED'}
with self.assertRaises(ValueError) as ex:
bpy.utils.register_class(TESTING_OT_exec_extra)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_exec_extra class "execute" function to have 2 args, found 3',
)
def test_invoke_missing_arg(self):
class TESTING_OT_invoke_missing(bpy.types.Operator):
bl_idname = "testing.invoke_missing"
bl_label = "Invoke Missing"
def invoke(self, context):
return {'FINISHED'}
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(ValueError) as ex:
bpy.utils.register_class(TESTING_OT_invoke_missing)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_invoke_missing class "invoke" function to have 3 args, found 2',
)
def test_draw_missing_arg(self):
class TESTING_OT_draw_missing(bpy.types.Operator):
bl_idname = "testing.draw_missing"
bl_label = "Draw Missing"
def draw(self):
pass
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(ValueError) as ex:
bpy.utils.register_class(TESTING_OT_draw_missing)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_draw_missing class "draw" function to have 2 args, found 1',
)
def test_poll_extra_arg(self):
class TESTING_OT_poll_extra(bpy.types.Operator):
bl_idname = "testing.poll_extra"
bl_label = "Poll Extra"
@classmethod
def poll(cls, context, extra):
return True
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(ValueError) as ex:
bpy.utils.register_class(TESTING_OT_poll_extra)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_poll_extra class "poll" function to have 2 args, found 3',
)
class TestOperatorRegisterCallbackWrongType(unittest.TestCase):
def test_execute_as_classmethod(self):
class TESTING_OT_exec_cls(bpy.types.Operator):
bl_idname = "testing.exec_cls"
bl_label = "Exec Cls"
@classmethod # Wrong: must be a regular `method`.
def execute(cls, context):
return {'FINISHED'}
with self.assertRaises(TypeError) as ex:
bpy.utils.register_class(TESTING_OT_exec_cls)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_exec_cls class "execute" attribute to be a function, not a method',
)
def test_execute_as_staticmethod(self):
class TESTING_OT_exec_static(bpy.types.Operator):
bl_idname = "testing.exec_static"
bl_label = "Exec Static"
@staticmethod # Wrong: must be a regular `method`.
def execute(context):
return {'FINISHED'}
with self.assertRaises(ValueError) as ex:
bpy.utils.register_class(TESTING_OT_exec_static)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_exec_static class "execute" function to have 2 args, found 1',
)
def test_execute_as_property(self):
class TESTING_OT_exec_prop(bpy.types.Operator):
bl_idname = "testing.exec_prop"
bl_label = "Exec Prop"
@property # Wrong: must be a regular `method`.
def execute(self):
return {'FINISHED'}
with self.assertRaises(TypeError) as ex:
bpy.utils.register_class(TESTING_OT_exec_prop)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_exec_prop class "execute" attribute to be a function, not a property',
)
def test_invoke_as_classmethod(self):
class TESTING_OT_invoke_cls(bpy.types.Operator):
bl_idname = "testing.invoke_cls"
bl_label = "Invoke Cls"
@classmethod # Wrong: must be a regular `method`.
def invoke(cls, context, event):
return {'FINISHED'}
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(TypeError) as ex:
bpy.utils.register_class(TESTING_OT_invoke_cls)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_invoke_cls class "invoke" attribute to be a function, not a method',
)
def test_poll_as_staticmethod(self):
class TESTING_OT_poll_static(bpy.types.Operator):
bl_idname = "testing.poll_static"
bl_label = "Poll Static"
@staticmethod # Wrong: must be a `@classmethod`.
def poll(context):
return True
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(TypeError) as ex:
bpy.utils.register_class(TESTING_OT_poll_static)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_poll_static class "poll" '
'attribute to be a static/class method, not a function',
)
def test_poll_as_regular_method(self):
class TESTING_OT_poll_regular(bpy.types.Operator):
bl_idname = "testing.poll_regular"
bl_label = "Poll Regular"
def poll(self, context): # Wrong: must be a `@classmethod`.
return True
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(TypeError) as ex:
bpy.utils.register_class(TESTING_OT_poll_regular)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_poll_regular class "poll" '
'attribute to be a static/class method, not a function',
)
def test_draw_as_classmethod(self):
class TESTING_OT_draw_cls(bpy.types.Operator):
bl_idname = "testing.draw_cls"
bl_label = "Draw Cls"
@classmethod # Wrong: must be a regular `method`.
def draw(cls, context):
pass
def execute(self, context):
return {'FINISHED'}
with self.assertRaises(TypeError) as ex:
bpy.utils.register_class(TESTING_OT_draw_cls)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_draw_cls class "draw" attribute to be a function, not a method',
)
def test_execute_as_string(self):
class TESTING_OT_exec_str(bpy.types.Operator):
bl_idname = "testing.exec_str"
bl_label = "Exec Str"
execute = "not_a_function" # Wrong: must be a `callable`.
with self.assertRaises(TypeError) as ex:
bpy.utils.register_class(TESTING_OT_exec_str)
self.assertEqual(
str(ex.exception),
'expected Operator, TESTING_OT_exec_str class "execute" attribute to be a function, not a str',
)
class TestOperatorRegisterComplete(unittest.TestCase):
"""Operator using every supported flag and callback."""
@classmethod
def setUpClass(cls):
class TESTING_OT_complete(bpy.types.Operator):
bl_idname = "testing.complete"
bl_label = "Complete"
bl_description = "A complete operator"
bl_translation_context = "*"
bl_undo_group = ""
bl_options = {item.identifier for item in bpy.types.Operator.bl_rna.properties['bl_options'].enum_items}
bl_cursor_pending = 'DEFAULT'
value: bpy.props.IntProperty(name="Value", default=0)
last_value = None
@classmethod
def poll(cls, context):
return True
def invoke(self, context, event):
return self.execute(context)
def execute(self, context):
type(self).last_value = self.value
return {'FINISHED'}
def draw(self, context):
self.layout.prop(self, 'value')
@classmethod
def description(cls, context, properties):
return "Complete operator description"
def cancel(self, context):
pass
def modal(self, context, event):
return {'FINISHED'}
def check(self, context):
return False
cls._op_class = TESTING_OT_complete
bpy.utils.register_class(TESTING_OT_complete)
cls.op = bpy.ops.testing.complete
@classmethod
def tearDownClass(cls):
bpy.utils.unregister_class(cls._op_class)
def test_all_callbacks_implemented(self):
"""Ensure the operator implements every registered callback from RNA."""
registered_callbacks = {
fn_name for fn_name, fn in bpy.types.Operator.bl_rna.functions.items()
if fn.is_registered or fn.is_registered_optional
}
implemented_callbacks = {
fn_name for fn_name in registered_callbacks
if fn_name in self._op_class.__dict__
}
self.assertEqual(registered_callbacks, implemented_callbacks)
def test_all_properties_implemented(self):
"""Ensure the operator implements every registered property from RNA."""
registered_properties = {
prop_name for prop_name, prop in bpy.types.Operator.bl_rna.properties.items()
if prop_name != "rna_type" and (prop.is_registered or prop.is_registered_optional)
}
implemented_properties = {
prop_name for prop_name in registered_properties
if prop_name in self._op_class.__dict__
}
self.assertEqual(registered_properties, implemented_properties)
def test_poll(self):
self.assertTrue(self.op.poll())
def test_execute(self):
self.assertEqual(self.op(value=99), {'FINISHED'})
self.assertEqual(self._op_class.last_value, 99)
def test_invoke(self):
self.assertEqual(self.op('INVOKE_DEFAULT', value=7), {'FINISHED'})
self.assertEqual(self._op_class.last_value, 7)
def test_idname(self):
self.assertEqual(self.op.idname(), 'TESTING_OT_complete')
def test_idname_py(self):
self.assertEqual(self.op.idname_py(), 'testing.complete')
def test_get_rna_type(self):
rna = self.op.get_rna_type()
self.assertEqual(rna.identifier, 'TESTING_OT_complete')
def test_bl_options(self):
all_options = {item.identifier for item in bpy.types.Operator.bl_rna.properties['bl_options'].enum_items}
self.assertEqual(self.op.bl_options, all_options)
def test_doc(self):
self.assertEqual(self.op.__doc__, "bpy.ops.testing.complete(value=0)\nA complete operator")
def test_str(self):
s = str(self.op)
self.assertTrue(s.startswith("<function bpy.ops.testing.complete at 0x"))
self.assertTrue(s.endswith(">"))
def test_repr(self):
self.assertEqual(repr(self.op), "bpy.ops.testing.complete(value=0)")
class TestOperatorWrapProperties(unittest.TestCase):
def _make_operator(self, bl_idname, prop_type):
"""Create an operator class that captures `value` on execute."""
class _OT(bpy.types.Operator):
bl_label = "Test"
last_value = None
def execute(self, context):
v = self.value
type(self).last_value = tuple(v) if hasattr(v, "__len__") and not isinstance(v, (str, bytes)) else v
return {'FINISHED'}
_OT.bl_idname = bl_idname
_OT.__name__ = "TESTING_OT_" + bl_idname.split(".")[1]
_OT.__annotations__ = {"value": prop_type}
return _OT
def _run_property_test(self, *, bl_idname, prop_type, value, expected=None):
if expected is None:
expected = value
op_cls = self._make_operator(bl_idname, prop_type)
with registered_operator(op_cls):
getattr(bpy.ops.testing, bl_idname.split(".")[1])(value=value)
result = op_cls.last_value
if isinstance(expected, float):
self.assertAlmostEqual(result, expected, places=2)
elif isinstance(expected, (list, tuple)):
self.assertEqual(list(result), list(expected))
else:
self.assertEqual(result, expected)
def test_string_property(self):
self._run_property_test(
bl_idname="testing.str_prop",
prop_type=bpy.props.StringProperty(),
value="hello",
)
def test_int_property(self):
self._run_property_test(
bl_idname="testing.int_prop",
prop_type=bpy.props.IntProperty(),
value=42,
)
def test_float_property(self):
self._run_property_test(
bl_idname="testing.float_prop",
prop_type=bpy.props.FloatProperty(),
value=3.14,
)
def test_bool_property(self):
self._run_property_test(
bl_idname="testing.bool_prop",
prop_type=bpy.props.BoolProperty(),
value=True,
)
def test_enum_property(self):
self._run_property_test(
bl_idname="testing.enum_prop",
prop_type=bpy.props.EnumProperty(items=[('A', "A", ""), ('B', "B", "")]),
value='B',
)
def test_byte_string_property(self):
self._run_property_test(
bl_idname="testing.byte_str_prop",
prop_type=bpy.props.StringProperty(subtype='BYTE_STRING'),
value=b"hello",
)
def test_int_vector_property(self):
self._run_property_test(
bl_idname="testing.int_vec_prop",
prop_type=bpy.props.IntVectorProperty(size=3),
value=(1, 2, 3),
)
def test_float_vector_property(self):
self._run_property_test(
bl_idname="testing.float_vec_prop",
prop_type=bpy.props.FloatVectorProperty(size=3),
value=(1.5, 2.5, 3.5),
)
def test_bool_vector_property(self):
self._run_property_test(
bl_idname="testing.bool_vec_prop",
prop_type=bpy.props.BoolVectorProperty(size=3),
value=(True, False, True),
)
def main():
import sys
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
unittest.main()
if __name__ == "__main__":
main()