use crate::tests::sdd::*;
track_file!("ref/asciidoctor/test/sections_test.rb");
non_normative!(
r##"
# frozen_string_literal: true
require_relative 'test_helper'
context 'Sections' do
context 'Ids' do
"##
);
mod ids {
use crate::tests::prelude::*;
#[test]
fn synthetic_id_is_generated_by_default() {
verifies!(
r##"
test 'synthetic id is generated by default' do
sec = block_from_string('== Section One')
assert_equal '_section_one', sec.id
end
"##
);
let doc = Parser::default().parse("== Section One");
assert_eq!(first_section(&doc).id(), Some("_section_one"));
}
#[test]
fn duplicate_synthetic_id_is_automatically_enumerated() {
verifies!(
r##"
test 'duplicate synthetic id is automatically enumerated' do
doc = document_from_string <<~'EOS'
== Section One
== Section One
EOS
assert_equal 2, doc.blocks.size
assert_equal '_section_one', doc.blocks[0].id
assert_equal '_section_one_2', doc.blocks[1].id
end
"##
);
let doc = Parser::default().parse("== Section One\n\n== Section One\n");
let blocks = top_blocks(&doc);
assert_eq!(blocks.len(), 2);
assert_eq!(as_section(blocks[0]).id(), Some("_section_one"));
assert_eq!(as_section(blocks[1]).id(), Some("_section_one_2"));
}
#[test]
fn synthetic_id_removes_non_word_characters() {
verifies!(
r##"
test 'synthetic id removes non-word characters' do
sec = block_from_string("== We’re back!")
assert_equal '_were_back', sec.id
end
"##
);
let doc = Parser::default().parse("== We\u{2019}re back!");
assert_eq!(first_section(&doc).id(), Some("_were_back"));
}
#[test]
fn synthetic_id_removes_repeating_separators() {
verifies!(
r##"
test 'synthetic id removes repeating separators' do
sec = block_from_string('== Section $ One')
assert_equal '_section_one', sec.id
end
"##
);
let doc = Parser::default().parse("== Section $ One");
assert_eq!(first_section(&doc).id(), Some("_section_one"));
}
#[test]
fn synthetic_id_removes_entities() {
verifies!(
r##"
test 'synthetic id removes entities' do
sec = block_from_string('== Ben & Jerry & Company¹ "Ice Cream Brothers" あ')
assert_equal '_ben_jerry_company_ice_cream_brothers', sec.id
end
"##
);
let doc = Parser::default()
.parse("== Ben & Jerry & Company¹ "Ice Cream Brothers" あ");
assert_eq!(
first_section(&doc).id(),
Some("_ben_jerry_company_ice_cream_brothers")
);
}
#[test]
fn synthetic_id_removes_adjacent_entities_with_mixed_case() {
verifies!(
r##"
test 'synthetic id removes adjacent entities with mixed case' do
sec = block_from_string('== a ®&© b')
assert_equal '_a_b', sec.id
end
"##
);
let doc = Parser::default().parse("== a ®&© b");
assert_eq!(first_section(&doc).id(), Some("_a_b"));
}
#[test]
fn synthetic_id_removes_xml_tags() {
verifies!(
r##"
test 'synthetic id removes XML tags' do
sec = block_from_string('== Use the `run` command to make it icon:gear[]')
assert_equal '_use_the_run_command_to_make_it_gear', sec.id
end
"##
);
let doc = Parser::default().parse("== Use the `run` command to make it icon:gear[]");
assert_eq!(
first_section(&doc).id(),
Some("_use_the_run_command_to_make_it_gear")
);
}
#[test]
fn synthetic_id_collapses_repeating_spaces() {
verifies!(
r##"
test 'synthetic id collapses repeating spaces' do
sec = block_from_string('== Go Far')
assert_equal '_go_far', sec.id
end
"##
);
let doc = Parser::default().parse("== Go Far");
assert_eq!(first_section(&doc).id(), Some("_go_far"));
}
#[test]
fn synthetic_id_replaces_hyphens_with_separator() {
verifies!(
r##"
test 'synthetic id replaces hyphens with separator' do
sec = block_from_string('== State-of-the-art design')
assert_equal '_state_of_the_art_design', sec.id
end
"##
);
let doc = Parser::default().parse("== State-of-the-art design");
assert_eq!(first_section(&doc).id(), Some("_state_of_the_art_design"));
}
#[test]
fn synthetic_id_replaces_dots_with_separator() {
verifies!(
r##"
test 'synthetic id replaces dots with separator' do
sec = block_from_string("== Section 1.1.1")
assert_equal '_section_1_1_1', sec.id
end
"##
);
let doc = Parser::default().parse("== Section 1.1.1");
assert_eq!(first_section(&doc).id(), Some("_section_1_1_1"));
}
#[test]
fn synthetic_id_prefix_can_be_customized() {
verifies!(
r##"
test 'synthetic id prefix can be customized' do
sec = block_from_string(":idprefix: id_\n\n== Section One")
assert_equal 'id_section_one', sec.id
end
"##
);
let doc = Parser::default().parse(":idprefix: id_\n\n== Section One");
assert_eq!(first_section(&doc).id(), Some("id_section_one"));
}
#[test]
fn synthetic_id_prefix_can_be_set_to_blank() {
verifies!(
r##"
test 'synthetic id prefix can be set to blank' do
sec = block_from_string(":idprefix:\n\n== Section One")
assert_equal 'section_one', sec.id
end
"##
);
let doc = Parser::default().parse(":idprefix:\n\n== Section One");
assert_eq!(first_section(&doc).id(), Some("section_one"));
}
#[test]
fn synthetic_id_prefix_is_stripped_from_beginning_of_id_if_set_to_blank() {
verifies!(
r##"
test 'synthetic id prefix is stripped from beginning of id if set to blank' do
sec = block_from_string(":idprefix:\n\n== & ! More")
assert_equal 'more', sec.id
end
"##
);
let doc = Parser::default().parse(":idprefix:\n\n== & ! More");
assert_eq!(first_section(&doc).id(), Some("more"));
}
#[test]
fn synthetic_id_separator_can_be_customized() {
verifies!(
r##"
test 'synthetic id separator can be customized' do
sec = block_from_string(":idseparator: -\n\n== Section One")
assert_equal '_section-one', sec.id
end
"##
);
let doc = Parser::default().parse(":idseparator: -\n\n== Section One");
assert_eq!(first_section(&doc).id(), Some("_section-one"));
}
#[test]
fn synthetic_id_separator_can_be_hyphen_and_hyphens_are_preserved() {
verifies!(
r##"
test 'synthetic id separator can be hyphen and hyphens are preserved' do
sec = block_from_string(":idseparator: -\n\n== State-of-the-art design")
assert_equal '_state-of-the-art-design', sec.id
end
"##
);
let doc = Parser::default().parse(":idseparator: -\n\n== State-of-the-art design");
assert_eq!(first_section(&doc).id(), Some("_state-of-the-art-design"));
}
#[test]
fn synthetic_id_separator_can_be_dot_and_dots_are_preserved() {
verifies!(
r##"
test 'synthetic id separator can be dot and dots are preserved' do
sec = block_from_string(":idseparator: .\n\n== Version 5.0.1")
assert_equal '_version.5.0.1', sec.id
end
"##
);
let doc = Parser::default().parse(":idseparator: .\n\n== Version 5.0.1");
assert_eq!(first_section(&doc).id(), Some("_version.5.0.1"));
}
#[test]
fn synthetic_id_separator_can_only_be_one_character() {
verifies!(
r##"
test 'synthetic id separator can only be one character' do
input = <<~'EOS'
:idseparator: -=-
== This Section Is All You Need
EOS
sec = block_from_string input
assert_equal '_this-section-is-all-you-need', sec.id
end
"##
);
let doc = Parser::default().parse(":idseparator: -=-\n\n== This Section Is All You Need\n");
assert_eq!(
first_section(&doc).id(),
Some("_this-section-is-all-you-need")
);
}
#[test]
fn synthetic_id_separator_can_be_set_to_blank() {
verifies!(
r##"
test 'synthetic id separator can be set to blank' do
sec = block_from_string(":idseparator:\n\n== Section One")
assert_equal '_sectionone', sec.id
end
"##
);
let doc = Parser::default().parse(":idseparator:\n\n== Section One");
assert_eq!(first_section(&doc).id(), Some("_sectionone"));
}
#[test]
fn synthetic_id_separator_can_be_set_to_blank_when_idprefix_is_blank() {
verifies!(
r##"
test 'synthetic id separator can be set to blank when idprefix is blank' do
sec = block_from_string(":idprefix:\n:idseparator:\n\n== Section One")
assert_equal 'sectionone', sec.id
end
"##
);
let doc = Parser::default().parse(":idprefix:\n:idseparator:\n\n== Section One");
assert_eq!(first_section(&doc).id(), Some("sectionone"));
}
#[test]
fn synthetic_id_separator_is_removed_from_beginning_of_id_when_idprefix_is_blank() {
verifies!(
r##"
test 'synthetic id separator is removed from beginning of id when idprefix is blank' do
sec = block_from_string(":idprefix:\n:idseparator: _\n\n== +Section One")
assert_equal 'section_one', sec.id
end
"##
);
let doc = Parser::default().parse(":idprefix:\n:idseparator: _\n\n== +Section One");
assert_eq!(first_section(&doc).id(), Some("section_one"));
}
#[test]
fn synthetic_ids_can_be_disabled() {
verifies!(
r##"
test 'synthetic ids can be disabled' do
sec = block_from_string(":sectids!:\n\n== Section One\n")
assert_nil sec.id
end
"##
);
let doc = Parser::default().parse(":sectids!:\n\n== Section One\n");
assert_eq!(first_section(&doc).id(), None);
}
#[test]
fn explicit_id_in_anchor_above_section_title_overrides_synthetic_id() {
verifies!(
r##"
test 'explicit id in anchor above section title overrides synthetic id' do
sec = block_from_string("[[one]]\n== Section One")
assert_equal 'one', sec.id
end
"##
);
let doc = Parser::default().parse("[[one]]\n== Section One");
assert_eq!(first_section(&doc).id(), Some("one"));
}
#[test]
fn explicit_id_in_block_attributes_above_section_title_overrides_synthetic_id() {
verifies!(
r##"
test 'explicit id in block attributes above section title overrides synthetic id' do
sec = block_from_string("[id=one]\n== Section One")
assert_equal 'one', sec.id
end
"##
);
let doc = Parser::default().parse("[id=one]\n== Section One");
assert_eq!(first_section(&doc).id(), Some("one"));
}
#[test]
fn explicit_id_set_using_shorthand_in_style_above_section_title_overrides_synthetic_id() {
verifies!(
r##"
test 'explicit id set using shorthand in style above section title overrides synthetic id' do
sec = block_from_string("[#one]\n== Section One")
assert_equal 'one', sec.id
end
"##
);
let doc = Parser::default().parse("[#one]\n== Section One");
assert_eq!(first_section(&doc).id(), Some("one"));
}
#[test]
fn should_use_explicit_id_from_last_block_attribute_line_above_section_title_that_defines_an_explicit_id()
{
verifies!(
r##"
test 'should use explicit id from last block attribute line above section title that defines an explicit id' do
input = <<~'EOS'
[#un]
[#one]
== Section One
EOS
sec = block_from_string input
assert_equal 'one', sec.id
end
"##
);
let doc = Parser::default().parse("[#un]\n[#one]\n== Section One\n");
assert_eq!(first_section(&doc).id(), Some("one"));
}
#[test]
fn explicit_id_can_be_defined_using_an_embedded_anchor() {
verifies!(
r##"
test 'explicit id can be defined using an embedded anchor' do
sec = block_from_string("== Section One [[one]] ==")
assert_equal 'one', sec.id
assert_equal 'Section One', sec.title
end
"##
);
let doc = Parser::default().parse("== Section One [[one]] ==");
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("one"));
assert_eq!(sec.section_title(), "Section One");
}
non_normative!(
r##"
test 'explicit id can be defined using an embedded anchor when using setext section titles' do
input = <<~'EOS'
Section Title [[refid,reftext]]
-------------------------------
EOS
sec = block_from_string input
assert_equal 'Section Title', sec.title
assert_equal 'refid', sec.id
assert_equal 'reftext', (sec.attr 'reftext')
end
"##
);
#[test]
fn explicit_id_can_be_defined_using_an_embedded_anchor_with_reftext() {
verifies!(
r##"
test 'explicit id can be defined using an embedded anchor with reftext' do
sec = block_from_string("== Section One [[one,Section Uno]] ==")
assert_equal 'one', sec.id
assert_equal 'Section One', sec.title
assert_equal 'Section Uno', (sec.attr 'reftext')
end
"##
);
let doc = Parser::default().parse("== Section One [[one,Section Uno]] ==");
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("one"));
assert_eq!(sec.section_title(), "Section One");
assert_eq!(
doc.catalog()
.get_ref("one")
.and_then(|r| r.reftext.as_deref()),
Some("Section Uno")
);
}
#[test]
fn reftext_in_embedded_anchor_substitutes_attributes() {
let doc = Parser::default().parse(
":platform-name: Linux\n\n== Install [[install,Install on {platform-name}]] ==\n\ncontent\n",
);
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("install"));
assert_eq!(sec.section_title(), "Install");
assert_eq!(
doc.catalog()
.get_ref("install")
.and_then(|r| r.reftext.as_deref()),
Some("Install on Linux")
);
assert_eq!(
doc.catalog().resolve_id("Install on Linux"),
Some("install".to_string())
);
}
#[test]
fn id_and_reftext_in_embedded_anchor_cannot_be_quoted() {
verifies!(
r##"
test 'id and reftext in embedded anchor cannot be quoted' do
sec = block_from_string(%(== Section One [["one","Section Uno"]] ==))
refute_equal 'one', sec.id
assert_equal 'Section One [["one","Section Uno"]]', sec.title
assert_nil(sec.attr 'reftext')
end
"##
);
let doc = Parser::default().parse(r#"== Section One [["one","Section Uno"]] =="#);
let sec = first_section(&doc);
assert_ne!(sec.id(), Some("one"));
assert_eq!(
sec.section_title(),
r#"Section One [["one","Section Uno"]]"#
);
assert!(doc.catalog().get_ref("one").is_none());
}
#[test]
fn reftext_in_embedded_anchor_may_contain_comma() {
verifies!(
r##"
test 'reftext in embedded anchor may contain comma' do
sec = block_from_string(%(== Section One [[one, Section,Uno]] ==))
assert_equal 'one', sec.id
assert_equal 'Section One', sec.title
assert_equal 'Section,Uno', (sec.attr 'reftext')
end
"##
);
let doc = Parser::default().parse("== Section One [[one, Section,Uno]] ==");
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("one"));
assert_eq!(sec.section_title(), "Section One");
assert_eq!(
doc.catalog()
.get_ref("one")
.and_then(|r| r.reftext.as_deref()),
Some("Section,Uno")
);
}
#[test]
fn should_unescape_but_not_process_inline_anchor() {
verifies!(
r##"
test 'should unescape but not process inline anchor' do
sec = block_from_string(%(== Section One \\[[one]] ==))
refute_equal 'one', sec.id
assert_equal 'Section One [[one]]', sec.title
end
"##
);
let doc = Parser::default().parse(r#"== Section One \[[one]] =="#);
let sec = first_section(&doc);
assert_ne!(sec.id(), Some("one"));
assert_eq!(sec.section_title(), "Section One [[one]]");
}
#[test]
fn should_not_process_inline_anchor_in_section_title_if_section_has_explicit_id() {
verifies!(
r##"
test 'should not process inline anchor in section title if section has explicit ID' do
sec = block_from_string(%([#sect-one]\n== Section One [[one]]))
assert_equal 'sect-one', sec.id
assert_equal 'Section One <a id="one"></a>', sec.title
end
"##
);
let doc = Parser::default().parse("[#sect-one]\n== Section One [[one]]");
assert_eq!(first_section(&doc).id(), Some("sect-one"));
assert_eq!(
first_section(&doc).section_title(),
r#"Section One <a id="one"></a>"#
);
}
#[test]
fn should_apply_substitutions_to_title_with_attribute_references_when_registering_section_with_auto_generated_id()
{
verifies!(
r##"
test 'should apply substititons to title with attribute references when registering section with auto-generated ID' do
input = <<~'EOS'
= Document Title
:foo: bar
See <<_section_baz>>.
:foo: baz
== Section {foo}
That's all, folks!
EOS
doc = document_from_string input
ref = doc.catalog[:refs]['_section_baz']
refute_nil ref
output = doc.convert standalone: false
assert_xpath '//a[@href="#_section_baz"][text()="Section baz"]', output, 1
assert_xpath '//h2[@id="_section_baz"][text()="Section baz"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Document Title\n:foo: bar\n\nSee <<_section_baz>>.\n\n:foo: baz\n\n== Section {foo}\n\nThat's all, folks!\n",
);
assert!(doc.catalog().get_ref("_section_baz").is_some());
assert_xpath(
&doc,
r##"//a[@href="#_section_baz"][text()="Section baz"]"##,
1,
);
assert_xpath(&doc, r#"//h2[@id="_section_baz"][text()="Section baz"]"#, 1);
}
#[test]
fn should_apply_substitutions_to_title_with_attribute_references_when_registering_section_with_explicit_id()
{
verifies!(
r##"
test 'should apply substititons to title with attribute references when registering section with explicit ID' do
input = <<~'EOS'
= Document Title
:foo: bar
See <<explicit>>.
:foo: baz
[#explicit]
== Section {foo}
That's all, folks!
EOS
doc = document_from_string input
ref = doc.catalog[:refs]['explicit']
refute_nil ref
output = doc.convert standalone: false
assert_xpath '//a[@href="#explicit"][text()="Section baz"]', output, 1
assert_xpath '//h2[@id="explicit"][text()="Section baz"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Document Title\n:foo: bar\n\nSee <<explicit>>.\n\n:foo: baz\n\n[#explicit]\n== Section {foo}\n\nThat's all, folks!\n",
);
assert!(doc.catalog().get_ref("explicit").is_some());
assert_xpath(&doc, r##"//a[@href="#explicit"][text()="Section baz"]"##, 1);
assert_xpath(&doc, r#"//h2[@id="explicit"][text()="Section baz"]"#, 1);
}
#[test]
fn title_substitutions_are_applied_before_generating_id() {
verifies!(
r##"
test 'title substitutions are applied before generating id' do
sec = block_from_string("== Section{sp}One\n")
assert_equal '_section_one', sec.id
end
"##
);
let doc = Parser::default().parse("== Section{sp}One\n");
assert_eq!(first_section(&doc).id(), Some("_section_one"));
}
#[test]
fn synthetic_ids_are_unique() {
verifies!(
r##"
test 'synthetic ids are unique' do
input = <<~'EOS'
== Some section
text
== Some section
text
EOS
doc = document_from_string input
assert_equal '_some_section', doc.blocks[0].id
assert_equal '_some_section_2', doc.blocks[1].id
end
"##
);
let doc = Parser::default().parse("== Some section\n\ntext\n\n== Some section\n\ntext\n");
let blocks = top_blocks(&doc);
assert_eq!(as_section(blocks[0]).id(), Some("_some_section"));
assert_eq!(as_section(blocks[1]).id(), Some("_some_section_2"));
}
non_normative!(
r##"
# NOTE test cannot be run in parallel with other tests
test 'can set start index of synthetic ids' do
old_unique_id_start_index = Asciidoctor::Compliance.unique_id_start_index
begin
input = <<~'EOS'
== Some section
text
== Some section
text
EOS
Asciidoctor::Compliance.unique_id_start_index = 1
doc = document_from_string input
assert_equal '_some_section', doc.blocks[0].id
assert_equal '_some_section_1', doc.blocks[1].id
ensure
Asciidoctor::Compliance.unique_id_start_index = old_unique_id_start_index
end
end
"##
);
#[test]
fn should_use_specified_id_and_reftext_when_registering_section_reference() {
verifies!(
r##"
test 'should use specified id and reftext when registering section reference' do
input = <<~'EOS'
[[install,Install Procedure]]
== Install
content
EOS
doc = document_from_string input
ref = doc.catalog[:refs]['install']
refute_nil ref
assert_equal 'Install Procedure', ref.reftext
assert_equal 'install', (doc.resolve_id 'Install Procedure')
end
"##
);
let doc = Parser::default().parse("[[install,Install Procedure]]\n== Install\n\ncontent\n");
let reff = doc.catalog().get_ref("install");
assert!(reff.is_some());
assert_eq!(reff.unwrap().reftext.as_deref(), Some("Install Procedure"));
assert_eq!(
doc.catalog().resolve_id("Install Procedure"),
Some("install".to_string())
);
}
#[test]
fn should_use_specified_reftext_when_registering_section_reference() {
verifies!(
r##"
test 'should use specified reftext when registering section reference' do
input = <<~'EOS'
[reftext="Install Procedure"]
== Install
content
EOS
doc = document_from_string input
ref = doc.catalog[:refs]['_install']
refute_nil ref
assert_equal 'Install Procedure', ref.reftext
assert_equal '_install', (doc.resolve_id 'Install Procedure')
end
"##
);
let doc =
Parser::default().parse("[reftext=\"Install Procedure\"]\n== Install\n\ncontent\n");
let reff = doc.catalog().get_ref("_install");
assert!(reff.is_some());
assert_eq!(reff.unwrap().reftext.as_deref(), Some("Install Procedure"));
assert_eq!(
doc.catalog().resolve_id("Install Procedure"),
Some("_install".to_string())
);
}
#[test]
fn should_resolve_attribute_reference_in_title_using_attribute_defined_at_location_of_section_title()
{
verifies!(
r##"
test 'should resolve attribute reference in title using attribute defined at location of section title' do
input = <<~'EOS'
:platform-id: linux
:platform-name: Linux
[#install-{platform-id}]
== Install on {platform-name}
content
:platform-id: win32
:platform-name: Windows
[#install-{platform-id}]
== Install on {platform-name}
content
EOS
doc = document_from_string input
ref = doc.catalog[:refs]['install-win32']
refute_nil ref
assert_equal 'Install on Windows', ref.title
assert_equal 'install-win32', (doc.resolve_id 'Install on Windows')
end
"##
);
let doc = Parser::default().parse(
":platform-id: linux\n:platform-name: Linux\n\n[#install-{platform-id}]\n== Install on {platform-name}\n\ncontent\n\n:platform-id: win32\n:platform-name: Windows\n\n[#install-{platform-id}]\n== Install on {platform-name}\n\ncontent\n",
);
assert!(doc.catalog().get_ref("install-win32").is_some());
let sec = all_sections(&doc)
.into_iter()
.find(|s| s.id() == Some("install-win32"))
.expect("section install-win32");
assert_eq!(sec.section_title(), "Install on Windows");
assert_eq!(
doc.catalog().resolve_id("Install on Windows"),
Some("install-win32".to_string())
);
}
#[test]
fn should_substitute_attributes_when_registering_reftext_for_section() {
verifies!(
r##"
test 'should substitute attributes when registering reftext for section' do
input = <<~'EOS'
:platform-name: n/a
== Overview
:platform-name: Linux
[[install,install on {platform-name}]]
== Install
content
EOS
doc = document_from_string input
ref = doc.catalog[:refs]['install']
refute_nil ref
assert_equal 'install on Linux', ref.reftext
assert_equal 'install', (doc.resolve_id 'install on Linux')
end
"##
);
let doc = Parser::default().parse(
":platform-name: n/a\n== Overview\n\n:platform-name: Linux\n\n[[install,install on {platform-name}]]\n== Install\n\ncontent\n",
);
let reff = doc.catalog().get_ref("install").unwrap();
assert_eq!(reff.reftext.as_deref(), Some("install on Linux"));
assert_eq!(
doc.catalog().resolve_id("install on Linux"),
Some("install".to_string())
);
assert_eq!(doc.catalog().resolve_id("install on {platform-name}"), None);
}
#[test]
fn duplicate_section_id_should_not_overwrite_existing_section_id_entry_in_references_table() {
verifies!(
r##"
test 'duplicate section id should not overwrite existing section id entry in references table' do
input = <<~'EOS'
[#install]
== First Install
content
[#install]
== Second Install
content
EOS
using_memory_logger do |logger|
doc = document_from_string input
ref = doc.catalog[:refs]['install']
refute_nil ref
assert_nil ref.reftext
assert_equal 'First Install', ref.title
assert_equal 'install', (doc.resolve_id 'First Install')
assert_message logger, :WARN, '<stdin>: line 7: id assigned to section already in use: install', Hash
end
end
"##
);
let doc = Parser::default().parse(
"[#install]\n== First Install\n\ncontent\n\n[#install]\n== Second Install\n\ncontent\n",
);
let reff = doc.catalog().get_ref("install");
assert!(reff.is_some());
assert_eq!(reff.unwrap().reftext.as_deref(), Some("First Install"));
let sec = all_sections(&doc)
.into_iter()
.find(|s| s.id() == Some("install"))
.expect("section install");
assert_eq!(sec.section_title(), "First Install");
assert_eq!(
doc.catalog().resolve_id("First Install"),
Some("install".to_string())
);
let warnings: Vec<_> = doc.warnings().collect();
assert_eq!(warnings.len(), 1);
assert!(matches!(&warnings[0].warning,
WarningType::DuplicateId(id) if id == "install"));
}
#[test]
fn should_warn_if_explicit_section_id_matches_auto_generated_section_id() {
verifies!(
r##"
test 'should warn if explicit section ID matches auto-generated section ID' do
input = <<~'EOS'
== Do Not Repeat Yourself
content
[#_do_not_repeat_yourself]
== Do Not Repeat Yourself
content
EOS
using_memory_logger do |logger|
doc = document_from_string input
ref = doc.catalog[:refs]['_do_not_repeat_yourself']
refute_nil ref
assert_nil ref.reftext
assert_equal 'Do Not Repeat Yourself', ref.title
assert_equal '_do_not_repeat_yourself', (doc.resolve_id 'Do Not Repeat Yourself')
assert_message logger, :WARN, '<stdin>: line 6: id assigned to section already in use: _do_not_repeat_yourself', Hash
assert_equal 2, (doc.convert.scan 'id="_do_not_repeat_yourself"').size
end
end
"##
);
let doc = Parser::default().parse(
"== Do Not Repeat Yourself\n\ncontent\n\n[#_do_not_repeat_yourself]\n== Do Not Repeat Yourself\n\ncontent\n",
);
let reff = doc.catalog().get_ref("_do_not_repeat_yourself");
assert!(reff.is_some());
assert_eq!(
reff.unwrap().reftext.as_deref(),
Some("Do Not Repeat Yourself")
);
let sec = all_sections(&doc)
.into_iter()
.find(|s| s.id() == Some("_do_not_repeat_yourself"))
.expect("section _do_not_repeat_yourself");
assert_eq!(sec.section_title(), "Do Not Repeat Yourself");
assert_eq!(
doc.catalog().resolve_id("Do Not Repeat Yourself"),
Some("_do_not_repeat_yourself".to_string())
);
let warnings: Vec<_> = doc.warnings().collect();
assert_eq!(warnings.len(), 1);
assert!(matches!(&warnings[0].warning,
WarningType::DuplicateId(id) if id == "_do_not_repeat_yourself"));
assert_css(&doc, r#"h2[id="_do_not_repeat_yourself"]"#, 2);
}
#[test]
fn duplicate_block_id_should_not_overwrite_existing_section_id_entry_in_references_table() {
verifies!(
r##"
test 'duplicate block id should not overwrite existing section id entry in references table' do
input = <<~'EOS'
[#install]
== First Install
content
[#install]
content
EOS
using_memory_logger do |logger|
doc = document_from_string input
ref = doc.catalog[:refs]['install']
refute_nil ref
assert_nil ref.reftext
assert_equal 'First Install', ref.title
assert_equal 'install', (doc.resolve_id 'First Install')
assert_message logger, :WARN, '<stdin>: line 7: id assigned to block already in use: install', Hash
end
end
"##
);
let doc = Parser::default()
.parse("[#install]\n== First Install\n\ncontent\n\n[#install]\ncontent\n");
let reff = doc.catalog().get_ref("install");
assert!(reff.is_some());
let sec = all_sections(&doc)
.into_iter()
.find(|s| s.id() == Some("install"))
.expect("section install");
assert_eq!(sec.section_title(), "First Install");
let warnings: Vec<_> = doc.warnings().collect();
assert_eq!(warnings.len(), 1);
assert!(matches!(&warnings[0].warning,
WarningType::DuplicateId(id) if id == "install"));
}
}
non_normative!(
r##"
end
context 'Levels' do
context 'Document Title (Level 0)' do
"##
);
mod levels {
use crate::tests::sdd::*;
mod document_title_level_0 {
use crate::tests::prelude::*;
non_normative!(
r##"
test "document title with multiline syntax" do
title = "My Title"
chars = "=" * title.length
assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars)
assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars + "\n")
end
test "document title with multiline syntax, give a char" do
title = "My Title"
chars = "=" * (title.length + 1)
assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars)
assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars + "\n")
end
test "document title with multiline syntax, take a char" do
title = "My Title"
chars = "=" * (title.length - 1)
assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars)
assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars + "\n")
end
test 'document title with multiline syntax and unicode characters' do
input = <<~'EOS'
AsciiDoc Writer’s Guide
=======================
Author Name
preamble
EOS
result = convert_string input
assert_xpath '//h1', result, 1
assert_xpath '//h1[text()="AsciiDoc Writer’s Guide"]', result, 1
end
test "not enough chars for a multiline document title" do
title = "My Title"
chars = "=" * (title.length - 2)
using_memory_logger do |logger|
output = convert_string(title + "\n" + chars)
assert_xpath '//h1', output, 0
refute logger.empty?
logger.clear
output = convert_string(title + "\n" + chars + "\n")
assert_xpath '//h1', output, 0
refute logger.empty?
end
end
test "too many chars for a multiline document title" do
title = "My Title"
chars = "=" * (title.length + 2)
using_memory_logger do |logger|
output = convert_string(title + "\n" + chars)
assert_xpath '//h1', output, 0
refute logger.empty?
logger.clear
output = convert_string(title + "\n" + chars + "\n")
assert_xpath '//h1', output, 0
refute logger.empty?
end
end
test "document title with multiline syntax cannot begin with a dot" do
title = ".My Title"
chars = "=" * title.length
using_memory_logger do |logger|
output = convert_string(title + "\n" + chars)
assert_xpath '//h1', output, 0
refute logger.empty?
end
end
"##
);
#[test]
fn document_title_with_atx_syntax() {
verifies!(
r##"
test "document title with atx syntax" do
assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string("= My Title")
end
"##
);
let doc = Parser::default().parse("= My Title");
assert_eq!(doc.doctitle(), Some("My Title"));
}
#[test]
fn document_title_with_symmetric_syntax() {
verifies!(
r##"
test "document title with symmetric syntax" do
assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string("= My Title =")
end
"##
);
let doc = Parser::default().parse("= My Title =");
assert_eq!(doc.doctitle(), Some("My Title"));
}
non_normative!(
r##"
test 'document title created from leveloffset shift defined in document' do
assert_xpath "//h1[not(@id)][text() = 'Document Title']", convert_string(%(:leveloffset: -1\n== Document Title))
end
test 'document title created from leveloffset shift defined in API' do
assert_xpath "//h1[not(@id)][text() = 'Document Title']", convert_string('== Document Title', attributes: { 'leveloffset' => '-1@' })
end
"##
);
non_normative!(
r##"
test 'should assign id on document title to body' do
input = <<~'EOS'
[[idname]]
= Document Title
content
EOS
output = convert_string input
assert_css 'body#idname', output, 1
end
test 'should assign id defined using shorthand syntax on document title to body' do
input = <<~'EOS'
[#idname]
= Document Title
content
EOS
output = convert_string input
assert_css 'body#idname', output, 1
end
test 'should use ID defined in block attributes instead of ID defined inline' do
input = <<~'EOS'
[#idname-block]
= Document Title [[idname-inline]]
content
EOS
output = convert_string input
assert_css 'body#idname-block', output, 1
end
"##
);
#[test]
fn block_id_above_document_title_sets_id_on_document() {
verifies!(
r##"
test 'block id above document title sets id on document' do
input = <<~'EOS'
[[reference]]
= Reference Manual
:css-signature: refguide
preamble
EOS
doc = document_from_string input
assert_equal 'reference', doc.id
assert_equal 'refguide', doc.attr('css-signature')
output = doc.convert
assert_css 'body#reference', output, 1
end
"##
);
use crate::document::InterpretedValue;
let doc = Parser::default()
.parse("[[reference]]\n= Reference Manual\n:css-signature: refguide\n\npreamble\n");
assert_eq!(doc.header().id(), Some("reference"));
assert_eq!(doc.header().title(), Some("Reference Manual"));
assert_eq!(
doc.attribute_value("css-signature"),
InterpretedValue::Value("refguide".to_string())
);
}
#[test]
fn should_register_document_in_catalog_if_id_is_set() {
verifies!(
r##"
test 'should register document in catalog if id is set' do
input = <<~'EOS'
[[manual,Manual]]
= Reference Manual
preamble
EOS
doc = document_from_string input
assert_equal 'manual', doc.id
assert_equal 'Manual', doc.attributes['reftext']
assert_equal doc, doc.catalog[:refs]['manual']
end
"##
);
use crate::document::InterpretedValue;
let doc =
Parser::default().parse("[[manual,Manual]]\n= Reference Manual\n\npreamble\n");
assert_eq!(doc.header().id(), Some("manual"));
assert_eq!(
doc.attribute_value("reftext"),
InterpretedValue::Value("Manual".to_string())
);
let entry = doc
.catalog()
.get_ref("manual")
.expect("document registered in the catalog under its id");
assert_eq!(entry.reftext.as_deref(), Some("Manual"));
}
#[test]
fn should_compute_xreftext_to_document_title() {
verifies!(
r##"
test 'should compute xreftext to document title' do
input = <<~'EOS'
[#manual]
= Reference Manual
:xrefstyle: full
This is the <<manual>>.
EOS
output = convert_string input
assert_xpath '//a[text()="Reference Manual"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"[#manual]\n= Reference Manual\n:xrefstyle: full\n\nThis is the <<manual>>.\n",
);
assert_eq!(
rendered_paragraphs(&doc),
vec![r##"This is the <a href="#manual">Reference Manual</a>."##]
);
}
non_normative!(
r##"
test 'should discard style, role and options shorthand attributes defined on document title' do
input = <<~'EOS'
[style#idname.rolename%optionname]
= Document Title
content
EOS
doc = document_from_string input
assert_empty doc.blocks[0].attributes
output = doc.convert
assert_css '#idname', output, 1
assert_css 'body#idname', output, 1
assert_css '.rolename', output, 1
assert_css 'body.rolename', output, 1
end
end
"##
);
}
non_normative!(
r##"
context 'Level 1' do
"##
);
mod level_1 {
use crate::tests::prelude::*;
non_normative!(
r##"
test "with multiline syntax" do
assert_xpath "//h2[@id='_my_section'][text() = 'My Section']", convert_string("My Section\n-----------")
end
"##
);
#[test]
fn should_not_recognize_underline_containing_a_mix_of_characters_as_setext_section_title() {
verifies!(
r##"
test 'should not recognize underline containing a mix of characters as setext section title' do
input = <<~'EOS'
My Section
----^^----
EOS
result = convert_string_to_embedded input
assert_xpath '//h2[@id="_my_section"][text() = "My Section"]', result, 0
assert_includes result, '----^^----'
end
"##
);
let doc = Parser::default().parse("My Section\n----^^----\n");
assert_xpath(&doc, r#"//h2[@id="_my_section"][text() = "My Section"]"#, 0);
assert_rendered_contains(&doc, "----^^----");
}
#[test]
fn should_not_recognize_section_title_that_does_not_contain_alphanumeric_character() {
verifies!(
r##"
test 'should not recognize section title that does not contain alphanumeric character' do
input = <<~'EOS'
!@#$
----
EOS
using_memory_logger do |logger|
result = convert_string_to_embedded input
assert_css 'h2', result, 0
end
end
"##
);
let doc = Parser::default().parse("!@#$\n----\n");
assert_css(&doc, "h2", 0);
}
#[test]
fn should_not_recognize_section_title_that_consists_of_only_underscores() {
verifies!(
r##"
test 'should not recognize section title that consists of only underscores' do
input = <<~'EOS'
____
----
EOS
using_memory_logger do |logger|
result = convert_string_to_embedded input
assert_css 'h2', result, 0
end
end
"##
);
let doc = Parser::default().parse("____\n----\n");
assert_css(&doc, "h2", 0);
}
non_normative!(
r##"
test 'should preprocess second line of setext section title' do
input = <<~'EOS'
Section Title
ifdef::asciidoctor[]
-------------
endif::[]
EOS
result = convert_string_to_embedded input
assert_xpath '//h2', result, 1
end
test "heading title with multiline syntax cannot begin with a dot" do
title = ".My Title"
chars = "-" * title.length
using_memory_logger do |logger|
output = convert_string(title + "\n" + chars)
assert_xpath '//h2', output, 0
refute logger.empty?
end
end
"##
);
#[test]
fn with_atx_syntax() {
verifies!(
r##"
test "with atx syntax" do
assert_xpath "//h2[@id='_my_title'][text() = 'My Title']", convert_string("== My Title")
end
"##
);
let doc = Parser::default().parse("== My Title");
let sec = first_section(&doc);
assert_eq!(sec.level(), 1);
assert_eq!(sec.id(), Some("_my_title"));
assert_eq!(sec.section_title(), "My Title");
}
#[test]
fn with_atx_symmetric_syntax() {
verifies!(
r##"
test "with atx symmetric syntax" do
assert_xpath "//h2[@id='_my_title'][text() = 'My Title']", convert_string("== My Title ==")
end
"##
);
let doc = Parser::default().parse("== My Title ==");
let sec = first_section(&doc);
assert_eq!(sec.level(), 1);
assert_eq!(sec.id(), Some("_my_title"));
assert_eq!(sec.section_title(), "My Title");
}
#[test]
fn with_atx_non_matching_symmetric_syntax() {
verifies!(
r##"
test "with atx non-matching symmetric syntax" do
assert_xpath "//h2[@id='_my_title'][text() = 'My Title ===']", convert_string("== My Title ===")
end
"##
);
let doc = Parser::default().parse("== My Title ===");
let sec = first_section(&doc);
assert_eq!(sec.level(), 1);
assert_eq!(sec.id(), Some("_my_title"));
assert_eq!(sec.section_title(), "My Title ===");
}
#[test]
fn symmetric_close_requires_ascii_blank_not_unicode_whitespace() {
let doc = Parser::default().parse("== My Title\u{a0}==");
let sec = first_section(&doc);
assert_eq!(sec.level(), 1);
assert!(
sec.section_title().ends_with("=="),
"expected the `==` to be retained, got {:?}",
sec.section_title()
);
}
#[test]
fn with_xml_entity() {
verifies!(
r##"
test "with XML entity" do
assert_xpath "//h2[@id='_whats_new'][text() = \"What#{decode_char 8217}s new?\"]", convert_string("== What's new?")
end
"##
);
let doc = Parser::default().parse("== What's new?");
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("_whats_new"));
assert_eq!(sec.section_title(), "What’s new?");
}
#[test]
fn with_non_word_character() {
verifies!(
r##"
test "with non-word character" do
assert_xpath "//h2[@id='_whats_new'][text() = \"What’s new?\"]", convert_string("== What’s new?")
end
"##
);
let doc = Parser::default().parse("== What\u{2019}s new?");
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("_whats_new"));
assert_eq!(sec.section_title(), "What\u{2019}s new?");
}
#[test]
fn with_sequential_non_word_characters() {
verifies!(
r##"
test "with sequential non-word characters" do
assert_xpath "//h2[@id='_what_the_is_this'][text() = 'What the \#@$ is this?']", convert_string('== What the #@$ is this?')
end
"##
);
let doc = Parser::default().parse("== What the #@$ is this?");
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("_what_the_is_this"));
assert_eq!(sec.section_title(), "What the #@$ is this?");
}
#[test]
fn with_trailing_whitespace() {
verifies!(
r##"
test "with trailing whitespace" do
assert_xpath "//h2[@id='_my_title'][text() = 'My Title']", convert_string("== My Title ")
end
"##
);
let doc = Parser::default().parse("== My Title ");
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("_my_title"));
assert_eq!(sec.section_title(), "My Title");
}
#[test]
fn with_custom_blank_idprefix() {
verifies!(
r##"
test "with custom blank idprefix" do
assert_xpath "//h2[@id='my_title'][text() = 'My Title']", convert_string(":idprefix:\n\n== My Title ")
end
"##
);
let doc = Parser::default().parse(":idprefix:\n\n== My Title ");
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("my_title"));
assert_eq!(sec.section_title(), "My Title");
}
#[test]
fn with_custom_non_blank_idprefix() {
verifies!(
r##"
test "with custom non-blank idprefix" do
assert_xpath "//h2[@id='ref_my_title'][text() = 'My Title']", convert_string(":idprefix: ref_\n\n== My Title ")
end
"##
);
let doc = Parser::default().parse(":idprefix: ref_\n\n== My Title ");
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("ref_my_title"));
assert_eq!(sec.section_title(), "My Title");
}
#[test]
fn with_multibyte_characters() {
verifies!(
r##"
test 'with multibyte characters' do
input = '== Asciidoctor in 中文'
output = convert_string input
assert_xpath '//h2[@id="_asciidoctor_in_中文"][text()="Asciidoctor in 中文"]', output
end
"##
);
let doc = Parser::default().parse("== Asciidoctor in 中文");
assert_xpath(
&doc,
r#"//h2[@id="_asciidoctor_in_中文"][text()="Asciidoctor in 中文"]"#,
1,
);
}
#[test]
fn with_only_multibyte_characters() {
verifies!(
r##"
test 'with only multibyte characters' do
input = '== 视图'
output = convert_string_to_embedded input
assert_xpath '//h2[@id="_视图"][text()="视图"]', output
end
"##
);
let doc = Parser::default().parse("== 视图");
assert_xpath(&doc, r#"//h2[@id="_视图"][text()="视图"]"#, 1);
}
non_normative!(
r##"
test 'multiline syntax with only multibyte characters' do
input = <<~'EOS'
视图
--
content
连接器
---
content
EOS
output = convert_string_to_embedded input
assert_xpath '//h2[@id="_视图"][text()="视图"]', output
assert_xpath '//h2[@id="_连接器"][text()="连接器"]', output
end
end
"##
);
}
non_normative!(
r##"
context 'Level 2' do
"##
);
mod level_2 {
use crate::tests::prelude::*;
non_normative!(
r##"
test "with multiline syntax" do
assert_xpath "//h3[@id='_my_section'][text() = 'My Section']", convert_string(":fragment:\nMy Section\n~~~~~~~~~~~")
end
"##
);
#[test]
fn with_atx_line_syntax() {
verifies!(
r##"
test "with atx line syntax" do
assert_xpath "//h3[@id='_my_title'][text() = 'My Title']", convert_string(":fragment:\n=== My Title")
end
end
"##
);
let doc = Parser::default().parse(":fragment:\n=== My Title");
let sec = first_section(&doc);
assert_eq!(sec.level(), 2);
assert_eq!(sec.id(), Some("_my_title"));
assert_eq!(sec.section_title(), "My Title");
}
}
non_normative!(
r##"
context 'Level 3' do
"##
);
mod level_3 {
use crate::tests::prelude::*;
non_normative!(
r##"
test "with multiline syntax" do
assert_xpath "//h4[@id='_my_section'][text() = 'My Section']", convert_string(":fragment:\nMy Section\n^^^^^^^^^^")
end
"##
);
#[test]
fn with_atx_line_syntax() {
verifies!(
r##"
test 'with atx line syntax' do
assert_xpath "//h4[@id='_my_title'][text() = 'My Title']", convert_string(":fragment:\n==== My Title")
end
end
"##
);
let doc = Parser::default().parse(":fragment:\n==== My Title");
let sec = first_section(&doc);
assert_eq!(sec.level(), 3);
assert_eq!(sec.id(), Some("_my_title"));
assert_eq!(sec.section_title(), "My Title");
}
}
non_normative!(
r##"
context 'Level 4' do
"##
);
mod level_4 {
use crate::tests::prelude::*;
non_normative!(
r##"
test "with multiline syntax" do
assert_xpath "//h5[@id='_my_section'][text() = 'My Section']", convert_string(":fragment:\nMy Section\n++++++++++")
end
"##
);
#[test]
fn with_atx_line_syntax() {
verifies!(
r##"
test "with atx line syntax" do
assert_xpath "//h5[@id='_my_title'][text() = 'My Title']", convert_string(":fragment:\n===== My Title")
end
end
"##
);
let doc = Parser::default().parse(":fragment:\n===== My Title");
let sec = first_section(&doc);
assert_eq!(sec.level(), 4);
assert_eq!(sec.id(), Some("_my_title"));
assert_eq!(sec.section_title(), "My Title");
}
}
non_normative!(
r##"
context 'Level 5' do
"##
);
mod level_5 {
use crate::tests::prelude::*;
#[test]
fn with_atx_line_syntax() {
verifies!(
r##"
test "with atx line syntax" do
assert_xpath "//h6[@id='_my_title'][text() = 'My Title']", convert_string(":fragment:\n====== My Title")
end
end
end
"##
);
let doc = Parser::default().parse(":fragment:\n====== My Title");
let sec = first_section(&doc);
assert_eq!(sec.level(), 5);
assert_eq!(sec.id(), Some("_my_title"));
assert_eq!(sec.section_title(), "My Title");
}
}
}
non_normative!(
r##"
context 'Substitutions' do
"##
);
mod substitutions {
use crate::tests::prelude::*;
#[test]
fn should_apply_substitutions_in_normal_order() {
verifies!(
r##"
test 'should apply substitutions in normal order' do
input = <<~'EOS'
== {link-url}[{link-text}]{tm}
The one and only!
EOS
output = convert_string_to_embedded input, attributes: {
'link-url' => 'https://acme.com',
'link-text' => 'ACME',
'tm' => '(TM)',
}
assert_css 'h2', output, 1
assert_css 'h2 a[href="https://acme.com"]', output, 1
assert_xpath %(//h2[contains(text(),"#{decode_char 8482}")]), output, 1
end
"##
);
let doc = Parser::default()
.with_intrinsic_attribute(
"link-url",
"https://acme.com",
ModificationContext::Anywhere,
)
.with_intrinsic_attribute("link-text", "ACME", ModificationContext::Anywhere)
.with_intrinsic_attribute("tm", "(TM)", ModificationContext::Anywhere)
.parse("== {link-url}[{link-text}]{tm}\n\nThe one and only!\n");
assert_css(&doc, "h2", 1);
assert_eq!(
first_section(&doc).section_title(),
r#"<a href="https://acme.com">ACME</a>™"#
);
}
}
non_normative!(
r##"
end
context 'Nesting' do
"##
);
mod nesting {
use crate::tests::prelude::*;
#[test]
fn should_warn_if_section_title_is_out_of_sequence() {
verifies!(
r##"
test 'should warn if section title is out of sequence' do
input = <<~'EOS'
= Document Title
== Section A
==== Nested Section
content
== Section B
content
EOS
using_memory_logger do |logger|
result = convert_string_to_embedded input
assert_xpath '//h4[text()="Nested Section"]', result, 1
assert_message logger, :WARN, '<stdin>: line 5: section title out of sequence: expected level 2, got level 3', Hash
end
end
"##
);
let doc = Parser::default().parse(
"= Document Title\n\n== Section A\n\n==== Nested Section\n\ncontent\n\n== Section B\n\ncontent\n",
);
let warnings: Vec<_> = doc.warnings().collect();
assert!(
warnings
.iter()
.any(|w| matches!(&w.warning, WarningType::SectionHeadingLevelSkipped(1, 3)))
);
let nested = all_sections(&doc)
.into_iter()
.find(|s| s.section_title() == "Nested Section")
.expect("nested section");
assert_eq!(nested.level(), 3);
}
non_normative!(
r##"
test 'should warn if chapter title is out of sequence' do
input = <<~'EOS'
= Document Title
:doctype: book
=== Not a Chapter
content
EOS
using_memory_logger do |logger|
result = convert_string_to_embedded input
assert_xpath '//h3[text()="Not a Chapter"]', result, 1
assert_message logger, :WARN, '<stdin>: line 4: section title out of sequence: expected levels 0 or 1, got level 2', Hash
end
end
"##
);
#[test]
fn should_not_warn_if_top_level_section_title_is_out_of_sequence_when_fragment_attribute_is_set_on_document()
{
verifies!(
r##"
test 'should not warn if top-level section title is out of sequence when fragment attribute is set on document' do
input = <<~'EOS'
= Document Title
=== First Section
content
EOS
using_memory_logger do |logger|
convert_string_to_embedded input, attributes: { 'fragment' => '' }
assert logger.empty?
end
end
"##
);
let doc = Parser::default()
.with_intrinsic_attribute("fragment", "", ModificationContext::Anywhere)
.parse("= Document Title\n\n=== First Section\n\ncontent\n");
assert!(
doc.warnings()
.all(|w| !matches!(w.warning, WarningType::SectionHeadingLevelSkipped(..)))
);
}
#[test]
fn top_level_section_out_of_sequence_warns_without_fragment() {
let doc = Parser::default().parse("= Document Title\n\n=== First Section\n\ncontent\n");
let warnings: Vec<_> = doc.warnings().collect();
assert_eq!(warnings.len(), 1);
assert!(matches!(
warnings[0].warning,
WarningType::SectionHeadingLevelSkipped(0, 2)
));
}
#[test]
fn discrete_heading_at_document_root_is_not_out_of_sequence() {
let doc = Parser::default().parse("= Document Title\n\n[float]\n=== Float Heading\n");
assert_eq!(first_section(&doc).section_type(), SectionType::Discrete);
assert!(
doc.warnings()
.all(|w| !matches!(w.warning, WarningType::SectionHeadingLevelSkipped(..)))
);
}
#[test]
fn should_warn_if_nested_section_title_is_out_of_sequence_when_fragment_attribute_is_set_on_document()
{
verifies!(
r##"
test 'should warn if nested section title is out of sequence when fragment attribute is set on document' do
input = <<~'EOS'
= Document Title
=== First Section
===== Nested Section
EOS
using_memory_logger do |logger|
convert_string_to_embedded input, attributes: { 'fragment' => '' }
assert_message logger, :WARN, '<stdin>: line 5: section title out of sequence: expected level 3, got level 4', Hash
end
end
"##
);
let doc = Parser::default()
.with_intrinsic_attribute("fragment", "", ModificationContext::Anywhere)
.parse("= Document Title\n\n=== First Section\n\n===== Nested Section\n");
let warnings: Vec<_> = doc.warnings().collect();
assert!(
warnings
.iter()
.any(|w| matches!(&w.warning, WarningType::SectionHeadingLevelSkipped(2, 4)))
);
}
non_normative!(
r##"
test 'should log error if subsections are found in special sections in article that do not support subsections' do
input = <<~'EOS'
= Document Title
== Section
=== Subsection of Section
allowed
[appendix]
== Appendix
=== Subsection of Appendix
allowed
[glossary]
== Glossary
=== Subsection of Glossary
not allowed
[bibliography]
== Bibliography
=== Subsection of Bibliography
not allowed
EOS
using_memory_logger do |logger|
convert_string_to_embedded input
assert_messages logger, [
[:ERROR, '<stdin>: line 19: glossary sections do not support nested sections', Hash],
[:ERROR, '<stdin>: line 26: bibliography sections do not support nested sections', Hash],
]
end
end
test 'should log error if subsections are found in special sections in book that do not support subsections' do
input = <<~'EOS'
= Document Title
:doctype: book
[preface]
= Preface
=== Subsection of Preface
allowed
[colophon]
= Colophon
=== Subsection of Colophon
not allowed
[dedication]
= Dedication
=== Subsection of Dedication
not allowed
= Part 1
[abstract]
== Abstract
=== Subsection of Abstract
allowed
== Chapter 1
=== Subsection of Chapter
allowed
[appendix]
= Appendix
=== Subsection of Appendix
allowed
[glossary]
= Glossary
=== Subsection of Glossary
not allowed
[bibliography]
= Bibliography
=== Subsection of Bibliography
not allowed
EOS
using_memory_logger do |logger|
convert_string_to_embedded input
assert_messages logger, [
[:ERROR, '<stdin>: line 14: colophon sections do not support nested sections', Hash],
[:ERROR, '<stdin>: line 21: dedication sections do not support nested sections', Hash],
[:ERROR, '<stdin>: line 50: glossary sections do not support nested sections', Hash],
[:ERROR, '<stdin>: line 57: bibliography sections do not support nested sections', Hash]
]
end
end
end
"##
);
}
non_normative!(
r##"
context 'Markdown-style headings' do
"##
);
mod markdown_style_headings {
use crate::tests::prelude::*;
#[test]
fn atx_document_title_with_leading_marker() {
verifies!(
r##"
test 'atx document title with leading marker' do
input = '# Document Title'
output = convert_string input
assert_xpath "//h1[not(@id)][text() = 'Document Title']", output, 1
end
"##
);
let doc = Parser::default().parse("# Document Title");
assert_eq!(doc.header().title(), Some("Document Title"));
assert!(rendered_paragraphs(&doc).is_empty());
}
#[test]
fn atx_document_title_with_symmetric_markers() {
verifies!(
r##"
test 'atx document title with symmetric markers' do
input = '# Document Title #'
output = convert_string input
assert_xpath "//h1[not(@id)][text() = 'Document Title']", output, 1
end
"##
);
let doc = Parser::default().parse("# Document Title #");
assert_eq!(doc.header().title(), Some("Document Title"));
}
#[test]
fn atx_section_title_with_leading_marker() {
verifies!(
r##"
test 'atx section title with leading marker' do
input = <<~'EOS'
## Section One
blah blah
EOS
output = convert_string input
assert_xpath "//h2[@id='_section_one'][text() = 'Section One']", output, 1
end
"##
);
let doc = Parser::default().parse("## Section One\n\nblah blah\n");
let sec = first_section(&doc);
assert_eq!(sec.level(), 1);
assert_eq!(sec.id(), Some("_section_one"));
assert_eq!(sec.section_title(), "Section One");
}
#[test]
fn atx_section_title_with_symmetric_markers() {
verifies!(
r##"
test 'atx section title with symmetric markers' do
input = <<~'EOS'
## Section One ##
blah blah
EOS
output = convert_string input
assert_xpath "//h2[@id='_section_one'][text() = 'Section One']", output, 1
end
"##
);
let doc = Parser::default().parse("## Section One ##\n\nblah blah\n");
let sec = first_section(&doc);
assert_eq!(sec.level(), 1);
assert_eq!(sec.id(), Some("_section_one"));
assert_eq!(sec.section_title(), "Section One");
}
#[test]
fn should_not_match_atx_syntax_with_mixed_markers() {
verifies!(
r##"
test 'should not match atx syntax with mixed markers' do
input = '=#= My Title'
output = convert_string_to_embedded input
assert_xpath "//h3[@id='_my_title'][text() = 'My Title']", output, 0
assert_includes output, '<p>=#= My Title</p>'
end
end
"##
);
let doc = Parser::default().parse("=#= My Title");
assert!(all_sections(&doc).is_empty());
assert_eq!(rendered_paragraphs(&doc), vec!["=#= My Title"]);
}
}
non_normative!(
r##"
context 'Discrete Heading' do
"##
);
mod discrete_heading {
use crate::tests::prelude::*;
non_normative!(
r##"
test 'should create discrete heading instead of section if style is float' do
input = <<~'EOS'
[float]
= Independent Heading!
not in section
EOS
output = convert_string_to_embedded input
assert_xpath '/h1[@id="_independent_heading"]', output, 1
assert_xpath '/h1[@class="float"]', output, 1
assert_xpath %(/h1[@class="float"][text()="Independent Heading!"]), output, 1
assert_xpath '/h1/following-sibling::*[@class="paragraph"]', output, 1
assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p', output, 1
assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p[text()="not in section"]', output, 1
end
"##
);
#[test]
fn should_create_discrete_heading_instead_of_section_if_style_is_discrete() {
verifies!(
r##"
test 'should create discrete heading instead of section if style is discrete' do
input = <<~'EOS'
[discrete]
=== Independent Heading!
not in section
EOS
output = convert_string_to_embedded input
assert_xpath '/h3', output, 1
assert_xpath '/h3[@id="_independent_heading"]', output, 1
assert_xpath '/h3[@class="discrete"]', output, 1
assert_xpath %(/h3[@class="discrete"][text()="Independent Heading!"]), output, 1
assert_xpath '/h3/following-sibling::*[@class="paragraph"]', output, 1
assert_xpath '/h3/following-sibling::*[@class="paragraph"]/p', output, 1
assert_xpath '/h3/following-sibling::*[@class="paragraph"]/p[text()="not in section"]', output, 1
end
"##
);
let doc =
Parser::default().parse("[discrete]\n=== Independent Heading!\n\nnot in section\n");
let sec = first_section(&doc);
assert_eq!(sec.section_type(), SectionType::Discrete);
assert_eq!(sec.level(), 2);
assert_eq!(sec.id(), Some("_independent_heading"));
assert_eq!(sec.section_title(), "Independent Heading!");
assert!(sec.child_blocks().next().is_none());
assert!(rendered_paragraphs(&doc).contains(&"not in section".to_string()));
}
#[test]
fn should_generate_id_for_discrete_heading_from_converted_title() {
verifies!(
r##"
test 'should generate id for discrete heading from converted title' do
input = <<~'EOS'
[discrete]
=== {sp}Heading{sp}
not in section
EOS
output = convert_string_to_embedded input
assert_xpath '/h3', output, 1
assert_xpath '/h3[@class="discrete"][@id="_heading"]', output, 1
assert_xpath '/h3[@class="discrete"][@id="_heading"][text()=" Heading "]', output, 1
end
"##
);
let doc = Parser::default().parse("[discrete]\n=== {sp}Heading{sp}\n\nnot in section\n");
let sec = first_section(&doc);
assert_eq!(sec.section_type(), SectionType::Discrete);
assert_eq!(sec.id(), Some("_heading"));
assert_eq!(sec.section_title(), " Heading ");
}
non_normative!(
r##"
test 'should create discrete heading if style is float with shorthand role and id' do
input = <<~'EOS'
[float.independent#first]
= Independent Heading!
not in section
EOS
output = convert_string_to_embedded input
assert_xpath '/h1[@id="first"]', output, 1
assert_xpath '/h1[@class="float independent"]', output, 1
assert_xpath %(/h1[@class="float independent"][text()="Independent Heading!"]), output, 1
assert_xpath '/h1/following-sibling::*[@class="paragraph"]', output, 1
assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p', output, 1
assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p[text()="not in section"]', output, 1
end
test 'should create discrete heading if style is discrete with shorthand role and id' do
input = <<~'EOS'
[discrete.independent#first]
= Independent Heading!
not in section
EOS
output = convert_string_to_embedded input
assert_xpath '/h1[@id="first"]', output, 1
assert_xpath '/h1[@class="discrete independent"]', output, 1
assert_xpath %(/h1[@class="discrete independent"][text()="Independent Heading!"]), output, 1
assert_xpath '/h1/following-sibling::*[@class="paragraph"]', output, 1
assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p', output, 1
assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p[text()="not in section"]', output, 1
end
"##
);
#[test]
fn discrete_heading_should_be_a_block_with_context_floating_title() {
verifies!(
r##"
test 'discrete heading should be a block with context floating_title' do
input = <<~'EOS'
[float]
=== Independent Heading!
not in section
EOS
doc = document_from_string input
heading = doc.blocks.first
assert_kind_of Asciidoctor::Block, heading
assert_equal :floating_title, heading.context
assert_equal '_independent_heading', heading.id
assert doc.catalog[:refs].key? '_independent_heading'
end
"##
);
let doc = Parser::default().parse("[float]\n=== Independent Heading!\n\nnot in section\n");
let heading = as_section(top_blocks(&doc)[0]);
assert_eq!(heading.section_type(), SectionType::Discrete);
assert_eq!(heading.id(), Some("_independent_heading"));
assert!(doc.catalog().contains_id("_independent_heading"));
}
non_normative!(
r##"
test 'should preprocess second line of setext discrete heading' do
input = <<~'EOS'
[discrete]
Heading Title
ifdef::asciidoctor[]
-------------
endif::[]
EOS
result = convert_string_to_embedded input
assert_xpath '//h2', result, 1
end
"##
);
#[test]
fn can_assign_explicit_id_to_discrete_heading() {
verifies!(
r##"
test 'can assign explicit id to discrete heading' do
input = <<~'EOS'
[[unchained]]
[float]
=== Independent Heading!
not in section
EOS
doc = document_from_string input
heading = doc.blocks.first
assert_equal 'unchained', heading.id
assert doc.catalog[:refs].key? 'unchained'
end
"##
);
let doc = Parser::default()
.parse("[[unchained]]\n[float]\n=== Independent Heading!\n\nnot in section\n");
let heading = as_section(top_blocks(&doc)[0]);
assert_eq!(heading.id(), Some("unchained"));
assert!(doc.catalog().contains_id("unchained"));
}
non_normative!(
r##"
test 'should not include discrete heading in toc' do
input = <<~'EOS'
:toc:
== Section One
[float]
=== Miss Independent
== Section Two
EOS
output = convert_string input
assert_xpath '//*[@id="toc"]', output, 1
assert_xpath %(//*[@id="toc"]//a[contains(text(), "Section ")]), output, 2
assert_xpath %(//*[@id="toc"]//a[text()="Miss Independent"]), output, 0
end
"##
);
#[test]
fn should_not_set_id_on_discrete_heading_if_sectids_attribute_is_unset() {
verifies!(
r##"
test 'should not set id on discrete heading if sectids attribute is unset' do
input = <<~'EOS'
[float]
=== Independent Heading!
not in section
EOS
output = convert_string_to_embedded input, attributes: { 'sectids' => nil }
assert_xpath '/h3', output, 1
assert_xpath '/h3[@id="_independent_heading"]', output, 0
assert_xpath '/h3[@class="float"]', output, 1
end
"##
);
let doc = Parser::default()
.with_intrinsic_attribute_bool("sectids", false, ModificationContext::Anywhere)
.parse("[float]\n=== Independent Heading!\n\nnot in section\n");
let sec = first_section(&doc);
assert_eq!(sec.section_type(), SectionType::Discrete);
assert_eq!(sec.id(), None);
}
#[test]
fn should_use_explicit_id_for_discrete_heading_if_specified() {
verifies!(
r##"
test 'should use explicit id for discrete heading if specified' do
input = <<~'EOS'
[[free]]
[float]
== Independent Heading!
not in section
EOS
output = convert_string_to_embedded input
assert_xpath '/h2', output, 1
assert_xpath '/h2[@id="free"]', output, 1
assert_xpath '/h2[@class="float"]', output, 1
end
"##
);
let doc = Parser::default()
.parse("[[free]]\n[float]\n== Independent Heading!\n\nnot in section\n");
let sec = first_section(&doc);
assert_eq!(sec.section_type(), SectionType::Discrete);
assert_eq!(sec.id(), Some("free"));
}
non_normative!(
r##"
test 'should add role to class attribute on discrete heading' do
input = <<~'EOS'
[float, role="isolated"]
== Independent Heading!
not in section
EOS
output = convert_string_to_embedded input
assert_xpath '/h2', output, 1
assert_xpath '/h2[@id="_independent_heading"]', output, 1
assert_xpath '/h2[@class="float isolated"]', output, 1
end
"##
);
non_normative!(
r##"
test 'should ignore title attribute on discrete heading' do
input = <<~'EOS'
[discrete,title="Captured!"]
== Independent Heading!
not in section
EOS
doc = document_from_string input
heading = doc.blocks[0]
assert_equal 'Independent Heading!', heading.title
refute heading.attributes.key? 'title'
end
"##
);
#[test]
fn should_use_specified_id_and_reftext_when_registering_discrete_section_reference() {
verifies!(
r##"
test 'should use specified id and reftext when registering discrete section reference' do
input = <<~'EOS'
[[install,Install Procedure]]
[discrete]
== Install
content
EOS
doc = document_from_string input
ref = doc.catalog[:refs]['install']
refute_nil ref
assert_equal 'Install Procedure', ref.reftext
assert_equal 'install', (doc.resolve_id 'Install Procedure')
end
"##
);
let doc = Parser::default()
.parse("[[install,Install Procedure]]\n[discrete]\n== Install\n\ncontent\n");
let reff = doc.catalog().get_ref("install");
assert!(reff.is_some());
assert_eq!(reff.unwrap().reftext.as_deref(), Some("Install Procedure"));
assert_eq!(
doc.catalog().resolve_id("Install Procedure"),
Some("install".to_string())
);
}
#[test]
fn should_use_specified_reftext_when_registering_discrete_section_reference() {
verifies!(
r##"
test 'should use specified reftext when registering discrete section reference' do
input = <<~'EOS'
[reftext="Install Procedure"]
[discrete]
== Install
content
EOS
doc = document_from_string input
ref = doc.catalog[:refs]['_install']
refute_nil ref
assert_equal 'Install Procedure', ref.reftext
assert_equal '_install', (doc.resolve_id 'Install Procedure')
end
"##
);
let doc = Parser::default()
.parse("[reftext=\"Install Procedure\"]\n[discrete]\n== Install\n\ncontent\n");
let reff = doc.catalog().get_ref("_install");
assert!(reff.is_some());
assert_eq!(reff.unwrap().reftext.as_deref(), Some("Install Procedure"));
assert_eq!(
doc.catalog().resolve_id("Install Procedure"),
Some("_install".to_string())
);
}
#[test]
fn should_not_process_inline_anchor_in_discrete_heading_if_explicit_id_is_assigned() {
verifies!(
r##"
test 'should not process inline anchor in discrete heading if explicit ID is assigned' do
input = <<~'EOS'
[discrete#install]
== Install [[installation]]
content
EOS
block = block_from_string input
assert_equal block.id, 'install'
assert_equal 'Install <a id="installation"></a>', block.title
end
end
"##
);
let doc =
Parser::default().parse("[discrete#install]\n== Install [[installation]]\n\ncontent\n");
let sec = first_section(&doc);
assert_eq!(sec.id(), Some("install"));
assert_eq!(sec.section_title(), r#"Install <a id="installation"></a>"#);
}
}
non_normative!(
r##"
context 'Level offset' do
"##
);
mod level_offset {
use crate::tests::prelude::*;
non_normative!(
r##"
test 'should print error if standalone document is included without level offset' do
input = <<~'EOS'
= Main Document
Doc Writer
text in main document
// begin simulated include::[]
= Standalone Document
:author: Junior Writer
text in standalone document
// end simulated include::[]
EOS
using_memory_logger do |logger|
convert_string input
assert_message logger, :ERROR, '<stdin>: line 7: level 0 sections can only be used when doctype is book', Hash
end
end
"##
);
#[test]
fn should_add_level_offset_to_section_level() {
verifies!(
r##"
test 'should add level offset to section level' do
input = <<~'EOS'
= Main Document
Doc Writer
Main document written by {author}.
:leveloffset: 1
// begin simulated include::[]
= Standalone Document
:author: Junior Writer
Standalone document written by {author}.
== Section in Standalone
Standalone section text.
// end simulated include::[]
:leveloffset!:
== Section in Main
Main section text.
EOS
output = nil
using_memory_logger do |logger|
output = convert_string input
assert logger.empty?
end
assert_match(/Main document written by Doc Writer/, output)
assert_match(/Standalone document written by Junior Writer/, output)
assert_xpath '//*[@class="sect1"]/h2[text() = "Standalone Document"]', output, 1
assert_xpath '//*[@class="sect2"]/h3[text() = "Section in Standalone"]', output, 1
assert_xpath '//*[@class="sect1"]/h2[text() = "Section in Main"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Main Document\nDoc Writer\n\nMain document written by {author}.\n\n:leveloffset: 1\n\n// begin simulated include::[]\n= Standalone Document\n:author: Junior Writer\n\nStandalone document written by {author}.\n\n== Section in Standalone\n\nStandalone section text.\n// end simulated include::[]\n\n:leveloffset!:\n\n== Section in Main\n\nMain section text.\n",
);
assert!(doc.warnings().next().is_none());
let by_title: Vec<(usize, &str)> = all_sections(&doc)
.iter()
.map(|s| (s.level(), s.section_title()))
.collect();
assert_eq!(
by_title,
vec![
(1, "Standalone Document"),
(2, "Section in Standalone"),
(1, "Section in Main"),
]
);
let paragraphs = rendered_paragraphs(&doc);
assert!(
paragraphs
.iter()
.any(|p| p == "Main document written by Doc Writer."),
"paragraphs: {paragraphs:?}"
);
assert!(
paragraphs
.iter()
.any(|p| p == "Standalone document written by Junior Writer."),
"paragraphs: {paragraphs:?}"
);
}
#[test]
fn level_offset_should_be_added_to_discrete_heading() {
verifies!(
r##"
test 'level offset should be added to discrete heading' do
input = <<~'EOS'
= Main Document
Doc Writer
:leveloffset: 1
[float]
= Discrete Heading
EOS
output = convert_string input
assert_xpath '//h2[@class="float"][text() = "Discrete Heading"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Main Document\nDoc Writer\n\n:leveloffset: 1\n\n[float]\n= Discrete Heading\n",
);
let sec = first_section(&doc);
assert_eq!(sec.section_type(), SectionType::Discrete);
assert_eq!(sec.level(), 1);
assert_eq!(sec.section_title(), "Discrete Heading");
}
#[test]
fn should_be_able_to_reset_level_offset() {
verifies!(
r##"
test 'should be able to reset level offset' do
input = <<~'EOS'
= Main Document
Doc Writer
Main preamble.
:leveloffset: 1
= Standalone Document
Standalone preamble.
:leveloffset!:
== Level 1 Section
EOS
output = convert_string input
assert_xpath '//*[@class = "sect1"]/h2[text() = "Standalone Document"]', output, 1
assert_xpath '//*[@class = "sect1"]/h2[text() = "Level 1 Section"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Main Document\nDoc Writer\n\nMain preamble.\n\n:leveloffset: 1\n\n= Standalone Document\n\nStandalone preamble.\n\n:leveloffset!:\n\n== Level 1 Section\n",
);
let by_title: Vec<(usize, &str)> = all_sections(&doc)
.iter()
.map(|s| (s.level(), s.section_title()))
.collect();
assert_eq!(
by_title,
vec![(1, "Standalone Document"), (1, "Level 1 Section")]
);
}
#[test]
fn should_add_relative_offset_value_to_current_leveloffset() {
verifies!(
r##"
test 'should add relative offset value to current leveloffset' do
input = <<~'EOS'
= Main Document
Doc Writer
Main preamble.
:leveloffset: 1
= Chapter 1
content
:leveloffset: +1
= Standalone Section
content
EOS
output = convert_string input
assert_xpath '//*[@class = "sect1"]/h2[text() = "Chapter 1"]', output, 1
assert_xpath '//*[@class = "sect2"]/h3[text() = "Standalone Section"]', output, 1
end
end
"##
);
let doc = Parser::default().parse(
"= Main Document\nDoc Writer\n\nMain preamble.\n\n:leveloffset: 1\n\n= Chapter 1\n\ncontent\n\n:leveloffset: +1\n\n= Standalone Section\n\ncontent\n",
);
let by_title: Vec<(usize, &str)> = all_sections(&doc)
.iter()
.map(|s| (s.level(), s.section_title()))
.collect();
assert_eq!(by_title, vec![(1, "Chapter 1"), (2, "Standalone Section")]);
}
}
non_normative!(
r##"
context 'Section Numbering' do
"##
);
mod section_numbering {
use crate::tests::prelude::*;
non_normative!(
r##"
test 'should create section number with one entry for level 1' do
doc = empty_document
sect1 = Asciidoctor::Section.new nil, nil, true
doc << sect1
assert_equal '1.', sect1.sectnum
end
test 'should create section number with two entries for level 2' do
doc = empty_document
sect1 = Asciidoctor::Section.new nil, nil, true
doc << sect1
sect1_1 = Asciidoctor::Section.new sect1, nil, true
sect1 << sect1_1
assert_equal '1.1.', sect1_1.sectnum
end
test 'should create section number with three entries for level 3' do
doc = empty_document
sect1 = Asciidoctor::Section.new nil, nil, true
doc << sect1
sect1_1 = Asciidoctor::Section.new sect1, nil, true
sect1 << sect1_1
sect1_1_1 = Asciidoctor::Section.new sect1_1, nil, true
sect1_1 << sect1_1_1
assert_equal '1.1.1.', sect1_1_1.sectnum
end
test 'should create section number for second section in level' do
doc = empty_document
sect1 = Asciidoctor::Section.new nil, nil, true
doc << sect1
sect1_1 = Asciidoctor::Section.new sect1, nil, true
sect1 << sect1_1
sect1_2 = Asciidoctor::Section.new sect1, nil, true
sect1 << sect1_2
assert_equal '1.2.', sect1_2.sectnum
end
test 'sectnum should use specified delimiter and append string' do
doc = empty_document
sect1 = Asciidoctor::Section.new nil, nil, true
doc << sect1
sect1_1 = Asciidoctor::Section.new sect1, nil, true
sect1 << sect1_1
sect1_1_1 = Asciidoctor::Section.new sect1_1, nil, true
sect1_1 << sect1_1_1
assert_equal '1,1,1,', sect1_1_1.sectnum(',')
assert_equal '1:1:1', sect1_1_1.sectnum(':', false)
end
"##
);
#[test]
fn should_output_section_numbers_when_sectnums_attribute_is_set() {
verifies!(
r##"
test 'should output section numbers when sectnums attribute is set' do
input = <<~'EOS'
= Title
:sectnums:
== Section_1
text
=== Section_1_1
text
==== Section_1_1_1
text
== Section_2
text
=== Section_2_1
text
=== Section_2_2
text
EOS
output = convert_string input
assert_xpath '//h2[@id="_section_1"][starts-with(text(), "1. ")]', output, 1
assert_xpath '//h3[@id="_section_1_1"][starts-with(text(), "1.1. ")]', output, 1
assert_xpath '//h4[@id="_section_1_1_1"][starts-with(text(), "1.1.1. ")]', output, 1
assert_xpath '//h2[@id="_section_2"][starts-with(text(), "2. ")]', output, 1
assert_xpath '//h3[@id="_section_2_1"][starts-with(text(), "2.1. ")]', output, 1
assert_xpath '//h3[@id="_section_2_2"][starts-with(text(), "2.2. ")]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Title\n:sectnums:\n\n== Section_1\n\ntext\n\n=== Section_1_1\n\ntext\n\n==== Section_1_1_1\n\ntext\n\n== Section_2\n\ntext\n\n=== Section_2_1\n\ntext\n\n=== Section_2_2\n\ntext\n",
);
let nums: Vec<(&str, Option<String>)> = all_sections(&doc)
.iter()
.map(|s| (s.section_title(), s.section_number().map(|n| n.to_string())))
.collect();
assert_eq!(
nums,
vec![
("Section_1", Some("1".to_string())),
("Section_1_1", Some("1.1".to_string())),
("Section_1_1_1", Some("1.1.1".to_string())),
("Section_2", Some("2".to_string())),
("Section_2_1", Some("2.1".to_string())),
("Section_2_2", Some("2.2".to_string())),
]
);
}
#[test]
fn should_output_section_numbers_when_numbered_attribute_is_set() {
verifies!(
r##"
test 'should output section numbers when numbered attribute is set' do
input = <<~'EOS'
= Title
:numbered:
== Section_1
text
=== Section_1_1
text
==== Section_1_1_1
text
== Section_2
text
=== Section_2_1
text
=== Section_2_2
text
EOS
output = convert_string input
assert_xpath '//h2[@id="_section_1"][starts-with(text(), "1. ")]', output, 1
assert_xpath '//h3[@id="_section_1_1"][starts-with(text(), "1.1. ")]', output, 1
assert_xpath '//h4[@id="_section_1_1_1"][starts-with(text(), "1.1.1. ")]', output, 1
assert_xpath '//h2[@id="_section_2"][starts-with(text(), "2. ")]', output, 1
assert_xpath '//h3[@id="_section_2_1"][starts-with(text(), "2.1. ")]', output, 1
assert_xpath '//h3[@id="_section_2_2"][starts-with(text(), "2.2. ")]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Title\n:numbered:\n\n== Section_1\n\ntext\n\n=== Section_1_1\n\ntext\n\n==== Section_1_1_1\n\ntext\n\n== Section_2\n\ntext\n\n=== Section_2_1\n\ntext\n\n=== Section_2_2\n\ntext\n",
);
let nums: Vec<(&str, Option<String>)> = all_sections(&doc)
.iter()
.map(|s| (s.section_title(), s.section_number().map(|n| n.to_string())))
.collect();
assert_eq!(
nums,
vec![
("Section_1", Some("1".to_string())),
("Section_1_1", Some("1.1".to_string())),
("Section_1_1_1", Some("1.1.1".to_string())),
("Section_2", Some("2".to_string())),
("Section_2_1", Some("2.1".to_string())),
("Section_2_2", Some("2.2".to_string())),
]
);
}
non_normative!(
r##"
test 'should not crash if child section of part is out of sequence and part numbering is disabled' do
input = <<~'EOS'
= Document Title
:doctype: book
:sectnums:
= Part
=== Out of Sequence Section
EOS
using_memory_logger do |logger|
output = convert_string input
assert_xpath '//h1[text()="Part"]', output, 1
assert_xpath '//h3[text()=".1. Out of Sequence Section"]', output, 1
end
end
test 'should not hang if relative leveloffset attempts to make resolved section level negative' do
input = <<~'EOS'
= Document Title
:doctype: book
:leveloffset: -1
= Part Title
== Chapter Title
EOS
using_memory_logger do |logger|
output = convert_string input
assert_xpath '//h1[text()="Part Title"]', output, 1
assert_xpath '//h1[text()="Chapter Title"]', output, 1
end
end
test 'should number parts when doctype is book and partnums attributes is set' do
input = <<~'EOS'
= Book Title
:doctype: book
:sectnums:
:partnums:
= Language
== Syntax
content
= Processor
== CLI
content
EOS
output = convert_string input
assert_xpath '//h1[@id="_language"][text() = "I: Language"]', output, 1
assert_xpath '//h1[@id="_processor"][text() = "II: Processor"]', output, 1
end
test 'should assign sequential roman numerals to book parts' do
input = <<~'EOS'
= Book Title
:doctype: book
:sectnums:
:partnums:
= First Part
part intro
== First Chapter
= Second Part
part intro
== Second Chapter
EOS
doc = document_from_string input
assert_equal 'I', doc.sections[0].numeral
assert_equal '1', doc.sections[0].sections[0].numeral
assert_equal 'II', doc.sections[1].numeral
assert_equal '2', doc.sections[1].sections[0].numeral
end
test 'should prepend value of part-signifier attribute to title of numbered part' do
input = <<~'EOS'
= Book Title
:doctype: book
:sectnums:
:partnums:
:part-signifier: Part
= Language
== Syntax
content
= Processor
== CLI
content
EOS
output = convert_string input
assert_xpath '//h1[@id="_language"][text() = "Part I: Language"]', output, 1
assert_xpath '//h1[@id="_processor"][text() = "Part II: Processor"]', output, 1
end
test 'should prepend value of chapter-signifier attribute to title of numbered chapter' do
input = <<~'EOS'
= Book Title
:doctype: book
:sectnums:
:partnums:
:chapter-signifier: Chapter
= Language
== Syntax
content
= Processor
== CLI
content
EOS
output = convert_string input
assert_xpath '//h2[@id="_syntax"][text() = "Chapter 1. Syntax"]', output, 1
assert_xpath '//h2[@id="_cli"][text() = "Chapter 2. CLI"]', output, 1
end
test 'should allow chapter number to be controlled using chapter-number attribute' do
input = <<~'EOS'
= Book Title
:doctype: book
:sectnums:
:chapter-signifier: Chapter
:chapter-number: 9
== Not the Beginning
== Maybe the End
EOS
output = convert_string input
assert_xpath '//h2[@id="_not_the_beginning"][text() = "Chapter 10. Not the Beginning"]', output, 1
assert_xpath '//h2[@id="_maybe_the_end"][text() = "Chapter 11. Maybe the End"]', output, 1
end
"##
);
#[test]
fn blocks_should_have_level() {
verifies!(
r##"
test 'blocks should have level' do
input = <<~'EOS'
= Title
preamble
== Section 1
paragraph
=== Section 1.1
paragraph
EOS
doc = document_from_string input
assert_equal 0, doc.blocks[0].level
assert_equal 1, doc.blocks[1].level
assert_equal 1, doc.blocks[1].blocks[0].level
assert_equal 2, doc.blocks[1].blocks[1].level
assert_equal 2, doc.blocks[1].blocks[1].blocks[0].level
end
"##
);
let doc = Parser::default().parse(
"= Title\n\npreamble\n\n== Section 1\n\nparagraph\n\n=== Section 1.1\n\nparagraph\n",
);
let levels: Vec<(usize, &str)> = all_sections(&doc)
.iter()
.map(|s| (s.level(), s.section_title()))
.collect();
assert_eq!(levels, vec![(1, "Section 1"), (2, "Section 1.1")]);
}
#[test]
fn section_numbers_should_not_increment_when_numbered_attribute_is_turned_off_within_document()
{
verifies!(
r##"
test 'section numbers should not increment when numbered attribute is turned off within document' do
input = <<~'EOS'
= Document Title
:numbered:
:numbered!:
== Colophon Section
== Another Colophon Section
== Final Colophon Section
:numbered:
== Section One
=== Section One Subsection
== Section Two
== Section Three
EOS
output = convert_string input
assert_xpath '//h1[text()="Document Title"]', output, 1
assert_xpath '//h2[@id="_colophon_section"][text()="Colophon Section"]', output, 1
assert_xpath '//h2[@id="_another_colophon_section"][text()="Another Colophon Section"]', output, 1
assert_xpath '//h2[@id="_final_colophon_section"][text()="Final Colophon Section"]', output, 1
assert_xpath '//h2[@id="_section_one"][text()="1. Section One"]', output, 1
assert_xpath '//h3[@id="_section_one_subsection"][text()="1.1. Section One Subsection"]', output, 1
assert_xpath '//h2[@id="_section_two"][text()="2. Section Two"]', output, 1
assert_xpath '//h2[@id="_section_three"][text()="3. Section Three"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Document Title\n:numbered:\n\n:numbered!:\n\n== Colophon Section\n\n== Another Colophon Section\n\n== Final Colophon Section\n\n:numbered:\n\n== Section One\n\n=== Section One Subsection\n\n== Section Two\n\n== Section Three\n",
);
let nums: Vec<(&str, Option<String>)> = all_sections(&doc)
.iter()
.map(|s| (s.section_title(), s.section_number().map(|n| n.to_string())))
.collect();
assert_eq!(
nums,
vec![
("Colophon Section", None),
("Another Colophon Section", None),
("Final Colophon Section", None),
("Section One", Some("1".to_string())),
("Section One Subsection", Some("1.1".to_string())),
("Section Two", Some("2".to_string())),
("Section Three", Some("3".to_string())),
]
);
}
#[test]
fn section_numbers_can_be_toggled_even_if_numbered_attribute_is_enabled_via_the_api() {
verifies!(
r##"
test 'section numbers can be toggled even if numbered attribute is enable via the API' do
input = <<~'EOS'
= Document Title
:numbered!:
== Colophon Section
== Another Colophon Section
== Final Colophon Section
:numbered:
== Section One
=== Section One Subsection
== Section Two
== Section Three
EOS
output = convert_string input, attributes: { 'numbered' => '' }
assert_xpath '//h1[text()="Document Title"]', output, 1
assert_xpath '//h2[@id="_colophon_section"][text()="Colophon Section"]', output, 1
assert_xpath '//h2[@id="_another_colophon_section"][text()="Another Colophon Section"]', output, 1
assert_xpath '//h2[@id="_final_colophon_section"][text()="Final Colophon Section"]', output, 1
assert_xpath '//h2[@id="_section_one"][text()="1. Section One"]', output, 1
assert_xpath '//h3[@id="_section_one_subsection"][text()="1.1. Section One Subsection"]', output, 1
assert_xpath '//h2[@id="_section_two"][text()="2. Section Two"]', output, 1
assert_xpath '//h2[@id="_section_three"][text()="3. Section Three"]', output, 1
end
"##
);
let doc = Parser::default()
.with_intrinsic_attribute("numbered", "", ModificationContext::ApiOnly)
.parse(
"= Document Title\n\n:numbered!:\n\n== Colophon Section\n\n== Another Colophon Section\n\n== Final Colophon Section\n\n:numbered:\n\n== Section One\n\n=== Section One Subsection\n\n== Section Two\n\n== Section Three\n",
);
let nums: Vec<(&str, Option<String>)> = all_sections(&doc)
.iter()
.map(|s| (s.section_title(), s.section_number().map(|n| n.to_string())))
.collect();
assert_eq!(
nums,
vec![
("Colophon Section", None),
("Another Colophon Section", None),
("Final Colophon Section", None),
("Section One", Some("1".to_string())),
("Section One Subsection", Some("1.1".to_string())),
("Section Two", Some("2".to_string())),
("Section Three", Some("3".to_string())),
]
);
}
#[test]
fn section_numbers_cannot_be_toggled_even_if_numbered_attribute_is_disabled_via_the_api() {
verifies!(
r##"
test 'section numbers cannot be toggled even if numbered attribute is disabled via the API' do
input = <<~'EOS'
= Document Title
:numbered!:
== Colophon Section
== Another Colophon Section
== Final Colophon Section
:numbered:
== Section One
=== Section One Subsection
== Section Two
== Section Three
EOS
output = convert_string input, attributes: { 'numbered!' => '' }
assert_xpath '//h1[text()="Document Title"]', output, 1
assert_xpath '//h2[@id="_colophon_section"][text()="Colophon Section"]', output, 1
assert_xpath '//h2[@id="_another_colophon_section"][text()="Another Colophon Section"]', output, 1
assert_xpath '//h2[@id="_final_colophon_section"][text()="Final Colophon Section"]', output, 1
assert_xpath '//h2[@id="_section_one"][text()="Section One"]', output, 1
assert_xpath '//h3[@id="_section_one_subsection"][text()="Section One Subsection"]', output, 1
assert_xpath '//h2[@id="_section_two"][text()="Section Two"]', output, 1
assert_xpath '//h2[@id="_section_three"][text()="Section Three"]', output, 1
end
"##
);
let doc = Parser::default()
.with_intrinsic_attribute_bool("numbered", false, ModificationContext::ApiOnly)
.parse(
"= Document Title\n\n:numbered!:\n\n== Colophon Section\n\n== Another Colophon Section\n\n== Final Colophon Section\n\n:numbered:\n\n== Section One\n\n=== Section One Subsection\n\n== Section Two\n\n== Section Three\n",
);
let nums: Vec<(&str, Option<String>)> = all_sections(&doc)
.iter()
.map(|s| (s.section_title(), s.section_number().map(|n| n.to_string())))
.collect();
assert_eq!(
nums,
vec![
("Colophon Section", None),
("Another Colophon Section", None),
("Final Colophon Section", None),
("Section One", None),
("Section One Subsection", None),
("Section Two", None),
("Section Three", None),
]
);
}
#[test]
fn section_numbers_should_not_increment_until_numbered_attribute_is_turned_back_on() {
verifies!(
r##"
# NOTE AsciiDoc.py fails this test because it does not properly check for a None value when looking up the numbered attribute
test 'section numbers should not increment until numbered attribute is turned back on' do
input = <<~'EOS'
= Document Title
:numbered!:
== Colophon Section
== Another Colophon Section
== Final Colophon Section
:numbered:
== Section One
=== Section One Subsection
== Section Two
== Section Three
EOS
output = convert_string input
assert_xpath '//h1[text()="Document Title"]', output, 1
assert_xpath '//h2[@id="_colophon_section"][text()="Colophon Section"]', output, 1
assert_xpath '//h2[@id="_another_colophon_section"][text()="Another Colophon Section"]', output, 1
assert_xpath '//h2[@id="_final_colophon_section"][text()="Final Colophon Section"]', output, 1
assert_xpath '//h2[@id="_section_one"][text()="1. Section One"]', output, 1
assert_xpath '//h3[@id="_section_one_subsection"][text()="1.1. Section One Subsection"]', output, 1
assert_xpath '//h2[@id="_section_two"][text()="2. Section Two"]', output, 1
assert_xpath '//h2[@id="_section_three"][text()="3. Section Three"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Document Title\n:numbered!:\n\n== Colophon Section\n\n== Another Colophon Section\n\n== Final Colophon Section\n\n:numbered:\n\n== Section One\n\n=== Section One Subsection\n\n== Section Two\n\n== Section Three\n",
);
let nums: Vec<(&str, Option<String>)> = all_sections(&doc)
.iter()
.map(|s| (s.section_title(), s.section_number().map(|n| n.to_string())))
.collect();
assert_eq!(
nums,
vec![
("Colophon Section", None),
("Another Colophon Section", None),
("Final Colophon Section", None),
("Section One", Some("1".to_string())),
("Section One Subsection", Some("1.1".to_string())),
("Section Two", Some("2".to_string())),
("Section Three", Some("3".to_string())),
]
);
}
#[test]
fn table_with_asciidoc_content_should_not_disable_numbering_of_subsequent_sections() {
verifies!(
r##"
test 'table with asciidoc content should not disable numbering of subsequent sections' do
input = <<~'EOS'
= Document Title
:numbered:
preamble
== Section One
|===
a|content
|===
== Section Two
content
EOS
output = convert_string input
assert_xpath '//h2[@id="_section_one"]', output, 1
assert_xpath '//h2[@id="_section_one"][text()="1. Section One"]', output, 1
assert_xpath '//h2[@id="_section_two"]', output, 1
assert_xpath '//h2[@id="_section_two"][text()="2. Section Two"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Document Title\n:numbered:\n\npreamble\n\n== Section One\n\n|===\na|content\n|===\n\n== Section Two\n\ncontent\n",
);
let nums: Vec<(&str, Option<String>)> = all_sections(&doc)
.iter()
.map(|s| (s.section_title(), s.section_number().map(|n| n.to_string())))
.collect();
assert_eq!(
nums,
vec![
("Section One", Some("1".to_string())),
("Section Two", Some("2".to_string())),
]
);
}
non_normative!(
r##"
test 'should not number parts when doctype is book' do
input = <<~'EOS'
= Document Title
:doctype: book
:numbered:
= Part 1
== Chapter 1
content
= Part 2
== Chapter 2
content
EOS
output = convert_string input
assert_xpath '(//h1)[1][text()="Document Title"]', output, 1
assert_xpath '(//h1)[2][text()="Part 1"]', output, 1
assert_xpath '(//h1)[3][text()="Part 2"]', output, 1
assert_xpath '(//h2)[1][text()="1. Chapter 1"]', output, 1
assert_xpath '(//h2)[2][text()="2. Chapter 2"]', output, 1
end
test 'should number chapters sequentially even when divided into parts' do
input = <<~'EOS'
= Document Title
:doctype: book
:numbered:
== Chapter 1
content
= Part 1
== Chapter 2
content
= Part 2
== Chapter 3
content
== Chapter 4
content
EOS
result = convert_string input
(1..4).each do |num|
assert_xpath %(//h2[@id="_chapter_#{num}"]), result, 1
assert_xpath %(//h2[@id="_chapter_#{num}"][text()="#{num}. Chapter #{num}"]), result, 1
end
end
test 'reindex_sections should correct section enumeration after sections are modified' do
input = <<~'EOS'
:sectnums:
== First Section
content
== Last Section
content
EOS
doc = document_from_string input
second_section = Asciidoctor::Section.new doc, nil, true
doc.blocks.insert 1, second_section
doc.reindex_sections
sections = doc.sections
[0, 1, 2].each do |index|
assert_equal index, sections[index].index
assert_equal (index + 1).to_s, sections[index].numeral
assert_equal index + 1, sections[index].number
end
end
test 'should allow sections to be renumbered using numeral or deprecated number property' do
input = <<~'EOS'
== Somewhere in the Middle
== A Bit Later
== Nearing the End
== The End
EOS
doc = document_from_string input, attributes: { 'sectnums' => '' }
doc.sections.each do |sect|
if sect.numeral.to_i.even?
sect.numeral.next!
else
sect.number += 1
end
end
output = doc.convert standalone: false
assert_xpath '//h2[text()="2. Somewhere in the Middle"]', output, 1
assert_xpath '//h2[text()="3. A Bit Later"]', output, 1
assert_xpath '//h2[text()="4. Nearing the End"]', output, 1
assert_xpath '//h2[text()="5. The End"]', output, 1
end
end
"##
);
}
non_normative!(
r##"
context 'Links and anchors' do
test 'should include anchor if sectanchors document attribute is set' do
input = <<~'EOS'
== Installation
Installation section.
=== Linux
Linux installation instructions.
EOS
output = convert_string_to_embedded input, attributes: { 'sectanchors' => '' }
assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a', output, 1
assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a[@class="anchor"][@href="#_installation"]', output, 1
assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a/following-sibling::text()="Installation"', output, true
assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a', output, 1
assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a[@class="anchor"][@href="#_linux"]', output, 1
assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a/following-sibling::text()="Linux"', output, true
end
test 'should position after title text if sectanchors is set to after' do
input = <<~'EOS'
== Installation
Installation section.
=== Linux
Linux installation instructions.
EOS
output = convert_string_to_embedded input, attributes: { 'sectanchors' => 'after' }
assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a', output, 1
assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a[@class="anchor"][@href="#_installation"]', output, 1
assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a/preceding-sibling::text()="Installation"', output, true
assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a', output, 1
assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a[@class="anchor"][@href="#_linux"]', output, 1
assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a/preceding-sibling::text()="Linux"', output, true
end
test 'should link section if sectlinks document attribute is set' do
input = <<~'EOS'
== Installation
Installation section.
=== Linux
Linux installation instructions.
EOS
output = convert_string_to_embedded input, attributes: { 'sectlinks' => '' }
assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a', output, 1
assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a[@class="link"][@href="#_installation"]', output, 1
assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a[text()="Installation"]', output, 1
assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a', output, 1
assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a[@class="link"][@href="#_linux"]', output, 1
assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a[text()="Linux"]', output, 1
end
test 'should start section link after supplemental anchors when sectlinks is set' do
input = <<~'EOS'
:sectlinks:
[#foo]
== [[fu]]Foo
EOS
output = convert_string_to_embedded input
assert_xpath '/*[@class="sect1"]/h2[@id="foo"]', output, 1
assert_xpath '/*[@class="sect1"]/h2[@id="foo"]/a', output, 2
assert_xpath '/*[@class="sect1"]/h2[@id="foo"]/a[@id="fu"]', output, 1
assert_xpath '/*[@class="sect1"]/h2[@id="foo"]/a[@class="link"]', output, 1
assert_xpath '/*[@class="sect1"]/h2[@id="foo"]/a[@id="fu"]/following-sibling::a[@class="link"]', output, 1
end
end
"##
);
non_normative!(
r##"
context 'Special sections' do
"##
);
mod special_sections {
use crate::tests::prelude::*;
non_normative!(
r##"
test 'should ignore style if it matches sectN' do
input = <<~'EOS'
= Document Title
[sect1]
== Section Level 1
content
[sect2]
== Section Level 2
content
EOS
output = convert_string input, backend: :docbook
assert_xpath '//section', output, 2
assert_xpath '//sect1', output, 0
assert_xpath '//sect2', output, 0
end
"##
);
#[test]
fn should_assign_sectname_caption_and_numeral_to_appendix_section_by_default() {
verifies!(
r##"
test 'should assign sectname, caption, and numeral to appendix section by default' do
input = <<~'EOS'
[appendix]
== Attribute Options
Details
EOS
appendix = block_from_string input
assert_equal 'appendix', appendix.sectname
assert_equal 'Appendix A: ', appendix.caption
assert_equal 'A', appendix.numeral
assert_equal 'A', appendix.number
assert_equal true, appendix.numbered
end
"##
);
let doc = Parser::default().parse("[appendix]\n== Attribute Options\n\nDetails\n");
let sec = first_section(&doc);
assert_eq!(sec.section_type(), SectionType::Appendix);
assert_eq!(sec.caption(), Some("Appendix A: "));
}
#[test]
fn should_prefix_appendix_title_by_numbered_label_even_when_section_numbering_is_disabled() {
verifies!(
r##"
test 'should prefix appendix title by numbered label even when section numbering is disabled' do
input = <<~'EOS'
[appendix]
== Attribute Options
Details
EOS
output = convert_string_to_embedded input
assert_xpath '//h2[text()="Appendix A: Attribute Options"]', output, 1
end
"##
);
let doc = Parser::default().parse("[appendix]\n== Attribute Options\n\nDetails\n");
let sec = first_section(&doc);
assert_eq!(sec.caption(), Some("Appendix A: "));
assert_eq!(sec.section_title(), "Attribute Options");
}
#[test]
fn should_allow_appendix_number_to_be_controlled_using_appendix_number_attribute() {
verifies!(
r##"
test 'should allow appendix number to be controlled using appendix-number attribute' do
input = <<~'EOS'
:appendix-number: α
[appendix]
== Attribute Options
Details
[appendix]
== All the Other Stuff
Details
EOS
output = convert_string_to_embedded input
assert_xpath %(//h2[text()="Appendix #{decode_char 946}: Attribute Options"]), output, 1
assert_xpath %(//h2[text()="Appendix #{decode_char 947}: All the Other Stuff"]), output, 1
end
"##
);
let doc = Parser::default().parse(
":appendix-number: \u{3b1}\n\n[appendix]\n== Attribute Options\n\nDetails\n\n[appendix]\n== All the Other Stuff\n\nDetails\n",
);
let sections = all_sections(&doc);
assert_eq!(sections.len(), 2);
assert_eq!(sections[0].caption(), Some("Appendix \u{3b2}: "));
assert_eq!(sections[0].section_title(), "Attribute Options");
assert_eq!(sections[1].caption(), Some("Appendix \u{3b3}: "));
assert_eq!(sections[1].section_title(), "All the Other Stuff");
}
#[test]
fn should_use_style_from_last_block_attribute_line_above_section_that_defines_a_style() {
verifies!(
r##"
test 'should use style from last block attribute line above section that defines a style' do
input = <<~'EOS'
[glossary]
[appendix]
== Attribute Options
Details
EOS
output = convert_string_to_embedded input
assert_xpath '//h2[text()="Appendix A: Attribute Options"]', output, 1
end
"##
);
let doc =
Parser::default().parse("[glossary]\n[appendix]\n== Attribute Options\n\nDetails\n");
let sec = first_section(&doc);
assert_eq!(sec.section_type(), SectionType::Appendix);
assert_eq!(sec.caption(), Some("Appendix A: "));
}
#[test]
fn setting_id_using_style_shorthand_should_not_clear_section_style() {
verifies!(
r##"
test 'setting ID using style shorthand should not clear section style' do
input = <<~'EOS'
[appendix]
[#attribute-options]
== Attribute Options
Details
EOS
output = convert_string_to_embedded input
assert_xpath '//h2[@id="attribute-options"][text()="Appendix A: Attribute Options"]', output, 1
end
"##
);
let doc = Parser::default()
.parse("[appendix]\n[#attribute-options]\n== Attribute Options\n\nDetails\n");
let sec = first_section(&doc);
assert_eq!(sec.section_type(), SectionType::Appendix);
assert_eq!(sec.id(), Some("attribute-options"));
assert_eq!(sec.caption(), Some("Appendix A: "));
}
#[test]
fn should_use_custom_appendix_caption_if_specified() {
verifies!(
r##"
test 'should use custom appendix caption if specified' do
input = <<~'EOS'
:appendix-caption: App
[appendix]
== Attribute Options
Details
EOS
output = convert_string_to_embedded input
assert_xpath '//h2[text()="App A: Attribute Options"]', output, 1
end
"##
);
let doc = Parser::default()
.parse(":appendix-caption: App\n\n[appendix]\n== Attribute Options\n\nDetails\n");
assert_eq!(first_section(&doc).caption(), Some("App A: "));
}
#[test]
fn should_only_assign_letter_to_appendix_when_numbered_is_enabled_and_appendix_caption_is_not_set()
{
verifies!(
r##"
test 'should only assign letter to appendix when numbered is enabled and appendix caption is not set' do
input = <<~'EOS'
:numbered:
:!appendix-caption:
[appendix]
== Attribute Options
Details
EOS
output = convert_string_to_embedded input
assert_xpath '//h2[text()="A. Attribute Options"]', output, 1
end
"##
);
let doc = Parser::default().parse(
":numbered:\n:!appendix-caption:\n\n[appendix]\n== Attribute Options\n\nDetails\n",
);
assert_eq!(first_section(&doc).caption(), Some("A. "));
}
#[test]
fn should_increment_appendix_number_for_each_appendix_section() {
verifies!(
r##"
test 'should increment appendix number for each appendix section' do
input = <<~'EOS'
[appendix]
== Attribute Options
Details
[appendix]
== Migration
Details
EOS
output = convert_string_to_embedded input
assert_xpath '(//h2)[1][text()="Appendix A: Attribute Options"]', output, 1
assert_xpath '(//h2)[2][text()="Appendix B: Migration"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"[appendix]\n== Attribute Options\n\nDetails\n\n[appendix]\n== Migration\n\nDetails\n",
);
let caps: Vec<Option<&str>> = all_sections(&doc).iter().map(|s| s.caption()).collect();
assert_eq!(caps, vec![Some("Appendix A: "), Some("Appendix B: ")]);
}
non_normative!(
r##"
test 'should continue numbering after appendix' do
input = <<~'EOS'
:numbered:
== First Section
content
[appendix]
== Attribute Options
content
== Migration
content
EOS
output = convert_string_to_embedded input
assert_xpath '(//h2)[1][text()="1. First Section"]', output, 1
assert_xpath '(//h2)[2][text()="Appendix A: Attribute Options"]', output, 1
assert_xpath '(//h2)[3][text()="2. Migration"]', output, 1
end
test 'should number appendix subsections using appendix letter' do
input = <<~'EOS'
:numbered:
[appendix]
== Attribute Options
Details
=== Optional Attributes
Details
EOS
output = convert_string_to_embedded input
assert_xpath '(//h2)[1][text()="Appendix A: Attribute Options"]', output, 1
assert_xpath '(//h3)[1][text()="A.1. Optional Attributes"]', output, 1
end
test 'should not number level 4 section by default' do
input = <<~'EOS'
:numbered:
== Level_1
=== Level_2
==== Level_3
===== Level_4
text
EOS
output = convert_string_to_embedded input
assert_xpath '//h5', output, 1
assert_xpath '//h5[text()="Level_4"]', output, 1
end
test 'should only number levels up to value defined by sectnumlevels attribute' do
input = <<~'EOS'
:numbered:
:sectnumlevels: 2
== Level_1
=== Level_2
==== Level_3
===== Level_4
text
EOS
output = convert_string_to_embedded input
assert_xpath '//h2', output, 1
assert_xpath '//h2[text()="1. Level_1"]', output, 1
assert_xpath '//h3', output, 1
assert_xpath '//h3[text()="1.1. Level_2"]', output, 1
assert_xpath '//h4', output, 1
assert_xpath '//h4[text()="Level_3"]', output, 1
assert_xpath '//h5', output, 1
assert_xpath '//h5[text()="Level_4"]', output, 1
end
test 'should not number sections or subsections in regions where numbered is off' do
input = <<~'EOS'
:numbered:
== Section One
:numbered!:
[appendix]
== Attribute Options
Details
[appendix]
== Migration
Details
=== Gotchas
Details
[glossary]
== Glossary
Terms
EOS
output = convert_string_to_embedded input
assert_xpath '(//h2)[1][text()="1. Section One"]', output, 1
assert_xpath '(//h2)[2][text()="Appendix A: Attribute Options"]', output, 1
assert_xpath '(//h2)[3][text()="Appendix B: Migration"]', output, 1
assert_xpath '(//h3)[1][text()="Gotchas"]', output, 1
assert_xpath '(//h2)[4][text()="Glossary"]', output, 1
end
test 'should not number sections or subsections in toc in regions where numbered is off' do
input = <<~'EOS'
:numbered:
:toc:
== Section One
:numbered!:
[appendix]
== Attribute Options
Details
[appendix]
== Migration
Details
=== Gotchas
Details
[glossary]
== Glossary
Terms
EOS
output = convert_string input
assert_xpath '//*[@id="toc"]/ul//li/a[text()="1. Section One"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix A: Attribute Options"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix B: Migration"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Gotchas"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Glossary"]', output, 1
end
test 'should only number sections in toc up to value defined by sectnumlevels attribute' do
input = <<~'EOS'
:numbered:
:toc:
:sectnumlevels: 2
:toclevels: 3
== Level 1
=== Level 2
==== Level 3
EOS
output = convert_string input
assert_xpath '//*[@id="toc"]//a[@href="#_level_1"][text()="1. Level 1"]', output, 1
assert_xpath '//*[@id="toc"]//a[@href="#_level_2"][text()="1.1. Level 2"]', output, 1
assert_xpath '//*[@id="toc"]//a[@href="#_level_3"][text()="Level 3"]', output, 1
end
test 'should not number special sections or their subsections by default except for appendices' do
input = <<~'EOS'
:doctype: book
:sectnums:
[preface]
== Preface
=== Preface Subsection
content
== Section One
content
[appendix]
== Attribute Options
Details
[appendix]
== Migration
Details
=== Gotchas
Details
[glossary]
== Glossary
Terms
EOS
output = convert_string_to_embedded input
assert_xpath '(//h2)[1][text()="Preface"]', output, 1
assert_xpath '(//h3)[1][text()="Preface Subsection"]', output, 1
assert_xpath '(//h2)[2][text()="1. Section One"]', output, 1
assert_xpath '(//h2)[3][text()="Appendix A: Attribute Options"]', output, 1
assert_xpath '(//h2)[4][text()="Appendix B: Migration"]', output, 1
assert_xpath '(//h3)[2][text()="B.1. Gotchas"]', output, 1
assert_xpath '(//h2)[5][text()="Glossary"]', output, 1
end
test 'should not number special sections or their subsections in toc by default except for appendices' do
input = <<~'EOS'
:doctype: book
:sectnums:
:toc:
[preface]
== Preface
=== Preface Subsection
content
== Section One
content
[appendix]
== Attribute Options
Details
[appendix]
== Migration
Details
=== Gotchas
Details
[glossary]
== Glossary
Terms
EOS
output = convert_string input
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Preface"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Preface Subsection"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="1. Section One"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix A: Attribute Options"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix B: Migration"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="B.1. Gotchas"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Glossary"]', output, 1
end
test 'should number special sections and their subsections when sectnums is all' do
input = <<~'EOS'
:doctype: book
:sectnums: all
[preface]
== Preface
=== Preface Subsection
content
== Section One
content
[appendix]
== Attribute Options
Details
[appendix]
== Migration
Details
=== Gotchas
Details
[glossary]
== Glossary
Terms
EOS
output = convert_string_to_embedded input
assert_xpath '(//h2)[1][text()="1. Preface"]', output, 1
assert_xpath '(//h3)[1][text()="1.1. Preface Subsection"]', output, 1
assert_xpath '(//h2)[2][text()="2. Section One"]', output, 1
assert_xpath '(//h2)[3][text()="Appendix A: Attribute Options"]', output, 1
assert_xpath '(//h2)[4][text()="Appendix B: Migration"]', output, 1
assert_xpath '(//h3)[2][text()="B.1. Gotchas"]', output, 1
assert_xpath '(//h2)[5][text()="3. Glossary"]', output, 1
end
test 'should number special sections and their subsections in toc when sectnums is all' do
input = <<~'EOS'
:doctype: book
:sectnums: all
:toc:
[preface]
== Preface
=== Preface Subsection
content
== Section One
content
[appendix]
== Attribute Options
Details
[appendix]
== Migration
Details
=== Gotchas
Details
[glossary]
== Glossary
Terms
EOS
output = convert_string input
assert_xpath '//*[@id="toc"]/ul//li/a[text()="1. Preface"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="1.1. Preface Subsection"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="2. Section One"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix A: Attribute Options"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix B: Migration"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="B.1. Gotchas"]', output, 1
assert_xpath '//*[@id="toc"]/ul//li/a[text()="3. Glossary"]', output, 1
end
test 'level 0 special sections in multipart book should be coerced to level 1' do
input = <<~'EOS'
= Multipart Book
Doc Writer
:doctype: book
[preface]
= Preface
Preface text
[appendix]
= Appendix
Appendix text
EOS
output = convert_string input
assert_xpath '//h2[@id = "_preface"]', output, 1
assert_xpath '//h2[@id = "_appendix"]', output, 1
end
test 'should output docbook elements that correspond to special sections in book doctype' do
input = <<~'EOS'
= Multipart Book
:doctype: book
:idprefix:
[abstract]
= Abstract Title
Normal chapter (no abstract in book)
[dedication]
= Dedication Title
Dedication content
[preface]
= Preface Title
Preface content
=== Preface sub-section
Preface subsection content
= Part 1
[partintro]
.Part intro title
Part intro content
== Chapter 1
blah blah
== Chapter 2
blah blah
= Part 2
[partintro]
blah blah
== Chapter 3
blah blah
== Chapter 4
blah blah
[appendix]
= Appendix Title
Appendix content
=== Appendix sub-section
Appendix sub-section content
[bibliography]
= Bibliography Title
Bibliography content
[glossary]
= Glossary Title
Glossary content
[colophon]
= Colophon Title
Colophon content
[index]
= Index Title
EOS
output = convert_string input, backend: 'docbook'
assert_xpath '/book/chapter[@xml:id="abstract_title"]', output, 1
assert_xpath '/book/chapter[@xml:id="abstract_title"]/title[text()="Abstract Title"]', output, 1
assert_xpath '/book/chapter/following-sibling::dedication[@xml:id="dedication_title"]', output, 1
assert_xpath '/book/chapter/following-sibling::dedication[@xml:id="dedication_title"]/title[text()="Dedication Title"]', output, 1
assert_xpath '/book/dedication/following-sibling::preface[@xml:id="preface_title"]', output, 1
assert_xpath '/book/dedication/following-sibling::preface[@xml:id="preface_title"]/title[text()="Preface Title"]', output, 1
assert_xpath '/book/preface/section[@xml:id="preface_sub_section"]', output, 1
assert_xpath '/book/preface/section[@xml:id="preface_sub_section"]/title[text()="Preface sub-section"]', output, 1
assert_xpath '/book/preface/following-sibling::part[@xml:id="part_1"]', output, 1
assert_xpath '/book/preface/following-sibling::part[@xml:id="part_1"]/title[text()="Part 1"]', output, 1
assert_xpath '/book/part[@xml:id="part_1"]/partintro', output, 1
assert_xpath '/book/part[@xml:id="part_1"]/partintro/title[text()="Part intro title"]', output, 1
assert_xpath '/book/part[@xml:id="part_1"]/partintro/following-sibling::chapter[@xml:id="chapter_1"]', output, 1
assert_xpath '/book/part[@xml:id="part_1"]/partintro/following-sibling::chapter[@xml:id="chapter_1"]/title[text()="Chapter 1"]', output, 1
assert_xpath '(/book/part)[2]/following-sibling::appendix[@xml:id="appendix_title"]', output, 1
assert_xpath '(/book/part)[2]/following-sibling::appendix[@xml:id="appendix_title"]/title[text()="Appendix Title"]', output, 1
assert_xpath '/book/appendix/section[@xml:id="appendix_sub_section"]', output, 1
assert_xpath '/book/appendix/section[@xml:id="appendix_sub_section"]/title[text()="Appendix sub-section"]', output, 1
assert_xpath '/book/appendix/following-sibling::bibliography[@xml:id="bibliography_title"]', output, 1
assert_xpath '/book/appendix/following-sibling::bibliography[@xml:id="bibliography_title"]/title[text()="Bibliography Title"]', output, 1
assert_xpath '/book/bibliography/following-sibling::glossary[@xml:id="glossary_title"]', output, 1
assert_xpath '/book/bibliography/following-sibling::glossary[@xml:id="glossary_title"]/title[text()="Glossary Title"]', output, 1
assert_xpath '/book/glossary/following-sibling::colophon[@xml:id="colophon_title"]', output, 1
assert_xpath '/book/glossary/following-sibling::colophon[@xml:id="colophon_title"]/title[text()="Colophon Title"]', output, 1
assert_xpath '/book/colophon/following-sibling::index[@xml:id="index_title"]', output, 1
assert_xpath '/book/colophon/following-sibling::index[@xml:id="index_title"]/title[text()="Index Title"]', output, 1
end
test 'abstract section maps to abstract element in docbook for article doctype' do
input = <<~'EOS'
= Article
:idprefix:
[abstract]
== Abstract Title
Abstract content
EOS
output = convert_string input, backend: 'docbook'
assert_xpath '/article/info/abstract[@xml:id="abstract_title"]', output, 1
assert_xpath '/article/info/abstract[@xml:id="abstract_title"]/title[text()="Abstract Title"]', output, 1
end
test 'should allow a special section to be nested at arbitrary depth in DocBook output' do
input = <<~'EOS'
= Document Title
:doctype: book
== Glossaries
[glossary]
=== Glossary A
Glossaries are optional.
Glossaries entries are an example of a style of AsciiDoc description lists.
[glossary]
A glossary term::
The corresponding definition.
A second glossary term::
The corresponding definition.
EOS
output = convert_string input, backend: :docbook
assert_xpath '//glossary', output, 1
assert_xpath '//chapter/glossary', output, 1
assert_xpath '//glossary/title[text()="Glossary A"]', output, 1
assert_xpath '//glossary/glossentry', output, 2
end
test 'should drop title on special section in DocBook output if notitle or untitled option is set' do
%w(notitle untitled).each do |option|
input = <<~EOS
[dedication%#{option}]
== Dedication
content
EOS
output = convert_string_to_embedded input, backend: :docbook
assert_xpath '/dedication', output, 1
assert_xpath '/dedication/title', output, 0
end
end
end
"##
);
}
non_normative!(
r##"
context "heading patterns in blocks" do
test "should not interpret a listing block as a heading" do
input = <<~'EOS'
Section
-------
----
code
----
fin.
EOS
output = convert_string input
assert_xpath "//h2", output, 1
end
test "should not interpret an open block as a heading" do
input = <<~'EOS'
Section
-------
--
ha
--
fin.
EOS
output = convert_string input
assert_xpath "//h2", output, 1
end
test "should not interpret an attribute list as a heading" do
input = <<~'EOS'
Section
=======
preamble
[TIP]
====
This should be a tip, not a heading.
====
EOS
output = convert_string input
assert_xpath "//*[@class='admonitionblock tip']//p[text() = 'This should be a tip, not a heading.']", output, 1
end
test "should not match a heading in a description list" do
input = <<~'EOS'
Section
-------
term1::
+
----
list = [1, 2, 3];
----
term2::
== not a heading
term3:: def
//
fin.
EOS
output = convert_string input
assert_xpath "//h2", output, 1
assert_xpath "//dl", output, 1
end
test "should not match a heading in a bulleted list" do
input = <<~'EOS'
Section
-------
* first
+
----
list = [1, 2, 3];
----
+
* second
== not a heading
* third
fin.
EOS
output = convert_string input
assert_xpath "//h2", output, 1
assert_xpath "//ul", output, 1
end
test "should not match a heading in a block" do
input = <<~'EOS'
====
== not a heading
====
EOS
output = convert_string input
assert_xpath "//h2", output, 0
assert_xpath "//*[@class='exampleblock']//p[text() = '== not a heading']", output, 1
end
end
"##
);
non_normative!(
r##"
context 'Table of Contents' do
"##
);
mod table_of_contents {
use crate::tests::prelude::*;
#[test]
fn should_output_unnumbered_table_of_contents_in_header_if_toc_attribute_is_set() {
verifies!(
r##"
test 'should output unnumbered table of contents in header if toc attribute is set' do
input = <<~'EOS'
= Article
:toc:
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
=== Interlude
While they were waiting...
== Section Three
That's all she wrote!
EOS
output = convert_string input
assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/*[@id="toctitle"][text()="Table of Contents"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul[@class="sectlevel1"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]//ul', output, 2
assert_xpath '//*[@id="header"]//*[@id="toc"]//li', output, 4
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="Section One"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul[@class="sectlevel2"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li/a[@href="#_interlude"][text()="Interlude"]', output, 1
assert_xpath '((//*[@id="header"]//*[@id="toc"]/ul)[1]/li)[3]/a[@href="#_section_three"][text()="Section Three"]', output, 1
end
"##
);
let doc = Parser::default().parse(
"= Article\n:toc:\n\n== Section One\n\nIt was a dark and stormy night...\n\n== Section Two\n\nThey couldn't believe their eyes when...\n\n=== Interlude\n\nWhile they were waiting...\n\n== Section Three\n\nThat's all she wrote!\n",
);
assert_xpath(&doc, r##"//*[@id="toc"][@class="toc"]"##, 1);
assert_xpath(
&doc,
r##"//*[@id="toc"]/*[@id="toctitle"][text()="Table of Contents"]"##,
1,
);
assert_xpath(&doc, r##"//*[@id="toc"]/ul[@class="sectlevel1"]"##, 1);
assert_xpath(&doc, r##"//*[@id="toc"]//ul"##, 2);
assert_xpath(&doc, r##"//*[@id="toc"]//li"##, 4);
assert_xpath(
&doc,
r##"//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="Section One"]"##,
1,
);
assert_xpath(&doc, r##"//*[@id="toc"]/ul/li/ul[@class="sectlevel2"]"##, 1);
assert_xpath(
&doc,
r##"//*[@id="toc"]/ul/li/ul/li/a[@href="#_interlude"][text()="Interlude"]"##,
1,
);
}
non_normative!(
r##"
test 'should output numbered table of contents in header if toc and numbered attributes are set' do
input = <<~'EOS'
= Article
:toc:
:numbered:
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
=== Interlude
While they were waiting...
== Section Three
That's all she wrote!
EOS
output = convert_string input
assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/*[@id="toctitle"][text()="Table of Contents"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]//ul', output, 2
assert_xpath '//*[@id="header"]//*[@id="toc"]//li', output, 4
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li/a[@href="#_interlude"][text()="2.1. Interlude"]', output, 1
assert_xpath '((//*[@id="header"]//*[@id="toc"]/ul)[1]/li)[3]/a[@href="#_section_three"][text()="3. Section Three"]', output, 1
end
test 'should output a table of contents that honors numbered setting at position of section in document' do
input = <<~'EOS'
= Article
:toc:
:numbered:
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
=== Interlude
While they were waiting...
:numbered!:
== Section Three
That's all she wrote!
EOS
output = convert_string input
assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/*[@id="toctitle"][text()="Table of Contents"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]//ul', output, 2
assert_xpath '//*[@id="header"]//*[@id="toc"]//li', output, 4
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
assert_xpath '((//*[@id="header"]//*[@id="toc"]/ul)[1]/li)[3]/a[@href="#_section_three"][text()="Section Three"]', output, 1
end
test 'should not number parts in table of contents for book doctype when numbered attribute is set' do
input = <<~'EOS'
= Book
:doctype: book
:toc:
:numbered:
= Part 1
== First Section of Part 1
blah
== Second Section of Part 1
blah
= Part 2
== First Section of Part 2
blah
EOS
output = convert_string input
assert_xpath '//*[@id="toc"]', output, 1
assert_xpath '//*[@id="toc"]/ul', output, 1
assert_xpath '//*[@id="toc"]/ul[@class="sectlevel0"]', output, 1
assert_xpath '//*[@id="toc"]/ul[@class="sectlevel0"]/li', output, 2
assert_xpath '(//*[@id="toc"]/ul[@class="sectlevel0"]/li)[1]/a[text()="Part 1"]', output, 1
assert_xpath '(//*[@id="toc"]/ul[@class="sectlevel0"]/li)[2]/a[text()="Part 2"]', output, 1
assert_xpath '(//*[@id="toc"]/ul[@class="sectlevel0"]/li)[1]/ul', output, 1
assert_xpath '(//*[@id="toc"]/ul[@class="sectlevel0"]/li)[1]/ul[@class="sectlevel1"]', output, 1
assert_xpath '(//*[@id="toc"]/ul[@class="sectlevel0"]/li)[1]/ul/li', output, 2
assert_xpath '((//*[@id="toc"]/ul[@class="sectlevel0"]/li)[1]/ul/li)[1]/a[text()="1. First Section of Part 1"]', output, 1
end
test 'should output table of contents in header if toc2 attribute is set' do
input = <<~'EOS'
= Article
:toc2:
:numbered:
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string input
assert_xpath '//body[@class="article toc2 toc-left"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc2"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
end
test 'should set toc position if toc attribute is set to position' do
input = <<~'EOS'
= Article
:toc: >
:numbered:
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string input
assert_xpath '//body[@class="article toc2 toc-right"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc2"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
end
test 'should set toc position if toc and toc-position attributes are set' do
input = <<~'EOS'
= Article
:toc:
:toc-position: right
:numbered:
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string input
assert_xpath '//body[@class="article toc2 toc-right"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc2"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
end
test 'should set toc position if toc2 and toc-position attribute are set' do
input = <<~'EOS'
= Article
:toc2:
:toc-position: right
:numbered:
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string input
assert_xpath '//body[@class="article toc2 toc-right"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc2"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
end
test 'should set toc position if toc attribute is set to direction' do
input = <<~'EOS'
= Article
:toc: right
:numbered:
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string input
assert_xpath '//body[@class="article toc2 toc-right"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc2"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
end
test 'should set toc placement to preamble if toc attribute is set to preamble' do
input = <<~'EOS'
= Article
:toc: preamble
Yada yada
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string input
assert_css '#preamble #toc', output, 1
assert_css '#preamble .sectionbody + #toc', output, 1
end
test 'should use document attributes toc-class, toc-title and toclevels to create toc' do
input = <<~'EOS'
= Article
:toc:
:toc-title: Contents
:toc-class: toc2
:toclevels: 1
== Section 1
=== Section 1.1
==== Section 1.1.1
==== Section 1.1.2
=== Section 1.2
== Section 2
Fin.
EOS
output = convert_string input
assert_css '#header #toc', output, 1
assert_css '#header #toc.toc2', output, 1
assert_css '#header #toc li', output, 2
assert_css '#header #toc #toctitle', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/*[@id="toctitle"][text()="Contents"]', output, 1
end
test 'should only show parts in toc if toclevels is 0' do
input = <<~'EOS'
= Article
:doctype: book
:toc:
:toclevels: 0
= Part 1
== Chapter 1
= Part 2
== Chapter 2
EOS
output = convert_string input
assert_css '#toc', output, 1
assert_css '#toc a[href="#_part_1"]', output, 1
assert_css '#toc a[href="#_part_2"]', output, 1
assert_css '#toc a[href="#_chapter_1"]', output, 0
assert_css '#toc a[href="#_chapter_2"]', output, 0
end
test 'should coerce minimum toclevels to 1 if first section of document is not a part' do
input = <<~'EOS'
= Article
:doctype: book
:toc:
:toclevels: 0
== Chapter 1
== Chapter 2
EOS
output = convert_string input
assert_css '#toc', output, 1
assert_css '#toc a[href="#_chapter_1"]', output, 1
assert_css '#toc a[href="#_chapter_2"]', output, 1
end
"##
);
#[test]
fn should_not_output_table_of_contents_if_toc_placement_attribute_is_unset() {
verifies!(
r##"
test 'should not output table of contents if toc-placement attribute is unset' do
input = <<~'EOS'
= Article
:toc:
:toc-placement!:
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string input
assert_xpath '//*[@id="toc"]', output, 0
end
"##
);
let doc = Parser::default().parse(
"= Article\n:toc:\n:toc-placement!:\n\n== Section One\n\nIt was a dark and stormy night...\n\n== Section Two\n\nThey couldn't believe their eyes when...\n",
);
assert_eq!(doc.toc_mode(), crate::document::TocMode::Macro);
assert_xpath(&doc, r##"//*[@id="toc"]"##, 0);
}
non_normative!(
r##"
test 'should output table of contents at location of toc macro' do
input = <<~'EOS'
= Article
:toc:
:toc-placement: macro
Once upon a time...
toc::[]
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string input
assert_css '#preamble #toc', output, 1
assert_css '#preamble .paragraph + #toc', output, 1
end
test 'should output table of contents at location of toc macro in embedded document' do
input = <<~'EOS'
= Article
:toc:
:toc-placement: macro
Once upon a time...
toc::[]
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string_to_embedded input
assert_css '#preamble:root #toc', output, 1
assert_css '#preamble:root .paragraph + #toc', output, 1
end
test 'should output table of contents at default location in embedded document if toc attribute is set' do
input = <<~'EOS'
= Article
:showtitle:
:toc:
Once upon a time...
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string_to_embedded input
assert_css 'h1:root', output, 1
assert_css 'h1:root + #toc:root', output, 1
assert_css 'h1:root + #toc:root + #preamble:root', output, 1
end
test 'should not activate toc macro if toc-placement is not set' do
input = <<~'EOS'
= Article
:toc:
Once upon a time...
toc::[]
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string input
assert_css '#toc', output, 1
assert_css '#toctitle', output, 1
assert_css '.toc', output, 1
assert_css '#content .toc', output, 0
end
test 'should only output toc at toc macro if toc is macro' do
input = <<~'EOS'
= Article
:toc: macro
Once upon a time...
toc::[]
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
EOS
output = convert_string input
assert_css '#toc', output, 1
assert_css '#toctitle', output, 1
assert_css '.toc', output, 1
assert_css '#content .toc', output, 1
end
test 'should use global attributes for toc-title, toc-class and toclevels for toc macro' do
input = <<~'EOS'
= Article
:toc:
:toc-placement: macro
:toc-title: Contents
:toc-class: contents
:toclevels: 1
Preamble.
toc::[]
== Section 1
=== Section 1.1
==== Section 1.1.1
==== Section 1.1.2
=== Section 1.2
== Section 2
Fin.
EOS
output = convert_string input
assert_css '#toc', output, 1
assert_css '#toctitle', output, 1
assert_css '#preamble #toc', output, 1
assert_css '#preamble #toc.contents', output, 1
assert_xpath '//*[@id="toc"]/*[@class="title"][text() = "Contents"]', output, 1
assert_css '#toc li', output, 2
assert_xpath '(//*[@id="toc"]//li)[1]/a[text() = "Section 1"]', output, 1
assert_xpath '(//*[@id="toc"]//li)[2]/a[text() = "Section 2"]', output, 1
end
test 'should honor id, title, role and level attributes on toc macro' do
input = <<~'EOS'
= Article
:toc:
:toc-placement: macro
:toc-title: Ignored
:toc-class: ignored
:tocmacrolevels: 3
Preamble.
[[contents]]
[role="contents"]
.Contents
toc::[levels={tocmacrolevels}]
== Section 1
=== Section 1.1
==== Section 1.1.1
==== Section 1.1.2
=== Section 1.2
== Section 2
Fin.
EOS
output = convert_string input
assert_css '#toc', output, 0
assert_css '#toctitle', output, 0
assert_css '#preamble #contents', output, 1
assert_css '#preamble #contents.contents', output, 1
assert_xpath '//*[@id="contents"]/*[@class="title"][text() = "Contents"]', output, 1
assert_css '#contents li', output, 6
assert_css '#contents a[href="#_section_1"]', output, 1
assert_css '#contents a[href="#_section_1_1"]', output, 1
assert_css '#contents a[href="#_section_1_1_1"]', output, 1
end
test 'child toc levels should not have additional bullet at parent level in html' do
input = <<~'EOS'
= Article
:toc:
== Section One
It was a dark and stormy night...
== Section Two
They couldn't believe their eyes when...
=== Interlude
While they were waiting...
== Section Three
That's all she wrote!
EOS
output = convert_string input
assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/*[@id="toctitle"][text()="Table of Contents"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]//ul', output, 2
assert_xpath '//*[@id="header"]//*[@id="toc"]//li', output, 4
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[2]/a[@href="#_section_two"][text()="Section Two"]', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[2]/ul/li', output, 1
assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li/a[@href="#_interlude"][text()="Interlude"]', output, 1
assert_xpath '((//*[@id="header"]//*[@id="toc"]/ul)[1]/li)[3]/a[@href="#_section_three"][text()="Section Three"]', output, 1
end
test 'should not display a table of contents if document has no sections' do
input_src = <<~'EOS'
= Document Title
:toc:
toc::[]
This document has no sections.
It only has content.
EOS
['', 'left', 'preamble', 'macro'].each do |placement|
input = input_src.gsub(':toc:', "\\& #{placement}")
output = convert_string input
assert_css '#toctitle', output, 0
end
end
test 'should drop anchors from contents of entries in table of contents' do
input = <<~'EOS'
= Document Title
:toc:
== [[un]]Section One
content
== [[two]][[deux]]Section Two
content
== Plant Trees by https://ecosia.org[Searching]
content
EOS
output = convert_string_to_embedded input
assert_xpath '/*[@id="toc"]', output, 1
toc_links = xmlnodes_at_xpath '/*[@id="toc"]//li', output
assert_equal 3, toc_links.size
assert_equal '<a href="#_section_one">Section One</a>', toc_links[0].inner_html
assert_equal '<a href="#_section_two">Section Two</a>', toc_links[1].inner_html
assert_equal '<a href="#_plant_trees_by_searching">Plant Trees by Searching</a>', toc_links[2].inner_html
end
test 'should not remove non-anchor tags from contents of entries in table of contents' do
input = <<~'EOS'
= Document Title
:toc:
:icons: font
== `run` command
content
== icon:bug[] Issues
content
== https://ecosia.org[_Sustainable_ Searches]
content
EOS
output = convert_string_to_embedded input, safe: :safe
assert_xpath '/*[@id="toc"]', output, 1
toc_links = xmlnodes_at_xpath '/*[@id="toc"]//li', output
assert_equal 3, toc_links.size
assert_equal '<a href="#_run_command"><code>run</code> command</a>', toc_links[0].inner_html
assert_equal '<a href="#_issues"><span class="icon"><i class="fa fa-bug"></i></span> Issues</a>', toc_links[1].inner_html
assert_equal '<a href="#_sustainable_searches"><em>Sustainable</em> Searches</a>', toc_links[2].inner_html
end
end
"##
);
}
non_normative!(
r##"
context 'article doctype' do
test 'should create only sections in docbook backend' do
input = <<~'EOS'
= Article
Doc Writer
== Section 1
The adventure.
=== Subsection One
It was a dark and stormy night...
=== Subsection Two
They couldn't believe their eyes when...
== Section 2
The return.
=== Subsection Three
While they were returning...
=== Subsection Four
That's all she wrote!
EOS
output = convert_string input, backend: 'docbook'
assert_xpath '//part', output, 0
assert_xpath '//chapter', output, 0
assert_xpath '/article/section', output, 2
assert_xpath '/article/section[1]/title[text() = "Section 1"]', output, 1
assert_xpath '/article/section[2]/title[text() = "Section 2"]', output, 1
assert_xpath '/article/section/section', output, 4
assert_xpath '/article/section[1]/section[1]/title[text() = "Subsection One"]', output, 1
assert_xpath '/article/section[2]/section[1]/title[text() = "Subsection Three"]', output, 1
end
end
"##
);
non_normative!(
r##"
context 'book doctype' do
"##
);
mod book_doctype {
use crate::tests::prelude::*;
non_normative!(
r##"
test 'document title with level 0 headings' do
input = <<~'EOS'
= Book
Doc Writer
:doctype: book
= Chapter One
[partintro]
It was a dark and stormy night...
== Scene One
Someone's gonna get axed.
= Chapter Two
[partintro]
They couldn't believe their eyes when...
== Interlude
While they were waiting...
= Chapter Three
== Scene One
That's all she wrote!
EOS
output = convert_string(input)
assert_css 'body.book', output, 1
assert_css 'h1', output, 4
assert_css '#header h1', output, 1
assert_css '#content h1', output, 3
assert_css '#content h1.sect0', output, 3
assert_css 'h2', output, 3
assert_css '#content h2', output, 3
assert_xpath '//h1[@id="_chapter_one"][text() = "Chapter One"]', output, 1
assert_xpath '//h1[@id="_chapter_two"][text() = "Chapter Two"]', output, 1
assert_xpath '//h1[@id="_chapter_three"][text() = "Chapter Three"]', output, 1
assert_css '#_chapter_one + .openblock.partintro p', output, 1
assert_css '#_chapter_two + .openblock.partintro p', output, 1
end
"##
);
#[test]
fn should_print_error_if_level_0_section_comes_after_nested_section_and_doctype_is_not_book() {
verifies!(
r##"
test 'should print error if level 0 section comes after nested section and doctype is not book' do
input = <<~'EOS'
= Document Title
== Level 1 Section
=== Level 2 Section
= Level 0 Section
EOS
using_memory_logger do |logger|
convert_string input
assert_message logger, :ERROR, '<stdin>: line 7: level 0 sections can only be used when doctype is book', Hash
end
end
"##
);
let doc = Parser::default().parse(
"= Document Title\n\n== Level 1 Section\n\n=== Level 2 Section\n\n= Level 0 Section\n",
);
let warnings: Vec<_> = doc.warnings().collect();
assert!(warnings.iter().any(|w| matches!(
w.warning,
WarningType::Level0SectionHeadingNotSupported
) && w.source.line() == 7));
}
non_normative!(
r##"
test 'should add class matching role to part' do
input = <<~'EOS'
= Book Title
:doctype: book
[.newbie]
= Part 1
== Chapter A
content
= Part 2
== Chapter B
content
EOS
result = convert_string_to_embedded input
assert_css 'h1.sect0', result, 2
assert_css 'h1.sect0.newbie', result, 1
assert_css 'h1.sect0.newbie#_part_1', result, 1
end
test 'should assign appropriate sectname for section type' do
input = <<~'EOS'
= Book Title
:doctype: book
:idprefix:
:idseparator: -
= Part Title
== Chapter Title
=== Section Title
content
[appendix]
== Appendix Title
=== Appendix Section Title
content
EOS
doc = document_from_string input
assert_equal 'header', doc.header.sectname
assert_equal 'part', (doc.find_by id: 'part-title')[0].sectname
assert_equal 'chapter', (doc.find_by id: 'chapter-title')[0].sectname
assert_equal 'section', (doc.find_by id: 'section-title')[0].sectname
assert_equal 'appendix', (doc.find_by id: 'appendix-title')[0].sectname
assert_equal 'section', (doc.find_by id: 'appendix-section-title')[0].sectname
end
test 'should allow part intro to be defined using special section' do
input = <<~'EOS'
= Book
:doctype: book
= Part 1
[partintro]
== Part Intro
Part intro content
== Chapter 1
Chapter content
EOS
output = convert_string input, backend: 'docbook'
assert_xpath '/book/part[@xml:id="_part_1"]', output, 1
assert_xpath '/book/part[@xml:id="_part_1"]/partintro', output, 1
assert_xpath '/book/part[@xml:id="_part_1"]/partintro[@xml:id="_part_intro"]', output, 1
assert_xpath '/book/part[@xml:id="_part_1"]/partintro[@xml:id="_part_intro"]/title[text()="Part Intro"]', output, 1
assert_xpath '/book/part[@xml:id="_part_1"]/partintro[@xml:id="_part_intro"]/following-sibling::chapter[@xml:id="_chapter_1"]', output, 1
end
test 'should add partintro style to child paragraph of part' do
input = <<~'EOS'
= Book
:doctype: book
= Part 1
part intro--a summary
== Chapter 1
EOS
doc = document_from_string input
partintro = doc.blocks.first.blocks.first
assert_equal :open, partintro.context
assert_equal :compound, partintro.content_model
assert_empty partintro.lines
assert_empty partintro.subs
assert_equal 'partintro', partintro.style
assert_equal :paragraph, partintro.blocks[0].context
assert_equal ['part intro--a summary'], partintro.blocks[0].lines
assert_include 'part intro—​a summary', partintro.convert
end
test 'should preserve title on partintro defined as partintro paragraph' do
input = <<~'EOS'
= Book
:doctype: book
= Part 1
.Intro
[partintro]
Read this first.
== Chapter 1
EOS
doc = document_from_string input
partintro = doc.blocks.first.blocks.first
assert_equal :open, partintro.context
assert_equal 'Intro', partintro.title
end
test 'should not promote title on partintro defined as normal paragraph' do
input = <<~'EOS'
= Book
:doctype: book
= Part 1
.Intro
Read this first.
== Chapter 1
EOS
doc = document_from_string input
partintro = doc.blocks.first.blocks.first
assert_equal :open, partintro.context
assert_nil partintro.title
assert_equal 'Intro', partintro.blocks[0].title
end
test 'should add partintro style to child open block of part' do
input = <<~'EOS'
= Book
:doctype: book
= Part 1
--
part intro
--
== Chapter 1
EOS
doc = document_from_string input
partintro = doc.blocks.first.blocks.first
assert_equal :open, partintro.context
assert_equal :compound, partintro.content_model
assert_equal 'partintro', partintro.style
assert_equal :paragraph, partintro.blocks[0].context
end
test 'should wrap child paragraphs of part in partintro open block' do
input = <<~'EOS'
= Book
:doctype: book
= Part 1
part intro
more part intro
== Chapter 1
EOS
doc = document_from_string input
partintro = doc.blocks.first.blocks.first
assert_equal :open, partintro.context
assert_equal :compound, partintro.content_model
assert_equal 'partintro', partintro.style
assert_equal 2, partintro.blocks.size
assert_equal :paragraph, partintro.blocks[0].context
assert_equal :paragraph, partintro.blocks[1].context
end
test 'should wrap abstract in implicit part intro in info tag when converting to DocBook' do
input = <<~'EOS'
= Book
:doctype: book
= Part 1
[abstract]
Abstract of part.
more part intro
== Chapter 1
EOS
output = convert_string input, backend: 'docbook'
assert_xpath '//abstract', output, 1
assert_xpath '//partintro/info/abstract', output, 1
end
test 'should wrap abstract in part intro section in info tag when converting to DocBook' do
input = <<~'EOS'
= Book
:doctype: book
= Part 1
[partintro]
== Part Intro
[abstract]
Abstract of part.
more part intro
== Chapter 1
EOS
output = convert_string input, backend: 'docbook'
assert_xpath '//abstract', output, 1
assert_xpath '//partintro/info/abstract', output, 1
assert_xpath '//partintro/simpara', output, 1
end
test 'should warn if part has no sections' do
input = <<~'EOS'
= Book
:doctype: book
= Part 1
[partintro]
intro
EOS
using_memory_logger do |logger|
document_from_string input
assert_message logger, :ERROR, '<stdin>: line 8: invalid part, must have at least one section (e.g., chapter, appendix, etc.)', Hash
end
end
test 'should create parts and chapters in docbook backend' do
input = <<~'EOS'
= Book
Doc Writer
:doctype: book
= Part 1
[partintro]
The adventure.
== Chapter One
It was a dark and stormy night...
== Chapter Two
They couldn't believe their eyes when...
= Part 2
[partintro]
The return.
== Chapter Three
While they were returning...
== Chapter Four
That's all she wrote!
EOS
output = convert_string input, backend: 'docbook'
assert_xpath '//chapter/chapter', output, 0
assert_xpath '/book/part', output, 2
assert_xpath '/book/part[1]/title[text() = "Part 1"]', output, 1
assert_xpath '/book/part[2]/title[text() = "Part 2"]', output, 1
assert_xpath '/book/part/chapter', output, 4
assert_xpath '/book/part[1]/chapter[1]/title[text() = "Chapter One"]', output, 1
assert_xpath '/book/part[2]/chapter[1]/title[text() = "Chapter Three"]', output, 1
end
test 'subsections in preface and appendix should start at level 2' do
input = <<~'EOS'
= Multipart Book
Doc Writer
:doctype: book
[preface]
= Preface
Preface content
=== Preface subsection
Preface subsection content
= Part 1
.Part intro title
[partintro]
Part intro content
== Chapter 1
content
[appendix]
= Appendix
Appendix content
=== Appendix subsection
Appendix subsection content
EOS
output = nil
using_memory_logger do |logger|
output = convert_string input, backend: 'docbook'
assert logger.empty?
end
assert_xpath '/book/preface', output, 1
assert_xpath '/book/preface/section', output, 1
assert_xpath '/book/part', output, 1
assert_xpath '/book/part/partintro', output, 1
assert_xpath '/book/part/partintro/title', output, 1
assert_xpath '/book/part/partintro/simpara', output, 1
assert_xpath '/book/appendix', output, 1
assert_xpath '/book/appendix/section', output, 1
end
end
end
"##
);
}