thibaudcolas: @loicteixeira up
diff --git a/README.rst b/README.rst
index b91b22c..94898d6 100644
--- a/README.rst
+++ b/README.rst
@@ -105,13 +105,13 @@ The exporter output is extensively configurable to cater for varied rich text re
                 'wrapper_props': {'class': 'bullet-list'},
             },
             # Use a custom component for more flexibility (reading block data or depth).
-            BLOCK_TYPES.BLOCKQUOTE: Blockquote,
+            BLOCK_TYPES.BLOCKQUOTE: blockquote,
             BLOCK_TYPES.ORDERED_LIST_ITEM: {
-                'element': ListItem,
-                'wrapper': OrderedList,
+                'element': list_item,
+                'wrapper': ordered_list,
             },
             # Provide a fallback component (advanced).
-            BLOCK_TYPES.FALLBACK: BlockFallback
+            BLOCK_TYPES.FALLBACK: block_fallback
         }),
         # `style_map` defines the HTML representation of inline elements.
         # Extend STYLE_MAP to start with sane defaults, or make your own from scratch.
@@ -123,25 +123,33 @@ The exporter output is extensively configurable to cater for varied rich text re
         }),
         'entity_decorators': {
             # Map entities to components so they can be rendered with their data.
-            ENTITY_TYPES.IMAGE: Image,
-            # Components can be defined as classes to receive extra parameters.
-            ENTITY_TYPES.LINK: Link(use_new_window=True),
+            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,
             # Provide a fallback component (advanced).
-            ENTITY_TYPES.FALLBACK: EntityFallback,
+            ENTITY_TYPES.FALLBACK: entity_fallback,
         },
         'composite_decorators': [
             # Use composite decorators to replace text based on a regular expression.
-            BR,
-            Hashtag,
-            Linkify,
+            {
+                'strategy': re.compile(r'\n'),
+                'component': br,
+            },
+            {
+                'strategy': re.compile(r'#\w+'),
+                'component': hashtag,
+            },
+            {
+                'strategy': LINKIFY_RE,
+                'component': linkify,
+            },
         ],
     }
 
-See ``examples.py`` in the repository for more details.
+See `examples.py <https://github.com/springload/draftjs_exporter/blob/master/example.py>`_ for more details.
 
 Advanced usage
 --------------
@@ -149,7 +157,7 @@ Advanced usage
 Custom components
 ~~~~~~~~~~~~~~~~~
 
-To produce arbitrary markup with dynamic data, draftjs_exporter comes with an API to create rendering components. This API mirrors React's `createElement <https://facebook.github.io/react/docs/top-level-api.html#react.createelement>`_ API (what JSX compiles to).
+To generate arbitrary markup with dynamic data, draftjs_exporter comes with an API to create rendering components. This API mirrors React's `createElement <https://facebook.github.io/react/docs/top-level-api.html#react.createelement>`_ API (what JSX compiles to).
 
 .. code:: python
 
@@ -158,7 +166,7 @@ To produce arbitrary markup with dynamic data, draftjs_exporter comes with an AP
 
 
     # Components are simple functions that take `props` as parameter and return DOM elements.
-    def Image(props):
+    def image(props):
         # This component creates an image element, with the relevant attributes.
         return DOM.create_element('img', {
             'src': props.get('src'),
@@ -168,7 +176,7 @@ To produce arbitrary markup with dynamic data, draftjs_exporter comes with an AP
         })
 
 
-    def Blockquote(props):
+    def blockquote(props):
         # This component uses block data to render a blockquote.
         block_data = props['block']['data']
 
@@ -178,20 +186,20 @@ To produce arbitrary markup with dynamic data, draftjs_exporter comes with an AP
         }, props['children'])
 
 
-    class Button:
-        def render(self, props):
-            href = props.get('href', '#')
-            icon = props.get('icon', None)
-            text = props.get('text', '')
+    def button(props):
+        href = props.get('href', '#')
+        icon_name = props.get('icon', None)
+        text = props.get('text', '')
 
+        return DOM.create_element('a', {
+                'class': 'icon-text' if icon_name else None,
+                'href': href,
+            },
             # There can be as many `children` as required.
             # It is also possible to reuse other components and render them instead of HTML tags.
-            return DOM.create_element(
-                'a',
-                {'class': 'icon-text' if icon else None, 'href': href},
-                DOM.create_element(Icon, {'name': icon}) if icon else None,
-                DOM.create_element('span', {'class': 'icon-text__text'}, text) if icon else text
-            )
+            DOM.create_element(icon, {'name': icon_name}) if icon_name else None,
+            DOM.create_element('span', {'class': 'icon-text'}, text) if icon_name else text
+        )
 
 Apart from ``create_element``, a ``parse_html`` method is also available. Use it to interface with other HTML generators, like template engines.
 
@@ -211,7 +219,7 @@ Add the following to the exporter config,
     config = {
         'block_map': dict(BLOCK_MAP, **{
             # Provide a fallback for block types.
-            BLOCK_TYPES.FALLBACK: BlockFallback
+            BLOCK_TYPES.FALLBACK: block_fallback
         }),
     }
 
@@ -219,7 +227,7 @@ This fallback component can now control the exporter behavior when normal compon
 
 .. code:: python
 
-    def BlockFallback(props):
+    def block_fallback(props):
         type_ = props['block']['type']
 
         if type_ == 'example-discard':
diff --git a/benchmark.py b/benchmark.py
index a9714de..40ae663 100644
--- a/benchmark.py
+++ b/benchmark.py
@@ -5,6 +5,7 @@ from __future__ import absolute_import, unicode_literals
 import cProfile
 import logging
 import os
+import re
 from pstats import Stats
 from memory_profiler import profile
 
@@ -15,23 +16,23 @@ from draftjs_exporter.html import HTML
 
 from markov_draftjs import get_content_sample
 
-from example import ListItem, OrderedList, Image, BR, EntityFallback
+from example import list_item, ordered_list, image, br, entity_fallback
 
 
-def Document(props):
+def document(props):
     return DOM.create_element('a', {
         'title': props.get('label'),
         'href': '/documents/%s' % props.get('id'),
     }, props['children'])
 
 
-def Link(props):
+def link(props):
     return DOM.create_element('a', {
         'href': props['url'],
     }, props['children'])
 
 
-def BlockFallback(props):
+def block_fallback(props):
     type_ = props['block']['type']
 
     logging.warn('Missing config for "%s".' % type_)
@@ -48,22 +49,25 @@ config = {
             'wrapper_props': {'class': 'bullet-list'},
         },
         BLOCK_TYPES.ORDERED_LIST_ITEM: {
-            'element': ListItem,
-            'wrapper': OrderedList,
+            'element': list_item,
+            'wrapper': ordered_list,
         },
-        BLOCK_TYPES.FALLBACK: BlockFallback
+        BLOCK_TYPES.FALLBACK: block_fallback
     }),
     'style_map': STYLE_MAP,
     'entity_decorators': {
-        ENTITY_TYPES.IMAGE: Image,
-        ENTITY_TYPES.LINK: Link,
-        ENTITY_TYPES.DOCUMENT: Document,
+        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: EntityFallback,
+        ENTITY_TYPES.FALLBACK: entity_fallback,
     },
     'composite_decorators': [
-        BR,
+        {
+            'strategy': re.compile(r'\n'),
+            'component': br,
+        }
     ],
     'engine': 'string',
 }
