Пример #1
0
def export():
    if request.json is None:
        abort(400)

    exporter_config = request.json['exporterConfig']

    entity_decorators = {}
    block_map = dict(BLOCK_MAP, **exporter_config.get('block_map', {}))
    style_map = dict(STYLE_MAP, **exporter_config.get('style_map', {}))

    entity_decorators[ENTITY_TYPES.FALLBACK] = import_decorator(
        'missing_inline')
    block_map[BLOCK_TYPES.FALLBACK] = import_decorator('missing_block')
    style_map[INLINE_STYLES.FALLBACK] = import_decorator('missing_inline')

    for type_, value in exporter_config.get('entity_decorators',
                                            {}).iteritems():
        entity_decorators[type_] = import_decorator(value)

    exporter = HTML({
        'entity_decorators': entity_decorators,
        'block_map': block_map,
        'style_map': style_map,
    })

    html = exporter.render(request.json['contentState'])

    return json.dumps({
        'html': html,
        'prettified': prettify(html),
    })
Пример #2
0
def export():
    if request.json is None:
        abort(400)

    exporter_config = request.json["exporterConfig"]

    entity_decorators = {}
    block_map = dict(BLOCK_MAP, **exporter_config.get("block_map", {}))
    style_map = dict(STYLE_MAP, **exporter_config.get("style_map", {}))

    entity_decorators[ENTITY_TYPES.FALLBACK] = import_decorator(
        "missing_inline"
    )
    block_map[BLOCK_TYPES.FALLBACK] = import_decorator("missing_block")
    style_map[INLINE_STYLES.FALLBACK] = import_decorator("missing_inline")

    for type_, value in exporter_config.get("entity_decorators", {}).items():
        entity_decorators[type_] = import_decorator(value)

    exporter = HTML(
        {
            "entity_decorators": entity_decorators,
            "block_map": block_map,
            "style_map": style_map,
        }
    )

    html = exporter.render(request.json["contentState"])
    markdown = render_markdown(request.json["contentState"])

    return json.dumps(
        {"html": html, "markdown": markdown, "prettified": prettify(html)}
    )
Пример #3
0
 def __init__(self, content_editor):
     self.content_editor = content_editor
     # XXX: we need one exporter per DraftJSHTMLExporter because
     #      self.render_media needs to access "entityMap" from local
     #      instance. If MEDIA rendering changes in the future, exporter
     #      should be global for all DraftJSHTMLExporter instances
     self.exporter = HTML({
         "engine":
         DOM.LXML,
         "style_map":
         dict(
             STYLE_MAP,
             **{
                 INLINE_STYLES.BOLD: "b",
                 INLINE_STYLES.ITALIC: "i",
                 INLINE_STYLES.FALLBACK: self.style_fallback,
             },
         ),
         "entity_decorators": {
             ENTITY_TYPES.LINK:
             self.render_link,
             ENTITY_TYPES.HORIZONTAL_RULE:
             lambda props: DOM.create_element("hr"),
             ENTITY_TYPES.EMBED:
             self.render_embed,
             MEDIA:
             self.render_media,
             ANNOTATION:
             self.render_annotation,
             TABLE:
             self.render_table,
         },
     })
Пример #4
0
    def __init__(self, features=None):
        self.features = features
        self.html_to_contentstate_handler = HtmlToContentStateHandler(features)

        exporter_config = {
            'block_map': {
                'unstyled': 'p',
                'atomic': render_children,
                'fallback': block_fallback,
            },
            'style_map': {},
            'entity_decorators': {
                'FALLBACK': entity_fallback,
            },
            'composite_decorators': [
                {
                    'strategy': re.compile(r'\n'),
                    'component': br,
                },
            ],
            'engine': DOM.STRING,
        }

        for feature in self.features:
            rule = feature_registry.get_converter_rule('contentstate', feature)
            if rule is not None:
                feature_config = rule['to_database_format']
                exporter_config['block_map'].update(feature_config.get('block_map', {}))
                exporter_config['style_map'].update(feature_config.get('style_map', {}))
                exporter_config['entity_decorators'].update(feature_config.get('entity_decorators', {}))

        self.exporter = HTMLExporter(exporter_config)
    def __init__(self, features=None):
        self.features = features
        self.html_to_contentstate_handler = HtmlToContentStateHandler(features)

        exporter_config = {
            "block_map": {
                "unstyled": "p",
                "atomic": render_children,
                "fallback": block_fallback,
            },
            "style_map": {},
            "entity_decorators": {"FALLBACK": entity_fallback},
            "composite_decorators": [{"strategy": re.compile(r"\n"), "component": br}],
            "engine": DOM.STRING,
        }

        for feature in self.features:
            rule = feature_registry.get_converter_rule("contentstate", feature)
            if rule is not None:
                feature_config = rule["to_database_format"]
                exporter_config["block_map"].update(feature_config.get("block_map", {}))
                exporter_config["style_map"].update(feature_config.get("style_map", {}))
                exporter_config["entity_decorators"].update(
                    feature_config.get("entity_decorators", {})
                )

        self.exporter = HTMLExporter(exporter_config)
Пример #6
0
 def test_render_with_element_options(self):
     self.assertEqual(
         HTML({
             'block_map':
             dict(
                 BLOCK_MAP, **{
                     BLOCK_TYPES.HEADER_TWO: {
                         'element':
                         ['h2', {
                             'className': 'c-amazing-heading'
                         }],
                     },
                 })
         }).render({
             'entityMap': {},
             'blocks': [
                 {
                     'key': 'dem1p',
                     'text': 'item1',
                     'type': 'header-two',
                     'depth': 0,
                     'inlineStyleRanges': [],
                     'entityRanges': []
                 },
             ],
         }), '<h2 class="c-amazing-heading">item1</h2>')
Пример #7
0
 def test_render_with_unknown_attribute(self):
     self.assertEqual(
         HTML({
             'block_map':
             dict(
                 BLOCK_MAP, **{
                     BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                         'element': 'li',
                         'wrapper': ['ul', {
                             '*ngFor': 'test'
                         }],
                     },
                 })
         }).render({
             'entityMap': {},
             'blocks': [
                 {
                     'key': 'dem1p',
                     'text': 'item1',
                     'type': 'unordered-list-item',
                     'depth': 0,
                     'inlineStyleRanges': [],
                     'entityRanges': []
                 },
             ],
         }), '<ul *ngfor="test"><li>item1</li></ul>')
Пример #8
0
 def test_render_with_boolean_attribute_false(self):
     self.assertEqual(
         HTML({
             'block_map':
             dict(
                 BLOCK_MAP, **{
                     BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                         'element': 'li',
                         'wrapper': ['ul', {
                             'disabled': False
                         }],
                     },
                 }),
         }).render({
             'entityMap': {},
             'blocks': [
                 {
                     'key': 'dem1p',
                     'text': 'item1',
                     'type': 'unordered-list-item',
                     'depth': 0,
                     'inlineStyleRanges': [],
                     'entityRanges': []
                 },
             ]
         }), '<ul disabled="False"><li>item1</li></ul>')
Пример #9
0
    def __init__(self, features=None):
        self.features = features
        self.html_to_contentstate_handler = HtmlToContentStateHandler(features)

        exporter_config = {
            'block_map': {
                'unstyled': 'p',
                'atomic': render_children,
                'fallback': block_fallback,
            },
            'style_map': {},
            'entity_decorators': {
                'FALLBACK': entity_fallback,
            },
            'composite_decorators': [
                {
                    'strategy': re.compile(r'\n'),
                    'component': br,
                },
            ],
            'engine': DOM.STRING,
        }

        for feature in self.features:
            rule = feature_registry.get_converter_rule('contentstate', feature)
            if rule is not None:
                feature_config = rule['to_database_format']
                exporter_config['block_map'].update(feature_config.get('block_map', {}))
                exporter_config['style_map'].update(feature_config.get('style_map', {}))
                exporter_config['entity_decorators'].update(feature_config.get('entity_decorators', {}))

        self.exporter = HTMLExporter(exporter_config)
Пример #10
0
 def test_render_with_default_block_map(self):
     self.assertEqual(HTML({
         'style_map': {
             INLINE_STYLES.ITALIC: {'element': 'em'},
             INLINE_STYLES.BOLD: {'element': 'strong'},
             'HIGHLIGHT': {'element': 'strong', 'props': {'style': {'textDecoration': 'underline'}}},
         },
     }).render({
         'entityMap': {},
         'blocks': [
             {
                 'key': 'dem5p',
                 'text': 'some paragraph text',
                 'type': 'unstyled',
                 'depth': 0,
                 'inlineStyleRanges': [
                     {
                         'offset': 0,
                         'length': 4,
                         'style': 'ITALIC'
                     }
                 ],
                 'entityRanges': []
             }
         ]
     }), '<p><em>some</em> paragraph text</p>')
Пример #11
0
 def test_render_with_default_style_map(self):
     self.assertEqual(HTML({
         'block_map': dict(BLOCK_MAP, **{
             BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                 'element': 'li',
                 'wrapper': 'ul',
                 'wrapper_props': {'class': 'steps'},
             },
         })
     }).render({
         'entityMap': {},
         'blocks': [
             {
                 'key': 'dem5p',
                 'text': 'some paragraph text',
                 'type': 'unstyled',
                 'depth': 0,
                 'inlineStyleRanges': [
                     {
                         'offset': 0,
                         'length': 4,
                         'style': 'ITALIC'
                     }
                 ],
                 'entityRanges': []
             }
         ]
     }), '<p><em>some</em> paragraph text</p>')
Пример #12
0
 def test_render_with_none_return_value(self):
     self.assertEqual(HTML({
         'block_map': dict(BLOCK_MAP, **{
             BLOCK_TYPES.UNSTYLED: lambda props: None,
         }),
     }).render({
         'entityMap': {},
         'blocks': [
             {
                 'key': 'dem12p',
                 'text': 'header',
                 'type': 'header-one',
                 'depth': 0,
                 'inlineStyleRanges': [],
                 'entityRanges': []
             },
             {
                 'key': 'dem1p',
                 'text': 'paragraph',
                 'type': 'unstyled',
                 'depth': 0,
                 'inlineStyleRanges': [],
                 'entityRanges': []
             },
         ],
     }), '<h1>header</h1>')
Пример #13
0
    def do_POST(self):
        try:
            content_length = int(self.headers["Content-Length"])
            post_data = self.rfile.read(content_length)
            request_json = json.loads(post_data)
        except Exception:
            self.send_response(400)
            return

        exporter_config = request_json["exporterConfig"]

        entity_decorators = {}
        block_map = dict(BLOCK_MAP, **exporter_config.get("block_map", {}))
        style_map = dict(STYLE_MAP, **exporter_config.get("style_map", {}))

        entity_decorators[ENTITY_TYPES.FALLBACK] = missing_inline
        block_map[BLOCK_TYPES.FALLBACK] = missing_block
        style_map[INLINE_STYLES.FALLBACK] = missing_inline

        for type_, value in exporter_config.get("entity_decorators",
                                                {}).items():
            entity_decorators[type_] = import_decorator(value)

        exporter = HTML({
            "entity_decorators": entity_decorators,
            "block_map": block_map,
            "style_map": style_map,
        })

        html = exporter.render(request_json["contentState"])
        markdown = render_markdown(request_json["contentState"])

        ret = json.dumps({
            "html": html,
            "markdown": markdown,
            "prettified": prettify(html),
            "version": __version__,
        })

        self.send_response(200)
        self.send_header("Content-type", "application/json; charset=utf-8")
        self.end_headers()
        self.wfile.write(ret.encode("utf8"))
        return
