diff --git a/packages/notus/lib/src/convert/markdown.dart b/packages/notus/lib/src/convert/markdown.dart
index 6e8f00a19..a1d3a99e8 100644
--- a/packages/notus/lib/src/convert/markdown.dart
+++ b/packages/notus/lib/src/convert/markdown.dart
@@ -4,20 +4,296 @@
 
 import 'dart:convert';
 
-import 'package:notus/notus.dart';
 import 'package:quill_delta/quill_delta.dart';
+import 'package:notus/notus.dart';
 
 class NotusMarkdownCodec extends Codec<Delta, String> {
   const NotusMarkdownCodec();
 
   @override
-  Converter<String, Delta> get decoder =>
-      throw UnimplementedError('Decoding is not implemented yet.');
+  Converter<String, Delta> get decoder => _NotusMarkdownDecoder();
 
   @override
   Converter<Delta, String> get encoder => _NotusMarkdownEncoder();
 }
 
+class _NotusMarkdownDecoder extends Converter<String, Delta> {
+  final List<Map<String, dynamic>> _attributesByStyleLength = [
+    null,
+    {'i': true}, // _
+    {'b': true}, // **
+    {'i': true, 'b': true} // **_
+  ];
+  final RegExp _headingRegExp = RegExp(r'(#+) *(.+)');
+  final RegExp _styleRegExp = RegExp(r'((?:\*|_){1,3})(.*?[^\1 ])\1');
+  final RegExp _linkRegExp = RegExp(r'\[([^\]]+)\]\(([^\)]+)\)');
+  final RegExp _ulRegExp = RegExp(r'^( *)\* +(.*)');
+  final RegExp _olRegExp = RegExp(r'^( *)\d+[\.)] +(.*)');
+  final RegExp _bqRegExp = RegExp(r'^> *(.*)');
+  final RegExp _codeRegExp = RegExp(r'^( *)```'); // TODO: inline code
+  bool _inBlockStack = false;
+//  final List<String> _blockStack = [];
+//  int _olDepth = 0;
+
+  @override
+  Delta convert(String input) {
+    final lines = input.split('\n');
+    final delta = Delta();
+
+    if(_allLinesEmpty(lines)) {
+      Map<String, dynamic> style;
+      _handleSpan(lines[0], delta, true, style);
+    } else {
+      for (var line in lines) {
+        _handleLine(line, delta);
+      }
+    }
+
+    return delta;
+  }
+
+  bool _allLinesEmpty(List<String> lines) {
+    for (var line in lines) {
+      if (line != '') {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  void _handleLine(String line, Delta delta, [Map<String, dynamic> attributes, bool isBlock]) {
+    if (_handleBlockQuote(line, delta, attributes)) {
+      return;
+    }
+    if (_handleBlock(line, delta, attributes)) {
+      return;
+    }
+    if (_handleHeading(line, delta, attributes)) {
+      return;
+    }
+
+    if (line.isNotEmpty) {
+      _handleSpan(line, delta, true, attributes, isBlock);
+    }
+  }
+
+  /// Markdown supports headings and blocks within blocks (except for within code)
+  /// but not blocks within headers, or ul within
+  bool _handleBlock(String line, Delta delta,
+      [Map<String, dynamic> attributes]) {
+    var match;
+
+    match = _codeRegExp.matchAsPrefix(line);
+    if (match != null) {
+      _inBlockStack = !_inBlockStack;
+      return true;
+    }
+    if (_inBlockStack) {
+      delta.insert(
+          line + '\n',
+          NotusAttribute.code
+              .toJson()); // TODO: replace with?: {'quote': true})
+      // Don't bother testing for code blocks within block stacks
+      return true;
+    }
+
+    if (_handleOrderedList(line, delta, attributes) ||
+        _handleUnorderedList(line, delta, attributes)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /// all blocks are supported within bq
+  bool _handleBlockQuote(String line, Delta delta,
+      [Map<String, dynamic> attributes]) {
+    var match = _bqRegExp.matchAsPrefix(line);
+    if (match != null) {
+      var span = match.group(1);
+      var newAttributes = NotusAttribute.bq.toJson(); // NotusAttribute.bq.toJson();
+      if (attributes != null) {
+        newAttributes.addAll(attributes);
+      }
+      // all blocks are supported within bq
+      _handleLine(span, delta, newAttributes, true);
+      return true;
+    }
+    return false;
+  }
+
+  /// ol is supported within ol and bq, but not supported within ul
+  bool _handleOrderedList(String line, Delta delta,
+      [Map<String, dynamic> attributes]) {
+    var match = _olRegExp.matchAsPrefix(line);
+    if (match != null) {
+// TODO: support nesting
+//      var depth =  match.group(1).length / 3;
+      var span = match.group(2);
+      var newAttributes = NotusAttribute.ol.toJson();
+      if (attributes != null) {
+        newAttributes.addAll(attributes);
+      }
+      // There's probably no reason why you would have other block types on the same line
+      _handleSpan(span, delta, true, newAttributes, true);
+      return true;
+    }
+    return false;
+  }
+
+  bool _handleUnorderedList(String line, Delta delta,
+      [Map<String, dynamic> attributes]) {
+    var match = _ulRegExp.matchAsPrefix(line);
+    if (match != null) {
+//      var depth = match.group(1).length / 3;
+      var span = match.group(2);
+      var newAttributes = NotusAttribute.ul.toJson();
+      if (attributes != null) {
+        newAttributes.addAll(attributes);
+      }
+      // There's probably no reason why you would have other block types on the same line
+      _handleSpan(span, delta, true, newAttributes, true);
+      return true;
+    }
+    return false;
+  }
+
+  bool _handleHeading(String line, Delta delta, [Map<String, dynamic> attributes]) {
+    var match = _headingRegExp.matchAsPrefix(line);
+    if (match != null) {
+      var level = match.group(1).length;
+      var newAttributes = <String, dynamic>{
+        'heading': level
+      }; // NotusAttribute.heading.withValue(level).toJson();
+      if (attributes != null) {
+        newAttributes.addAll(attributes);
+      }
+
+      var span = match.group(2);
+      // TODO: true or false?
+      _handleSpan(span, delta, true, newAttributes, true);
+//      delta.insert('\n', attribute.toJson());
+      return true;
+    }
+
+    return false;
+  }
+
+  void _handleSpan(String span, Delta delta, bool addNewLine,
+      Map<String, dynamic> outerStyle, [bool isBlock]) {
+    var start = _handleStyles(span, delta, outerStyle);
+    span = span.substring(start);
+
+    if (span.isNotEmpty) {
+      start = _handleLinks(span, delta, outerStyle);
+      span = span.substring(start);
+    }
+
+    if (span.isNotEmpty) {
+      if (addNewLine) {
+        if(isBlock != null && isBlock){
+          delta.insert(span);
+          delta.insert('\n', outerStyle);
+        } else {
+          delta.insert('$span\n', outerStyle);
+        }
+      } else {
+        delta.insert(span, outerStyle);
+      }
+    } else if (addNewLine) {
+      delta.insert('\n', outerStyle);
+    }
+  }
+
+  int _handleStyles(String span, Delta delta, Map<String, dynamic> outerStyle) {
+    var start = 0;
+
+    var matches = _styleRegExp.allMatches(span);
+    matches.forEach((match) {
+      if (match.start > start) {
+        var validInlineStyles = _getValidInlineStyles(outerStyle);
+        if (span.substring(match.start - 1, match.start) == '[') {
+          var text = span.substring(start, match.start - 1);
+          validInlineStyles != null ? delta.insert(text, validInlineStyles) : delta.insert(text);
+          start = match.start -
+              1 +
+              _handleLinks(span.substring(match.start - 1), delta, validInlineStyles);
+          return;
+        } else {
+          var text = span.substring(start, match.start);
+
+          validInlineStyles != null ? delta.insert(text, validInlineStyles) : delta.insert(text);
+        }
+      }
+
+      var text = match.group(2);
+      var newStyle = Map<String, dynamic>.from(
+          _attributesByStyleLength[match.group(1).length]);
+      
+      var validInlineStyles = _getValidInlineStyles(outerStyle);
+      if (validInlineStyles != null) {
+        newStyle.addAll(validInlineStyles);
+      }
+
+      _handleSpan(text, delta, false, newStyle);
+      start = match.end;
+    });
+
+    return start;
+  }
+
+  Map<String, dynamic> _getValidInlineStyles(Map<String, dynamic> outerStyle) {
+    Map<String, dynamic> leafStyles;
+
+    if(outerStyle == null) {
+      return null;
+    }
+
+    if(outerStyle.containsKey(NotusAttribute.bold.key)){
+      leafStyles = {'b': true};
+    }
+
+    if(outerStyle.containsKey(NotusAttribute.italic.key)){
+      leafStyles = {'i': true};
+    }
+
+    if(outerStyle.containsKey(NotusAttribute.link.key)){
+      leafStyles = {NotusAttribute.link.key: outerStyle[NotusAttribute.link.key]};
+    }
+
+    return leafStyles;
+  }
+
+  int _handleLinks(String span, Delta delta, Map<String, dynamic> outerStyle) {
+    var start = 0;
+
+    var matches = _linkRegExp.allMatches(span);
+    matches.forEach((match) {
+      if (match.start > start) {
+        var text = span.substring(start, match.start);
+        delta.insert(text); //, outerStyle);
+      }
+
+      var text = match.group(1);
+      var href = match.group(2);
+      var newAttributes = <String, dynamic>{
+        'a': href
+      }; // NotusAttribute.link.fromString(href).toJson();
+
+      var validInlineStyles = _getValidInlineStyles(outerStyle);
+      if (validInlineStyles != null) {
+        newAttributes.addAll(validInlineStyles);
+      }
+
+      _handleSpan(text, delta, false, newAttributes);
+      start = match.end;
+    });
+
+    return start;
+  }
+}
+
 class _NotusMarkdownEncoder extends Converter<Delta, String> {
   static const kBold = '**';
   static const kItalic = '_';
@@ -34,13 +310,27 @@ class _NotusMarkdownEncoder extends Converter<Delta, String> {
     final lineBuffer = StringBuffer();
     NotusAttribute<String> currentBlockStyle;
     var currentInlineStyle = NotusStyle();
-    var currentBlockLines = [];
+    var currentBlockLines = <String>[];
+
+    bool _allLinesEmpty(List<String> lines) {
+      for (var line in lines) {
+        if (line != '') {
+          return false;
+        }
+      }
+
+      return true;
+    }
 
     void _handleBlock(NotusAttribute<String> blockStyle) {
       if (currentBlockLines.isEmpty) {
         return; // Empty block
       }
 
+      if(_allLinesEmpty(currentBlockLines)){
+        return;
+      }
+
       if (blockStyle == null) {
         buffer.write(currentBlockLines.join('\n\n'));
         buffer.writeln();
@@ -142,7 +432,7 @@ class _NotusMarkdownEncoder extends Converter<Delta, String> {
       if (padding.isNotEmpty) buffer.write(padding);
     }
     // Now open any new styles.
-    for (var value in style.values) {
+    for (var value in style.values.toList().reversed) {
       if (value.scope == NotusAttributeScope.line) continue;
       if (currentStyle.containsSame(value)) continue;
       final originalText = text;
@@ -210,4 +500,4 @@ class _NotusMarkdownEncoder extends Converter<Delta, String> {
       buffer.write(tag);
     }
   }
-}
+}
\ No newline at end of file
diff --git a/packages/notus/test/convert/markdown_test.dart b/packages/notus/test/convert/markdown_test.dart
index 9c02a62d3..9db191185 100644
--- a/packages/notus/test/convert/markdown_test.dart
+++ b/packages/notus/test/convert/markdown_test.dart
@@ -1,23 +1,385 @@
+
 // Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-import 'dart:convert';
 
-import 'package:notus/convert.dart';
-import 'package:notus/notus.dart';
-import 'package:quill_delta/quill_delta.dart';
+import 'dart:convert';
 import 'package:test/test.dart';
+import 'package:quill_delta/quill_delta.dart';
+import 'package:notus/notus.dart';
+import 'package:notus/convert.dart';
 
 void main() {
-  group('$NotusMarkdownCodec.encode', () {
-    test('unimplemented', () {
-      expect(() {
-        notusMarkdown.decode('test');
-      }, throwsUnimplementedError);
+  group('$NotusMarkdownCodec.decode', () {
+    test('should convert empty markdown to valid empty notus document', () {
+      final markdown = '';
+      final newNotusDoc = NotusDocument();
+      final delta = notusMarkdown.decode(markdown);
+      expect(delta.elementAt(0).data, '\n');
+      expect(delta, newNotusDoc.toDelta());
+    });
+
+    test('should convert invalid markdown with only line breaks to valid empty notus document', () {
+      final markdown = '\n\n\n';
+      final delta = notusMarkdown.decode(markdown);
+      expect(delta.elementAt(0).data, '\n');
+      final newNotusDoc = NotusDocument();
+      expect(delta, newNotusDoc.toDelta());
+    });
+
+    test('paragraphs', () {
+      final markdown = 'First line\n\nSecond line\n\n';
+      final delta = notusMarkdown.decode(markdown);
+      expect(delta.elementAt(0).data, 'First line\nSecond line\n');
+      final andBack = notusMarkdown.encode(delta);
+      expect(andBack, markdown);
+    });
+
+    test('italics', () {
+      void runFor(String markdown, bool testEncode) {
+        final delta = notusMarkdown.decode(markdown);
+        expect(delta.elementAt(0).data, 'italics');
+        expect(delta.elementAt(0).attributes['i'], true);
+        expect(delta.elementAt(0).attributes['b'], null);
+        if (testEncode) {
+          final andBack = notusMarkdown.encode(delta);
+          expect(andBack, markdown);
+        }
+      }
+
+      runFor('_italics_\n\n', true);
+      runFor('*italics*\n\n', false);
+    });
+
+    test('multi-word italics', () {
+      void runFor(String markdown, bool testEncode) {
+        final delta = notusMarkdown.decode(markdown);
+        expect(delta.elementAt(0).data, 'Okay, ');
+        expect(delta.elementAt(0).attributes, null);
+
+        expect(delta.elementAt(1).data, 'this is in italics');
+        expect(delta.elementAt(1).attributes['i'], true);
+        expect(delta.elementAt(1).attributes['b'], null);
+
+        expect(delta.elementAt(3).data, 'so is all of _ this');
+        expect(delta.elementAt(3).attributes['i'], true);
+
+        expect(delta.elementAt(4).data, ' but this is not\n');
+        expect(delta.elementAt(4).attributes, null);
+        if (testEncode) {
+          final andBack = notusMarkdown.encode(delta);
+          expect(andBack, markdown);
+        }
+      }
+
+      runFor(
+          'Okay, _this is in italics_ and _so is all of _ this_ but this is not\n\n',
+          true);
+      runFor(
+          'Okay, *this is in italics* and *so is all of _ this* but this is not\n\n',
+          false);
+    });
+
+    test('bold', () {
+      void runFor(String markdown, bool testEncode) {
+        final delta = notusMarkdown.decode(markdown);
+        expect(delta.elementAt(0).data, 'bold');
+        expect(delta.elementAt(0).attributes['b'], true);
+        expect(delta.elementAt(0).attributes['i'], null);
+        if (testEncode) {
+          final andBack = notusMarkdown.encode(delta);
+          expect(andBack, markdown);
+        }
+      }
+
+      runFor('**bold**\n\n', true);
+      runFor('__bold__\n\n', false);
+    });
+
+    test('multi-word bold', () {
+      void runFor(String markdown, bool testEncode) {
+        final delta = notusMarkdown.decode(markdown);
+        expect(delta.elementAt(0).data, 'Okay, ');
+        expect(delta.elementAt(0).attributes, null);
+
+        expect(delta.elementAt(1).data, 'this is bold');
+        expect(delta.elementAt(1).attributes['b'], true);
+        expect(delta.elementAt(1).attributes['i'], null);
+
+        expect(delta.elementAt(3).data, 'so is all of __ this');
+        expect(delta.elementAt(3).attributes['b'], true);
+
+        expect(delta.elementAt(4).data, ' but this is not\n');
+        expect(delta.elementAt(4).attributes, null);
+        if (testEncode) {
+          final andBack = notusMarkdown.encode(delta);
+          expect(andBack, markdown);
+        }
+      }
+
+      runFor(
+          'Okay, **this is bold** and **so is all of __ this** but this is not\n\n',
+          true);
+      runFor(
+          'Okay, __this is bold__ and __so is all of __ this__ but this is not\n\n',
+          false);
+    });
+
+    test('intersecting inline styles', () {
+      var markdown = 'This **house _is a_ circus**\n\n';
+      final delta = notusMarkdown.decode(markdown);
+      expect(delta.elementAt(1).data, 'house ');
+      expect(delta.elementAt(1).attributes['b'], true);
+      expect(delta.elementAt(1).attributes['i'], null);
+
+      expect(delta.elementAt(2).data, 'is a');
+      expect(delta.elementAt(2).attributes['b'], true);
+      expect(delta.elementAt(2).attributes['i'], true);
+
+      expect(delta.elementAt(3).data, ' circus');
+      expect(delta.elementAt(3).attributes['b'], true);
+      expect(delta.elementAt(3).attributes['i'], null);
+
+      final andBack = notusMarkdown.encode(delta);
+      expect(andBack, markdown);
+    });
+
+    test('bold and italics', () {
+      void runFor(String markdown, bool testEncode) {
+        final delta = notusMarkdown.decode(markdown);
+        expect(delta.elementAt(0).data, 'this is bold and italic');
+        expect(delta.elementAt(0).attributes['b'], true);
+        expect(delta.elementAt(0).attributes['i'], true);
+
+        expect(delta.elementAt(1).data, '\n');
+        expect(delta.length, 2);
+
+        if (testEncode) {
+          final andBack = notusMarkdown.encode(delta);
+          expect(andBack, markdown);
+        }
+      }
+
+      runFor('**_this is bold and italic_**\n\n', true);
+      runFor('_**this is bold and italic**_\n\n', true);
+      runFor('***this is bold and italic***\n\n', false);
+      runFor('___this is bold and italic___\n\n', false);
+    });
+
+    test('bold and italics combinations', () {
+      void runFor(String markdown, bool testEncode) {
+        final delta = notusMarkdown.decode(markdown);
+        expect(delta.elementAt(0).data, 'this is bold');
+        expect(delta.elementAt(0).attributes['b'], true);
+        expect(delta.elementAt(0).attributes['i'], null);
+
+        expect(delta.elementAt(2).data, 'this is in italics');
+        expect(delta.elementAt(2).attributes['b'], null);
+        expect(delta.elementAt(2).attributes['i'], true);
+
+        expect(delta.elementAt(4).data, 'this is both');
+        expect(delta.elementAt(4).attributes['b'], true);
+        expect(delta.elementAt(4).attributes['i'], true);
+
+        if (testEncode) {
+          final andBack = notusMarkdown.encode(delta);
+          expect(andBack, markdown);
+        }
+      }
+
+      runFor('**this is bold** _this is in italics_ and **_this is both_**\n\n',
+          true);
+      runFor('**this is bold** *this is in italics* and ***this is both***\n\n',
+          false);
+      runFor('__this is bold__ _this is in italics_ and ___this is both___\n\n',
+          false);
+    });
+
+    test('link', () {
+      var markdown = 'This **house** is a [circus](https://github.com)\n\n';
+      final delta = notusMarkdown.decode(markdown);
+
+      expect(delta.elementAt(1).data, 'house');
+      expect(delta.elementAt(1).attributes['b'], true);
+      expect(delta.elementAt(1).attributes['a'], null);
+
+      expect(delta.elementAt(3).data, 'circus');
+      expect(delta.elementAt(3).attributes['b'], null);
+      expect(delta.elementAt(3).attributes['a'], 'https://github.com');
+
+      final andBack = notusMarkdown.encode(delta);
+      expect(andBack, markdown);
+    });
+
+    test('style around link', () {
+      var markdown = 'This **house** is a **[circus](https://github.com)**\n\n';
+      final delta = notusMarkdown.decode(markdown);
+
+      expect(delta.elementAt(1).data, 'house');
+      expect(delta.elementAt(1).attributes['b'], true);
+      expect(delta.elementAt(1).attributes['a'], null);
+
+      expect(delta.elementAt(3).data, 'circus');
+      expect(delta.elementAt(3).attributes['b'], true);
+      expect(delta.elementAt(3).attributes['a'], 'https://github.com');
+
+      final andBack = notusMarkdown.encode(delta);
+      expect(andBack, markdown);
+    });
+
+    test('style within link', () {
+      var markdown = 'This **house** is a [**circus**](https://github.com)\n\n';
+      final delta = notusMarkdown.decode(markdown);
+
+      expect(delta.elementAt(1).data, 'house');
+      expect(delta.elementAt(1).attributes['b'], true);
+      expect(delta.elementAt(1).attributes['a'], null);
+
+      expect(delta.elementAt(2).data, ' is a ');
+      expect(delta.elementAt(2).attributes, null);
+
+      expect(delta.elementAt(3).data, 'circus');
+      expect(delta.elementAt(3).attributes['b'], true);
+      expect(delta.elementAt(3).attributes['a'], 'https://github.com');
+
+      expect(delta.elementAt(4).data, '\n');
+      expect(delta.length, 5);
+
+      final andBack = notusMarkdown.encode(delta);
+      expect(andBack, markdown);
+    });
+
+    test('heading styles', () {
+      void runFor(String markdown, int level) {
+        final delta = notusMarkdown.decode(markdown);
+        expect(delta.elementAt(0).data, 'This is an H$level');
+
+        expect(delta.elementAt(1).data, '\n');
+        expect(delta.elementAt(1).attributes['heading'], level);
+        final andBack = notusMarkdown.encode(delta);
+        expect(andBack, markdown);
+      }
+
+      runFor('# This is an H1\n\n', 1);
+      runFor('## This is an H2\n\n', 2);
+      runFor('### This is an H3\n\n', 3);
+    });
+
+    test('ul', () {
+      var markdown = '* a bullet point\n* another bullet point\n\n';
+      final delta = notusMarkdown.decode(markdown);
+      print(delta);
+
+      final andBack = notusMarkdown.encode(delta);
+      expect(andBack, markdown);
+    });
+
+    test('ol', () {
+      var markdown = '1. 1st point\n1. 2nd point\n\n';
+      final delta = notusMarkdown.decode(markdown);
+
+      final andBack = notusMarkdown.encode(delta);
+      expect(andBack, markdown);
+    });
+
+    test('simple bq', () {
+//      var markdown = '> quote\n> > nested\n>#Heading\n>**bold**\n>_italics_\n>* bullet\n>1. 1st point\n>1. 2nd point\n\n';
+      var markdown =
+          '> quote\n> # Heading in Quote\n> # **Styled** heading in _block quote_\n> **bold text**\n> _text in italics_\n\n';
+      final delta = notusMarkdown.decode(markdown);
+
+      expect(delta.elementAt(0).data, 'quote');
+      expect(delta.elementAt(0).attributes, null);
+
+      expect(delta.elementAt(1).data, '\n');
+      expect(delta.elementAt(1).attributes['block'], 'quote');
+      expect(delta.elementAt(1).attributes.length, 1);
+
+      expect(delta.elementAt(2).data, 'Heading in Quote');
+      expect(delta.elementAt(2).attributes, null);
+
+      expect(delta.elementAt(3).data, '\n');
+      expect(delta.elementAt(3).attributes['block'], 'quote');
+      expect(delta.elementAt(3).attributes['heading'], 1);
+      expect(delta.elementAt(3).attributes.length, 2);
+
+      expect(delta.elementAt(4).data, 'Styled');
+      expect(delta.elementAt(4).attributes['b'], true);
+      expect(delta.elementAt(4).attributes.length, 1);
+
+      expect(delta.elementAt(5).data, ' heading in ');
+      expect(delta.elementAt(5).attributes, null);
+
+      expect(delta.elementAt(6).data, 'block quote');
+      expect(delta.elementAt(6).attributes['i'], true);
+      expect(delta.elementAt(6).attributes.length, 1);
+
+      expect(delta.elementAt(7).data, '\n');
+      expect(delta.elementAt(7).attributes['block'], 'quote');
+      expect(delta.elementAt(7).attributes['heading'], 1);
+      expect(delta.elementAt(7).attributes.length, 2);
+
+      expect(delta.elementAt(8).data, 'bold text');
+      expect(delta.elementAt(8).attributes['b'], true);
+      expect(delta.elementAt(8).attributes.length, 1);
+
+      expect(delta.elementAt(9).data, '\n');
+      expect(delta.elementAt(9).attributes['block'], 'quote');
+      expect(delta.elementAt(9).attributes.length, 1);
+
+      expect(delta.elementAt(10).data, 'text in italics');
+      expect(delta.elementAt(10).attributes['i'], true);
+      expect(delta.elementAt(10).attributes.length, 1);
+
+      expect(delta.elementAt(11).data, '\n');
+      expect(delta.elementAt(11).attributes['block'], 'quote');
+      expect(delta.elementAt(11).attributes.length, 1);
+
+      final andBack = notusMarkdown.encode(delta);
+      expect(andBack, markdown);
+    });
+
+//    test('nested bq', () {
+//      var markdown = '> > nested\n>* bullet\n>1. 1st point\n>1. 2nd point\n\n';
+//      final delta = notusMarkdown.decode(markdown);
+//      final andBack = notusMarkdown.encode(delta);
+//      expect(andBack, markdown);
+//    });
+
+
+//    test('code in bq', () {
+//      var markdown = '> ```\n> print("Hello world!")\n> ```\n\n';
+//      final delta = notusMarkdown.decode(markdown);
+//      final andBack = notusMarkdown.encode(delta);
+//      expect(andBack, markdown);
+//    });
+
+    test('multiple styles', () {
+      final delta = notusMarkdown.decode(expectedMarkdown);
+//      expect(delta, doc);
+      final andBack = notusMarkdown.encode(delta);
+      expect(andBack, expectedMarkdown);
     });
   });
 
   group('$NotusMarkdownCodec.encode', () {
+    test('should convert empty valid notus document to empty markdown', () {
+      final delta = NotusDocument().toDelta();
+      final result = notusMarkdown.encode(delta);
+      expect(result, '');
+    });
+
+    test('should convert delta with only line breaks to empty markdown', () {
+      final delta = Delta()
+        ..insert('\n')
+        ..insert('\n')
+        ..insert('\n')
+        ..insert('\n');
+
+      final result = notusMarkdown.encode(delta);
+      expect(result, '');
+    });
+    
     test('split adjacent paragraphs', () {
       final delta = Delta()..insert('First line\nSecond line\n');
       final result = notusMarkdown.encode(delta);
@@ -87,8 +449,7 @@ void main() {
     });
 
     test('heading styles', () {
-      void runFor(
-          NotusAttribute<int> attribute, String source, String expected) {
+      void runFor(NotusAttribute<int> attribute, String source, String expected) {
         final delta = Delta()..insert(source)..insert('\n', attribute.toJson());
         final result = notusMarkdown.encode(delta);
         expect(result, expected);
@@ -100,8 +461,7 @@ void main() {
     });
 
     test('block styles', () {
-      void runFor(
-          NotusAttribute<String> attribute, String source, String expected) {
+      void runFor(NotusAttribute<String> attribute, String source, String expected) {
         final delta = Delta()..insert(source)..insert('\n', attribute.toJson());
         final result = notusMarkdown.encode(delta);
         expect(result, expected);
@@ -114,8 +474,7 @@ void main() {
     });
 
     test('multiline blocks', () {
-      void runFor(
-          NotusAttribute<String> attribute, String source, String expected) {
+      void runFor(NotusAttribute<String> attribute, String source, String expected) {
         final delta = Delta()
           ..insert(source)
           ..insert('\n', attribute.toJson())