use crate::tests::prelude::*;
track_file!("ref/asciidoc-lang/docs/modules/tables/pages/add-header-row.adoc");
fn parse_table(source: &str) -> crate::blocks::TableBlock<'_> {
let mut parser = Parser::default();
let mi = crate::blocks::Block::parse(crate::Span::new(source), &mut parser)
.unwrap_if_no_warnings()
.unwrap();
match mi.item {
crate::blocks::Block::Table(table) => table,
other => panic!("expected a table block, got {other:?}"),
}
}
fn simple_text(cell: &crate::blocks::TableCell<'_>) -> String {
match cell.content() {
crate::blocks::TableCellContent::Simple(content) => content.rendered().to_string(),
crate::blocks::TableCellContent::AsciiDoc(_) => panic!("expected simple cell content"),
}
}
fn header_texts(table: &crate::blocks::TableBlock<'_>) -> Vec<String> {
table
.header_row()
.expect("expected a header row")
.cells()
.iter()
.map(simple_text)
.collect()
}
non_normative!(
r#"
= Create a Header Row
"#
);
#[test]
fn intro() {
verifies!(
r#"
The first row of a table is promoted to a header row if the `header` value is assigned to the table's `options` attribute.
You can assign `header` to a table's first row explicitly or implicitly.
"#
);
let explicit =
parse_table("[options=\"header\"]\n|===\n|Column 1 |Column 2\n|Cell 1 |Cell 2\n|===");
assert!(explicit.header_row().is_some());
let implicit = parse_table("|===\n|Column 1 |Column 2\n\n|Cell 1 |Cell 2\n|===");
assert!(implicit.header_row().is_some());
}
#[test]
fn header_ignores_operators() {
verifies!(
r#"
TIP: The header row ignores any style operators assigned via column and cell specifiers.
"#
);
let table = parse_table(
"[cols=\"a,1\",options=\"header\"]\n|===\n|Column 1 |Column 2\n\n|* item\n|Cell 2\n|===",
);
assert!(matches!(
table.header_row().unwrap().cells()[0].content(),
crate::blocks::TableCellContent::Simple(_)
));
assert!(matches!(
table.body_rows()[0].cells()[0].content(),
crate::blocks::TableCellContent::AsciiDoc(_)
));
to_do_verifies!(
r#"
It also ignores alignment operators assigned to the table's column specifiers; however, any alignment operators assigned to a cell specifier in the header row are applied.
"#
);
if false {
todo!("cell specifiers and cell-level alignment for the header row");
}
}
#[test]
fn explicitly_assign_header() {
non_normative!(
r#"
== Explicitly assign header to the first row
"#
);
verifies!(
r#"
The header row semantics and styles are explicitly assigned to the first row in a table by assigning `header` to the `options` attribute.
The `options` attribute is set in the table's attribute list using the shorthand (`%value`) or formal syntax (`options="value"`).
"#
);
let shorthand = parse_table(
"[%header,cols=\"2,2,1\"]\n|===\n|Column 1, header row\n|Column 2, header row\n|Column 3, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n|Cell in column 3, row 2\n|===",
);
assert_eq!(
header_texts(&shorthand),
vec![
"Column 1, header row",
"Column 2, header row",
"Column 3, header row"
]
);
assert_eq!(shorthand.body_rows().len(), 1);
let formal = parse_table(
"[cols=\"2*\",options=\"header\"]\n|===\n|Column 1, header row\n|Column 2, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|===",
);
assert_eq!(
header_texts(&formal),
vec!["Column 1, header row", "Column 2, header row"]
);
assert_eq!(formal.body_rows().len(), 2);
verifies!(
r#"
The `options` attribute is represented by the percent sign (`%`) when it's set using the shorthand syntax.
"#
);
let table = parse_table("[%header]\n|===\n|Column 1 |Column 2\n|Cell 1 |Cell 2\n|===");
assert!(table.header_row().is_some());
verifies!(
r#"
In <<ex-short>>, `header` is assigned to using the shorthand syntax for `options`.
.Table with `header` assigned using the shorthand syntax
[source#ex-short]
----
[%header,cols="2,2,1"] <.>
|===
|Column 1, header row
|Column 2, header row
|Column 3, header row
|Cell in column 1, row 2
|Cell in column 2, row 2
|Cell in column 3, row 2
|===
----
"#
);
let ex_short = parse_table(
"[%header,cols=\"2,2,1\"]\n|===\n|Column 1, header row\n|Column 2, header row\n|Column 3, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n|Cell in column 3, row 2\n|===",
);
assert!(ex_short.header_row().is_some());
verifies!(
r#"
<.> Values assigned using the shorthand syntax must be entered before the `cols` attribute (or any other named attributes) in a table's attribute list, otherwise the processor will ignore them.
"#
);
let before = parse_table(
"[%header,cols=\"2,2,1\"]\n|===\n|Column 1 |Column 2 |Column 3\n|Cell 1 |Cell 2 |Cell 3\n|===",
);
assert!(before.header_row().is_some());
let after = parse_table(
"[cols=\"2,2,1\",%header]\n|===\n|Column 1 |Column 2 |Column 3\n|Cell 1 |Cell 2 |Cell 3\n|===",
);
assert!(after.header_row().is_none());
verifies!(
r#"
The table from <<ex-short>> is displayed below.
.Result of <<ex-short>>
[%header,cols="2,2,1"]
|===
|Column 1, header row
|Column 2, header row
|Column 3, header row
|Cell in column 1, row 2
|Cell in column 2, row 2
|Cell in column 3, row 2
|===
"#
);
let ex_short_result = parse_table(
"[%header,cols=\"2,2,1\"]\n|===\n|Column 1, header row\n|Column 2, header row\n|Column 3, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n|Cell in column 3, row 2\n|===",
);
assert_eq!(
header_texts(&ex_short_result),
vec![
"Column 1, header row",
"Column 2, header row",
"Column 3, header row"
]
);
verifies!(
r#"
In <<ex-formal>>, the `options` attribute is set and assigned the `header` value using the formal syntax.
The `options` attribute accepts a comma-separated list of values.
.Table with header assigned to the options attribute
[source#ex-formal]
----
include::example$row.adoc[tag=opt-h]
----
"#
);
let ex_formal = parse_table(
"[cols=\"2*\",options=\"header\"]\n|===\n|Column 1, header row\n|Column 2, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|===",
);
assert!(ex_formal.header_row().is_some());
let table = parse_table(
"[options=\"header,unbreakable\"]\n|===\n|Column 1 |Column 2\n|Cell 1 |Cell 2\n|===",
);
assert!(table.header_row().is_some());
verifies!(
r#"
The first row of the table in <<ex-formal>> is rendered using the corresponding header styles and semantics.
.Result of <<ex-formal>>
include::example$row.adoc[tag=opt-h]
"#
);
let ex_formal_result = parse_table(
"[cols=\"2*\",options=\"header\"]\n|===\n|Column 1, header row\n|Column 2, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|===",
);
assert_eq!(
header_texts(&ex_formal_result),
vec!["Column 1, header row", "Column 2, header row"]
);
}
#[test]
fn implicitly_assign_header() {
non_normative!(
r#"
== Implicitly assign header to the first row
"#
);
verifies!(
r#"
You can implicitly define a header row based on how you layout the table.
The following conventions determine when the first row automatically becomes the header row:
. The first line of content inside the table delimiters is not empty.
. The second line of content inside the table delimiters is empty.
"#
);
let table = parse_table(
"|===\n|Column 1, header row |Column 2, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|===",
);
assert_eq!(
header_texts(&table),
vec!["Column 1, header row", "Column 2, header row"]
);
assert_eq!(table.body_rows().len(), 2);
let no_header = parse_table("|===\n|Cell 1 |Cell 2\n|Cell 3 |Cell 4\n|===");
assert!(no_header.header_row().is_none());
verifies!(
r#"
.First row is implicitly assigned header
[source#ex-implicit]
----
include::example$row.adoc[tag=impl-h]
----
"#
);
let ex_implicit = parse_table(
"|===\n|Column 1, header row |Column 2, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|===",
);
assert!(ex_implicit.header_row().is_some());
verifies!(
r#"
As seen in the result below, if all of these rules hold true, then the first row of the table is treated as a header row.
.Result of <<ex-implicit>>
include::example$row.adoc[tag=impl-h]
"#
);
let ex_implicit_result = parse_table(
"|===\n|Column 1, header row |Column 2, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|===",
);
assert_eq!(
header_texts(&ex_implicit_result),
vec!["Column 1, header row", "Column 2, header row"]
);
}
#[test]
fn deactivate_implicit_header() {
non_normative!(
r#"
=== Deactivate the implicit assignment of header
"#
);
verifies!(
r#"
To suppress the implicit behavior of promoting the first row to a header row, assign the value `noheader` to the `options` attribute using the formal (`options=noheader`) or shorthand (`%noheader`) syntax.
In <<ex-noheader>>, `noheader` is assigned using the shorthand syntax.
"#
);
let table = parse_table(
"[%noheader]\n|===\n|Cell in column 1, row 1 |Cell in column 2, row 1\n\n|Cell in column 1, row 2 |Cell in column 2, row 2\n|===",
);
assert!(table.header_row().is_none());
assert_eq!(table.body_rows().len(), 2);
let table =
parse_table("[options=\"noheader\"]\n|===\n|Cell 1 |Cell 2\n\n|Cell 3 |Cell 4\n|===");
assert!(table.header_row().is_none());
let table = parse_table("[%header%noheader]\n|===\n|Cell 1 |Cell 2\n\n|Cell 3 |Cell 4\n|===");
assert!(table.header_row().is_some());
verifies!(
r#"
.Deactivate implicit header row with noheader
[source#ex-noheader]
----
[%noheader]
|===
|Cell in column 1, row 1 |Cell in column 2, row 1
|Cell in column 1, row 2 |Cell in column 2, row 2
|===
----
"#
);
let ex_noheader = parse_table(
"[%noheader]\n|===\n|Cell in column 1, row 1 |Cell in column 2, row 1\n\n|Cell in column 1, row 2 |Cell in column 2, row 2\n|===",
);
assert!(ex_noheader.header_row().is_none());
assert_eq!(ex_noheader.body_rows().len(), 2);
verifies!(
r#"
The table from <<ex-noheader>> is displayed below.
.Result of <<ex-noheader>>
[%noheader]
|===
|Cell in column 1, row 1 |Cell in column 2, row 1
|Cell in column 1, row 2 |Cell in column 2, row 2
|===
"#
);
let ex_noheader_result = parse_table(
"[%noheader]\n|===\n|Cell in column 1, row 1 |Cell in column 2, row 1\n\n|Cell in column 1, row 2 |Cell in column 2, row 2\n|===",
);
assert!(ex_noheader_result.header_row().is_none());
assert_eq!(ex_noheader_result.body_rows().len(), 2);
non_normative!(
r#"
//CAUTION: We're considering using a similar convention for enabling the footer in the future.
//Thus, if you rely on this convention to enable the header row, it's advised that you not put all the cells in the last row on the same line unless you intend on making it the footer row.
"#
);
}