Пример #14
0
class ContentstateConverter:
    def __init__(self, features=None):
        self.features = features
        self.html_to_contentstate_handler = HtmlToContentStateHandler(features)

        exporter_config = {
            "block_map": {
                "unstyled": persist_key_for_block("p"),
                "atomic": render_children,
                "fallback": block_fallback,
            },
            "style_map": {
                "FALLBACK": style_fallback,
            },
            "entity_decorators": {
                "FALLBACK": entity_fallback,
            },
            "composite_decorators": [
                {
                    "strategy": re.compile(r"\n"),
                    "component": br,
                },
            ],
            "engine": DOM.STRING,
        }

        for feature in self.features:
            rule = feature_registry.get_converter_rule("contentstate", feature)
            if rule is not None:
                feature_config = rule["to_database_format"]
                exporter_config["block_map"].update(
                    {
                        block_type: persist_key_for_block(config)
                        for block_type, config in feature_config.get(
                            "block_map", {}
                        ).items()
                    }
                )
                exporter_config["style_map"].update(feature_config.get("style_map", {}))
                exporter_config["entity_decorators"].update(
                    feature_config.get("entity_decorators", {})
                )

        self.exporter = HTMLExporter(exporter_config)

    def from_database_format(self, html):
        self.html_to_contentstate_handler.reset()
        self.html_to_contentstate_handler.feed(html)
        self.html_to_contentstate_handler.close()

        return self.html_to_contentstate_handler.contentstate.as_json(
            indent=4, separators=(",", ": ")
        )

    def to_database_format(self, contentstate_json):
        return self.exporter.render(json.loads(contentstate_json))
Пример #15
0
class ContentstateConverter():
    def __init__(self, features=None):
        self.features = features
        self.html_to_contentstate_handler = HtmlToContentStateHandler(features)

        exporter_config = {
            'block_map': {
                'unstyled': persist_key_for_block('p'),
                'atomic': render_children,
                'fallback': block_fallback,
            },
            'style_map': {
                'FALLBACK': style_fallback,
            },
            'entity_decorators': {
                'FALLBACK': entity_fallback,
            },
            'composite_decorators': [
                {
                    'strategy': re.compile(r'\n'),
                    'component': br,
                },
            ],
            'engine':
            DOM.STRING,
        }

        for feature in self.features:
            rule = feature_registry.get_converter_rule('contentstate', feature)
            if rule is not None:
                feature_config = rule['to_database_format']
                exporter_config['block_map'].update({
                    block_type: persist_key_for_block(config)
                    for block_type, config in feature_config.get(
                        'block_map', {}).items()
                })
                exporter_config['style_map'].update(
                    feature_config.get('style_map', {}))
                exporter_config['entity_decorators'].update(
                    feature_config.get('entity_decorators', {}))

        self.exporter = HTMLExporter(exporter_config)

    def from_database_format(self, html):
        self.html_to_contentstate_handler.reset()
        self.html_to_contentstate_handler.feed(html)
        self.html_to_contentstate_handler.close()

        return self.html_to_contentstate_handler.contentstate.as_json(
            indent=4, separators=(',', ': '))

    def to_database_format(self, contentstate_json):
        return self.exporter.render(json.loads(contentstate_json))
Пример #16
0
class ContentstateConverter():
    def __init__(self, features=None):
        self.features = features
        self.html_to_contentstate_handler = HtmlToContentStateHandler(features)

        exporter_config = {
            'block_map': {
                'unstyled': 'p',
                'atomic': render_children,
                'fallback': block_fallback,
            },
            'style_map': {},
            'entity_decorators': {
                'FALLBACK': entity_fallback,
            },
            'composite_decorators': [
                {
                    'strategy': re.compile(r'\n'),
                    'component': br,
                },
            ],
            'engine':
            DOM.STRING,
        }

        for feature in self.features:
            rule = feature_registry.get_converter_rule('contentstate', feature)
            if rule is not None:
                feature_config = rule['to_database_format']
                exporter_config['block_map'].update(
                    feature_config.get('block_map', {}))
                exporter_config['style_map'].update(
                    feature_config.get('style_map', {}))
                exporter_config['entity_decorators'].update(
                    feature_config.get('entity_decorators', {}))

        self.exporter = HTMLExporter(exporter_config)

    def from_database_format(self, html):
        self.html_to_contentstate_handler.reset()
        self.html_to_contentstate_handler.feed(html)

        if not self.html_to_contentstate_handler.contentstate.blocks:
            # Draftail does not accept an empty block list as valid, but does accept 'null' as meaning "no content"
            return 'null'

        return self.html_to_contentstate_handler.contentstate.as_json(
            indent=4, separators=(',', ': '))

    def to_database_format(self, contentstate_json):
        return self.exporter.render(json.loads(contentstate_json))
Пример #17
0
class DraftText(RichText):
    def __init__(self, value, **kwargs):
        super(DraftText, self).__init__(value or '{}', **kwargs)
        self.exporter = HTML(get_exporter_config())

    def get_json(self):
        return self.source

    @cached_property
    def _html(self):
        return self.exporter.render(json.loads(self.source))

    def __html__(self):
        return self._html

    def __eq__(self, other):
        return hasattr(other, '__html__') and self.__html__() == other.__html__()
Пример #18
0
class ContentstateConverter():
    def __init__(self, features=None):
        self.features = features
        self.html_to_contentstate_handler = HtmlToContentStateHandler(features)

        exporter_config = {
            'block_map': {
                'unstyled': 'p',
                'atomic': render_children,
                'fallback': block_fallback,
            },
            'style_map': {},
            'entity_decorators': {
                'FALLBACK': entity_fallback,
            },
            'composite_decorators': [
                {
                    'strategy': re.compile(r'\n'),
                    'component': br,
                },
            ],
            'engine': DOM.STRING,
        }

        for feature in self.features:
            rule = feature_registry.get_converter_rule('contentstate', feature)
            if rule is not None:
                feature_config = rule['to_database_format']
                exporter_config['block_map'].update(feature_config.get('block_map', {}))
                exporter_config['style_map'].update(feature_config.get('style_map', {}))
                exporter_config['entity_decorators'].update(feature_config.get('entity_decorators', {}))

        self.exporter = HTMLExporter(exporter_config)

    def from_database_format(self, html):
        self.html_to_contentstate_handler.reset()
        self.html_to_contentstate_handler.feed(html)

        if not self.html_to_contentstate_handler.contentstate.blocks:
            # Draftail does not accept an empty block list as valid, but does accept 'null' as meaning "no content"
            return 'null'

        return self.html_to_contentstate_handler.contentstate.as_json(indent=4, separators=(',', ': '))

    def to_database_format(self, contentstate_json):
        return self.exporter.render(json.loads(contentstate_json))
Пример #19
0
 def test_render_with_default_config(self):
     self.assertEqual(HTML().render({
         'entityMap': {},
         'blocks': [
             {
                 'key': 'dem5p',
                 'text': 'some paragraph text',
                 'type': 'unstyled',
                 'depth': 0,
                 'inlineStyleRanges': [
                     {
                         'offset': 0,
                         'length': 4,
                         'style': 'ITALIC'
                     }
                 ],
                 'entityRanges': []
             }
         ]
     }), '<p><em>some</em> paragraph text</p>')
Пример #20
0
 def test_render_with_many_line_breaks(self):
     self.assertEqual(
         HTML().render({
             'entityMap': {},
             'blocks': [{
                 'key':
                 'dem5p',
                 'text':
                 '\nsome paragraph text\nsplit in half\n',
                 'type':
                 'unstyled',
                 'depth':
                 0,
                 'inlineStyleRanges': [{
                     'offset': 1,
                     'length': 4,
                     'style': 'ITALIC'
                 }],
                 'entityRanges': []
             }]
         }),
         '<p><br/><em>some</em> paragraph text<br/>split in half<br/></p>')
