Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace AttrDict with dotmap #49

Merged
merged 2 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ jobs:
- name: Install dependencies
run: |
pip install -e .[testing]
pip install pytest

- name: Run test suite
run: |
Expand Down
6 changes: 4 additions & 2 deletions mjml/core/api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@

from ..lib import AttrDict, merge_dicts
from dotmap import DotMap

from ..lib import merge_dicts
from .registry import components


Expand Down Expand Up @@ -30,7 +32,7 @@ def __init__(self, *, attributes=None, children=(), content='', context=None,
self.context = context
self.tagName = tagName

self.props = AttrDict(merge_dicts(props, {'children': children, 'content': content}))
self.props = DotMap(merge_dicts(props, {'children': children, 'content': content}))

# upstream also checks "self.allowed_attrs"
self.attrs = merge_dicts(
Expand Down
6 changes: 4 additions & 2 deletions mjml/elements/_base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@


from dotmap import DotMap

from ..core import Component, initComponent
from ..core.registry import components
from ..helpers import *
from ..lib import AttrDict, merge_dicts
from ..lib import merge_dicts


__all__ = [
Expand Down Expand Up @@ -39,7 +41,7 @@ def getBoxWidths(self):
paddings = get_padding('right') + get_padding('left')
borders = self.getShorthandBorderValue('right') + self.getShorthandBorderValue('left')

return AttrDict({
return DotMap({
'totalWidth': parsedWidth,
'borders' : borders,
'paddings' : paddings,
Expand Down
6 changes: 4 additions & 2 deletions mjml/elements/mj_section.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from collections import namedtuple
from decimal import Decimal

from dotmap import DotMap

from ..helpers import parse_percentage, strip_unit, suffixCssClasses
from ..lib import AttrDict, merge_dicts
from ..lib import merge_dicts
from ._base import BodyComponent


Expand Down Expand Up @@ -188,7 +190,7 @@ def renderSimple(self):

def getBackgroundPosition(self):
pos = self.parseBackgroundPosition()
return AttrDict({
return DotMap({
'posX': self.getAttribute('background-position-x') or pos.x,
'posY': self.getAttribute('background-position-y') or pos.y,
})
Expand Down
1 change: 0 additions & 1 deletion mjml/lib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@

from .attribute_dict import *
from .dict_merger import *
35 changes: 0 additions & 35 deletions mjml/lib/attribute_dict.py

This file was deleted.

27 changes: 12 additions & 15 deletions mjml/lib/tests/dict_merger_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@
# Copyright 2015 Felix Schwarz
# The source code in this file is licensed under the MIT license.

from pythonic_testcase import *

from ..dict_merger import merge_dicts


class DictMergerTest(PythonicTestCase):
def test_returns_single_dict_unmodified(self):
assert_equals({}, merge_dicts({}))
assert_equals({'bar': 42}, merge_dicts({'bar': 42}))
def test_returns_single_dict_unmodified():
assert merge_dicts({}) == {}
assert merge_dicts({'bar': 42}) == {'bar': 42}

def test_can_merge_two_dicts_without_modifying_inputs(self):
a = {'a': 1}
b = {'b': 2}
assert_equals({'a': 1, 'b': 2}, merge_dicts(a, b))
def test_can_merge_two_dicts_without_modifying_inputs():
a = {'a': 1}
b = {'b': 2}
assert merge_dicts(a, b) == {'a': 1, 'b': 2}

def test_can_merge_three_dicts_without_modifying_inputs(self):
a = {'a': 1}
b = {'b': 2}
c = {'c': 3}
assert_equals({'a': 1, 'b': 2, 'c': 3}, merge_dicts(a, b, c))
def test_can_merge_three_dicts_without_modifying_inputs():
a = {'a': 1}
b = {'b': 2}
c = {'c': 3}
assert merge_dicts(a, b, c) == {'a': 1, 'b': 2, 'c': 3}
11 changes: 6 additions & 5 deletions mjml/mjml2html.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
from typing import List, Optional

from bs4 import BeautifulSoup
from dotmap import DotMap

from .core import initComponent
from .core.registry import register_components, register_core_components
from .helpers import json_to_xml, mergeOutlookConditionnals, omit, skeleton_str as default_skeleton
from .lib import AttrDict, merge_dicts
from .lib import merge_dicts


def ignore_empty(values):
Expand Down Expand Up @@ -52,7 +53,7 @@ def mjml_to_html(xml_fp_or_json, skeleton=None, template_dir=None,
}
# LATER: ability to override fonts via **options

globalDatas = AttrDict({
globalDatas = DotMap({
'backgroundColor' : None,
'breakpoint' : '480px',
'classes' : {},
Expand Down Expand Up @@ -173,7 +174,7 @@ def addComponentHeadSyle(headStyle):
def setBackgroundColor(color):
globalDatas.backgroundColor = color

bodyHelpers = AttrDict(
bodyHelpers = DotMap(
addHeadStyle = addHeadStyle,
addMediaQuery = addMediaQuery,
addComponentHeadSyle = addComponentHeadSyle,
Expand All @@ -199,7 +200,7 @@ def _head_data_add(attr, *params):
assert len(param_values) == 1, 'shortcut in implementation'
current_attr_value[param_key] = param_values[0]

headHelpers = AttrDict(
headHelpers = DotMap(
add = _head_data_add,
)
globalDatas.headRaw = processing(mjHead, headHelpers)
Expand Down Expand Up @@ -241,7 +242,7 @@ def _head_data_add(attr, *params):

content = mergeOutlookConditionnals(content)

return AttrDict({
return DotMap({
'html': content,
'errors': errors,
})
Expand Down
10 changes: 5 additions & 5 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ zip_safe = true
include_package_data = true

install_requires =
beautifulsoup4
docopt
jinja2
beautifulsoup4
dotmap
docopt
jinja2

scripts =
mjml/scripts/mjml-html-compare
Expand All @@ -54,11 +55,10 @@ exclude =

[options.extras_require]
testing =
ddt
FakeFSHelpers
HTMLCompare >= 0.3.0 # >= 0.3.0: ability to ignore attribute ordering in HTML
lxml
PythonicTestcase
pytest
css_inlining =
css_inline >= 0.11, < 0.12 # >= 0.11, < 0.12: CSSInliner(inline_style_tags=..., keep_link_tags=..., keep_style_tags=...)

Expand Down
7 changes: 2 additions & 5 deletions tests/border_parser_test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@

from unittest import TestCase

from mjml.helpers import borderParser


class BorderParserTest(TestCase):
def test_can_parse_css_none(self):
self.assertEqual(0, borderParser('none'))
def test_can_parse_css_none():
assert borderParser('none') == 0
17 changes: 7 additions & 10 deletions tests/custom_components_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@

from unittest import TestCase

from htmlcompare import assert_same_html

from mjml import mjml_to_html
Expand Down Expand Up @@ -33,12 +31,11 @@ def render(self):
return f'<div>***</div>{content}<div>***</div>'


class CustomComponentsTest(TestCase):
def test_custom_components(self):
expected_html = load_expected_html('_custom')
with get_mjml_fp('_custom') as mjml_fp:
result_list = mjml_to_html(mjml_fp, custom_components=[MjTextCustom, MjTextOverride])
def test_custom_components():
expected_html = load_expected_html('_custom')
with get_mjml_fp('_custom') as mjml_fp:
result_list = mjml_to_html(mjml_fp, custom_components=[MjTextCustom, MjTextOverride])

assert not result_list.errors
list_actual_html = result_list.html
assert_same_html(expected_html, list_actual_html, verbose=True)
assert not result_list.errors
list_actual_html = result_list.html
assert_same_html(expected_html, list_actual_html, verbose=True)
55 changes: 30 additions & 25 deletions tests/includes_with_umlauts_test.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@

from io import StringIO
from unittest import TestCase

import pytest
from schwarz.fakefs_helpers import FakeFS

from mjml import mjml_to_html


class IncludesWithUmlautsTest(TestCase):
def test_can_properly_handle_include_umlauts(self):
fs = FakeFS.set_up(test=self)
included_mjml = (
'<mj-section>'
' <mj-column>'
' <mj-text>äöüß</mj-text>'
' </mj-column>'
'</mj-section>'
)
mjml = (
'<mjml>'
' <mj-body>'
' <mj-text>foo bar</mj-text>'
' <mj-include path="./footer.mjml" />'
' </mj-body>'
'</mjml>'
)
fs.create_file('footer.mjml', contents=included_mjml.encode('utf8'))

result = mjml_to_html(StringIO(mjml))
html = result.html

assert ('äöüß' in html)
@pytest.fixture
def fs():
_fs = FakeFS.set_up()
yield _fs
_fs.tear_down()


def test_can_properly_handle_include_umlauts(fs):
included_mjml = (
'<mj-section>'
' <mj-column>'
' <mj-text>äöüß</mj-text>'
' </mj-column>'
'</mj-section>'
)
mjml = (
'<mjml>'
' <mj-body>'
' <mj-text>foo bar</mj-text>'
' <mj-include path="./footer.mjml" />'
' </mj-body>'
'</mjml>'
)
fs.create_file('footer.mjml', contents=included_mjml.encode('utf8'))

result = mjml_to_html(StringIO(mjml))
html = result.html

assert ('äöüß' in html)
56 changes: 16 additions & 40 deletions tests/missing_functionality_test.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,26 @@

from pathlib import Path
from unittest import TestCase, expectedFailure

from ddt import data as ddt_data, ddt as DataDrivenTestCase
import pytest
from htmlcompare import assert_same_html

from mjml import mjml_to_html


TESTDATA_DIR = Path(__file__).parent / 'missing_functionality'

def patch_nose1(func):
def _wrapper(test, *args, **kwargs):
_patch_nose1_result(test)
return func(test, *args, **kwargs)
return _wrapper


@DataDrivenTestCase
class MissingFeaturesTest(TestCase):
@ddt_data(
)
@expectedFailure
@patch_nose1
def test_ensure_same_html(self, test_id):
mjml_filename = f'{test_id}.mjml'
html_filename = f'{test_id}-expected.html'
with (TESTDATA_DIR / html_filename).open('rb') as html_fp:
expected_html = html_fp.read()

with (TESTDATA_DIR / mjml_filename).open('rb') as mjml_fp:
result = mjml_to_html(mjml_fp)

assert not result.errors
actual_html = result.html
assert_same_html(expected_html, actual_html, verbose=True)


def _patch_nose1_result(test):
# nose's TextTestResult does not support "expected failures" but I still
# like that test runner. Just treat an expected failure like a skipped test.
result = test._outcome.result
if not hasattr(result, 'addExpectedFailure'):
result.addExpectedFailure = result.addSkip
if not hasattr(result, 'addUnexpectedSuccess'):
def _addUnexpectedSuccess(test):
error = (AssertionError, AssertionError('unexpected success'), None)
return result.addFailure(test, error)
result.addUnexpectedSuccess = _addUnexpectedSuccess
# currently there are no tests which are expected to fail
@pytest.mark.parametrize('test_id', [])
@pytest.mark.xfail
def test_missing_functionality(test_id):
mjml_filename = f'{test_id}.mjml'
html_filename = f'{test_id}-expected.html'
with (TESTDATA_DIR / html_filename).open('rb') as html_fp:
expected_html = html_fp.read()

with (TESTDATA_DIR / mjml_filename).open('rb') as mjml_fp:
result = mjml_to_html(mjml_fp)

assert not result.errors
actual_html = result.html
assert_same_html(expected_html, actual_html, verbose=True)
Loading