tests/testthat/test-parse-fenced_divs.R

# Tests around basic expression parsers

test_that("Basic parser tests - open", {

  ## Valid inputs

  expect_equal(
    check_fdiv_open_parser("::: test\n"),
    rmd_fenced_div_open(classes = ".test")
  )

  expect_equal(
    check_fdiv_open_parser("::: {#test}\n"),
    rmd_fenced_div_open(id = "#test")
  )

  expect_equal(
    check_fdiv_open_parser(":::: test\n"),
    rmd_fenced_div_open(classes = ".test")
  )


  ## Trailing : or " "
  expect_equal(
    check_fdiv_open_parser("::: test \n"),
    rmd_fenced_div_open(classes = ".test")
  )

  expect_equal(
    check_fdiv_open_parser("::: test ::::\n"),
    rmd_fenced_div_open(classes = ".test")
  )

  expect_equal(
    check_fdiv_open_parser("::: test :: \n"),
    rmd_fenced_div_open(classes = ".test")
  )

  expect_equal(
    check_fdiv_open_parser("::: {}\n"),
    rmd_fenced_div_open()
  )

  expect_equal(
    check_fdiv_open_parser(":::{}\n"),
    rmd_fenced_div_open()
  )

  expect_equal(
    check_fdiv_open_parser("::: {   }\n"),
    rmd_fenced_div_open()
  )

  ## Bad inputs

  expect_snapshot( # No attribute
    check_fdiv_open_parser(":::\n"), error=TRUE
  )

  expect_snapshot( # Multiple unbraced attributes
    check_fdiv_open_parser("::: a x\n"), error=TRUE
  )

  expect_snapshot( # Incomplete opening fence
    check_fdiv_open_parser(":: {}\n"), error=TRUE
  )

  expect_snapshot( # Incomplete opening fence
    check_fdiv_open_parser(":: \n"), error=TRUE
  )

})

test_that("Basic parser tests - close", {
  # Valid
  expect_equal(
    check_fdiv_close_parser(":::\n"),
    rmd_fenced_div_close()
  )

  expect_equal(
    check_fdiv_close_parser("::::::\n"),
    rmd_fenced_div_close()
  )

  expect_equal(
    check_fdiv_close_parser("::: \n"),
    rmd_fenced_div_close()
  )

  # Invalid

  expect_snapshot(
    check_fdiv_close_parser("::: a\n"), error=TRUE
  )

  expect_snapshot(
    check_fdiv_close_parser("::: {a}\n"), error=TRUE
  )

  expect_snapshot(
    check_fdiv_close_parser("::: {}\n"), error=TRUE
  )

  expect_snapshot(
    check_fdiv_close_parser(":: a\n"), error=TRUE
  )

})

test_that("Nested inputs", {
  expect_equal(
    parse_rmd_cpp("::: test1\n::: test2\n:::\n:::\n"),
    rmd_ast( list(
      rmd_fenced_div_open(classes = ".test1"),
      rmd_fenced_div_open(classes = ".test2"),
      rmd_fenced_div_close(),
      rmd_fenced_div_close()
    ) )
  )


  expect_equal(
    parse_rmd_cpp("::: test1\n::: test2\n::: test3\n:::\n:::\n:::\n"),
    rmd_ast( list(
      rmd_fenced_div_open(classes = ".test1"),
      rmd_fenced_div_open(classes = ".test2"),
      rmd_fenced_div_open(classes = ".test3"),
      rmd_fenced_div_close(),
      rmd_fenced_div_close(),
      rmd_fenced_div_close()
    ) )
  )
})

test_that("Bad fdivs", {
  expect_snapshot(
    parse_rmd("::: test1\n"), error=TRUE
  )

  expect_snapshot(
    parse_rmd(":::\n"), error=TRUE
  )

  expect_snapshot(
    parse_rmd("::: test1\n::: test2\n:::\n"), error=TRUE
  )

  expect_snapshot(
    parse_rmd("::: test1\n:::\n:::\n"), error=TRUE
  )
})

test_that("Pandoc examples", {

  ex1 = "::::: {#special .sidebar}
Here is a paragraph.

And another.
:::::
"
  expect_equal(
    parse_rmd(ex1),
    rmd_ast( list(
      rmd_fenced_div_open(id = "#special", classes = ".sidebar"),
      rmd_markdown(c("Here is a paragraph.", "", "And another.")),
      rmd_fenced_div_close()
    ) )
  )

  ex2 = "::: Warning ::::::
This is a warning.

::: Danger
This is a warning within a warning.
:::
::::::::::::::::::
"

  expect_equal(
    parse_rmd(ex2),
    rmd_ast( list(
      rmd_fenced_div_open(classes = ".Warning"),
      rmd_markdown("This is a warning."),
      rmd_fenced_div_open(classes = ".Danger"),
      rmd_markdown("This is a warning within a warning."),
      rmd_fenced_div_close(),
      rmd_fenced_div_close()
    ) )
  )

})