Пример #21
0
class TestHTML(unittest.TestCase):
    def setUp(self):
        self.exporter = HTML(config)

    def test_init(self):
        self.assertIsInstance(self.exporter, HTML)

    def test_render_block_exists(self):
        self.assertTrue('render_block' in dir(self.exporter))

    def test_build_style_commands_empty(self):
        self.assertEqual(
            str(
                self.exporter.build_style_commands({
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                })), str([]))

    def test_build_style_commands_single(self):
        self.assertEqual(
            str(
                self.exporter.build_style_commands({
                    'key':
                    '5s7g9',
                    'text':
                    'Header',
                    'type':
                    'header-one',
                    'depth':
                    0,
                    'inlineStyleRanges': [{
                        'offset': 0,
                        'length': 4,
                        'style': 'ITALIC'
                    }],
                    'entityRanges': []
                })),
            str([
                Command('start_inline_style', 0, 'ITALIC'),
                Command('stop_inline_style', 4, 'ITALIC'),
            ]))

    def test_build_style_commands_multiple(self):
        self.assertEqual(
            str(
                self.exporter.build_style_commands({
                    'key':
                    '5s7g9',
                    'text':
                    'Header',
                    'type':
                    'header-one',
                    'depth':
                    0,
                    'inlineStyleRanges': [{
                        'offset': 0,
                        'length': 4,
                        'style': 'ITALIC'
                    }, {
                        'offset': 9,
                        'length': 3,
                        'style': 'BOLD'
                    }],
                    'entityRanges': []
                })),
            str([
                Command('start_inline_style', 0, 'ITALIC'),
                Command('stop_inline_style', 4, 'ITALIC'),
                Command('start_inline_style', 9, 'BOLD'),
                Command('stop_inline_style', 12, 'BOLD'),
            ]))

    def test_build_entity_commands_empty(self):
        self.assertEqual(
            str(
                self.exporter.build_entity_commands({
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                })), str([]))

    def test_build_entity_commands_single(self):
        self.assertEqual(
            str(
                self.exporter.build_entity_commands({
                    'key':
                    'dem5p',
                    'text':
                    'some paragraph text',
                    'type':
                    'unstyled',
                    'depth':
                    0,
                    'inlineStyleRanges': [],
                    'entityRanges': [{
                        'offset': 5,
                        'length': 9,
                        'key': 0
                    }]
                })),
            str([
                Command('start_entity', 5, 0),
                Command('stop_entity', 14, 0),
            ]))

    def test_build_entity_commands_multiple(self):
        self.assertEqual(
            str(
                self.exporter.build_entity_commands({
                    'key':
                    'dem5p',
                    'text':
                    'some paragraph text',
                    'type':
                    'unstyled',
                    'depth':
                    0,
                    'inlineStyleRanges': [],
                    'entityRanges': [{
                        'offset': 5,
                        'length': 9,
                        'key': 0
                    }, {
                        'offset': 0,
                        'length': 4,
                        'key': 1
                    }]
                })),
            str([
                Command('start_entity', 5, 0),
                Command('stop_entity', 14, 0),
                Command('start_entity', 0, 1),
                Command('stop_entity', 4, 1),
            ]))

    def test_build_commands_empty(self):
        self.assertEqual(
            str(
                self.exporter.build_commands({
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                })), str([
                    Command('start_text', 0),
                    Command('stop_text', 19),
                ]))

    def test_build_commands_multiple(self):
        self.assertEqual(
            str(
                self.exporter.build_commands({
                    'key':
                    'dem5p',
                    'text':
                    'some paragraph text',
                    'type':
                    'unstyled',
                    'depth':
                    0,
                    'inlineStyleRanges': [{
                        'offset': 0,
                        'length': 4,
                        'style': 'ITALIC'
                    }, {
                        'offset': 9,
                        'length': 3,
                        'style': 'BOLD'
                    }],
                    'entityRanges': [{
                        'offset': 5,
                        'length': 9,
                        'key': 0
                    }, {
                        'offset': 0,
                        'length': 4,
                        'key': 1
                    }]
                })),
            str([
                Command('start_text', 0),
                Command('stop_text', 19),
                Command('start_inline_style', 0, 'ITALIC'),
                Command('stop_inline_style', 4, 'ITALIC'),
                Command('start_inline_style', 9, 'BOLD'),
                Command('stop_inline_style', 12, 'BOLD'),
                Command('start_entity', 5, 0),
                Command('stop_entity', 14, 0),
                Command('start_entity', 0, 1),
                Command('stop_entity', 4, 1),
            ]))

    def test_build_command_groups_empty(self):
        self.assertEqual(
            str(
                self.exporter.build_command_groups({
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                })),
            str([
                ('some paragraph text', [Command('start_text', 0)]),
                ('', [Command('stop_text', 19)]),
            ]))

    def test_build_command_groups_multiple(self):
        self.assertEqual(
            str(
                self.exporter.build_command_groups({
                    'key':
                    'dem5p',
                    'text':
                    'some paragraph text',
                    'type':
                    'unstyled',
                    'depth':
                    0,
                    'inlineStyleRanges': [{
                        'offset': 0,
                        'length': 4,
                        'style': 'ITALIC'
                    }, {
                        'offset': 9,
                        'length': 3,
                        'style': 'BOLD'
                    }],
                    'entityRanges': [{
                        'offset': 5,
                        'length': 9,
                        'key': 0
                    }, {
                        'offset': 0,
                        'length': 4,
                        'key': 1
                    }]
                })),
            str([
                ('some', [
                    Command('start_text', 0),
                    Command('start_inline_style', 0, 'ITALIC'),
                    Command('start_entity', 0, 1),
                ]),
                (' ', [
                    Command('stop_inline_style', 4, 'ITALIC'),
                    Command('stop_entity', 4, 1),
                ]),
                ('para', [
                    Command('start_entity', 5, 0),
                ]),
                ('gra', [
                    Command('start_inline_style', 9, 'BOLD'),
                ]),
                ('ph', [
                    Command('stop_inline_style', 12, 'BOLD'),
                ]),
                (' text', [
                    Command('stop_entity', 14, 0),
                ]),
                ('', [Command('stop_text', 19)]),
            ]))

    def test_render(self):
        self.assertEqual(
            self.exporter.render({
                'entityMap': {},
                'blocks': [
                    {
                        'key': '5s7g9',
                        'text': 'Header',
                        'type': 'header-one',
                        'depth': 0,
                        'inlineStyleRanges': [],
                        'entityRanges': []
                    },
                ]
            }), '<h1>Header</h1>')

    def test_render_twice(self):
        """Asserts no state is kept during renders."""
        self.assertEqual(
            self.exporter.render({
                'entityMap': {},
                'blocks': [
                    {
                        'key': '5s7g9',
                        'text': 'Header',
                        'type': 'header-one',
                        'depth': 0,
                        'inlineStyleRanges': [],
                        'entityRanges': []
                    },
                ]
            }), '<h1>Header</h1>')
        self.assertEqual(
            self.exporter.render({
                'entityMap': {},
                'blocks': [
                    {
                        'key': '5s7g9',
                        'text': 'Header',
                        'type': 'header-one',
                        'depth': 0,
                        'inlineStyleRanges': [],
                        'entityRanges': []
                    },
                ]
            }), '<h1>Header</h1>')
Пример #22
0
        ENTITY_TYPES.LINK: link,
        ENTITY_TYPES.DOCUMENT: document,
        ENTITY_TYPES.HORIZONTAL_RULE: lambda props: DOM.create_element('hr'),
        ENTITY_TYPES.EMBED: None,
        ENTITY_TYPES.FALLBACK: entity_fallback,
    },
    'composite_decorators': [
        {
            'strategy': re.compile(r'\n'),
            'component': br,
        }
    ],
    'engine': DOM.STRING,
}

exporter = HTML(config)

content_states = get_content_sample()

BENCHMARK_RUNS = int(os.environ.get('BENCHMARK_RUNS', 1))

print('Exporting %s ContentStates %s times' % (len(content_states), BENCHMARK_RUNS))

pr = cProfile.Profile()
pr.enable()

for i in range(0, BENCHMARK_RUNS):
    for content_state in content_states:
        exporter.render(content_state)

pr.disable()
Пример #23
0
def memory_consumption_run():
    exporter = HTML(config)

    for content_state in content_states:
        exporter.render(content_state)
Пример #24
0
class TestHTML(unittest.TestCase):
    def setUp(self):
        self.exporter = HTML(config)

    def test_init(self):
        self.assertIsInstance(self.exporter, HTML)

    def test_init_dom_engine_default(self):
        HTML()
        self.assertEqual(DOM.dom, DOMString)

    def test_render_block_exists(self):
        self.assertTrue('render_block' in dir(self.exporter))

    def test_build_style_commands_empty(self):
        self.assertEqual(str(self.exporter.build_style_commands({
            'key': '5s7g9',
            'text': 'Header',
            'type': 'header-one',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': []
        })), str([]))

    def test_build_style_commands_single(self):
        self.assertEqual(str(self.exporter.build_style_commands({
            'key': '5s7g9',
            'text': 'Header',
            'type': 'header-one',
            'depth': 0,
            'inlineStyleRanges': [
                {
                    'offset': 0,
                    'length': 4,
                    'style': 'ITALIC'
                }
            ],
            'entityRanges': []
        })), str([
            Command('start_inline_style', 0, 'ITALIC'),
            Command('stop_inline_style', 4, 'ITALIC'),
        ]))

    def test_build_style_commands_multiple(self):
        self.assertEqual(str(self.exporter.build_style_commands({
            'key': '5s7g9',
            'text': 'Header',
            'type': 'header-one',
            'depth': 0,
            'inlineStyleRanges': [
                {
                    'offset': 0,
                    'length': 4,
                    'style': 'ITALIC'
                },
                {
                    'offset': 9,
                    'length': 3,
                    'style': 'BOLD'
                }
            ],
            'entityRanges': []
        })), str([
            Command('start_inline_style', 0, 'ITALIC'),
            Command('stop_inline_style', 4, 'ITALIC'),
            Command('start_inline_style', 9, 'BOLD'),
            Command('stop_inline_style', 12, 'BOLD'),
        ]))

    def test_build_entity_commands_empty(self):
        self.assertEqual(str(self.exporter.build_entity_commands({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': []
        })), str([]))

    def test_build_entity_commands_single(self):
        self.assertEqual(str(self.exporter.build_entity_commands({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': [
                {
                    'offset': 5,
                    'length': 9,
                    'key': 0
                }
            ]
        })), str([
            Command('start_entity', 5, 0),
            Command('stop_entity', 14, 0),
        ]))

    def test_build_entity_commands_multiple(self):
        self.assertEqual(str(self.exporter.build_entity_commands({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': [
                {
                    'offset': 5,
                    'length': 9,
                    'key': 0
                },
                {
                    'offset': 0,
                    'length': 4,
                    'key': 1
                }
            ]
        })), str([
            Command('start_entity', 5, 0),
            Command('stop_entity', 14, 0),
            Command('start_entity', 0, 1),
            Command('stop_entity', 4, 1),
        ]))

    def test_build_commands_empty(self):
        self.assertEqual(str(self.exporter.build_commands({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': []
        })), str([
            Command('start_text', 0),
            Command('stop_text', 19),
        ]))

    def test_build_commands_multiple(self):
        self.assertEqual(str(self.exporter.build_commands({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [
                {
                    'offset': 0,
                    'length': 4,
                    'style': 'ITALIC'
                },
                {
                    'offset': 9,
                    'length': 3,
                    'style': 'BOLD'
                }
            ],
            'entityRanges': [
                {
                    'offset': 5,
                    'length': 9,
                    'key': 0
                },
                {
                    'offset': 0,
                    'length': 4,
                    'key': 1
                }
            ]
        })), str([
            Command('start_text', 0),
            Command('stop_text', 19),
            Command('start_inline_style', 0, 'ITALIC'),
            Command('stop_inline_style', 4, 'ITALIC'),
            Command('start_inline_style', 9, 'BOLD'),
            Command('stop_inline_style', 12, 'BOLD'),
            Command('start_entity', 5, 0),
            Command('stop_entity', 14, 0),
            Command('start_entity', 0, 1),
            Command('stop_entity', 4, 1),
        ]))

    def test_build_command_groups_empty(self):
        self.assertEqual(str(self.exporter.build_command_groups({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': []
        })), str([
            ('some paragraph text', [
                Command('start_text', 0),
            ]),
            ('', [
                Command('stop_text', 19),
            ])
        ]))

    def test_build_command_groups_multiple(self):
        self.assertEqual(str(self.exporter.build_command_groups({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [
                {
                    'offset': 0,
                    'length': 4,
                    'style': 'ITALIC'
                },
                {
                    'offset': 9,
                    'length': 3,
                    'style': 'BOLD'
                }
            ],
            'entityRanges': [
                {
                    'offset': 5,
                    'length': 9,
                    'key': 0
                },
                {
                    'offset': 0,
                    'length': 4,
                    'key': 1
                }
            ]
        })), str([
            ('some', [
                Command('start_text', 0),
                Command('start_inline_style', 0, 'ITALIC'),
                Command('start_entity', 0, 1),
            ]),
            (' ', [
                Command('stop_inline_style', 4, 'ITALIC'),
                Command('stop_entity', 4, 1),
            ]),
            ('para', [
                Command('start_entity', 5, 0),
            ]),
            ('gra', [
                Command('start_inline_style', 9, 'BOLD'),
            ]),
            ('ph', [
                Command('stop_inline_style', 12, 'BOLD'),
            ]),
            (' text', [
                Command('stop_entity', 14, 0),
            ]),
            ('', [
                Command('stop_text', 19),
            ])
        ]))

    def test_render(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ]
        }), '<h1>Header</h1>')

    def test_render_empty(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
            ]
        }), '')

    def test_render_none(self):
        self.assertEqual(self.exporter.render(None), '')

    def test_render_twice(self):
        """Asserts no state is kept during renders."""
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ]
        }), '<h1>Header</h1>')
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ]
        }), '<h1>Header</h1>')
