diff options
Diffstat (limited to 'tests/test_regression.py')
-rw-r--r-- | tests/test_regression.py | 496 |
1 files changed, 291 insertions, 205 deletions
diff --git a/tests/test_regression.py b/tests/test_regression.py index df91785..47e0073 100644 --- a/tests/test_regression.py +++ b/tests/test_regression.py @@ -24,94 +24,109 @@ from jinja2._compat import text_type @pytest.mark.regression class TestCorner(object): - def test_assigned_scoping(self, env): - t = env.from_string(''' + t = env.from_string( + """ {%- for item in (1, 2, 3, 4) -%} [{{ item }}] {%- endfor %} {{- item -}} - ''') - assert t.render(item=42) == '[1][2][3][4]42' + """ + ) + assert t.render(item=42) == "[1][2][3][4]42" - t = env.from_string(''' + t = env.from_string( + """ {%- for item in (1, 2, 3, 4) -%} [{{ item }}] {%- endfor %} {%- set item = 42 %} {{- item -}} - ''') - assert t.render() == '[1][2][3][4]42' + """ + ) + assert t.render() == "[1][2][3][4]42" - t = env.from_string(''' + t = env.from_string( + """ {%- set item = 42 %} {%- for item in (1, 2, 3, 4) -%} [{{ item }}] {%- endfor %} {{- item -}} - ''') - assert t.render() == '[1][2][3][4]42' + """ + ) + assert t.render() == "[1][2][3][4]42" def test_closure_scoping(self, env): - t = env.from_string(''' + t = env.from_string( + """ {%- set wrapper = "<FOO>" %} {%- for item in (1, 2, 3, 4) %} {%- macro wrapper() %}[{{ item }}]{% endmacro %} {{- wrapper() }} {%- endfor %} {{- wrapper -}} - ''') - assert t.render() == '[1][2][3][4]<FOO>' + """ + ) + assert t.render() == "[1][2][3][4]<FOO>" - t = env.from_string(''' + t = env.from_string( + """ {%- for item in (1, 2, 3, 4) %} {%- macro wrapper() %}[{{ item }}]{% endmacro %} {{- wrapper() }} {%- endfor %} {%- set wrapper = "<FOO>" %} {{- wrapper -}} - ''') - assert t.render() == '[1][2][3][4]<FOO>' + """ + ) + assert t.render() == "[1][2][3][4]<FOO>" - t = env.from_string(''' + t = env.from_string( + """ {%- for item in (1, 2, 3, 4) %} {%- macro wrapper() %}[{{ item }}]{% endmacro %} {{- wrapper() }} {%- endfor %} {{- wrapper -}} - ''') - assert t.render(wrapper=23) == '[1][2][3][4]23' + """ + ) + assert t.render(wrapper=23) == "[1][2][3][4]23" @pytest.mark.regression class TestBug(object): - def test_keyword_folding(self, env): env = Environment() - env.filters['testing'] = lambda value, some: value + some - assert env.from_string("{{ 'test'|testing(some='stuff') }}") \ - .render() == 'teststuff' + env.filters["testing"] = lambda value, some: value + some + assert ( + env.from_string("{{ 'test'|testing(some='stuff') }}").render() + == "teststuff" + ) def test_extends_output_bugs(self, env): - env = Environment(loader=DictLoader({ - 'parent.html': '(({% block title %}{% endblock %}))' - })) + env = Environment( + loader=DictLoader({"parent.html": "(({% block title %}{% endblock %}))"}) + ) t = env.from_string( '{% if expr %}{% extends "parent.html" %}{% endif %}' - '[[{% block title %}title{% endblock %}]]' - '{% for item in [1, 2, 3] %}({{ item }}){% endfor %}' + "[[{% block title %}title{% endblock %}]]" + "{% for item in [1, 2, 3] %}({{ item }}){% endfor %}" ) - assert t.render(expr=False) == '[[title]](1)(2)(3)' - assert t.render(expr=True) == '((title))' + assert t.render(expr=False) == "[[title]](1)(2)(3)" + assert t.render(expr=True) == "((title))" def test_urlize_filter_escaping(self, env): tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}') - assert tmpl.render() == '<a href="http://www.example.org/<foo" rel="noopener">'\ - 'http://www.example.org/<foo</a>' + assert ( + tmpl.render() == '<a href="http://www.example.org/<foo" rel="noopener">' + "http://www.example.org/<foo</a>" + ) def test_loop_call_loop(self, env): - tmpl = env.from_string(''' + tmpl = env.from_string( + """ {% macro test() %} {{ caller() }} @@ -125,29 +140,35 @@ class TestBug(object): {% endcall %} {% endfor %} - ''') + """ + ) - assert tmpl.render().split() \ - == [text_type(x) for x in range(1, 11)] * 5 + assert tmpl.render().split() == [text_type(x) for x in range(1, 11)] * 5 def test_weird_inline_comment(self, env): - env = Environment(line_statement_prefix='%') - pytest.raises(TemplateSyntaxError, env.from_string, - '% for item in seq {# missing #}\n...% endfor') + env = Environment(line_statement_prefix="%") + pytest.raises( + TemplateSyntaxError, + env.from_string, + "% for item in seq {# missing #}\n...% endfor", + ) def test_old_macro_loop_scoping_bug(self, env): - tmpl = env.from_string('{% for i in (1, 2) %}{{ i }}{% endfor %}' - '{% macro i() %}3{% endmacro %}{{ i() }}') - assert tmpl.render() == '123' + tmpl = env.from_string( + "{% for i in (1, 2) %}{{ i }}{% endfor %}" + "{% macro i() %}3{% endmacro %}{{ i() }}" + ) + assert tmpl.render() == "123" def test_partial_conditional_assignments(self, env): - tmpl = env.from_string('{% if b %}{% set a = 42 %}{% endif %}{{ a }}') - assert tmpl.render(a=23) == '23' - assert tmpl.render(b=True) == '42' + tmpl = env.from_string("{% if b %}{% set a = 42 %}{% endif %}{{ a }}") + assert tmpl.render(a=23) == "23" + assert tmpl.render(b=True) == "42" def test_stacked_locals_scoping_bug(self, env): - env = Environment(line_statement_prefix='#') - t = env.from_string('''\ + env = Environment(line_statement_prefix="#") + t = env.from_string( + """\ # for j in [1, 2]: # set x = 1 # for i in [1, 2]: @@ -166,11 +187,13 @@ class TestBug(object): # else # print 'D' # endif - ''') - assert t.render(a=0, b=False, c=42, d=42.0) == '1111C' + """ + ) + assert t.render(a=0, b=False, c=42, d=42.0) == "1111C" def test_stacked_locals_scoping_bug_twoframe(self, env): - t = Template(''' + t = Template( + """ {% set x = 1 %} {% for item in foo %} {% if item == 1 %} @@ -178,12 +201,14 @@ class TestBug(object): {% endif %} {% endfor %} {{ x }} - ''') + """ + ) rv = t.render(foo=[1]).strip() - assert rv == u'1' + assert rv == u"1" def test_call_with_args(self, env): - t = Template("""{% macro dump_users(users) -%} + t = Template( + """{% macro dump_users(users) -%} <ul> {%- for user in users -%} <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li> @@ -198,32 +223,40 @@ class TestBug(object): <dl>Description</dl> <dd>{{ user.description }}</dd> </dl> - {% endcall %}""") - - assert [x.strip() for x in t.render(list_of_user=[{ - 'username': 'apo', - 'realname': 'something else', - 'description': 'test' - }]).splitlines()] == [ - u'<ul><li><p>apo</p><dl>', - u'<dl>Realname</dl>', - u'<dd>something else</dd>', - u'<dl>Description</dl>', - u'<dd>test</dd>', - u'</dl>', - u'</li></ul>' + {% endcall %}""" + ) + + assert [ + x.strip() + for x in t.render( + list_of_user=[ + { + "username": "apo", + "realname": "something else", + "description": "test", + } + ] + ).splitlines() + ] == [ + u"<ul><li><p>apo</p><dl>", + u"<dl>Realname</dl>", + u"<dd>something else</dd>", + u"<dl>Description</dl>", + u"<dd>test</dd>", + u"</dl>", + u"</li></ul>", ] def test_empty_if_condition_fails(self, env): - pytest.raises(TemplateSyntaxError, - Template, '{% if %}....{% endif %}') - pytest.raises(TemplateSyntaxError, - Template, '{% if foo %}...{% elif %}...{% endif %}') - pytest.raises(TemplateSyntaxError, - Template, '{% for x in %}..{% endfor %}') + pytest.raises(TemplateSyntaxError, Template, "{% if %}....{% endif %}") + pytest.raises( + TemplateSyntaxError, Template, "{% if foo %}...{% elif %}...{% endif %}" + ) + pytest.raises(TemplateSyntaxError, Template, "{% for x in %}..{% endfor %}") def test_recursive_loop_bug(self, env): - tpl1 = Template(""" + tpl1 = Template( + """ {% for p in foo recursive%} {{p.bar}} {% for f in p.fields recursive%} @@ -234,9 +267,11 @@ class TestBug(object): {% endif %} {% endfor %} {% endfor %} - """) + """ + ) - tpl2 = Template(""" + tpl2 = Template( + """ {% for p in foo%} {{p.bar}} {% for f in p.fields recursive%} @@ -247,26 +282,27 @@ class TestBug(object): {% endif %} {% endfor %} {% endfor %} - """) + """ + ) def test_else_loop_bug(self, env): - t = Template(''' + t = Template( + """ {% for x in y %} {{ loop.index0 }} {% else %} {% for i in range(3) %}{{ i }}{% endfor %} {% endfor %} - ''') - assert t.render(y=[]).strip() == '012' + """ + ) + assert t.render(y=[]).strip() == "012" def test_correct_prefix_loader_name(self, env): - env = Environment(loader=PrefixLoader({ - 'foo': DictLoader({}) - })) + env = Environment(loader=PrefixLoader({"foo": DictLoader({})})) with pytest.raises(TemplateNotFound) as e: - env.get_template('foo/bar.html') + env.get_template("foo/bar.html") - assert e.value.name == 'foo/bar.html' + assert e.value.name == "foo/bar.html" def test_contextfunction_callable_classes(self, env): from jinja2.utils import contextfunction @@ -274,74 +310,84 @@ class TestBug(object): class CallableClass(object): @contextfunction def __call__(self, ctx): - return ctx.resolve('hello') + return ctx.resolve("hello") tpl = Template("""{{ callableclass() }}""") - output = tpl.render(callableclass=CallableClass(), hello='TEST') - expected = 'TEST' + output = tpl.render(callableclass=CallableClass(), hello="TEST") + expected = "TEST" assert output == expected - @pytest.mark.skipif(sys.version_info[0] > 2, - reason='This only works on 2.x') + @pytest.mark.skipif(sys.version_info[0] > 2, reason="This only works on 2.x") def test_old_style_attribute(self, env): class Foo: x = 42 - assert env.getitem(Foo(), 'x') == 42 + + assert env.getitem(Foo(), "x") == 42 def test_block_set_with_extends(self): - env = Environment(loader=DictLoader({ - 'main': '{% block body %}[{{ x }}]{% endblock %}', - })) + env = Environment( + loader=DictLoader({"main": "{% block body %}[{{ x }}]{% endblock %}",}) + ) t = env.from_string('{% extends "main" %}{% set x %}42{% endset %}') - assert t.render() == '[42]' + assert t.render() == "[42]" def test_nested_for_else(self, env): - tmpl = env.from_string('{% for x in y %}{{ loop.index0 }}{% else %}' - '{% for i in range(3) %}{{ i }}{% endfor %}' - '{% endfor %}') - assert tmpl.render() == '012' + tmpl = env.from_string( + "{% for x in y %}{{ loop.index0 }}{% else %}" + "{% for i in range(3) %}{{ i }}{% endfor %}" + "{% endfor %}" + ) + assert tmpl.render() == "012" def test_macro_var_bug(self, env): - tmpl = env.from_string(''' + tmpl = env.from_string( + """ {% set i = 1 %} {% macro test() %} {% for i in range(0, 10) %}{{ i }}{% endfor %} {% endmacro %}{{ test() }} - ''') - assert tmpl.render().strip() == '0123456789' + """ + ) + assert tmpl.render().strip() == "0123456789" def test_macro_var_bug_advanced(self, env): - tmpl = env.from_string(''' + tmpl = env.from_string( + """ {% macro outer() %} {% set i = 1 %} {% macro test() %} {% for i in range(0, 10) %}{{ i }}{% endfor %} {% endmacro %}{{ test() }} {% endmacro %}{{ outer() }} - ''') - assert tmpl.render().strip() == '0123456789' + """ + ) + assert tmpl.render().strip() == "0123456789" def test_callable_defaults(self): env = Environment() - env.globals['get_int'] = lambda: 42 - t = env.from_string(''' + env.globals["get_int"] = lambda: 42 + t = env.from_string( + """ {% macro test(a, b, c=get_int()) -%} {{ a + b + c }} {%- endmacro %} {{ test(1, 2) }}|{{ test(1, 2, 3) }} - ''') - assert t.render().strip() == '45|6' + """ + ) + assert t.render().strip() == "45|6" def test_macro_escaping(self): env = Environment( - autoescape=lambda x: False, extensions=['jinja2.ext.autoescape']) + autoescape=lambda x: False, extensions=["jinja2.ext.autoescape"] + ) template = "{% macro m() %}<html>{% endmacro %}" template += "{% autoescape true %}{{ m() }}{% endautoescape %}" assert env.from_string(template).render() def test_macro_scoping(self, env): - tmpl = env.from_string(''' + tmpl = env.from_string( + """ {% set n=[1,2,3,4,5] %} {% for n in [[1,2,3], [3,4,5], [5,6,7]] %} @@ -353,47 +399,56 @@ class TestBug(object): {{ x(n) }} {% endfor %} - ''') - assert list(map(int, tmpl.render().split())) == \ - [3, 2, 1, 5, 4, 3, 7, 6, 5] + """ + ) + assert list(map(int, tmpl.render().split())) == [3, 2, 1, 5, 4, 3, 7, 6, 5] def test_scopes_and_blocks(self): - env = Environment(loader=DictLoader({ - 'a.html': ''' + env = Environment( + loader=DictLoader( + { + "a.html": """ {%- set foo = 'bar' -%} {% include 'x.html' -%} - ''', - 'b.html': ''' + """, + "b.html": """ {%- set foo = 'bar' -%} {% block test %}{% include 'x.html' %}{% endblock -%} - ''', - 'c.html': ''' + """, + "c.html": """ {%- set foo = 'bar' -%} {% block test %}{% set foo = foo %}{% include 'x.html' %}{% endblock -%} - ''', - 'x.html': '''{{ foo }}|{{ test }}''' - })) + """, + "x.html": """{{ foo }}|{{ test }}""", + } + ) + ) - a = env.get_template('a.html') - b = env.get_template('b.html') - c = env.get_template('c.html') + a = env.get_template("a.html") + b = env.get_template("b.html") + c = env.get_template("c.html") - assert a.render(test='x').strip() == 'bar|x' - assert b.render(test='x').strip() == 'bar|x' - assert c.render(test='x').strip() == 'bar|x' + assert a.render(test="x").strip() == "bar|x" + assert b.render(test="x").strip() == "bar|x" + assert c.render(test="x").strip() == "bar|x" def test_scopes_and_include(self): - env = Environment(loader=DictLoader({ - 'include.html': '{{ var }}', - 'base.html': '{% include "include.html" %}', - 'child.html': '{% extends "base.html" %}{% set var = 42 %}', - })) - t = env.get_template('child.html') - assert t.render() == '42' + env = Environment( + loader=DictLoader( + { + "include.html": "{{ var }}", + "base.html": '{% include "include.html" %}', + "child.html": '{% extends "base.html" %}{% set var = 42 %}', + } + ) + ) + t = env.get_template("child.html") + assert t.render() == "42" def test_caller_scoping(self, env): - t = env.from_string(''' + t = env.from_string( + """ {% macro detail(icon, value) -%} {% if value -%} <p><span class="fa fa-fw fa-{{ icon }}"></span> @@ -411,54 +466,67 @@ class TestBug(object): <a href="{{ href }}">{{ value }}</a> {%- endcall %} {%- endmacro %} - ''') + """ + ) - assert t.module.link_detail('circle', 'Index', '/') == ( - '<p><span class="fa fa-fw fa-circle">' - '</span><a href="/">Index</a></p>') + assert t.module.link_detail("circle", "Index", "/") == ( + '<p><span class="fa fa-fw fa-circle"></span><a href="/">Index</a></p>' + ) def test_variable_reuse(self, env): - t = env.from_string('{% for x in x.y %}{{ x }}{% endfor %}') - assert t.render(x={'y': [0, 1, 2]}) == '012' + t = env.from_string("{% for x in x.y %}{{ x }}{% endfor %}") + assert t.render(x={"y": [0, 1, 2]}) == "012" - t = env.from_string('{% for x in x.y %}{{ loop.index0 }}|{{ x }}{% endfor %}') - assert t.render(x={'y': [0, 1, 2]}) == '0|01|12|2' + t = env.from_string("{% for x in x.y %}{{ loop.index0 }}|{{ x }}{% endfor %}") + assert t.render(x={"y": [0, 1, 2]}) == "0|01|12|2" - t = env.from_string('{% for x in x.y recursive %}{{ x }}{% endfor %}') - assert t.render(x={'y': [0, 1, 2]}) == '012' + t = env.from_string("{% for x in x.y recursive %}{{ x }}{% endfor %}") + assert t.render(x={"y": [0, 1, 2]}) == "012" def test_double_caller(self, env): - t = env.from_string('{% macro x(caller=none) %}[{% if caller %}' - '{{ caller() }}{% endif %}]{% endmacro %}' - '{{ x() }}{% call x() %}aha!{% endcall %}') - assert t.render() == '[][aha!]' + t = env.from_string( + "{% macro x(caller=none) %}[{% if caller %}" + "{{ caller() }}{% endif %}]{% endmacro %}" + "{{ x() }}{% call x() %}aha!{% endcall %}" + ) + assert t.render() == "[][aha!]" def test_double_caller_no_default(self, env): with pytest.raises(TemplateAssertionError) as exc_info: - env.from_string('{% macro x(caller) %}[{% if caller %}' - '{{ caller() }}{% endif %}]{% endmacro %}') - assert exc_info.match(r'"caller" argument must be omitted or ' - r'be given a default') + env.from_string( + "{% macro x(caller) %}[{% if caller %}" + "{{ caller() }}{% endif %}]{% endmacro %}" + ) + assert exc_info.match( + r'"caller" argument must be omitted or ' r"be given a default" + ) - t = env.from_string('{% macro x(caller=none) %}[{% if caller %}' - '{{ caller() }}{% endif %}]{% endmacro %}') + t = env.from_string( + "{% macro x(caller=none) %}[{% if caller %}" + "{{ caller() }}{% endif %}]{% endmacro %}" + ) with pytest.raises(TypeError) as exc_info: t.module.x(None, caller=lambda: 42) - assert exc_info.match(r'\'x\' was invoked with two values for the ' - r'special caller argument') + assert exc_info.match( + r"\'x\' was invoked with two values for the " r"special caller argument" + ) def test_macro_blocks(self, env): - t = env.from_string('{% macro x() %}{% block foo %}x{% ' - 'endblock %}{% endmacro %}{{ x() }}') - assert t.render() == 'x' + t = env.from_string( + "{% macro x() %}{% block foo %}x{% endblock %}{% endmacro %}{{ x() }}" + ) + assert t.render() == "x" def test_scoped_block(self, env): - t = env.from_string('{% set x = 1 %}{% with x = 2 %}{% block y scoped %}' - '{{ x }}{% endblock %}{% endwith %}') - assert t.render() == '2' + t = env.from_string( + "{% set x = 1 %}{% with x = 2 %}{% block y scoped %}" + "{{ x }}{% endblock %}{% endwith %}" + ) + assert t.render() == "2" def test_recursive_loop_filter(self, env): - t = env.from_string(''' + t = env.from_string( + """ <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> {%- for page in [site.root] if page.url != this recursive %} @@ -466,49 +534,65 @@ class TestBug(object): {{- loop(page.children) }} {%- endfor %} </urlset> - ''') - sm =t.render(this='/foo', site={'root': { - 'url': '/', - 'children': [ - {'url': '/foo'}, - {'url': '/bar'}, - ] - }}) + """ + ) + sm = t.render( + this="/foo", + site={ + "root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"},]} + }, + ) lines = [x.strip() for x in sm.splitlines() if x.strip()] assert lines == [ '<?xml version="1.0" encoding="UTF-8"?>', '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">', - '<url><loc>/</loc></url>', - '<url><loc>/bar</loc></url>', - '</urlset>', + "<url><loc>/</loc></url>", + "<url><loc>/bar</loc></url>", + "</urlset>", ] def test_empty_if(self, env): - t = env.from_string('{% if foo %}{% else %}42{% endif %}') - assert t.render(foo=False) == '42' + t = env.from_string("{% if foo %}{% else %}42{% endif %}") + assert t.render(foo=False) == "42" def test_subproperty_if(self, env): - t = env.from_string('{% if object1.subproperty1 is eq object2.subproperty2 %}42{% endif %}') - assert t.render(object1={'subproperty1': 'value'}, object2={'subproperty2': 'value'}) == '42' + t = env.from_string( + "{% if object1.subproperty1 is eq object2.subproperty2 %}42{% endif %}" + ) + assert ( + t.render( + object1={"subproperty1": "value"}, object2={"subproperty2": "value"} + ) + == "42" + ) def test_set_and_include(self): - env = Environment(loader=DictLoader({ - 'inc': 'bar', - 'main': '{% set foo = "foo" %}{{ foo }}{% include "inc" %}' - })) - assert env.get_template('main').render() == 'foobar' + env = Environment( + loader=DictLoader( + { + "inc": "bar", + "main": '{% set foo = "foo" %}{{ foo }}{% include "inc" %}', + } + ) + ) + assert env.get_template("main").render() == "foobar" def test_loop_include(self): - env = Environment(loader=DictLoader({ - 'inc': '{{ item }}', - 'main': '{% for item in [1, 2, 3] %}{% include "inc" %}{% endfor %}', - })) - assert env.get_template('main').render() == '123' + env = Environment( + loader=DictLoader( + { + "inc": "{{ item }}", + "main": '{% for item in [1, 2, 3] %}{% include "inc" %}{% endfor %}', + } + ) + ) + assert env.get_template("main").render() == "123" def test_grouper_repr(self): from jinja2.filters import _GroupTuple - t = _GroupTuple('foo', [1, 2]) - assert t.grouper == 'foo' + + t = _GroupTuple("foo", [1, 2]) + assert t.grouper == "foo" assert t.list == [1, 2] assert repr(t) == "('foo', [1, 2])" assert str(t) == "('foo', [1, 2])" @@ -522,33 +606,35 @@ class TestBug(object): class MyEnvironment(Environment): context_class = MyContext - loader = DictLoader({'base': '{{ foobar }}', - 'test': '{% extends "base" %}'}) + loader = DictLoader({"base": "{{ foobar }}", "test": '{% extends "base" %}'}) env = MyEnvironment(loader=loader) - assert env.get_template('test').render(foobar='test') == 'test' + assert env.get_template("test").render(foobar="test") == "test" def test_legacy_custom_context(self, env): from jinja2.runtime import Context, Undefined, missing class MyContext(Context): def resolve(self, name): - if name == 'foo': + if name == "foo": return 42 return super(MyContext, self).resolve(name) - x = MyContext(env, parent={'bar': 23}, name='foo', blocks={}) + x = MyContext(env, parent={"bar": 23}, name="foo", blocks={}) assert x._legacy_resolve_mode - assert x.resolve_or_missing('foo') == 42 - assert x.resolve_or_missing('bar') == 23 - assert x.resolve_or_missing('baz') is missing + assert x.resolve_or_missing("foo") == 42 + assert x.resolve_or_missing("bar") == 23 + assert x.resolve_or_missing("baz") is missing def test_recursive_loop_bug(self, env): - tmpl = env.from_string(''' + tmpl = env.from_string( + """ {%- for value in values recursive %}1{% else %}0{% endfor -%} - ''') - assert tmpl.render(values=[]) == '0' + """ + ) + assert tmpl.render(values=[]) == "0" def test_markup_and_chainable_undefined(self): from jinja2 import Markup from jinja2.runtime import ChainableUndefined - assert str(Markup(ChainableUndefined())) == '' + + assert str(Markup(ChainableUndefined())) == "" |