#test_that("Utils - fenced_div_depth()", {
#
#  expect_equal(
#    fenced_div_depth( check_fenced_div_parser(
#      "::: a\n:::\n"
#    ) ),
#    1
#  )
#
#  expect_equal(
#    fenced_div_depth( check_fenced_div_parser(
#      ":::: a\n::: b\n:::\n::::\n"
#    ) ),
#    2
#  )
#
#  expect_equal(
#    fenced_div_depth( check_fenced_div_parser(
#      ":::: a\n::: b1\n:::\n::: b2\n:::\n::::\n"
#    ) ),
#    2
#  )
#
#  expect_equal(
#    fenced_div_depth( check_fenced_div_parser(
#      ":::: a\n::: b\n::: c\n:::\n:::\n::::\n"
#    ) ),
#    3
#  )
#
#  expect_equal(
#    fenced_div_depth( check_fenced_div_parser(
#      ":::: a\n::: b1\n::: c\n:::\n:::\n::: b2\n:::\n::::\n"
#    ) ),
#    3
#  )
#
#  expect_equal(
#    fenced_div_depth( check_fenced_div_parser(
#      ":::: a\n::: b1\n:::\n::: b2\n::: c\n:::\n:::\n::::\n"
#    ) ),
#    3
#  )
#})


test_that("Utils - as_document()", {
  expect_round_trip_identical = function(x) {
    x = parse_rmd_cpp(x)
    expect_identical(
      x,
      parse_rmd_cpp(
        paste(c(as_document(x),""), collapse="\n")
      )
    )
  }

  expect_round_trip_identical("::: a\n:::\n")
  expect_round_trip_identical("::: a\n::: b\n:::\n:::\n")
  expect_round_trip_identical("::: a\n::: b1\n:::\n::: b2\n:::\n:::\n")
  expect_round_trip_identical("::: a\n::: b\n::: c\n:::\n:::\n:::\n")
  expect_round_trip_identical("::: a\n::: b1\n::: c\n:::\n:::\n::: b2\n:::\n:::\n")
  expect_round_trip_identical("::: a\n::: b1\n:::\n::: b2\n::: c\n:::\n:::\n:::\n")
})