Пример #25
0
class TestOutput(unittest.TestCase):
    """
    Test cases related to specific features of the HTML builder.
    """

    def setUp(self):
        self.maxDiff = None
        self.exporter = HTML(config)

    def test_render_empty(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': []
        }), '')

    def test_render_with_different_blocks(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<h1>Header</h1><p>some paragraph text</p>')

    def test_render_with_unicode(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'Emojis! 🍺',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<p>Emojis! \U0001f37a</p>')

    def test_render_with_inline_styles(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text</p>')

    def test_render_with_multiple_inline_styles(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                }
            },
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 2,
                            'style': 'BOLD'
                        }
                    ],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'HIGHLIGHT'
                        }
                    ],
                    'entityRanges': [
                        {
                            'offset': 5,
                            'length': 9,
                            'key': 0
                        }
                    ]
                }
            ]
        }), '<h1><strong>He</strong>ader</h1><p><strong style="text-decoration: underline;">some</strong> <a href="http://example.com">paragraph</a> text</p>')

    def test_render_with_entities(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                }
            },
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 5,
                            'length': 9,
                            'key': 0
                        }
                    ]
                }
            ]
        }), '<p>some <a href="http://example.com">paragraph</a> text</p>')

    def test_render_with_entities_crossing_raises(self):
        with self.assertRaises(EntityException):
            self.exporter.render({
                'entityMap': {
                    '0': {
                        'type': 'LINK',
                        'mutability': 'MUTABLE',
                        'data': {
                            'url': 'http://example.com'
                        }
                    },
                    '1': {
                        'type': 'LINK',
                        'mutability': 'MUTABLE',
                        'data': {
                            'url': 'http://bar.example.com'
                        }
                    }
                },
                'blocks': [
                    {
                        'key': 'dem5p',
                        'text': 'some paragraph text',
                        'type': 'unstyled',
                        'depth': 0,
                        'inlineStyleRanges': [],
                        'entityRanges': [
                            {
                                'offset': 5,
                                'length': 9,
                                'key': 0
                            },
                            {
                                'offset': 2,
                                'length': 9,
                                'key': 1
                            }
                        ]
                    }
                ]
            })

    def test_render_with_styles_in_entities(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                },
                '1': {
                    'type': 'HORIZONTAL_RULE',
                    'mutability': 'IMMUTABLE',
                    'data': {},
                },
            },
            'blocks': [
                {
                    'key': 'f4gp0',
                    'text': 'test style object to style string).',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 5,
                            'length': 12,
                            'style': 'CODE',
                        },
                        {
                            'offset': 21,
                            'length': 12,
                            'style': 'CODE',
                        }
                    ],
                    'entityRanges': [
                        {
                            'offset': 5,
                            'length': 28,
                            'key': 0
                        },
                    ],
                    'data': {},
                }, {
                    'key': 'f4fp0',
                    'text': ' ',
                    'type': 'atomic',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [{
                        'offset': 0,
                        'length': 1,
                        'key': 1
                    }],
                    'data': {},
                }
            ]
        }), '<ul class="steps"><li>test <a href="http://example.com"><code>style object</code> to <code>style string</code></a>).</li></ul><hr/>')

    def test_render_with_wrapping(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'dem5p',
                    'text': 'item2',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<ul class="steps"><li>item1</li><li>item2</li></ul>')

    def test_render_with_number_attribute(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'length': 5},
                },
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<ul length="5"><li>item1</li></ul>')

    def test_render_with_boolean_attribute_true(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'data-test': True},
                },
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<ul data-test="true"><li>item1</li></ul>')

    def test_render_with_boolean_attribute_false(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'data-test': False},
                },
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ]
        }), '<ul data-test="false"><li>item1</li></ul>')

    def test_render_with_none_attribute(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'data-test': None},
                },
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<ul><li>item1</li></ul>')

    def test_render_with_unknown_attribute(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'*ngFor': 'test'},
                },
            })
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<ul *ngFor="test"><li>item1</li></ul>')

    def test_render_with_element_options(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.HEADER_TWO: {
                    'element': 'h2',
                    'props': {'class': 'c-amazing-heading'},
                },
            })
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'header-two',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<h2 class="c-amazing-heading">item1</h2>')

    def test_render_with_none_component(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNSTYLED: None,
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem12p',
                    'text': 'header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem1p',
                    'text': 'paragraph',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<h1>header</h1>')

    def test_render_with_none_return_value(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNSTYLED: lambda props: None,
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem12p',
                    'text': 'header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem1p',
                    'text': 'paragraph',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<h1>header</h1>')

    def test_render_with_entity(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {
                '2': {
                    'type': 'HORIZONTAL_RULE',
                    'mutability': 'IMMUTABLE',
                    'data': {},
                },
            },
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'item2',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': '672oo',
                    'text': ' ',
                    'type': 'atomic',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 1,
                            'key': 2,
                        },
                    ],
                },
            ]
        }), '<ul class="steps"><li>item1</li><li>item2</li></ul><hr/>')

    def test_render_with_wrapping_reset(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': '1',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': '2',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': '3',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': '4',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': '5',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<p>1</p><ul class="steps"><li>2</li></ul><p>3</p><ul class="steps"><li>4</li></ul><p>5</p>')

    def test_render_with_wrapping_reset_block_components(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': '1',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': '2',
                    'type': 'blockquote',
                    'depth': 0,
                    'data': {
                        'cite': '2'
                    },
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': '3',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': '4',
                    'type': 'blockquote',
                    'depth': 0,
                    'data': {
                        'cite': '4'
                    },
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': '5',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<p>1</p><div><blockquote cite="2">2</blockquote></div><p>3</p><div><blockquote cite="4">4</blockquote></div><p>5</p>')

    def test_render_with_unidirectional_nested_wrapping(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': 'Oops!',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': 'Does this support nesting?',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': 'Maybe?',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': 'Yep it does!',
                    'type': 'unordered-list-item',
                    'depth': 3,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '28umf',
                    'text': 'How many levels deep?',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'd81ns',
                    'text': 'Lots.',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'b0tsc',
                    'text': 'Ah.',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li>A list item<ul class="steps"><li>Oops!<ul class="steps"><li>Does this support nesting?</li><li>Maybe?<ul class="steps"><li>Yep it does!<ul class="steps"><li>How many levels deep?</li><li>Lots.</li><li>Ah.</li></ul></li></ul></li></ul></li></ul></li></ul>')

    def test_render_with_backtracking_nested_wrapping(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': 'Oops! (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': 'Does this support nesting? (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': 'Maybe? (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': 'Yep it does! (3)',
                    'type': 'unordered-list-item',
                    'depth': 3,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '28umf',
                    'text': 'How many levels deep? (4)',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c2gc4',
                    'text': 'Backtracking, two at once... (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1gcb',
                    'text': 'Uh oh (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c2gh4',
                    'text': 'Up, up, and away! (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1ghb',
                    'text': 'Arh! (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1gc9',
                    'text': 'Did this work? (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1gc9',
                    'text': 'Yes! (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li>A list item (0)<ul class="steps"><li>Oops! (1)<ul class="steps"><li>Does this support nesting? (2)</li><li>Maybe? (2)<ul class="steps"><li>Yep it does! (3)<ul class="steps"><li>How many levels deep? (4)</li></ul></li></ul></li><li>Backtracking, two at once... (2)</li></ul></li><li>Uh oh (1)<ul class="steps"><li>Up, up, and away! (2)</li></ul></li><li>Arh! (1)</li></ul></li><li>Did this work? (0)</li><li>Yes! (0)</li></ul>')

    def test_render_with_jumping_wrapping(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': 'Jumps (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': 'Back (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': 'Jumps again (3)',
                    'type': 'unordered-list-item',
                    'depth': 3,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': 'Back (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li>A list item (0)<ul class="steps"><li><ul class="steps"><li>Jumps (2)</li></ul></li></ul></li><li>Back (0)<ul class="steps"><li><ul class="steps"><li><ul class="steps"><li>Jumps again (3)</li></ul></li></ul></li><li>Back (1)</li></ul></li></ul>')

    def test_render_with_immediate_jumping(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '93agv',
                    'text': 'A list item (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li><ul class="steps"><li><ul class="steps"><li>A list item (2)</li></ul></li></ul></li><li>A list item (0)</li></ul>')

    def test_render_with_no_zero_depth(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '93agv',
                    'text': 'A list item (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li><ul class="steps"><li><ul class="steps"><li>A list item (2)</li><li>A list item (2)</li></ul></li></ul></li></ul>')

    def test_render_with_big_content(self):
        self.assertEqual(HTML({
            'entity_decorators': {
                'LINK': link
            },
            'block_map': {
                'header-two': {'element': 'h2'},
                'blockquote': {'element': 'blockquote'},
                'unordered-list-item': {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {},
                },
                'unstyled': {'element': 'p'}
            },
            'style_map': {
                'ITALIC': {'element': 'em'},
                'BOLD': {'element': 'strong'}
            }
        }).render({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                },
                '1': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'https://www.springload.co.nz/work/nz-festival/'
                    }
                }
            },
            'blocks': [
                {
                    'key': '6mgfh',
                    'text': 'User experience (UX) design',
                    'type': 'header-two',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 16,
                            'length': 4,
                            'style': 'BOLD'
                        }
                    ],
                    'entityRanges': []
                },
                {
                    'key': '5384u',
                    'text': 'Everyone at Springload applies the best principles of UX to their work.',
                    'type': 'blockquote',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'eelkd',
                    'text': 'The design decisions we make building tools and services for your customers are based on empathy for what your customers need.',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'b9grk',
                    'text': 'User research',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'a1tis',
                    'text': 'User testing and analysis',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 25,
                            'key': 0
                        }
                    ]
                },
                {
                    'key': 'adjdn',
                    'text': 'A/B testing',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': '62lio',
                    'text': 'Prototyping',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'fq3f',
                    'text': 'How we made it delightful and easy for people to find NZ Festival shows',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 71,
                            'key': 1
                        }
                    ]
                }
            ]
        }), '<h2>User experience <strong>(UX)</strong> design</h2><blockquote>Everyone at Springload applies the best principles of UX to their work.</blockquote><p>The design decisions we make building tools and services for your customers are based on empathy for what your customers need.</p><ul><li>User research</li><li><a href="http://example.com">User testing and analysis</a></li><li>A/B testing</li><li>Prototyping</li></ul><p><a href="https://www.springload.co.nz/work/nz-festival/">How we made it delightful and easy for people to find NZ Festival shows</a></p>')

    def test_render_with_default_block_map(self):
        self.assertEqual(HTML({
            'style_map': {
                INLINE_STYLES.ITALIC: {'element': 'em'},
                INLINE_STYLES.BOLD: {'element': 'strong'},
                'HIGHLIGHT': {'element': 'strong', 'props': {'style': {'textDecoration': 'underline'}}},
            },
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text</p>')

    def test_render_with_default_style_map(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'class': 'steps'},
                },
            })
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text</p>')

    def test_render_with_default_config(self):
        self.assertEqual(HTML().render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text</p>')

    def test_render_with_line_breaks(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text\nsplit in half',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text<br/>split in half</p>')

    def test_render_with_many_line_breaks(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': '\nsome paragraph text\nsplit in half\n',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 1,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><br/><em>some</em> paragraph text<br/>split in half<br/></p>')

    def test_render_with_entity_and_decorators(self):
        """
        The composite decorator should never render text in any entities.
        """
        self.assertEqual(self.exporter.render({
            'entityMap': {
                '1': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://amazon.us'
                    }
                }
            },
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'search http://a.us or https://yahoo.com or www.google.com for #github and #facebook',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 7,
                            'length': 11,
                            'key': 1
                        }
                    ],
                },
                {
                    'key': '34a12',
                    'text': '#check www.example.com',
                    'type': 'code-block',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ]
        }),
            '<p>search <a href="http://amazon.us">http://a.us</a> or '
            '<a href="https://yahoo.com">https://yahoo.com</a> or '
            '<a href="http://www.google.com">www.google.com</a> for '
            '<span class="hashtag">#github</span> and '
            '<span class="hashtag">#facebook</span></p>'
            '<pre><code>#check www.example.com</code></pre>')

    def test_render_with_multiple_decorators(self):
        """
        When multiple decorators match the same part of text,
        only the first one should perform the replacement.
        """
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'search http://www.google.com#world for the #world',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ]
        }),
            '<p>search <a href="http://www.google.com#world">'
            'http://www.google.com#world</a> for the '
            '<span class="hashtag">#world</span></p>')
