diff --git a/api_test/main.c b/api_test/main.c
index 5ad9a66bf..4c490f50b 100644
--- a/api_test/main.c
+++ b/api_test/main.c
@@ -308,9 +308,24 @@ static void inline_code_info(test_batch_runner *runner) {
static void node_clone(test_batch_runner *runner) {
static const char markdown[] =
- "## Heading\n\nruby:`Object.new` and [link](url 'title')\n";
+ "## Heading\n\n"
+ "ruby:`Object.new` and [link](url 'title')\n\n"
+ "> Quote with **strong** and *emphasis*.\n\n"
+ "3. Ordered\n"
+ "4. List\n\n"
+ "- Bullet\n\n"
+ "~~~~ ruby lineno=1\n"
+ "puts 'fenced'\n"
+ "~~~~\n\n"
+ " indented\n\n"
+ "
html
\n\n"
+ "\n\n"
+ "---\n\n"
+ "Footnote[^note].\n\n"
+ "[^note]: Definition.\n";
cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1,
- CMARK_OPT_INLINE_CODE_INFO);
+ CMARK_OPT_INLINE_CODE_INFO |
+ CMARK_OPT_FOOTNOTES);
cmark_node *clone = cmark_node_clone(doc);
OK(runner, clone != NULL, "clone a document");
@@ -323,6 +338,57 @@ static void node_clone(test_batch_runner *runner) {
STR_EQ(runner, cmark_node_get_literal(code), "Object.new",
"clone inline code literal");
+ cmark_node *block_quote = cmark_node_next(paragraph);
+ cmark_node *ordered_list = cmark_node_next(block_quote);
+ INT_EQ(runner, cmark_node_get_list_type(ordered_list), CMARK_ORDERED_LIST,
+ "clone ordered list type");
+ INT_EQ(runner, cmark_node_get_list_start(ordered_list), 3,
+ "clone ordered list start");
+ INT_EQ(runner, cmark_node_get_list_delim(ordered_list), CMARK_PERIOD_DELIM,
+ "clone ordered list delimiter");
+
+ cmark_node *bullet_list = cmark_node_next(ordered_list);
+ INT_EQ(runner, cmark_node_get_list_type(bullet_list), CMARK_BULLET_LIST,
+ "clone bullet list type");
+
+ cmark_node *fenced = cmark_node_next(bullet_list);
+ int fence_length, fence_offset;
+ char fence_character;
+ OK(runner, cmark_node_get_fenced(fenced, &fence_length, &fence_offset,
+ &fence_character),
+ "clone fenced code metadata");
+ INT_EQ(runner, fence_length, 4, "clone fence length");
+ INT_EQ(runner, fence_offset, 0, "clone fence offset");
+ INT_EQ(runner, fence_character, '~', "clone fence character");
+ STR_EQ(runner, cmark_node_get_code_info(fenced), "ruby lineno=1",
+ "clone fenced code info");
+
+ char *original_html =
+ cmark_render_html(doc, CMARK_OPT_DEFAULT | CMARK_OPT_UNSAFE, NULL);
+ char *cloned_html =
+ cmark_render_html(clone, CMARK_OPT_DEFAULT | CMARK_OPT_UNSAFE, NULL);
+ STR_EQ(runner, cloned_html, original_html, "clone renders identical HTML");
+ free(cloned_html);
+ free(original_html);
+
+ char *original_commonmark =
+ cmark_render_commonmark(doc, CMARK_OPT_DEFAULT, 0);
+ char *cloned_commonmark =
+ cmark_render_commonmark(clone, CMARK_OPT_DEFAULT, 0);
+ STR_EQ(runner, cloned_commonmark, original_commonmark,
+ "clone renders identical CommonMark");
+ free(cloned_commonmark);
+ free(original_commonmark);
+
+ char *original_xml =
+ cmark_render_xml(doc, CMARK_OPT_DEFAULT | CMARK_OPT_SOURCEPOS);
+ char *cloned_xml =
+ cmark_render_xml(clone, CMARK_OPT_DEFAULT | CMARK_OPT_SOURCEPOS);
+ STR_EQ(runner, cloned_xml, original_xml,
+ "clone preserves source positions");
+ free(cloned_xml);
+ free(original_xml);
+
cmark_node_set_literal(code, "Class.new");
cmark_node *original_paragraph = cmark_node_next(cmark_node_first_child(doc));
cmark_node *original_code = cmark_node_first_child(original_paragraph);
@@ -332,14 +398,16 @@ static void node_clone(test_batch_runner *runner) {
cmark_node *paragraph_clone = cmark_node_clone(original_paragraph);
OK(runner, paragraph_clone != NULL, "clone child node");
+ char *expected_html =
+ cmark_render_html(clone, CMARK_OPT_DEFAULT | CMARK_OPT_UNSAFE, NULL);
+
cmark_node_free(doc);
- char *html = cmark_render_html(clone, CMARK_OPT_DEFAULT, NULL);
- STR_EQ(runner, html,
- "Heading
\n"
- "Class.new and "
- "link
\n",
+ char *html =
+ cmark_render_html(clone, CMARK_OPT_DEFAULT | CMARK_OPT_UNSAFE, NULL);
+ STR_EQ(runner, html, expected_html,
"clone remains valid after source is freed");
free(html);
+ free(expected_html);
cmark_node_free(clone);
html = cmark_render_html(paragraph_clone, CMARK_OPT_DEFAULT, NULL);
@@ -355,13 +423,30 @@ static void node_clone(test_batch_runner *runner) {
static void node_clone_extensions(test_batch_runner *runner) {
static const char markdown[] =
- "| Left | Right |\n| :--- | ---: |\n| A | B |\n";
+ "| Left | Right |\n"
+ "| :--- | ---: |\n"
+ "| A | B |\n\n"
+ "- [x] Complete\n"
+ "- [ ] Pending\n\n"
+ "~deleted~\n\n"
+ "https://example.com\n\n"
+ "\n";
cmark_gfm_core_extensions_ensure_registered();
- cmark_syntax_extension *table_extension =
- cmark_find_syntax_extension("table");
+ const char *extension_names[] = {"table", "tasklist", "strikethrough",
+ "autolink", "tagfilter"};
+ const int extension_count =
+ sizeof(extension_names) / sizeof(extension_names[0]);
+ cmark_mem *mem = cmark_get_default_mem_allocator();
+ cmark_llist *extensions = NULL;
cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT);
- cmark_parser_attach_syntax_extension(parser, table_extension);
+ for (int i = 0; i < extension_count; ++i) {
+ cmark_syntax_extension *extension =
+ cmark_find_syntax_extension(extension_names[i]);
+ OK(runner, extension != NULL, "find %s extension", extension_names[i]);
+ cmark_parser_attach_syntax_extension(parser, extension);
+ extensions = cmark_llist_append(mem, extensions, extension);
+ }
cmark_parser_feed(parser, markdown, sizeof(markdown) - 1);
cmark_node *doc = cmark_parser_finish(parser);
cmark_parser_free(parser);
@@ -388,31 +473,28 @@ static void node_clone_extensions(test_batch_runner *runner) {
OK(runner, cmark_gfm_extensions_get_table_row_is_header(header),
"clone table header metadata");
+ cmark_node *task_list = cmark_node_next(cloned_table);
+ OK(runner,
+ cmark_gfm_extensions_get_tasklist_item_checked(
+ cmark_node_first_child(task_list)),
+ "clone checked task list state");
+ OK(runner,
+ !cmark_gfm_extensions_get_tasklist_item_checked(
+ cmark_node_next(cmark_node_first_child(task_list))),
+ "clone unchecked task list state");
+
+ char *original_html =
+ cmark_render_html(doc, CMARK_OPT_DEFAULT, extensions);
+
cmark_node_free(doc);
INT_EQ(runner, cmark_gfm_extensions_get_table_columns(cloned_table), 2,
"cloned table remains valid after source is freed");
- cmark_mem *mem = cmark_get_default_mem_allocator();
- cmark_llist *extensions = NULL;
- extensions = cmark_llist_append(mem, extensions, table_extension);
char *html = cmark_render_html(clone, CMARK_OPT_DEFAULT, extensions);
- STR_EQ(runner, html,
- "\n"
- "\n"
- "\n"
- "| Left | \n"
- "Right | \n"
- "
\n"
- "\n"
- "\n"
- "\n"
- "| A | \n"
- "B | \n"
- "
\n"
- "\n"
- "
\n",
- "render cloned table");
+ STR_EQ(runner, html, original_html,
+ "render cloned extension nodes after source is freed");
free(html);
+ free(original_html);
cmark_llist_free(mem, extensions);
cmark_node_free(clone);
}
@@ -592,7 +674,16 @@ static void custom_nodes(test_batch_runner *runner) {
"render_man");
free(man);
+ cmark_node *clone = cmark_node_clone(doc);
+ OK(runner, clone != NULL, "clone custom nodes");
cmark_node_free(doc);
+
+ html = cmark_render_html(clone, CMARK_OPT_DEFAULT, NULL);
+ STR_EQ(runner, html, "
\n