From 9bd3cb22c13b7c52550134f3ce7642a9f363fa05 Mon Sep 17 00:00:00 2001 From: Dag Wieers Date: Sat, 17 Mar 2018 04:00:11 +0100 Subject: [PATCH] Add more strict type tests This PR adds a few more type-related tests. - boolean Testing of an object is a boolean required 2 tests. - false Make this similar to testing none value - true Make this similar to testing none value - integer The existing 'number' test does not make a distinction between integer, float or even booleans - float The existing 'number' test does not make a distinction between integer, float or even booleans --- CHANGES.rst | 2 + jinja2/tests.py | 47 +++++++++++++++ tests/test_tests.py | 136 +++++++++++++++++++++++++++++++------------- 3 files changed, 146 insertions(+), 39 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 35673a0a..2c1e2863 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -32,6 +32,8 @@ Unreleased - Parentheses around comparisons are preserved, so ``{{ 2 * (3 < 5) }}`` outputs "2" instead of "False". :issue:`755`, :pr:`938` +- Add new ``boolean``, ``false``, ``true``, ``integer`` and ``float`` + tests. :pr:`824` Version 2.10.3 diff --git a/jinja2/tests.py b/jinja2/tests.py index bc99d66c..87f9c0c2 100644 --- a/jinja2/tests.py +++ b/jinja2/tests.py @@ -63,6 +63,48 @@ def test_none(value): return value is None +def test_boolean(value): + """Return true if the object is a boolean value. + + .. versionadded:: 2.11 + """ + return value is True or value is False + + +def test_false(value): + """Return true if the object is False. + + .. versionadded:: 2.11 + """ + return value is False + + +def test_true(value): + """Return true if the object is True. + + .. versionadded:: 2.11 + """ + return value is True + + +# NOTE: The existing Jinja2 'number' test matches booleans and floats +def test_integer(value): + """Return true if the object is an integer. + + .. versionadded:: 2.11 + """ + return isinstance(value, integer_types) and value is not True and value is not False + + +# NOTE: The existing Jinja2 'number' test matches booleans and integers +def test_float(value): + """Return true if the object is a float. + + .. versionadded:: 2.11 + """ + return isinstance(value, float) + + def test_lower(value): """Return true if the variable is lowercased.""" return text_type(value).islower() @@ -145,6 +187,11 @@ TESTS = { 'defined': test_defined, 'undefined': test_undefined, 'none': test_none, + 'boolean': test_boolean, + 'false': test_false, + 'true': test_true, + 'integer': test_integer, + 'float': test_float, 'lower': test_lower, 'upper': test_upper, 'string': test_string, diff --git a/tests/test_tests.py b/tests/test_tests.py index 84df5ea7..a596022c 100644 --- a/tests/test_tests.py +++ b/tests/test_tests.py @@ -12,6 +12,8 @@ import pytest from jinja2 import Markup, Environment +class MyDict(dict): + pass @pytest.mark.test_tests class TestTestsCase(object): @@ -33,45 +35,101 @@ class TestTestsCase(object): tmpl = env.from_string('''{{ "foo" is lower }}|{{ "FOO" is lower }}''') assert tmpl.render() == 'True|False' - def test_typechecks(self, env): - tmpl = env.from_string(''' - {{ 42 is undefined }} - {{ 42 is defined }} - {{ 42 is none }} - {{ none is none }} - {{ 42 is number }} - {{ 42 is string }} - {{ "foo" is string }} - {{ "foo" is sequence }} - {{ [1] is sequence }} - {{ range is callable }} - {{ 42 is callable }} - {{ range(5) is iterable }} - {{ {} is mapping }} - {{ mydict is mapping }} - {{ [] is mapping }} - {{ 10 is number }} - {{ (10 ** 100) is number }} - {{ 3.14159 is number }} - {{ complex is number }} - ''') - - class MyDict(dict): - pass - - assert tmpl.render(mydict=MyDict(), complex=complex(1, 2)).split() == [ - 'False', 'True', 'False', 'True', 'True', 'False', - 'True', 'True', 'True', 'True', 'False', 'True', - 'True', 'True', 'False', 'True', 'True', 'True', 'True' - ] - - def test_sequence(self, env): - tmpl = env.from_string( - '{{ [1, 2, 3] is sequence }}|' - '{{ "foo" is sequence }}|' - '{{ 42 is sequence }}' - ) - assert tmpl.render() == 'True|True|False' + # Test type checks + @pytest.mark.parametrize('op,expect', ( + ('none is none', True), + ('false is none', False), + ('true is none', False), + ('42 is none', False), + + ('none is true', False), + ('false is true', False), + ('true is true', True), + ('0 is true', False), + ('1 is true', False), + ('42 is true', False), + + ('none is false', False), + ('false is false', True), + ('true is false', False), + ('0 is false', False), + ('1 is false', False), + ('42 is false', False), + + ('none is boolean', False), + ('false is boolean', True), + ('true is boolean', True), + ('0 is boolean', False), + ('1 is boolean', False), + ('42 is boolean', False), + ('0.0 is boolean', False), + ('1.0 is boolean', False), + ('3.14159 is boolean', False), + + ('none is integer', False), + ('false is integer', False), + ('true is integer', False), + ('42 is integer', True), + ('3.14159 is integer', False), + ('(10 ** 100) is integer', True), + + ('none is float', False), + ('false is float', False), + ('true is float', False), + ('42 is float', False), + ('4.2 is float', True), + ('(10 ** 100) is float', False), + + ('none is number', False), + ('false is number', True), + ('true is number', True), + ('42 is number', True), + ('3.14159 is number', True), + ('complex is number', True), + ('(10 ** 100) is number', True), + + ('none is string', False), + ('false is string', False), + ('true is string', False), + ('42 is string', False), + ('"foo" is string', True), + + ('none is sequence', False), + ('false is sequence', False), + ('42 is sequence', False), + ('"foo" is sequence', True), + ('[] is sequence', True), + ('[1, 2, 3] is sequence', True), + ('{} is sequence', True), + + ('none is mapping', False), + ('false is mapping', False), + ('42 is mapping', False), + ('"foo" is mapping', False), + ('[] is mapping', False), + ('{} is mapping', True), + ('mydict is mapping', True), + + ('none is iterable', False), + ('false is iterable', False), + ('42 is iterable', False), + ('"foo" is iterable', True), + ('[] is iterable', True), + ('{} is iterable', True), + ('range(5) is iterable', True), + + ('none is callable', False), + ('false is callable', False), + ('42 is callable', False), + ('"foo" is callable', False), + ('[] is callable', False), + ('{} is callable', False), + ('range is callable', True), + )) + def test_types(self, env, op, expect): + t = env.from_string('{{{{ {op} }}}}'.format(op=op)) + assert t.render(mydict=MyDict(), complex=complex(1, 2)) == str(expect) + def test_upper(self, env): tmpl = env.from_string('{{ "FOO" is upper }}|{{ "foo" is upper }}') -- 2.47.2