Пример #26
0
class TestOutput(unittest.TestCase):
    """
    Test cases related to specific features of the HTML builder.
    """

    def setUp(self):
        self.maxDiff = None
        self.exporter = HTML(config)

    def test_render_empty(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': []
        }), '')

    def test_render_with_different_blocks(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<h1>Header</h1><p>some paragraph text</p>')

    def test_render_with_unicode(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'Emojis! 🍺',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<p>Emojis! \U0001f37a</p>')

    def test_render_with_inline_styles(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text</p>')

    def test_render_with_multiple_inline_styles(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                }
            },
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 2,
                            'style': 'BOLD'
                        }
                    ],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'HIGHLIGHT'
                        }
                    ],
                    'entityRanges': [
                        {
                            'offset': 5,
                            'length': 9,
                            'key': 0
                        }
                    ]
                }
            ]
        }), '<h1><strong>He</strong>ader</h1><p><strong style="text-decoration: underline;">some</strong> <a href="http://example.com">paragraph</a> text</p>')

    def test_render_with_entities(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                }
            },
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 5,
                            'length': 9,
                            'key': 0
                        }
                    ]
                }
            ]
        }), '<p>some <a href="http://example.com">paragraph</a> text</p>')

    def test_render_with_entities_crossing_raises(self):
        with self.assertRaises(EntityException):
            self.exporter.render({
                'entityMap': {
                    '0': {
                        'type': 'LINK',
                        'mutability': 'MUTABLE',
                        'data': {
                            'url': 'http://example.com'
                        }
                    },
                    '1': {
                        'type': 'LINK',
                        'mutability': 'MUTABLE',
                        'data': {
                            'url': 'http://bar.example.com'
                        }
                    }
                },
                'blocks': [
                    {
                        'key': 'dem5p',
                        'text': 'some paragraph text',
                        'type': 'unstyled',
                        'depth': 0,
                        'inlineStyleRanges': [],
                        'entityRanges': [
                            {
                                'offset': 5,
                                'length': 9,
                                'key': 0
                            },
                            {
                                'offset': 2,
                                'length': 9,
                                'key': 1
                            }
                        ]
                    }
                ]
            })

    def test_render_with_styles_in_entities(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                },
                '1': {
                    'type': 'HORIZONTAL_RULE',
                    'mutability': 'IMMUTABLE',
                    'data': {},
                },
            },
            'blocks': [
                {
                    'key': 'f4gp0',
                    'text': 'test style object to style string).',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 5,
                            'length': 12,
                            'style': 'CODE',
                        },
                        {
                            'offset': 21,
                            'length': 12,
                            'style': 'CODE',
                        }
                    ],
                    'entityRanges': [
                        {
                            'offset': 5,
                            'length': 28,
                            'key': 0
                        },
                    ],
                    'data': {},
                }, {
                    'key': 'f4fp0',
                    'text': ' ',
                    'type': 'atomic',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [{
                        'offset': 0,
                        'length': 1,
                        'key': 1
                    }],
                    'data': {},
                }
            ]
        }), '<ul class="steps"><li>test <a href="http://example.com"><code>style object</code> to <code>style string</code></a>).</li></ul><hr/>')

    def test_render_with_wrapping(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'dem5p',
                    'text': 'item2',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<ul class="steps"><li>item1</li><li>item2</li></ul>')

    def test_render_with_number_attribute(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'length': 5},
                },
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<ul length="5"><li>item1</li></ul>')

    def test_render_with_boolean_attribute_true(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'data-test': True},
                },
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<ul data-test="true"><li>item1</li></ul>')

    def test_render_with_boolean_attribute_false(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'data-test': False},
                },
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ]
        }), '<ul data-test="false"><li>item1</li></ul>')

    def test_render_with_none_attribute(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'data-test': None},
                },
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<ul><li>item1</li></ul>')

    def test_render_with_unknown_attribute(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'*ngFor': 'test'},
                },
            })
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<ul *ngFor="test"><li>item1</li></ul>')

    def test_render_with_element_options(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.HEADER_TWO: {
                    'element': 'h2',
                    'props': {'class': 'c-amazing-heading'},
                },
            })
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'header-two',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<h2 class="c-amazing-heading">item1</h2>')

    def test_render_with_none_component(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNSTYLED: None,
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem12p',
                    'text': 'header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem1p',
                    'text': 'paragraph',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<h1>header</h1>')

    def test_render_with_none_return_value(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNSTYLED: lambda props: None,
            }),
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem12p',
                    'text': 'header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem1p',
                    'text': 'paragraph',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
            ],
        }), '<h1>header</h1>')

    def test_render_with_entity(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {
                '2': {
                    'type': 'HORIZONTAL_RULE',
                    'mutability': 'IMMUTABLE',
                    'data': {},
                },
            },
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'item2',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': '672oo',
                    'text': ' ',
                    'type': 'atomic',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 1,
                            'key': 2,
                        },
                    ],
                },
            ]
        }), '<ul class="steps"><li>item1</li><li>item2</li></ul><hr/>')

    def test_render_with_wrapping_reset(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': '1',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': '2',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': '3',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': '4',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': '5',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<p>1</p><ul class="steps"><li>2</li></ul><p>3</p><ul class="steps"><li>4</li></ul><p>5</p>')

    def test_render_with_wrapping_reset_block_components(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': '1',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': '2',
                    'type': 'blockquote',
                    'depth': 0,
                    'data': {
                        'cite': '2'
                    },
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': '3',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': '4',
                    'type': 'blockquote',
                    'depth': 0,
                    'data': {
                        'cite': '4'
                    },
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': '5',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<p>1</p><div><blockquote cite="2">2</blockquote></div><p>3</p><div><blockquote cite="4">4</blockquote></div><p>5</p>')

    def test_render_with_unidirectional_nested_wrapping(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': 'Oops!',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': 'Does this support nesting?',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': 'Maybe?',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': 'Yep it does!',
                    'type': 'unordered-list-item',
                    'depth': 3,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '28umf',
                    'text': 'How many levels deep?',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'd81ns',
                    'text': 'Lots.',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'b0tsc',
                    'text': 'Ah.',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li>A list item<ul class="steps"><li>Oops!<ul class="steps"><li>Does this support nesting?</li><li>Maybe?<ul class="steps"><li>Yep it does!<ul class="steps"><li>How many levels deep?</li><li>Lots.</li><li>Ah.</li></ul></li></ul></li></ul></li></ul></li></ul>')

    def test_render_with_backtracking_nested_wrapping(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': 'Oops! (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': 'Does this support nesting? (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': 'Maybe? (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': 'Yep it does! (3)',
                    'type': 'unordered-list-item',
                    'depth': 3,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '28umf',
                    'text': 'How many levels deep? (4)',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c2gc4',
                    'text': 'Backtracking, two at once... (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1gcb',
                    'text': 'Uh oh (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c2gh4',
                    'text': 'Up, up, and away! (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1ghb',
                    'text': 'Arh! (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1gc9',
                    'text': 'Did this work? (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1gc9',
                    'text': 'Yes! (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li>A list item (0)<ul class="steps"><li>Oops! (1)<ul class="steps"><li>Does this support nesting? (2)</li><li>Maybe? (2)<ul class="steps"><li>Yep it does! (3)<ul class="steps"><li>How many levels deep? (4)</li></ul></li></ul></li><li>Backtracking, two at once... (2)</li></ul></li><li>Uh oh (1)<ul class="steps"><li>Up, up, and away! (2)</li></ul></li><li>Arh! (1)</li></ul></li><li>Did this work? (0)</li><li>Yes! (0)</li></ul>')

    def test_render_with_jumping_wrapping(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': 'Jumps (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': 'Back (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': 'Jumps again (3)',
                    'type': 'unordered-list-item',
                    'depth': 3,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': 'Back (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li>A list item (0)<ul class="steps"><li><ul class="steps"><li>Jumps (2)</li></ul></li></ul></li><li>Back (0)<ul class="steps"><li><ul class="steps"><li><ul class="steps"><li>Jumps again (3)</li></ul></li></ul></li><li>Back (1)</li></ul></li></ul>')

    def test_render_with_immediate_jumping(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '93agv',
                    'text': 'A list item (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li><ul class="steps"><li><ul class="steps"><li>A list item (2)</li></ul></li></ul></li><li>A list item (0)</li></ul>')

    def test_render_with_no_zero_depth(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '93agv',
                    'text': 'A list item (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li><ul class="steps"><li><ul class="steps"><li>A list item (2)</li><li>A list item (2)</li></ul></li></ul></li></ul>')

    def test_render_with_big_content(self):
        self.assertEqual(HTML({
            'entity_decorators': {
                'LINK': link
            },
            'block_map': {
                'header-two': {'element': 'h2'},
                'blockquote': {'element': 'blockquote'},
                'unordered-list-item': {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {},
                },
                'unstyled': {'element': 'p'}
            },
            'style_map': {
                'ITALIC': {'element': 'em'},
                'BOLD': {'element': 'strong'}
            }
        }).render({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                },
                '1': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'https://www.springload.co.nz/work/nz-festival/'
                    }
                }
            },
            'blocks': [
                {
                    'key': '6mgfh',
                    'text': 'User experience (UX) design',
                    'type': 'header-two',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 16,
                            'length': 4,
                            'style': 'BOLD'
                        }
                    ],
                    'entityRanges': []
                },
                {
                    'key': '5384u',
                    'text': 'Everyone at Springload applies the best principles of UX to their work.',
                    'type': 'blockquote',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'eelkd',
                    'text': 'The design decisions we make building tools and services for your customers are based on empathy for what your customers need.',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'b9grk',
                    'text': 'User research',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'a1tis',
                    'text': 'User testing and analysis',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 25,
                            'key': 0
                        }
                    ]
                },
                {
                    'key': 'adjdn',
                    'text': 'A/B testing',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': '62lio',
                    'text': 'Prototyping',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'fq3f',
                    'text': 'How we made it delightful and easy for people to find NZ Festival shows',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 71,
                            'key': 1
                        }
                    ]
                }
            ]
        }), '<h2>User experience <strong>(UX)</strong> design</h2><blockquote>Everyone at Springload applies the best principles of UX to their work.</blockquote><p>The design decisions we make building tools and services for your customers are based on empathy for what your customers need.</p><ul><li>User research</li><li><a href="http://example.com">User testing and analysis</a></li><li>A/B testing</li><li>Prototyping</li></ul><p><a href="https://www.springload.co.nz/work/nz-festival/">How we made it delightful and easy for people to find NZ Festival shows</a></p>')

    def test_render_with_default_block_map(self):
        self.assertEqual(HTML({
            'style_map': {
                INLINE_STYLES.ITALIC: {'element': 'em'},
                INLINE_STYLES.BOLD: {'element': 'strong'},
                'HIGHLIGHT': {'element': 'strong', 'textDecoration': 'underline'},
            },
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text</p>')

    def test_render_with_default_style_map(self):
        self.assertEqual(HTML({
            'block_map': dict(BLOCK_MAP, **{
                BLOCK_TYPES.UNORDERED_LIST_ITEM: {
                    'element': 'li',
                    'wrapper': 'ul',
                    'wrapper_props': {'class': 'steps'},
                },
            })
        }).render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text</p>')

    def test_render_with_default_config(self):
        self.assertEqual(HTML().render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text</p>')

    def test_render_with_line_breaks(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text\nsplit in half',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text<br/>split in half</p>')

    def test_render_with_many_line_breaks(self):
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': '\nsome paragraph text\nsplit in half\n',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 1,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><br/><em>some</em> paragraph text<br/>split in half<br/></p>')

    def test_render_with_entity_and_decorators(self):
        """
        The composite decorator should never render text in any entities.
        """
        self.assertEqual(self.exporter.render({
            'entityMap': {
                '1': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://amazon.us'
                    }
                }
            },
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'search http://a.us or https://yahoo.com or www.google.com for #github and #facebook',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 7,
                            'length': 11,
                            'key': 1
                        }
                    ],
                },
                {
                    'key': '34a12',
                    'text': '#check www.example.com',
                    'type': 'code-block',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ]
        }),
            '<p>search <a href="http://amazon.us">http://a.us</a> or '
            '<a href="https://yahoo.com">https://yahoo.com</a> or '
            '<a href="http://www.google.com">www.google.com</a> for '
            '<span class="hashtag">#github</span> and '
            '<span class="hashtag">#facebook</span></p>'
            '<pre><code>#check www.example.com</code></pre>')

    def test_render_with_multiple_decorators(self):
        """
        When multiple decorators match the same part of text,
        only the first one should perform the replacement.
        """
        self.assertEqual(self.exporter.render({
            'entityMap': {},
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'search http://www.google.com#world for the #world',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ]
        }),
            '<p>search <a href="http://www.google.com#world">'
            'http://www.google.com#world</a> for the '
            '<span class="hashtag">#world</span></p>')