diff --git a/docs/example.md b/docs/example.md
index 299010b..c0fedf4 100644
--- a/docs/example.md
+++ b/docs/example.md
@@ -4,7 +4,7 @@
 -----
 <h2>
    draftjs_exporter is an HTML exporter for
-   <a href="https://github.com/facebook/draft-js" rel="noreferrer noopener" target="_blank">
+   <a href="https://github.com/facebook/draft-js">
     Draft.js
    </a>
    content
@@ -17,7 +17,7 @@
   </h3>
   <p>
    The exporter aims to provide sensible defaults from basic block types and inline styles to HTML, that can easily be customised when required. For more advanced scenarios, an API is provided (mimicking React's
-   <a href="https://facebook.github.io/react/docs/top-level-api.html#react.createelement" rel="noreferrer noopener" target="_blank">
+   <a href="https://facebook.github.io/react/docs/top-level-api.html#react.createelement">
     <code>
      createElement
     </code>
@@ -39,7 +39,7 @@
    </li>
    <li>
     Automatic conversion of entity data to HTML attributes (int &amp; boolean to string,
-    <a href="https://facebook.github.io/react/docs/jsx-in-depth.html" rel="noreferrer noopener" target="_blank">
+    <a href="https://facebook.github.io/react/docs/jsx-in-depth.html">
      <code>
       style object
      </code>
@@ -120,7 +120,7 @@
            #hashtag
           </span>
           support via
-          <a href="https://github.com/springload/draftjs_exporter/pull/17" rel="noreferrer noopener" target="_blank">
+          <a href="https://github.com/springload/draftjs_exporter/pull/17">
            #CompositeDecorators
           </a>
           .
@@ -177,7 +177,7 @@
     </ol>
    </li>
   </ol>
-  <pre><code>def Blockquote(props):
+  <pre><code>def blockquote(props):
     block_data = props['block']['data']
     return DOM.create_element('blockquote', {
         'cite': block_data.get('cite')
diff --git a/draftjs_exporter/composite_decorators.py b/draftjs_exporter/composite_decorators.py
index e8c8053..8080fd5 100644
--- a/draftjs_exporter/composite_decorators.py
+++ b/draftjs_exporter/composite_decorators.py
@@ -9,7 +9,7 @@ def get_decorations(decorators, text):
     decorations = []
 
     for decorator in decorators:
-        for match in decorator.SEARCH_RE.finditer(text):
+        for match in decorator['strategy'].finditer(text):
             begin, end = match.span()
             if not any(occupied.get(i) for i in range(begin, end)):
                 for i in range(begin, end):
@@ -29,7 +29,7 @@ def apply_decorators(decorators, text, block):
         if pointer < begin:
             yield text[pointer:begin]
 
-        yield DOM.create_element(decorator, {
+        yield DOM.create_element(decorator['component'], {
             'match': match,
             'block': {
                 'type': block['type'],
diff --git a/draftjs_exporter/defaults.py b/draftjs_exporter/defaults.py
index 89514f8..045ba56 100644
--- a/draftjs_exporter/defaults.py
+++ b/draftjs_exporter/defaults.py
@@ -12,7 +12,7 @@ def render_children(props):
     return props['children']
 
 
-def CodeBlock(props):
+def code_block(props):
     return DOM.create_element('pre', {}, DOM.create_element('code', {}, props['children']))
 
 
@@ -29,7 +29,7 @@ BLOCK_MAP = {
     BLOCK_TYPES.ORDERED_LIST_ITEM: {'element': 'li', 'wrapper': 'ol'},
     BLOCK_TYPES.BLOCKQUOTE: 'blockquote',
     BLOCK_TYPES.PRE: 'pre',
-    BLOCK_TYPES.CODE: CodeBlock,
+    BLOCK_TYPES.CODE: code_block,
     BLOCK_TYPES.ATOMIC: render_children,
 }
 
diff --git a/draftjs_exporter/dom.py b/draftjs_exporter/dom.py
index 406667b..e3b9424 100644
--- a/draftjs_exporter/dom.py
+++ b/draftjs_exporter/dom.py
@@ -80,13 +80,7 @@ class DOM(object):
         # The children prop is the first child if there is only one.
         props['children'] = children[0] if len(children) == 1 else children
 
-        if inspect.isclass(type_):
-            # Class component, not instantiated.
-            elt = type_().render(props)
-        elif callable(getattr(type_, 'render', None)):
-            # Class component, already instantiated.
-            elt = type_.render(props)
-        elif callable(type_):
+        if callable(type_):
             # Function component, via def or lambda.
             elt = type_(props)
         else:
diff --git a/example.py b/example.py
index 03684a1..bd4f657 100644
--- a/example.py
+++ b/example.py
@@ -17,7 +17,7 @@ from draftjs_exporter.dom import DOM
 from draftjs_exporter.html import HTML
 
 
-def Blockquote(props):
+def blockquote(props):
     block_data = props['block']['data']
 
     return DOM.create_element('blockquote', {
@@ -25,7 +25,7 @@ def Blockquote(props):
     }, props['children'])
 
 
-def ListItem(props):
+def list_item(props):
     depth = props['block']['depth']
 
     return DOM.create_element('li', {
@@ -33,7 +33,7 @@ def ListItem(props):
     }, props['children'])
 
 
-def OrderedList(props):
+def ordered_list(props):
     depth = props['block']['depth']
 
     return DOM.create_element('ol', {
@@ -41,7 +41,7 @@ def OrderedList(props):
     }, props['children'])
 
 
-def Image(props):
+def image(props):
     return DOM.create_element('img', {
         'src': props.get('src'),
         'width': props.get('width'),
@@ -50,78 +50,61 @@ def Image(props):
     })
 
 
-class Link:
-    def __init__(self, use_new_window=False):
-        self.use_new_window = use_new_window
-
-    def render(self, props):
-        link_props = {
-            'href': props['url'],
-        }
-
-        if self.use_new_window:
-            link_props['target'] = '_blank'
-            link_props['rel'] = 'noreferrer noopener'
-
-        return DOM.create_element('a', link_props, props['children'])
+def link(props):
+    return DOM.create_element('a', {
+        'href': props['url']
+    }, props['children'])
 
 
-class BR:
+def br(props):
     """
     Replace line breaks (\n) with br tags.
     """
-    SEARCH_RE = re.compile(r'\n')
-
-    def render(self, props):
-        # Do not process matches inside code blocks.
-        if props['block']['type'] == BLOCK_TYPES.CODE:
-            return props['children']
+    # Do not process matches inside code blocks.
+    if props['block']['type'] == BLOCK_TYPES.CODE:
+        return props['children']
 
-        return DOM.create_element('br')
+    return DOM.create_element('br')
 
 
-class Hashtag:
+def hashtag(props):
     """
     Wrap hashtags in spans with a specific class.
     """
-    SEARCH_RE = re.compile(r'#\w+')
+    # Do not process matches inside code blocks.
+    if props['block']['type'] == BLOCK_TYPES.CODE:
+        return props['children']
 
-    def render(self, props):
-        # Do not process matches inside code blocks.
-        if props['block']['type'] == BLOCK_TYPES.CODE:
-            return props['children']
+    return DOM.create_element('span', {'class': 'hashtag'}, props['children'])
 
-        return DOM.create_element('span', {'class': 'hashtag'}, props['children'])
 
+# See http://pythex.org/?regex=(http%3A%2F%2F%7Chttps%3A%2F%2F%7Cwww%5C.)(%5Ba-zA-Z0-9%5C.%5C-%25%2F%5C%3F%26_%3D%5C%2B%23%3A~!%2C%5C%27%5C*%5C%5E%24%5D%2B)&test_string=search%20http%3A%2F%2Fa.us%20or%20https%3A%2F%2Fyahoo.com%20or%20www.google.com%20for%20%23github%20and%20%23facebook&ignorecase=0&multiline=0&dotall=0&verbose=0
+LINKIFY_RE = re.compile(r'(http://|https://|www\.)([a-zA-Z0-9\.\-%/\?&_=\+#:~!,\'\*\^$]+)')
 
-class Linkify:
+
+def linkify(props):
     """
     Wrap plain URLs with link tags.
-    See http://pythex.org/?regex=(http%3A%2F%2F%7Chttps%3A%2F%2F%7Cwww%5C.)(%5Ba-zA-Z0-9%5C.%5C-%25%2F%5C%3F%26_%3D%5C%2B%23%3A~!%2C%5C%27%5C*%5C%5E%24%5D%2B)&test_string=search%20http%3A%2F%2Fa.us%20or%20https%3A%2F%2Fyahoo.com%20or%20www.google.com%20for%20%23github%20and%20%23facebook&ignorecase=0&multiline=0&dotall=0&verbose=0
-    for an example.
     """
-    SEARCH_RE = re.compile(r'(http://|https://|www\.)([a-zA-Z0-9\.\-%/\?&_=\+#:~!,\'\*\^$]+)')
-
-    def render(self, props):
-        match = props['match']
-        protocol = match.group(1)
-        url = match.group(2)
-        href = protocol + url
+    match = props['match']
+    protocol = match.group(1)
+    url = match.group(2)
+    href = protocol + url
 
-        if props['block']['type'] == BLOCK_TYPES.CODE:
-            return href
+    if props['block']['type'] == BLOCK_TYPES.CODE:
+        return href
 
-        link_props = {
-            'href': href,
-        }
+    link_props = {
+        'href': href,
+    }
 
-        if href.startswith('www'):
-            link_props['href'] = 'http://' + href
+    if href.startswith('www'):
+        link_props['href'] = 'http://' + href
 
-        return DOM.create_element('a', link_props, href)
+    return DOM.create_element('a', link_props, href)
 
 
-def BlockFallback(props):
+def block_fallback(props):
     type_ = props['block']['type']
 
     if type_ == 'example-discard':
@@ -138,7 +121,7 @@ def BlockFallback(props):
         return DOM.create_element('div', {}, props['children'])
 
 
-def EntityFallback(props):
+def entity_fallback(props):
     type_ = props['entity']['type']
     logging.warn('Missing config for "%s".' % type_)
     return DOM.create_element('span', {'class': 'missing-entity'}, props['children'])
@@ -160,13 +143,13 @@ if __name__ == '__main__':
                 'wrapper_props': {'class': 'bullet-list'},
             },
             # Use a custom component for more flexibility (reading block data or depth).
-            BLOCK_TYPES.BLOCKQUOTE: Blockquote,
+            BLOCK_TYPES.BLOCKQUOTE: blockquote,
             BLOCK_TYPES.ORDERED_LIST_ITEM: {
-                'element': ListItem,
-                'wrapper': OrderedList,
+                'element': list_item,
+                'wrapper': ordered_list,
             },
             # Provide a fallback component (advanced).
-            BLOCK_TYPES.FALLBACK: BlockFallback
+            BLOCK_TYPES.FALLBACK: block_fallback
         }),
         # `style_map` defines the HTML representation of inline elements.
         # Extend STYLE_MAP to start with sane defaults, or make your own from scratch.
@@ -178,21 +161,29 @@ if __name__ == '__main__':
         }),
         'entity_decorators': {
             # Map entities to components so they can be rendered with their data.
-            ENTITY_TYPES.IMAGE: Image,
-            # Components can be defined as classes to receive extra parameters.
-            ENTITY_TYPES.LINK: Link(use_new_window=True),
+            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,
             # Provide a fallback component (advanced).
-            ENTITY_TYPES.FALLBACK: EntityFallback,
+            ENTITY_TYPES.FALLBACK: entity_fallback,
         },
         'composite_decorators': [
             # Use composite decorators to replace text based on a regular expression.
-            BR,
-            Hashtag,
-            Linkify,
+            {
+                'strategy': re.compile(r'\n'),
+                'component': br,
+            },
+            {
+                'strategy': re.compile(r'#\w+'),
+                'component': hashtag,
+            },
+            {
+                'strategy': LINKIFY_RE,
+                'component': linkify,
+            },
         ],
         # Specify which DOM backing engine to use.
         'engine': 'string',
@@ -579,7 +570,7 @@ if __name__ == '__main__':
             "data": {}
         }, {
             "key": "ed7hu",
-            "text": "def Blockquote(props):\n    block_data = props['block']['data']\n    return DOM.create_element('blockquote', {\n        'cite': block_data.get('cite')\n    }, props['children'])\n",
+            "text": "def blockquote(props):\n    block_data = props['block']['data']\n    return DOM.create_element('blockquote', {\n        'cite': block_data.get('cite')\n    }, props['children'])\n",
             "type": "code-block",
             "depth": 0,
             "inlineStyleRanges": [],

Remove support for class based decorator
Capturing offline discussions for a public discussion.

Currently a decorator can be written as a `function` (accepting a single positional argument `props`) or a `class` (with a single `render` method accepting a single positional argument `props`) which has a few issues:
- Nothing is passed to the `__init__` of the `class` but instead everything is passed to `props` which makes the `class` quite useless. Moving the props to the `__init__` will either create  boilerplate or will force the user to inherit from a custom class which does that for them. In short, it's not clear which benefit there is to use a class.
- To keep naming consistent, functions are names are camel cased (so when you update your config file, you don't have to think whether it's a function or a class) which gets the linter to complain. The library should not encourage non-PEP8 compliant code.

We were therefore thinking of removing support for class based decorators. Any thoughts?
diff --git a/tests/test_composite_decorators.py b/tests/test_composite_decorators.py
index 94a8f2f..721f4dd 100644
--- a/tests/test_composite_decorators.py
+++ b/tests/test_composite_decorators.py
@@ -6,127 +6,64 @@ import unittest
 from draftjs_exporter.composite_decorators import render_decorators
 from draftjs_exporter.constants import BLOCK_TYPES
 from draftjs_exporter.dom import DOM
+from example import LINKIFY_RE, br, hashtag, linkify
 
+BR_DECORATOR = {
+    'strategy': re.compile(r'\n'),
+    'component': br,
+}
 
-class Linkify:
-    """
-    Wrap plain URLs with link tags.
-    See http://pythex.org/?regex=(http%3A%2F%2F%7Chttps%3A%2F%2F%7Cwww%5C.)(%5Ba-zA-Z0-9%5C.%5C-%25%2F%5C%3F%26_%3D%5C%2B%23%3A~!%2C%5C%27%5C*%5C%5E%24%5D%2B)&test_string=search%20http%3A%2F%2Fa.us%20or%20https%3A%2F%2Fyahoo.com%20or%20www.google.com%20for%20%23github%20and%20%23facebook&ignorecase=0&multiline=0&dotall=0&verbose=0
-    for an example.
-    """
-    SEARCH_RE = re.compile(r'(http://|https://|www\.)([a-zA-Z0-9\.\-%/\?&_=\+#:~!,\'\*\^$]+)')
+HASHTAG_DECORATOR = {
+    'strategy': re.compile(r'#\w+'),
+    'component': hashtag,
+}
 
-    def __init__(self, use_new_window=False):
-        self.use_new_window = use_new_window
-
-    def render(self, props):
-        match = props['match']
-        protocol = match.group(1)
-        url = match.group(2)
-        href = protocol + url
-
-        if props['block']['type'] == BLOCK_TYPES.CODE:
-            return href
-
-        link_props = {
-            'href': href,
-        }
-
-        if self.use_new_window:
-            link_props['target'] = '_blank'
-            link_props['rel'] = 'noreferrer noopener'
-
-        if href.startswith('www'):
-            link_props['href'] = 'http://' + href
-
-        return DOM.create_element('a', link_props, href)
-
-
-class Hashtag:
-    """
-    Wrap hashtags in spans with a specific class.
-    """
-    SEARCH_RE = re.compile(r'#\w+')
-
-    def render(self, props):
-        # Do not process matches inside code blocks.
-        if props['block']['type'] == BLOCK_TYPES.CODE:
-            return props['children']
-
-        return DOM.create_element('span', {'class': 'hashtag'}, props['children'])
-
-
-class BR:
-    """
-    Replace line breaks (\n) with br tags.
-    """
-    SEARCH_RE = re.compile(r'\n')
-
-    def render(self, props):
-        # Do not process matches inside code blocks.
-        if props['block']['type'] == BLOCK_TYPES.CODE:
-            return props['children']
-
-        return DOM.create_element('br')
+LINKIFY_DECORATOR = {
+    'strategy': LINKIFY_RE,
+    'component': linkify,
+}
 
 
 class TestLinkify(unittest.TestCase):
-    def test_init(self):
-        self.assertIsInstance(Linkify(), Linkify)
-
     def test_render(self):
-        match = next(Linkify.SEARCH_RE.finditer('test https://www.example.com'))
+        match = next(LINKIFY_DECORATOR['strategy'].finditer('test https://www.example.com'))
 
-        self.assertEqual(DOM.render(DOM.create_element(Linkify, {
+        self.assertEqual(DOM.render(DOM.create_element(LINKIFY_DECORATOR['component'], {
             'block': {'type': BLOCK_TYPES.UNSTYLED},
             'match': match,
         }, match.group(0))), '<a href="https://www.example.com">https://www.example.com</a>')
 
     def test_render_www(self):
-        match = next(Linkify.SEARCH_RE.finditer('test www.example.com'))
+        match = next(LINKIFY_DECORATOR['strategy'].finditer('test www.example.com'))
 
-        self.assertEqual(DOM.render(DOM.create_element(Linkify, {
+        self.assertEqual(DOM.render(DOM.create_element(LINKIFY_DECORATOR['component'], {
             'block': {'type': BLOCK_TYPES.UNSTYLED},
             'match': match,
         }, match.group(0))), '<a href="http://www.example.com">www.example.com</a>')
 
     def test_render_code_block(self):
-        match = next(Linkify.SEARCH_RE.finditer('test https://www.example.com'))
+        match = next(LINKIFY_DECORATOR['strategy'].finditer('test https://www.example.com'))
 
-        self.assertEqual(DOM.create_element(Linkify, {
+        self.assertEqual(DOM.create_element(LINKIFY_DECORATOR['component'], {
             'block': {'type': BLOCK_TYPES.CODE},
             'match': match,
         }, match.group(0)), match.group(0))
 
-    def test_render_new_window(self):
-        match = next(Linkify.SEARCH_RE.finditer('test https://www.example.com'))
-
-        self.assertEqual(DOM.render(DOM.create_element(Linkify(use_new_window=True), {
-            'block': {'type': BLOCK_TYPES.UNSTYLED},
-            'match': match,
-        }, match.group(0))), '<a href="https://www.example.com" rel="noreferrer noopener" target="_blank">https://www.example.com</a>')
-
 
 class TestHashtag(unittest.TestCase):
-    def test_init(self):
-        self.assertIsInstance(Hashtag(), Hashtag)
-
     def test_render(self):
-        self.assertEqual(DOM.render(DOM.create_element(Hashtag, {'block': {'type': BLOCK_TYPES.UNSTYLED}}, '#hashtagtest')), '<span class="hashtag">#hashtagtest</span>')
+        self.assertEqual(DOM.render(DOM.create_element(HASHTAG_DECORATOR['component'], {'block': {'type': BLOCK_TYPES.UNSTYLED}}, '#hashtagtest')), '<span class="hashtag">#hashtagtest</span>')
 
     def test_render_code_block(self):
-        self.assertEqual(DOM.render(DOM.create_element(Hashtag, {'block': {'type': BLOCK_TYPES.CODE}}, '#hashtagtest')), '#hashtagtest')
+        self.assertEqual(DOM.render(DOM.create_element(HASHTAG_DECORATOR['component'], {'block': {'type': BLOCK_TYPES.CODE}}, '#hashtagtest')), '#hashtagtest')
 
 
 class TestBR(unittest.TestCase):
-    def test_init(self):
-        self.assertIsInstance(BR(), BR)
-
     def test_render(self):
-        self.assertEqual(DOM.render(DOM.create_element(BR, {'block': {'type': BLOCK_TYPES.UNSTYLED}}, '\n')), '<br/>')
+        self.assertEqual(DOM.render(DOM.create_element(BR_DECORATOR['component'], {'block': {'type': BLOCK_TYPES.UNSTYLED}}, '\n')), '<br/>')
 
     def test_render_code_block(self):
-        self.assertEqual(DOM.create_element(BR, {'block': {'type': BLOCK_TYPES.CODE}}, '\n'), '\n')
+        self.assertEqual(DOM.create_element(BR_DECORATOR['component'], {'block': {'type': BLOCK_TYPES.CODE}}, '\n'), '\n')
 
 
 class TestCompositeDecorators(unittest.TestCase):
@@ -134,10 +71,10 @@ class TestCompositeDecorators(unittest.TestCase):
         self.assertEqual(DOM.render(render_decorators([], 'test https://www.example.com#hash #hashtagtest', {'type': BLOCK_TYPES.UNSTYLED, 'depth': 0})), 'test https://www.example.com#hash #hashtagtest')
 
     def test_render_decorators_single(self):
-        self.assertEqual(DOM.render(render_decorators([Linkify()], 'test https://www.example.com#hash #hashtagtest', {'type': BLOCK_TYPES.UNSTYLED, 'depth': 0})), 'test <a href="https://www.example.com#hash">https://www.example.com#hash</a> #hashtagtest')
+        self.assertEqual(DOM.render(render_decorators([LINKIFY_DECORATOR], 'test https://www.example.com#hash #hashtagtest', {'type': BLOCK_TYPES.UNSTYLED, 'depth': 0})), 'test <a href="https://www.example.com#hash">https://www.example.com#hash</a> #hashtagtest')
 
     def test_render_decorators_conflicting_order_one(self):
-        self.assertEqual(DOM.render(render_decorators([Linkify(), Hashtag()], 'test https://www.example.com#hash #hashtagtest', {'type': BLOCK_TYPES.UNSTYLED, 'depth': 0})), 'test <a href="https://www.example.com#hash">https://www.example.com#hash</a> <span class="hashtag">#hashtagtest</span>')
+        self.assertEqual(DOM.render(render_decorators([LINKIFY_DECORATOR, HASHTAG_DECORATOR], 'test https://www.example.com#hash #hashtagtest', {'type': BLOCK_TYPES.UNSTYLED, 'depth': 0})), 'test <a href="https://www.example.com#hash">https://www.example.com#hash</a> <span class="hashtag">#hashtagtest</span>')
 
     def test_render_decorators_conflicting_order_two(self):
-        self.assertEqual(DOM.render(render_decorators([Hashtag(), Linkify()], 'test https://www.example.com#hash #hashtagtest', {'type': BLOCK_TYPES.UNSTYLED, 'depth': 0})), 'test https://www.example.com<span class="hashtag">#hash</span> <span class="hashtag">#hashtagtest</span>')
+        self.assertEqual(DOM.render(render_decorators([HASHTAG_DECORATOR, LINKIFY_DECORATOR], 'test https://www.example.com#hash #hashtagtest', {'type': BLOCK_TYPES.UNSTYLED, 'depth': 0})), 'test https://www.example.com<span class="hashtag">#hash</span> <span class="hashtag">#hashtagtest</span>')
diff --git a/tests/test_defaults.py b/tests/test_defaults.py
index 14bdcd7..58839d7 100644
--- a/tests/test_defaults.py
+++ b/tests/test_defaults.py
@@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals
 
 import unittest
 
-from draftjs_exporter.defaults import BLOCK_MAP, STYLE_MAP, CodeBlock, render_children
+from draftjs_exporter.defaults import BLOCK_MAP, STYLE_MAP, code_block, render_children
 from draftjs_exporter.dom import DOM
 
 
@@ -16,7 +16,5 @@ class TestDefaults(unittest.TestCase):
     def test_render_children(self):
         self.assertEqual(render_children({'children': 'test'}), 'test')
 
-
-class TestCodeBlock(unittest.TestCase):
-    def test_render(self):
-        self.assertEqual(DOM.render_debug(CodeBlock({'children': 'test'})), '<pre><code>test</code></pre>')
+    def test_render_code_block(self):
+        self.assertEqual(DOM.render_debug(code_block({'children': 'test'})), '<pre><code>test</code></pre>')
diff --git a/tests/test_dom.py b/tests/test_dom.py
index 0695b50..e4b7617 100644
--- a/tests/test_dom.py
+++ b/tests/test_dom.py
@@ -6,7 +6,7 @@ from draftjs_exporter.dom import DOM
 from draftjs_exporter.engines.html5lib import DOM_HTML5LIB
 from draftjs_exporter.engines.lxml import DOM_LXML
 from draftjs_exporter.error import ConfigException
-from tests.test_entities import Icon
+from tests.test_entities import icon
 
 
 class DOMTestImpl(object):
@@ -54,10 +54,7 @@ class TestDOM(unittest.TestCase):
         self.assertEqual(DOM.render_debug(DOM.create_element('a', {}, None, DOM.create_element('span', {}, 'Test test'))), '<a><span>Test test</span></a>')
 
     def test_create_element_entity(self):
-        self.assertEqual(DOM.render_debug(DOM.create_element(Icon, {'name': 'rocket'})), '<svg class="icon"><use xlink:href="#icon-rocket"></use></svg>')
-
-    def test_create_element_entity_configured(self):
-        self.assertEqual(DOM.render_debug(DOM.create_element(Icon(icon_class='i'), {'name': 'rocket'})), '<svg class="i"><use xlink:href="#icon-rocket"></use></svg>')
+        self.assertEqual(DOM.render_debug(DOM.create_element(icon, {'name': 'rocket'})), '<svg class="icon"><use xlink:href="#icon-rocket"></use></svg>')
 
     def test_parse_html(self):
         self.assertEqual(DOM.render_debug(DOM.parse_html('<p><span>Test text</span></p>')), '<p><span>Test text</span></p>')
diff --git a/tests/test_entities.py b/tests/test_entities.py
index d80a3b5..eea6c4e 100644
--- a/tests/test_entities.py
+++ b/tests/test_entities.py
@@ -5,25 +5,20 @@ import unittest
 from draftjs_exporter.dom import DOM
 
 
-def Null(props):
-    return DOM.create_element()
-
-
-def HR(props):
+def hr(props):
     return DOM.create_element('hr')
 
 
-class Link:
-    def render(self, props):
-        attributes = {}
-        for key in props:
-            attr = key if key != 'url' else 'href'
-            attributes[attr] = props[key]
+def link(props):
+    attributes = {}
+    for key in props:
+        attr = key if key != 'url' else 'href'
+        attributes[attr] = props[key]
 
-        return DOM.create_element('a', attributes, props['children'])
+    return DOM.create_element('a', attributes, props['children'])
 
 
-def Image(props):
+def image(props):
     return DOM.create_element('img', {
         'src': props.get('src'),
         'width': props.get('width'),
@@ -32,45 +27,32 @@ def Image(props):
     })
 
 
-class Icon:
-    def __init__(self, icon_class='icon'):
-        self.icon_class = icon_class
+def icon(props):
+    href = '#icon-%s' % props.get('name', '')
+    return DOM.create_element('svg', {'class': 'icon'}, DOM.create_element('use', {'xlink:href': href}))
 
-    def render(self, props):
-        href = '#icon-%s' % props.get('name', '')
-        return DOM.create_element('svg', {'class': self.icon_class}, DOM.create_element('use', {'xlink:href': href}))
 
+def button(props):
+    href = props.get('href', '#')
+    icon_name = props.get('icon', None)
+    text = props.get('text', '')
 
-class Button:
-    def render(self, props):
-        href = props.get('href', '#')
-        icon = props.get('icon', None)
-        text = props.get('text', '')
-
-        return DOM.create_element(
-            'a',
-            {'class': 'icon-text' if icon else None, 'href': href},
-            DOM.create_element(Icon, {'name': icon}) if icon else None,
-            DOM.create_element('span', {'class': 'icon-text__text'}, text) if icon else text
-        )
-
-
-class TestNull(unittest.TestCase):
-    def test_render(self):
-        self.assertEqual(DOM.render(DOM.create_element(Null)), '')
+    return DOM.create_element(
+        'a',
+        {'class': 'icon-text' if icon_name else None, 'href': href},
+        DOM.create_element(icon, {'name': icon_name}) if icon_name else None,
+        DOM.create_element('span', {'class': 'icon-text__text'}, text) if icon_name else text
+    )
 
 
 class TestIcon(unittest.TestCase):
     def test_render(self):
-        self.assertEqual(DOM.render(DOM.create_element(Icon, {'name': 'rocket'})), '<svg class="icon"><use xlink:href="#icon-rocket"></use></svg>')
-
-    def test_render_configured(self):
-        self.assertEqual(DOM.render(DOM.create_element(Icon(icon_class='i'), {'name': 'rocket'})), '<svg class="i"><use xlink:href="#icon-rocket"></use></svg>')
+        self.assertEqual(DOM.render(DOM.create_element(icon, {'name': 'rocket'})), '<svg class="icon"><use xlink:href="#icon-rocket"></use></svg>')
 
 
 class TestImage(unittest.TestCase):
     def test_render(self):
-        self.assertEqual(DOM.render(DOM.create_element(Image, {
+        self.assertEqual(DOM.render(DOM.create_element(image, {
             'src': 'http://example.com/example.png',
             'width': 320,
             'height': 580,
@@ -79,21 +61,21 @@ class TestImage(unittest.TestCase):
 
 class TestLink(unittest.TestCase):
     def test_render(self):
-        self.assertEqual(DOM.render(DOM.create_element(Link, {
+        self.assertEqual(DOM.render(DOM.create_element(link, {
             'url': 'http://example.com',
         }, 'wow')), '<a href="http://example.com">wow</a>')
 
 
 class TestButton(unittest.TestCase):
     def test_render_with_icon(self):
-        self.assertEqual(DOM.render(DOM.create_element(Button, {
+        self.assertEqual(DOM.render(DOM.create_element(button, {
             'href': 'http://example.com',
             'icon': 'rocket',
             'text': 'Launch',
         })), '<a class="icon-text" href="http://example.com"><svg class="icon"><use xlink:href="#icon-rocket"></use></svg><span class="icon-text__text">Launch</span></a>')
 
     def test_render_without_icon(self):
-        self.assertEqual(DOM.render(DOM.create_element(Button, {
+        self.assertEqual(DOM.render(DOM.create_element(button, {
             'href': 'http://example.com',
             'text': 'Launch',
         })), '<a href="http://example.com">Launch</a>')
diff --git a/tests/test_entity_state.py b/tests/test_entity_state.py
index 7c91ff0..743d76d 100644
--- a/tests/test_entity_state.py
+++ b/tests/test_entity_state.py
@@ -4,10 +4,10 @@ import unittest
 
 from draftjs_exporter.command import Command
 from draftjs_exporter.entity_state import EntityException, EntityState
-from tests.test_entities import Link
+from tests.test_entities import link
 
 entity_decorators = {
-    'LINK': Link()
+    'LINK': link
 }
 
 entity_map = {
diff --git a/tests/test_exports.py b/tests/test_exports.py
index f9b8650..dfd50a6 100644
--- a/tests/test_exports.py
+++ b/tests/test_exports.py
@@ -12,23 +12,23 @@ from draftjs_exporter.constants import BLOCK_TYPES, ENTITY_TYPES
 from draftjs_exporter.defaults import BLOCK_MAP, STYLE_MAP
 from draftjs_exporter.dom import DOM
 from draftjs_exporter.html import HTML
-from tests.test_composite_decorators import BR, Hashtag, Linkify
-from tests.test_entities import HR, Image, Link
+from tests.test_composite_decorators import BR_DECORATOR, HASHTAG_DECORATOR, LINKIFY_DECORATOR
+from tests.test_entities import hr, image, link
 
 fixtures_path = os.path.join(os.path.dirname(__file__), 'test_exports.json')
 fixtures = json.loads(open(fixtures_path, 'r').read())
 
 exporter = HTML({
     'entity_decorators': {
-        ENTITY_TYPES.LINK: Link,
-        ENTITY_TYPES.IMAGE: Image,
-        ENTITY_TYPES.HORIZONTAL_RULE: HR,
+        ENTITY_TYPES.LINK: link,
+        ENTITY_TYPES.HORIZONTAL_RULE: hr,
+        ENTITY_TYPES.IMAGE: image,
         ENTITY_TYPES.EMBED: None,
     },
     'composite_decorators': [
-        BR,
-        Linkify,
-        Hashtag,
+        BR_DECORATOR,
+        LINKIFY_DECORATOR,
+        HASHTAG_DECORATOR,
     ],
     'block_map': dict(BLOCK_MAP, **{
         BLOCK_TYPES.UNORDERED_LIST_ITEM: {
diff --git a/tests/test_output.py b/tests/test_output.py
index cc643f1..fac2bfb 100644
--- a/tests/test_output.py
+++ b/tests/test_output.py
@@ -7,20 +7,20 @@ from draftjs_exporter.constants import BLOCK_TYPES, ENTITY_TYPES, INLINE_STYLES
 from draftjs_exporter.defaults import BLOCK_MAP
 from draftjs_exporter.entity_state import EntityException
 from draftjs_exporter.html import HTML
-from tests.test_composite_decorators import BR, Hashtag, Linkify
-from tests.test_entities import HR, Image, Link
-from tests.test_wrapper_state import Blockquote
+from tests.test_composite_decorators import BR_DECORATOR, HASHTAG_DECORATOR, LINKIFY_DECORATOR
+from tests.test_entities import hr, image, link
+from tests.test_wrapper_state import blockquote
 
 config = {
     'entity_decorators': {
-        ENTITY_TYPES.LINK: Link,
-        ENTITY_TYPES.HORIZONTAL_RULE: HR,
-        ENTITY_TYPES.IMAGE: Image
+        ENTITY_TYPES.LINK: link,
+        ENTITY_TYPES.HORIZONTAL_RULE: hr,
+        ENTITY_TYPES.IMAGE: image
     },
     'composite_decorators': [
-        Linkify,
-        Hashtag,
-        BR,
+        LINKIFY_DECORATOR,
+        HASHTAG_DECORATOR,
+        BR_DECORATOR,
     ],
     'block_map': dict(BLOCK_MAP, **{
         BLOCK_TYPES.UNORDERED_LIST_ITEM: {
@@ -29,7 +29,7 @@ config = {
             'wrapper_props': {'class': 'steps'},
         },
         'blockquote': {
-            'element': Blockquote,
+            'element': blockquote,
             'wrapper': 'div',
         },
     }),
@@ -924,7 +924,7 @@ class TestOutput(unittest.TestCase):
     def test_render_with_big_content(self):
         self.assertEqual(HTML({
             'entity_decorators': {
-                'LINK': Link()
+                'LINK': link
             },
             'block_map': {
                 'header-two': {'element': 'h2'},
diff --git a/tests/test_wrapper_state.py b/tests/test_wrapper_state.py
index ca58cd5..60deb78 100644
--- a/tests/test_wrapper_state.py
+++ b/tests/test_wrapper_state.py
@@ -4,30 +4,7 @@ import unittest
 
 from draftjs_exporter.dom import DOM
 from draftjs_exporter.wrapper_state import WrapperState
-
-
-def Blockquote(props):
-    block_data = props['block']['data']
-
-    return DOM.create_element('blockquote', {
-        'cite': block_data.get('cite')
-    }, props['children'])
-
-
-def ListItem(props):
-    depth = props['block']['depth']
-
-    return DOM.create_element('li', {
-        'class': 'list-item--depth-{0}'.format(depth)
-    }, props['children'])
-
-
-def OrderedList(props):
-    depth = props['block']['depth']
-
-    return DOM.create_element('ol', {
-        'class': 'list--depth-{0}'.format(depth)
-    }, props['children'])
+from example import blockquote, list_item, ordered_list
 
 
 class TestWrapperState(unittest.TestCase):
@@ -39,10 +16,10 @@ class TestWrapperState(unittest.TestCase):
             'unstyled': 'div',
             'atomic': lambda props: props['children'],
             'ignore': None,
-            'blockquote': Blockquote,
+            'blockquote': blockquote,
             'ordered-list-item': {
-                'element': ListItem,
-                'wrapper': OrderedList
+                'element': list_item,
+                'wrapper': ordered_list
             },
         })
 
@@ -132,7 +109,7 @@ class TestBlockquote(unittest.TestCase):
         DOM.use(DOM.HTML5LIB)
 
     def test_render_debug(self):
-        self.assertEqual(DOM.render_debug(DOM.create_element(Blockquote, {
+        self.assertEqual(DOM.render_debug(DOM.create_element(blockquote, {
             'block': {
                 'data': {
                     'cite': 'http://example.com/',
@@ -146,7 +123,7 @@ class TestListItem(unittest.TestCase):
         DOM.use(DOM.HTML5LIB)
 
     def test_render_debug(self):
-        self.assertEqual(DOM.render_debug(DOM.create_element(ListItem, {
+        self.assertEqual(DOM.render_debug(DOM.create_element(list_item, {
             'block': {
                 'depth': 5,
             },

pip install -e .[testing,docs]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
beautifulsoup4==4.13.3
cachetools==5.5.2
chardet==5.2.0
colorama==0.4.6
coverage==7.8.0
distlib==0.3.9
-e git+https://github.com/springload/draftjs_exporter.git@13e1d0e7f159abcdc2e547806fe69cac1cf15e1a#egg=draftjs_exporter
exceptiongroup==1.2.2
execnet==2.1.1
filelock==3.18.0
flake8==7.2.0
html5lib==1.0b10
iniconfig==2.1.0
isort==4.2.5
lxml==5.3.1
markov-draftjs==0.1.1
mccabe==0.7.0
memory_profiler==0.47
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
psutil==5.4.1
pycodestyle==2.13.0
pyflakes==3.3.1
pyproject-api==1.9.0
pytest==8.3.5
pytest-asyncio==0.26.0
pytest-cov==6.0.0
pytest-mock==3.14.0
pytest-xdist==3.6.1
six==1.17.0
soupsieve==2.6
tomli==2.2.1
tox==4.25.0
typing_extensions==4.13.0
virtualenv==20.29.3
webencodings==0.5.1

name: draftjs_exporter
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - beautifulsoup4==4.13.3
      - cachetools==5.5.2
      - chardet==5.2.0
      - colorama==0.4.6
      - coverage==7.8.0
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - filelock==3.18.0
      - flake8==7.2.0
      - html5lib==1.0b10
      - iniconfig==2.1.0
      - isort==4.2.5
      - lxml==5.3.1
      - markov-draftjs==0.1.1
      - mccabe==0.7.0
      - memory-profiler==0.47
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - psutil==5.4.1
      - pycodestyle==2.13.0
      - pyflakes==3.3.1
      - pyproject-api==1.9.0
      - pytest==8.3.5
      - pytest-asyncio==0.26.0
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - pytest-xdist==3.6.1
      - six==1.17.0
      - soupsieve==2.6
      - tomli==2.2.1
      - tox==4.25.0
      - typing-extensions==4.13.0
      - virtualenv==20.29.3
      - webencodings==0.5.1
prefix: /opt/conda/envs/draftjs_exporter

tests/test_composite_decorators.py::TestLinkify::test_render tests/test_composite_decorators.py::TestLinkify::test_render_code_block tests/test_composite_decorators.py::TestLinkify::test_render_www tests/test_composite_decorators.py::TestHashtag::test_render tests/test_composite_decorators.py::TestHashtag::test_render_code_block tests/test_composite_decorators.py::TestBR::test_render tests/test_composite_decorators.py::TestBR::test_render_code_block tests/test_composite_decorators.py::TestCompositeDecorators::test_render_decorators_conflicting_order_one tests/test_composite_decorators.py::TestCompositeDecorators::test_render_decorators_conflicting_order_two tests/test_composite_decorators.py::TestCompositeDecorators::test_render_decorators_empty tests/test_composite_decorators.py::TestCompositeDecorators::test_render_decorators_single tests/test_defaults.py::TestDefaults::test_default_block_map tests/test_defaults.py::TestDefaults::test_default_style_map tests/test_defaults.py::TestDefaults::test_render_children tests/test_defaults.py::TestDefaults::test_render_code_block tests/test_dom.py::TestDOM::test_append_child tests/test_dom.py::TestDOM::test_camel_to_dash tests/test_dom.py::TestDOM::test_create_element tests/test_dom.py::TestDOM::test_create_element_empty tests/test_dom.py::TestDOM::test_create_element_entity tests/test_dom.py::TestDOM::test_create_element_nested tests/test_dom.py::TestDOM::test_create_element_none tests/test_dom.py::TestDOM::test_create_element_style_dict tests/test_dom.py::TestDOM::test_create_element_style_str tests/test_dom.py::TestDOM::test_parse_html tests/test_dom.py::TestDOM::test_render_debug tests/test_dom.py::TestDOM::test_use_custom tests/test_dom.py::TestDOM::test_use_html5lib tests/test_dom.py::TestDOM::test_use_invalid tests/test_dom.py::TestDOM::test_use_lxml tests/test_entities.py::TestIcon::test_render tests/test_entities.py::TestImage::test_render tests/test_entities.py::TestLink::test_render tests/test_entities.py::TestButton::test_render_with_icon tests/test_entities.py::TestButton::test_render_without_icon tests/test_entity_state.py::TestEntityState::test_apply_raises tests/test_entity_state.py::TestEntityState::test_apply_start_entity tests/test_entity_state.py::TestEntityState::test_apply_stop_entity tests/test_entity_state.py::TestEntityState::test_get_entity_details tests/test_entity_state.py::TestEntityState::test_get_entity_details_raises tests/test_entity_state.py::TestEntityState::test_has_no_entity_default tests/test_entity_state.py::TestEntityState::test_has_no_entity_styled tests/test_entity_state.py::TestEntityState::test_init tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_adjacent_inline_styles tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_all_plain_html_elements_we_need tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_big_content_export tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_entity tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_entity_with_data-* tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_entity_with_inline_style tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_from_https://github.com/icelab/draft-js-ast-exporter/blob/651c807bea12d97dad6f4965ab40481c8f2130dd/test/fixtures/content.js tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_html_entities_escaping tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_multiple_decorators tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_nested_inline_styles tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_nested_inline_styles_(inverted) tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_ordered_list tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_plain_text tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_same_content_multiple_times tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_single_inline_style tests/test_exports.py::TestExportsHTML5LIB::test_export_html5lib_style_map_defaults tests/test_exports.py::TestExportsHTML5LIB::test_init_html5lib tests/test_exports.py::TestExportsLXML::test_export_lxml_adjacent_inline_styles tests/test_exports.py::TestExportsLXML::test_export_lxml_all_plain_html_elements_we_need tests/test_exports.py::TestExportsLXML::test_export_lxml_entity tests/test_exports.py::TestExportsLXML::test_export_lxml_entity_with_inline_style tests/test_exports.py::TestExportsLXML::test_export_lxml_from_https://github.com/icelab/draft-js-ast-exporter/blob/651c807bea12d97dad6f4965ab40481c8f2130dd/test/fixtures/content.js tests/test_exports.py::TestExportsLXML::test_export_lxml_html_entities_escaping tests/test_exports.py::TestExportsLXML::test_export_lxml_multiple_decorators tests/test_exports.py::TestExportsLXML::test_export_lxml_nested_inline_styles tests/test_exports.py::TestExportsLXML::test_export_lxml_nested_inline_styles_(inverted) tests/test_exports.py::TestExportsLXML::test_export_lxml_ordered_list tests/test_exports.py::TestExportsLXML::test_export_lxml_plain_text tests/test_exports.py::TestExportsLXML::test_export_lxml_same_content_multiple_times tests/test_exports.py::TestExportsLXML::test_export_lxml_single_inline_style tests/test_exports.py::TestExportsLXML::test_export_lxml_style_map_defaults tests/test_exports.py::TestExportsLXML::test_init tests/test_exports.py::TestExportsSTRING::test_export_string_adjacent_inline_styles tests/test_exports.py::TestExportsSTRING::test_export_string_all_plain_html_elements_we_need tests/test_exports.py::TestExportsSTRING::test_export_string_big_content_export tests/test_exports.py::TestExportsSTRING::test_export_string_entity tests/test_exports.py::TestExportsSTRING::test_export_string_entity_with_data-* tests/test_exports.py::TestExportsSTRING::test_export_string_entity_with_inline_style tests/test_exports.py::TestExportsSTRING::test_export_string_from_https://github.com/icelab/draft-js-ast-exporter/blob/651c807bea12d97dad6f4965ab40481c8f2130dd/test/fixtures/content.js tests/test_exports.py::TestExportsSTRING::test_export_string_html_entities_escaping tests/test_exports.py::TestExportsSTRING::test_export_string_multiple_decorators tests/test_exports.py::TestExportsSTRING::test_export_string_nested_inline_styles tests/test_exports.py::TestExportsSTRING::test_export_string_nested_inline_styles_(inverted) tests/test_exports.py::TestExportsSTRING::test_export_string_ordered_list tests/test_exports.py::TestExportsSTRING::test_export_string_plain_text tests/test_exports.py::TestExportsSTRING::test_export_string_same_content_multiple_times tests/test_exports.py::TestExportsSTRING::test_export_string_single_inline_style tests/test_exports.py::TestExportsSTRING::test_export_string_style_map_defaults tests/test_exports.py::TestExportsSTRING::test_init tests/test_output.py::TestOutput::test_render_empty tests/test_output.py::TestOutput::test_render_with_backtracking_nested_wrapping tests/test_output.py::TestOutput::test_render_with_big_content tests/test_output.py::TestOutput::test_render_with_boolean_attribute_false tests/test_output.py::TestOutput::test_render_with_boolean_attribute_true tests/test_output.py::TestOutput::test_render_with_default_block_map tests/test_output.py::TestOutput::test_render_with_default_config tests/test_output.py::TestOutput::test_render_with_default_style_map tests/test_output.py::TestOutput::test_render_with_different_blocks tests/test_output.py::TestOutput::test_render_with_element_options tests/test_output.py::TestOutput::test_render_with_entities tests/test_output.py::TestOutput::test_render_with_entities_crossing_raises tests/test_output.py::TestOutput::test_render_with_entity tests/test_output.py::TestOutput::test_render_with_entity_and_decorators tests/test_output.py::TestOutput::test_render_with_immediate_jumping tests/test_output.py::TestOutput::test_render_with_inline_styles tests/test_output.py::TestOutput::test_render_with_jumping_wrapping tests/test_output.py::TestOutput::test_render_with_line_breaks tests/test_output.py::TestOutput::test_render_with_many_line_breaks tests/test_output.py::TestOutput::test_render_with_multiple_decorators tests/test_output.py::TestOutput::test_render_with_multiple_inline_styles tests/test_output.py::TestOutput::test_render_with_no_zero_depth tests/test_output.py::TestOutput::test_render_with_none_attribute tests/test_output.py::TestOutput::test_render_with_none_component tests/test_output.py::TestOutput::test_render_with_none_return_value tests/test_output.py::TestOutput::test_render_with_number_attribute tests/test_output.py::TestOutput::test_render_with_styles_in_entities tests/test_output.py::TestOutput::test_render_with_unicode tests/test_output.py::TestOutput::test_render_with_unidirectional_nested_wrapping tests/test_output.py::TestOutput::test_render_with_unknown_attribute tests/test_output.py::TestOutput::test_render_with_wrapping tests/test_output.py::TestOutput::test_render_with_wrapping_reset tests/test_output.py::TestOutput::test_render_with_wrapping_reset_block_components tests/test_wrapper_state.py::TestWrapperState::test_element_for_component tests/test_wrapper_state.py::TestWrapperState::test_element_for_component_wrapper tests/test_wrapper_state.py::TestWrapperState::test_element_for_dismiss_content tests/test_wrapper_state.py::TestWrapperState::test_element_for_element_content tests/test_wrapper_state.py::TestWrapperState::test_element_for_no_block tests/test_wrapper_state.py::TestWrapperState::test_element_for_simple_content tests/test_wrapper_state.py::TestWrapperState::test_init tests/test_wrapper_state.py::TestWrapperState::test_str tests/test_wrapper_state.py::TestWrapperState::test_str_elts tests/test_wrapper_state.py::TestBlockquote::test_render_debug tests/test_wrapper_state.py::TestListItem::test_render_debug
tests/test_exports.py::TestExportsLXML::test_export_lxml_big_content_export tests/test_exports.py::TestExportsLXML::test_export_lxml_entity_with_data-*
README.rst draftjs_exporter/dom.py benchmark.py draftjs_exporter/defaults.py example.py draftjs_exporter/composite_decorators.py docs/example.md
README.rst draftjs_exporter/dom.py benchmark.py draftjs_exporter/defaults.py example.py draftjs_exporter/composite_decorators.py docs/example.md