test_that("Enhanced attribute parsing - individual types", {
  
  # Class attributes
  expect_equal(
    check_fdiv_open_parser("::: {.warning}\n"),
    rmd_fenced_div_open(classes = ".warning")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {.my-class}\n"),
    rmd_fenced_div_open(classes = ".my-class")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {.class_with_underscores}\n"),
    rmd_fenced_div_open(classes = ".class_with_underscores")
  )
  
  # ID attributes
  expect_equal(
    check_fdiv_open_parser("::: {#special}\n"),
    rmd_fenced_div_open(id = "#special")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {#my-id}\n"),
    rmd_fenced_div_open(id = "#my-id")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {#id_with_underscores}\n"),
    rmd_fenced_div_open(id = "#id_with_underscores")
  )
  
  # Key=value attributes - unquoted
  expect_equal(
    check_fdiv_open_parser("::: {data-toggle=collapse}\n"),
    rmd_fenced_div_open(attr = c("data-toggle" = "collapse"))
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {style=color:red}\n"),
    rmd_fenced_div_open(attr = c("style" = "color:red"))
  )
  
  # Key=value attributes - double quoted
  expect_equal(
    check_fdiv_open_parser("::: {title=\"My Title\"}\n"),
    rmd_fenced_div_open(attr = c("title" = "\"My Title\""))
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {data-content=\"Hello World\"}\n"),
    rmd_fenced_div_open(attr = c("data-content" = "\"Hello World\""))
  )
  
  # Key=value attributes - single quoted
  expect_equal(
    check_fdiv_open_parser("::: {title='Single Quoted'}\n"),
    rmd_fenced_div_open(attr = c("title" = "'Single Quoted'"))
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {alt='Image description'}\n"),
    rmd_fenced_div_open(attr = c("alt" = "'Image description'"))
  )
  
})

test_that("Enhanced attribute parsing - combinations", {
  
  # Two classes
  expect_equal(
    check_fdiv_open_parser("::: {.primary .large}\n"),
    rmd_fenced_div_open(classes = c(".primary", ".large"))
  )
  
  # ID and class
  expect_equal(
    check_fdiv_open_parser("::: {#myid .myclass}\n"),
    rmd_fenced_div_open(id = "#myid", classes = ".myclass")
  )
  
  # Class and ID (correct order: ID first, then class)  
  expect_equal(
    check_fdiv_open_parser("::: {#myid .myclass}\n"),
    rmd_fenced_div_open(id = "#myid", classes = ".myclass")
  )
  
  # Class and key=value (correct order: class first, then key=value)
  expect_equal(
    check_fdiv_open_parser("::: {.highlight data-value=test}\n"),
    rmd_fenced_div_open(classes = ".highlight", attr = c("data-value" = "test"))
  )
  
  # Three attributes: ID, class, key=value (correct order)
  expect_equal(
    check_fdiv_open_parser("::: {#section .important data-role=note}\n"),
    rmd_fenced_div_open(id = "#section", classes = ".important", attr = c("data-role" = "note"))
  )
  
  # Three attributes: ID, class, key=value (correct order)
  expect_equal(
    check_fdiv_open_parser("::: {#submit .primary data-role=button}\n"),
    rmd_fenced_div_open(id = "#submit", classes = ".primary", attr = c("data-role" = "button"))
  )
  
  # Multiple attributes: ID, multiple classes, multiple key=value (correct order)
  expect_equal(
    check_fdiv_open_parser("::: {#main .container .highlight data-test=value data-role=section}\n"),
    rmd_fenced_div_open(id = "#main", classes = c(".container", ".highlight"), attr = c("data-test" = "value", "data-role" = "section"))
  )
  
  # Multiple key=value pairs
  expect_equal(
    check_fdiv_open_parser("::: {width=100 height=200}\n"),
    rmd_fenced_div_open(attr = c("width" = "100", "height" = "200"))
  )
  
  # Mixed quoted and unquoted values (correct order: class first, then key=value)
  expect_equal(
    check_fdiv_open_parser("::: {.highlighted title=\"Main Section\" data-id=section1}\n"),
    rmd_fenced_div_open(classes = ".highlighted", attr = c("title" = "\"Main Section\"", "data-id" = "section1"))
  )
})

test_that("Enhanced attribute parsing - edge cases", {
  
  # Empty braces (should work as before)
  expect_equal(
    check_fdiv_open_parser("::: {}\n"),
    rmd_fenced_div_open()
  )
  
  # Extra spaces
  expect_equal(
    check_fdiv_open_parser("::: {  .class1   .class2  }\n"),
    rmd_fenced_div_open(classes = c(".class1", ".class2"))
  )
  
  # Complex key=value with special characters
  expect_equal(
    check_fdiv_open_parser("::: {data-url=\"https://example.com/path?q=test\"}\n"),
    rmd_fenced_div_open(attr = c("data-url" = "\"https://example.com/path?q=test\""))
  )
  
  # Hyphenated and underscored names (correct order: ID first, then class)
  expect_equal(
    check_fdiv_open_parser("::: {#my-id_value .my-class_name}\n"),
    rmd_fenced_div_open(id = "#my-id_value", classes = ".my-class_name")
  )
  
  # Numbers in attributes (correct order: ID, class, key=value)
  expect_equal(
    check_fdiv_open_parser("::: {#id2 .class1 data-level=3}\n"),
    rmd_fenced_div_open(id = "#id2", classes = ".class1", attr = c("data-level" = "3"))
  )
})

test_that("ID and class allowed character combinations", {
  
  # Specific requested test case
  expect_equal(
    check_fdiv_open_parser(":::{#exr-var-of-binom-2-0.50}\n"),
    rmd_fenced_div_open(id = "#exr-var-of-binom-2-0.50")
  )
  
  # Letters only (a-z, A-Z)
  expect_equal(
    check_fdiv_open_parser("::: {#myId .myClass}\n"),
    rmd_fenced_div_open(id = "#myId", classes = ".myClass")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {#ALLCAPS .lowercase}\n"),
    rmd_fenced_div_open(id = "#ALLCAPS", classes = ".lowercase")
  )
  
  # Letters with digits (not as first character)
  expect_equal(
    check_fdiv_open_parser("::: {#section1 .class2test}\n"),
    rmd_fenced_div_open(id = "#section1", classes = ".class2test")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {#test123 .example456}\n"),
    rmd_fenced_div_open(id = "#test123", classes = ".example456")
  )
  
  # Letters with hyphens
  expect_equal(
    check_fdiv_open_parser("::: {#multi-word-id .multi-word-class}\n"),
    rmd_fenced_div_open(id = "#multi-word-id", classes = ".multi-word-class")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {#start-middle-end .nav-item-active}\n"),
    rmd_fenced_div_open(id = "#start-middle-end", classes = ".nav-item-active")
  )
  
  # Letters with underscores
  expect_equal(
    check_fdiv_open_parser("::: {#snake_case_id .snake_case_class}\n"),
    rmd_fenced_div_open(id = "#snake_case_id", classes = ".snake_case_class")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {#my_long_identifier .utility_class_name}\n"),
    rmd_fenced_div_open(id = "#my_long_identifier", classes = ".utility_class_name")
  )
  
  # Letters with periods (for classes)
  expect_equal(
    check_fdiv_open_parser("::: {.class.with.dots}\n"),
    rmd_fenced_div_open(classes = ".class.with.dots")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {.namespace.component.modifier}\n"),
    rmd_fenced_div_open(classes = ".namespace.component.modifier")
  )
  
  # Complex combinations of all allowed characters
  expect_equal(
    check_fdiv_open_parser("::: {#complex_id-with.dots123 .complex_class-name.with123}\n"),
    rmd_fenced_div_open(id = "#complex_id-with.dots123", classes = ".complex_class-name.with123")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {#section_2-test.version1 .btn-primary.size-lg.state-active2}\n"),
    rmd_fenced_div_open(id = "#section_2-test.version1", classes = ".btn-primary.size-lg.state-active2")
  )
  
  # Mixed case with all character types
  expect_equal(
    check_fdiv_open_parser("::: {#MyComplex_ID-with.Dots123 .MyClass_Name-with.Periods456}\n"),
    rmd_fenced_div_open(id = "#MyComplex_ID-with.Dots123", classes = ".MyClass_Name-with.Periods456")
  )
  
  # Edge cases with starting characters (must be letters)
  expect_equal(
    check_fdiv_open_parser("::: {#a1 .z9}\n"),
    rmd_fenced_div_open(id = "#a1", classes = ".z9")
  )
  
  expect_equal(
    check_fdiv_open_parser("::: {#A_test .Z-test}\n"),
    rmd_fenced_div_open(id = "#A_test", classes = ".Z-test")
  )
})

test_that("Enhanced attribute parsing - error cases", {
  
  # Invalid class (no name after dot)
  expect_snapshot(
    check_fdiv_open_parser("::: {.}\n"), error=TRUE
  )
  
  # Invalid ID (no name after hash)
  expect_snapshot(
    check_fdiv_open_parser("::: {#}\n"), error=TRUE
  )
  
  # Invalid key=value (no value)
  expect_snapshot(
    check_fdiv_open_parser("::: {key=}\n"), error=TRUE
  )
  
  
  # Missing closing brace
  expect_snapshot(
    check_fdiv_open_parser("::: {.class\n"), error=TRUE
  )
  
  # Invalid ordering: class before ID (should be ID first)
  expect_snapshot(
    check_fdiv_open_parser("::: {.myclass #myid}\n"), error=TRUE
  )
  
  # Invalid ordering: class before ID (another example)
  expect_snapshot(
    check_fdiv_open_parser("::: {.my-class_name #my-id_value}\n"), error=TRUE
  )
  
  # Invalid ordering: class before ID (third example)
  expect_snapshot(
    check_fdiv_open_parser("::: {.class1 #id2 data-level=3}\n"), error=TRUE
  )
  
  # Invalid ordering: key=value before class
  expect_snapshot(
    check_fdiv_open_parser("::: {data-value=test .highlight}\n"), error=TRUE
  )
  
  # Invalid ordering: key=value before class (another example)
  expect_snapshot(
    check_fdiv_open_parser("::: {title=\"Main Section\" data-id=section1 .highlighted}\n"), error=TRUE
  )
  
  # Invalid ordering: key=value before ID  
  expect_snapshot(
    check_fdiv_open_parser("::: {data-role=button #submit}\n"), error=TRUE
  )
  
  # Invalid: plain attributes not allowed in braced form
  expect_snapshot(
    check_fdiv_open_parser("::: {warning}\n"), error=TRUE
  )
  
  # Invalid: plain attributes not allowed in braced form (other examples)
  expect_snapshot(
    check_fdiv_open_parser("::: {test}\n"), error=TRUE
  )
  
  # Invalid: multiple IDs  
  expect_snapshot(
    check_fdiv_open_parser("::: {#id1 #id2}\n"), error=TRUE
  )
})

Try the parsermd package in your browser

Any scripts or data that you put into this service are public.

parsermd documentation built on Aug. 21, 2025, 5:27 p.m.