Пример #27
0
 def setUp(self):
     self.maxDiff = None
     self.exporter = HTML(config)
Пример #28
0
class TestOutput(unittest.TestCase):
    def setUp(self):
        self.maxDiff = None
        self.exporter = HTML(config)

    def test_call_with_different_blocks(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {},
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<h1>Header</h1><p>some paragraph text</p>')

    def test_call_with_unicode(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'Emojis! 🍺',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<p>Emojis! &#127866;</p>')

    def test_call_with_inline_styles(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text</p>')

    def test_call_with_multiple_inline_styles(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                }
            },
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 2,
                            'style': 'BOLD'
                        }
                    ],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'HIGHLIGHT'
                        }
                    ],
                    'entityRanges': [
                        {
                            'offset': 5,
                            'length': 9,
                            'key': 0
                        }
                    ]
                }
            ]
        }), '<h1><strong>He</strong>ader</h1><p><strong style="text-decoration: underline;">some</strong> <a href="http://example.com">paragraph</a> text</p>')

    def test_call_with_entities(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                }
            },
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 5,
                            'length': 9,
                            'key': 0
                        }
                    ]
                }
            ]
        }), '<p>some <a href="http://example.com">paragraph</a> text</p>')

    def test_call_with_entities_crossing_raises(self):
        with self.assertRaises(EntityException):
            self.exporter.call({
                'entityMap': {
                    '0': {
                        'type': 'LINK',
                        'mutability': 'MUTABLE',
                        'data': {
                            'url': 'http://example.com'
                        }
                    },
                    '1': {
                        'type': 'LINK',
                        'mutability': 'MUTABLE',
                        'data': {
                            'url': 'http://bar.example.com'
                        }
                    }
                },
                'blocks': [
                    {
                        'key': 'dem5p',
                        'text': 'some paragraph text',
                        'type': 'unstyled',
                        'depth': 0,
                        'inlineStyleRanges': [],
                        'entityRanges': [
                            {
                                'offset': 5,
                                'length': 9,
                                'key': 0
                            },
                            {
                                'offset': 2,
                                'length': 9,
                                'key': 1
                            }
                        ]
                    }
                ]
            })

    def test_call_with_wrapping(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'dem5p',
                    'text': 'item2',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<ul class="steps"><li>item1</li><li>item2</li></ul>')

    def test_call_with_token_entity(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {
                '2': {
                    'type': 'TOKEN',
                    'mutability': 'IMMUTABLE',
                    'data': {},
                },
            },
            'blocks': [
                {
                    'key': 'dem1p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'item2',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': '672oo',
                    'text': ' ',
                    'type': 'horizontal-rule',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 1,
                            'key': 2,
                        },
                    ],
                },
            ]
        }), '<ul class="steps"><li>item1</li><li>item2</li></ul><hr>')

    def test_call_with_unidirectional_nested_wrapping(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': 'Oops!',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': 'Does this support nesting?',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': 'Maybe?',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': 'Yep it does!',
                    'type': 'unordered-list-item',
                    'depth': 3,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '28umf',
                    'text': 'How many levels deep?',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'd81ns',
                    'text': 'Lots.',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'b0tsc',
                    'text': 'Ah.',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li>A list item<ul class="steps"><li>Oops!<ul class="steps"><li>Does this support nesting?</li><li>Maybe?<ul class="steps"><li>Yep it does!<ul class="steps"><li>How many levels deep?</li><li>Lots.</li><li>Ah.</li></ul></li></ul></li></ul></li></ul></li></ul>')

    def test_call_with_backtracking_nested_wrapping(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {},
            'blocks': [
                {
                    'key': '93agv',
                    'text': 'A list item (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '4ht9m',
                    'text': 'Oops! (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc4',
                    'text': 'Does this support nesting? (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c6gc3',
                    'text': 'Maybe? (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '3mn5b',
                    'text': 'Yep it does! (3)',
                    'type': 'unordered-list-item',
                    'depth': 3,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': '28umf',
                    'text': 'How many levels deep? (4)',
                    'type': 'unordered-list-item',
                    'depth': 4,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c2gc4',
                    'text': 'Backtracking, two at once... (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1gcb',
                    'text': 'Uh oh (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c2gh4',
                    'text': 'Up, up, and away! (2)',
                    'type': 'unordered-list-item',
                    'depth': 2,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1ghb',
                    'text': 'Arh! (1)',
                    'type': 'unordered-list-item',
                    'depth': 1,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1gc9',
                    'text': 'Did this work? (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'c1gc9',
                    'text': 'Yes! (0)',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
            ],
        }), '<ul class="steps"><li>A list item (0)<ul class="steps"><li>Oops! (1)<ul class="steps"><li>Does this support nesting? (2)</li><li>Maybe? (2)<ul class="steps"><li>Yep it does! (3)<ul class="steps"><li>How many levels deep? (4)</li></ul></li></ul></li></ul><ul class="steps"><li>Backtracking, two at once... (2)</li></ul></li></ul><ul class="steps"><li>Uh oh (1)<ul class="steps"><li>Up, up, and away! (2)</li></ul></li></ul><ul class="steps"><li>Arh! (1)</li></ul></li></ul><ul class="steps"><li>Did this work? (0)</li><li>Yes! (0)</li></ul>')

    def test_call_with_big_content(self):
        self.assertEqual(HTML({
            'entity_decorators': {
                'LINK': Link()
            },
            'block_map': {
                'header-two': {'element': 'h2'},
                'blockquote': {'element': 'blockquote'},
                'unordered-list-item': {
                    'element': 'li',
                    'wrapper': ['ul', {}]
                },
                'unstyled': {'element': 'p'}
            },
            'style_map': {
                'ITALIC': {'element': 'em'},
                'BOLD': {'element': 'strong'}
            }
        }).call({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                },
                '1': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'https://www.springload.co.nz/work/nz-festival/'
                    }
                }
            },
            'blocks': [
                {
                    'key': '6mgfh',
                    'text': 'User experience (UX) design',
                    'type': 'header-two',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 16,
                            'length': 4,
                            'style': 'BOLD'
                        }
                    ],
                    'entityRanges': []
                },
                {
                    'key': '5384u',
                    'text': 'Everyone at Springload applies the best principles of UX to their work.',
                    'type': 'blockquote',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'eelkd',
                    'text': 'The design decisions we make building tools and services for your customers are based on empathy for what your customers need.',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'b9grk',
                    'text': 'User research',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'a1tis',
                    'text': 'User testing and analysis',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 25,
                            'key': 0
                        }
                    ]
                },
                {
                    'key': 'adjdn',
                    'text': 'A/B testing',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': '62lio',
                    'text': 'Prototyping',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'fq3f',
                    'text': 'How we made it delightful and easy for people to find NZ Festival shows',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 71,
                            'key': 1
                        }
                    ]
                }
            ]
        }), '<h2>User experience <strong>(UX)</strong> design</h2><blockquote>Everyone at Springload applies the best principles of UX to their work.</blockquote><p>The design decisions we make building tools and services for your customers are based on empathy for what your customers need.</p><ul><li>User research</li><li><a href="http://example.com">User testing and analysis</a></li><li>A/B testing</li><li>Prototyping</li></ul><p><a href="https://www.springload.co.nz/work/nz-festival/">How we made it delightful and easy for people to find NZ Festival shows</a></p>')
Пример #29
0
        ENTITY_TYPES.IMAGE: image,
        ENTITY_TYPES.LINK: link,
        ENTITY_TYPES.DOCUMENT: document,
        ENTITY_TYPES.HORIZONTAL_RULE: lambda props: DOM.create_element("hr"),
        ENTITY_TYPES.EMBED: None,
        ENTITY_TYPES.FALLBACK: entity_fallback,
    },
    "composite_decorators": [{
        "strategy": re.compile(r"\n"),
        "component": br
    }],
    "engine":
    DOM.STRING,
}

exporter = HTML(config)

content_states = get_content_sample()

BENCHMARK_RUNS = int(os.environ.get("BENCHMARK_RUNS", 1))

print(f"Exporting {len(content_states)} ContentStates {BENCHMARK_RUNS} times")

pr = cProfile.Profile()
pr.enable()

for i in range(0, BENCHMARK_RUNS):
    for content_state in content_states:
        exporter.render(content_state)

pr.disable()
Пример #30
0
class TestHTML(unittest.TestCase):
    def setUp(self):
        self.exporter = HTML(config)

    def test_init(self):
        self.assertIsInstance(self.exporter, HTML)

    def test_render_block_exists(self):
        self.assertTrue('render_block' in dir(self.exporter))

    def test_build_style_commands_empty(self):
        self.assertEqual(str(self.exporter.build_style_commands({
            'key': '5s7g9',
            'text': 'Header',
            'type': 'header-one',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': []
        })), str([]))

    def test_build_style_commands_single(self):
        self.assertEqual(str(self.exporter.build_style_commands({
            'key': '5s7g9',
            'text': 'Header',
            'type': 'header-one',
            'depth': 0,
            'inlineStyleRanges': [
                {
                    'offset': 0,
                    'length': 4,
                    'style': 'ITALIC'
                }
            ],
            'entityRanges': []
        })), str([
            Command('start_inline_style', 0, 'ITALIC'),
            Command('stop_inline_style', 4, 'ITALIC'),
        ]))

    def test_build_style_commands_multiple(self):
        self.assertEqual(str(self.exporter.build_style_commands({
            'key': '5s7g9',
            'text': 'Header',
            'type': 'header-one',
            'depth': 0,
            'inlineStyleRanges': [
                {
                    'offset': 0,
                    'length': 4,
                    'style': 'ITALIC'
                },
                {
                    'offset': 9,
                    'length': 3,
                    'style': 'BOLD'
                }
            ],
            'entityRanges': []
        })), str([
            Command('start_inline_style', 0, 'ITALIC'),
            Command('stop_inline_style', 4, 'ITALIC'),
            Command('start_inline_style', 9, 'BOLD'),
            Command('stop_inline_style', 12, 'BOLD'),
        ]))

    def test_build_entity_commands_empty(self):
        self.assertEqual(str(self.exporter.build_entity_commands({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': []
        })), str([]))

    def test_build_entity_commands_single(self):
        self.assertEqual(str(self.exporter.build_entity_commands({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': [
                {
                    'offset': 5,
                    'length': 9,
                    'key': 0
                }
            ]
        })), str([
            Command('start_entity', 5, 0),
            Command('stop_entity', 14, 0),
        ]))

    def test_build_entity_commands_multiple(self):
        self.assertEqual(str(self.exporter.build_entity_commands({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': [
                {
                    'offset': 5,
                    'length': 9,
                    'key': 0
                },
                {
                    'offset': 0,
                    'length': 4,
                    'key': 1
                }
            ]
        })), str([
            Command('start_entity', 5, 0),
            Command('stop_entity', 14, 0),
            Command('start_entity', 0, 1),
            Command('stop_entity', 4, 1),
        ]))

    def test_build_commands_empty(self):
        self.assertEqual(str(self.exporter.build_commands({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': []
        })), str([
            Command('start_text', 0),
            Command('stop_text', 19),
        ]))

    def test_build_commands_multiple(self):
        self.assertEqual(str(self.exporter.build_commands({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [
                {
                    'offset': 0,
                    'length': 4,
                    'style': 'ITALIC'
                },
                {
                    'offset': 9,
                    'length': 3,
                    'style': 'BOLD'
                }
            ],
            'entityRanges': [
                {
                    'offset': 5,
                    'length': 9,
                    'key': 0
                },
                {
                    'offset': 0,
                    'length': 4,
                    'key': 1
                }
            ]
        })), str([
            Command('start_text', 0),
            Command('stop_text', 19),
            Command('start_inline_style', 0, 'ITALIC'),
            Command('stop_inline_style', 4, 'ITALIC'),
            Command('start_inline_style', 9, 'BOLD'),
            Command('stop_inline_style', 12, 'BOLD'),
            Command('start_entity', 5, 0),
            Command('stop_entity', 14, 0),
            Command('start_entity', 0, 1),
            Command('stop_entity', 4, 1),
        ]))

    def test_build_command_groups_empty(self):
        self.assertEqual(str(self.exporter.build_command_groups({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [],
            'entityRanges': []
        })), str([
            ('some paragraph text', [
                Command('start_text', 0)
            ]),
            ('', [
                Command('stop_text', 19)
            ]),
        ]))

    def test_build_command_groups_multiple(self):
        self.assertEqual(str(self.exporter.build_command_groups({
            'key': 'dem5p',
            'text': 'some paragraph text',
            'type': 'unstyled',
            'depth': 0,
            'inlineStyleRanges': [
                {
                    'offset': 0,
                    'length': 4,
                    'style': 'ITALIC'
                },
                {
                    'offset': 9,
                    'length': 3,
                    'style': 'BOLD'
                }
            ],
            'entityRanges': [
                {
                    'offset': 5,
                    'length': 9,
                    'key': 0
                },
                {
                    'offset': 0,
                    'length': 4,
                    'key': 1
                }
            ]
        })), str([
            ('some', [
                Command('start_text', 0),
                Command('start_inline_style', 0, 'ITALIC'),
                Command('start_entity', 0, 1),
            ]),
            (' ', [
                Command('stop_inline_style', 4, 'ITALIC'),
                Command('stop_entity', 4, 1),
            ]),
            ('para', [
                Command('start_entity', 5, 0),
            ]),
            ('gra', [
                Command('start_inline_style', 9, 'BOLD'),
            ]),
            ('ph', [
                Command('stop_inline_style', 12, 'BOLD'),
            ]),
            (' text', [
                Command('stop_entity', 14, 0),
            ]),
            ('', [
                Command('stop_text', 19)
            ]),
        ]))
Пример #31
0
 def setUp(self):
     self.maxDiff = None
     self.exporter = HTML(config)
Пример #32
0
 def setUp(self):
     self.exporter = HTML(config)
Пример #33
0
    def test_exports(self):
        self.maxDiff = None

        for export in fixtures:
            exporter = HTML(config)
            self.assertEqual(exporter.call(export.get('content_state')), export.get('output'))
Пример #34
0
 def test_init_dom_engine_default(self):
     HTML()
     self.assertEqual(DOM.dom, DOMString)
Пример #35
0
                'component': br,
            },
            {
                'strategy': re.compile(r'#\w+'),
                'component': hashtag,
            },
            {
                'strategy': LINKIFY_RE,
                'component': linkify,
            },
        ],
        # Specify which DOM backing engine to use.
        'engine': DOM.STRING,
    }

    exporter = HTML(config)

    content_state = {
        "entityMap": {
            "0": {
                "type": "LINK",
                "mutability": "MUTABLE",
                "data": {
                    "url": "https://github.com/facebook/draft-js"
                }
            },
            "1": {
                "type": "LINK",
                "mutability": "MUTABLE",
                "data": {
                    "url": "https://facebook.github.io/react/docs/top-level-api.html#react.createelement"
Пример #36
0
            },
            {
                'strategy': re.compile(r'#\w+'),
                'component': hashtag,
            },
            {
                'strategy': LINKIFY_RE,
                'component': linkify,
            },
        ],
        # Specify which DOM backing engine to use.
        'engine':
        DOM.STRING,
    }

    exporter = HTML(config)

    content_state = {
        "entityMap": {
            "0": {
                "type": "LINK",
                "mutability": "MUTABLE",
                "data": {
                    "url": "https://github.com/facebook/draft-js"
                }
            },
            "1": {
                "type": "LINK",
                "mutability": "MUTABLE",
                "data": {
                    "url":
Пример #37
0
        BLOCK_TYPES.BLOCKQUOTE: {'element': 'blockquote'},
        # TODO Ideally would want double wrapping in pre + code.
        # See https://github.com/sstur/draft-js-export-html/blob/master/src/stateToHTML.js#L88
        BLOCK_TYPES.CODE: {'element': 'pre'},
        BLOCK_TYPES.HORIZONTAL_RULE: {'element': 'hr'},
    },
    'style_map': {
        INLINE_STYLES.ITALIC: {'element': 'em'},
        INLINE_STYLES.BOLD: {'element': 'strong'},
        INLINE_STYLES.CODE: {'element': 'code'},
        INLINE_STYLES.STRIKETHROUGH: {'textDecoration': 'line-through'},
        INLINE_STYLES.UNDERLINE: {'textDecoration': 'underline'},
    },
}

exporter = HTML(config)

content_state = {
    'entityMap': {
        '0': {
            'type': 'LINK',
            'mutability': 'MUTABLE',
            'data': {
                'url': 'http://example.com',
            },
        },
        '1': {
            'type': 'LINK',
            'mutability': 'MUTABLE',
            'data': {
                'url': 'https://www.springload.co.nz/work/nz-festival/',
Пример #38
0
class TestOutput(unittest.TestCase):
    def setUp(self):
        self.maxDiff = None
        self.exporter = HTML(config)

    def test_call_with_different_blocks_decodes(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {},
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<h1>Header</h1><p>some paragraph text</p>')

    def test_call_with_unicode(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'Emojis! 🍺',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<p>Emojis! &#127866;</p>')

    def test_call_with_inline_styles_decodes(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'ITALIC'
                        }
                    ],
                    'entityRanges': []
                }
            ]
        }), '<p><em>some</em> paragraph text</p>')

    def test_call_with_multiple_inline_styles_decodes(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                }
            },
            'blocks': [
                {
                    'key': '5s7g9',
                    'text': 'Header',
                    'type': 'header-one',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 2,
                            'style': 'BOLD'
                        }
                    ],
                    'entityRanges': []
                },
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 0,
                            'length': 4,
                            'style': 'HIGHLIGHT'
                        }
                    ],
                    'entityRanges': [
                        {
                            'offset': 5,
                            'length': 9,
                            'key': 0
                        }
                    ]
                }
            ]
        }), '<h1><strong>He</strong>ader</h1><p><strong style="text-decoration: underline;">some</strong> <a href="http://example.com">paragraph</a> text</p>')

    def test_call_with_entities_decodes(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                }
            },
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'some paragraph text',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 5,
                            'length': 9,
                            'key': 0
                        }
                    ]
                }
            ]
        }), '<p>some <a href="http://example.com">paragraph</a> text</p>')

    def test_call_with_entities_crossing_raises(self):
        with self.assertRaises(EntityException):
            self.exporter.call({
                'entityMap': {
                    '0': {
                        'type': 'LINK',
                        'mutability': 'MUTABLE',
                        'data': {
                            'url': 'http://example.com'
                        }
                    },
                    '1': {
                        'type': 'LINK',
                        'mutability': 'MUTABLE',
                        'data': {
                            'url': 'http://bar.example.com'
                        }
                    }
                },
                'blocks': [
                    {
                        'key': 'dem5p',
                        'text': 'some paragraph text',
                        'type': 'unstyled',
                        'depth': 0,
                        'inlineStyleRanges': [],
                        'entityRanges': [
                            {
                                'offset': 5,
                                'length': 9,
                                'key': 0
                            },
                            {
                                'offset': 2,
                                'length': 9,
                                'key': 1
                            }
                        ]
                    }
                ]
            })

    def test_call_with_wrapped_blocks(self):
        self.assertEqual(self.exporter.call({
            'entityMap': {},
            'blocks': [
                {
                    'key': 'dem5p',
                    'text': 'item1',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [],
                },
                {
                    'key': 'dem5p',
                    'text': 'item2',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                }
            ]
        }), '<ul class="steps"><li>item1</li><li>item2</li></ul>')

    def test_call_with_big_content(self):
        self.assertEqual(HTML({
            'entity_decorators': {
                'LINK': Link()
            },
            'block_map': {
                'header-two': {'element': 'h2'},
                'blockquote': {'element': 'blockquote'},
                'unordered-list-item': {
                    'element': 'li',
                    'wrapper': ['ul', {}]
                },
                'unstyled': {'element': 'p'}
            },
            'style_map': {
                'ITALIC': {'element': 'em'},
                'BOLD': {'element': 'strong'}
            }
        }).call({
            'entityMap': {
                '0': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'http://example.com'
                    }
                },
                '1': {
                    'type': 'LINK',
                    'mutability': 'MUTABLE',
                    'data': {
                        'url': 'https://www.springload.co.nz/work/nz-festival/'
                    }
                }
            },
            'blocks': [
                {
                    'key': '6mgfh',
                    'text': 'User experience (UX) design',
                    'type': 'header-two',
                    'depth': 0,
                    'inlineStyleRanges': [
                        {
                            'offset': 16,
                            'length': 4,
                            'style': 'BOLD'
                        }
                    ],
                    'entityRanges': []
                },
                {
                    'key': '5384u',
                    'text': 'Everyone at Springload applies the best principles of UX to their work.',
                    'type': 'blockquote',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'eelkd',
                    'text': 'The design decisions we make building tools and services for your customers are based on empathy for what your customers need.',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'b9grk',
                    'text': 'User research',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'a1tis',
                    'text': 'User testing and analysis',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 25,
                            'key': 0
                        }
                    ]
                },
                {
                    'key': 'adjdn',
                    'text': 'A/B testing',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': '62lio',
                    'text': 'Prototyping',
                    'type': 'unordered-list-item',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': []
                },
                {
                    'key': 'fq3f',
                    'text': 'How we made it delightful and easy for people to find NZ Festival shows',
                    'type': 'unstyled',
                    'depth': 0,
                    'inlineStyleRanges': [],
                    'entityRanges': [
                        {
                            'offset': 0,
                            'length': 71,
                            'key': 1
                        }
                    ]
                }
            ]
        }), '<h2>User experience <strong>(UX)</strong> design</h2><blockquote>Everyone at Springload applies the best principles of UX to their work.</blockquote><p>The design decisions we make building tools and services for your customers are based on empathy for what your customers need.</p><ul><li>User research</li><li><a href="http://example.com">User testing and analysis</a></li><li>A/B testing</li><li>Prototyping</li></ul><p><a href="https://www.springload.co.nz/work/nz-festival/">How we made it delightful and easy for people to find NZ Festival shows</a></p>')
Пример #39
0
 def test_render_with_big_content(self):
     self.assertEqual(HTML({
         'entity_decorators': {
             'LINK': link
         },
         'block_map': {
             'header-two': {'element': 'h2'},
             'blockquote': {'element': 'blockquote'},
             'unordered-list-item': {
                 'element': 'li',
                 'wrapper': 'ul',
                 'wrapper_props': {},
             },
             'unstyled': {'element': 'p'}
         },
         'style_map': {
             'ITALIC': {'element': 'em'},
             'BOLD': {'element': 'strong'}
         }
     }).render({
         'entityMap': {
             '0': {
                 'type': 'LINK',
                 'mutability': 'MUTABLE',
                 'data': {
                     'url': 'http://example.com'
                 }
             },
             '1': {
                 'type': 'LINK',
                 'mutability': 'MUTABLE',
                 'data': {
                     'url': 'https://www.springload.co.nz/work/nz-festival/'
                 }
             }
         },
         'blocks': [
             {
                 'key': '6mgfh',
                 'text': 'User experience (UX) design',
                 'type': 'header-two',
                 'depth': 0,
                 'inlineStyleRanges': [
                     {
                         'offset': 16,
                         'length': 4,
                         'style': 'BOLD'
                     }
                 ],
                 'entityRanges': []
             },
             {
                 'key': '5384u',
                 'text': 'Everyone at Springload applies the best principles of UX to their work.',
                 'type': 'blockquote',
                 'depth': 0,
                 'inlineStyleRanges': [],
                 'entityRanges': []
             },
             {
                 'key': 'eelkd',
                 'text': 'The design decisions we make building tools and services for your customers are based on empathy for what your customers need.',
                 'type': 'unstyled',
                 'depth': 0,
                 'inlineStyleRanges': [],
                 'entityRanges': []
             },
             {
                 'key': 'b9grk',
                 'text': 'User research',
                 'type': 'unordered-list-item',
                 'depth': 0,
                 'inlineStyleRanges': [],
                 'entityRanges': []
             },
             {
                 'key': 'a1tis',
                 'text': 'User testing and analysis',
                 'type': 'unordered-list-item',
                 'depth': 0,
                 'inlineStyleRanges': [],
                 'entityRanges': [
                     {
                         'offset': 0,
                         'length': 25,
                         'key': 0
                     }
                 ]
             },
             {
                 'key': 'adjdn',
                 'text': 'A/B testing',
                 'type': 'unordered-list-item',
                 'depth': 0,
                 'inlineStyleRanges': [],
                 'entityRanges': []
             },
             {
                 'key': '62lio',
                 'text': 'Prototyping',
                 'type': 'unordered-list-item',
                 'depth': 0,
                 'inlineStyleRanges': [],
                 'entityRanges': []
             },
             {
                 'key': 'fq3f',
                 'text': 'How we made it delightful and easy for people to find NZ Festival shows',
                 'type': 'unstyled',
                 'depth': 0,
                 'inlineStyleRanges': [],
                 'entityRanges': [
                     {
                         'offset': 0,
                         'length': 71,
                         'key': 1
                     }
                 ]
             }
         ]
     }), '<h2>User experience <strong>(UX)</strong> design</h2><blockquote>Everyone at Springload applies the best principles of UX to their work.</blockquote><p>The design decisions we make building tools and services for your customers are based on empathy for what your customers need.</p><ul><li>User research</li><li><a href="http://example.com">User testing and analysis</a></li><li>A/B testing</li><li>Prototyping</li></ul><p><a href="https://www.springload.co.nz/work/nz-festival/">How we made it delightful and easy for people to find NZ Festival shows</a></p>')
Пример #40
0
}

# Demo content from https://github.com/springload/draftjs_exporter/blob/master/example.py.
# with open('docs/example.json') as example:
#     content_state = json.load(example)

if __name__ == '__main__':
    exporter = HTML({
        'block_map': BLOCK_MAP,
        'style_map': STYLE_MAP,
        'entity_decorators': {
            # Map entities to components so they can be rendered with their data.
            ENTITY_TYPES.IMAGE:
            image,
            ENTITY_TYPES.LINK:
            link,
            # Lambdas work too.
            ENTITY_TYPES.HORIZONTAL_RULE:
            lambda props: DOM.create_element('hr'),
            # Discard those entities.
            ENTITY_TYPES.EMBED:
            None,
        },
        'engine': 'draftjs_exporter_rust_engine.engine.DOMString',
    })

    markup = exporter.render(content_state)

    print(markup)

    # Output to a Markdown file to showcase the output in GitHub (and see changes in git).
    with codecs.open('docs/example.txt', 'w', 'utf-8') as file:
Пример #41
0
 def setUp(self):
     self.exporter = HTML(config)
Пример #42
0
    def _parse_editor_state(self, article, ninjs):
        """Parse editor_state (DraftJs internals) to retrieve annotations

        body_html will be rewritten with HTML generated from DraftJS representation
        and annotation will be included in <span> elements
        :param article: item to modify, must contain "editor_state" data
        :param ninjs: ninjs item which will be formatted
        """
        blocks = article['editor_state']['blocks']
        blocks_map = {}
        ann_idx = 0
        data = {}
        config = {
            'engine': 'lxml',
            'entity_decorators': {
                ENTITY_TYPES.LINK:
                self._render_link,
                ENTITY_TYPES.HORIZONTAL_RULE:
                lambda props: DOM.create_element('hr'),
                ENTITY_TYPES.EMBED:
                self._render_embed,
                MEDIA:
                self._render_media,
                ANNOTATION:
                self._render_annotation
            }
        }
        renderer = HTML(config)

        for block in blocks:
            blocks_map[block['key']] = block
            data.update(block['data'])

        # we sort data keys to have consistent annotations ids
        for key in sorted(data):
            data_block = data[key]
            if data_block['type'] == ANNOTATION:
                ninjs.setdefault('annotations', []).append({
                    'id':
                    ann_idx,
                    'type':
                    data_block['annotationType'],
                    'body':
                    renderer.render(json.loads(data_block['msg']))
                })
                entity_key = '_annotation_{}'.format(ann_idx)
                article['editor_state']['entityMap'][entity_key] = {
                    'type': ANNOTATION,
                    'data': {
                        'id': ann_idx
                    }
                }
                ann_idx += 1
                selection = json.loads(key)
                if selection['isBackward']:
                    first, second = 'focus', 'anchor'
                else:
                    first, second = 'anchor', 'focus'
                first_key = selection[first + 'Key']
                second_key = selection[second + 'Key']
                first_offset = selection[first + 'Offset']
                second_offset = selection[second + 'Offset']
                # we want to style annotation with <span>, so we put them as entities
                if first_key == second_key:
                    # selection is done in a single block
                    annotated_block = blocks_map[first_key]
                    annotated_block.setdefault('entityRanges', []).append({
                        'key':
                        entity_key,
                        'offset':
                        first_offset,
                        'length':
                        second_offset - first_offset
                    })
                else:
                    # selection is done on multiple blocks, we have to select them
                    started = False
                    for block in blocks:
                        if block['key'] == first_key:
                            started = True
                            block.setdefault('entityRanges', []).append({
                                'key':
                                entity_key,
                                'offset':
                                first_offset,
                                'length':
                                len(block['text']) - first_offset
                            })
                        elif started:
                            inline = {'key': entity_key, 'offset': 0}
                            block.setdefault('entityRanges', []).append(inline)
                            if block['key'] == second_key:
                                # last block, we end the annotation here
                                inline['length'] = second_offset
                                break
                            else:
                                # intermediate block, we annotate it whole
                                inline['length'] = len(block['text'])
        # HTML rendering
        # now we have annotation ready, we can render HTML
        # we change body_html if and only if we have annotations to render
        if ninjs.get('annotations'):
            article['body_html'] = renderer.render(article['editor_state'])
Пример #43
0
def memory_consumption_run():
    exporter = HTML(config)

    for content_state in content_states:
        exporter.render(content_state)
class TestCompositeDecorator(unittest.TestCase):
    def setUp(self):
        self.exporter = HTML(config)
        self.maxDiff = None

    def test_render_with_entity_and_decorators(self):
        """
        The composite decorator should never render text in any entities.
        """
        self.assertEqual(
            self.exporter.render({
                'entityMap': {
                    '1': {
                        'type': 'LINK',
                        'mutability': 'MUTABLE',
                        'data': {
                            'url': 'http://amazon.us'
                        }
                    }
                },
                'blocks': [
                    {
                        'key': '5s7g9',
                        'text':
                        'search http://a.us or https://yahoo.com or www.google.com for #github and #facebook',
                        'type': 'unstyled',
                        'depth': 0,
                        'inlineStyleRanges': [],
                        'entityRanges': [{
                            'offset': 7,
                            'length': 11,
                            'key': 1
                        }],
                    },
                    {
                        'key': '34a12',
                        'text': '#check www.example.com',
                        'type': 'code-block',
                        'inlineStyleRanges': [],
                    },
                ]
            }), '<div>search <a href="http://amazon.us">http://a.us</a> or '
            '<a href="https://yahoo.com">https://yahoo.com</a> or '
            '<a href="http://www.google.com">www.google.com</a> for '
            '<span class="hash_tag">#github</span> and '
            '<span class="hash_tag">#facebook</span></div>'
            '<pre>#check www.example.com</pre>')

    def test_render_with_multiple_decorators(self):
        """
        When multiple decorators match the same part of text,
        only the first one should perform the replacement.
        """
        self.assertEqual(
            self.exporter.render({
                'entityMap': {},
                'blocks': [
                    {
                        'key': '5s7g9',
                        'text':
                        'search http://www.google.com#world for the #world',
                        'type': 'unstyled',
                        'depth': 0,
                        'inlineStyleRanges': [],
                        'entityRanges': [],
                    },
                ]
            }), '<div>search <a href="http://www.google.com#world">'
            'http://www.google.com#world</a> for the '
            '<span class="hash_tag">#world</span></div>')