mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Expand native Mermaid flowchart syntax support (#48895)
## What changed - Default `flowchart` and `graph` to top-down layout when no direction is specified. - Support solid and dashed edges with directed, undirected, or bidirectional endpoints: `-->`, `---`, `<-->`, `-.->`, `-.-`, and `<-.->`. - Accept pipe labels on these edges and spaced directed infix labels such as `A -- send --> B` and `A -. retry .-> B`. - Expand `&` groups into all source-to-target edges, including in chains. For example, `A & B --> C & D` creates four edges. Enforce the 24-edge limit after expansion and cap each group at 24 node references. ## Testing Add coverage for equivalent syntax, edge styles and endpoints, malformed input, and expansion limits. Extend rendering snapshots across all four directions and the TUI native-renderer test with the new syntax. GitOrigin-RevId: 6e51653ebf6b8b7e7e1edec0c2e09a5d9c8dec74
This commit is contained in:
@@ -8,7 +8,7 @@ production dependency is needed.
|
||||
|
||||
| Family | Supported syntax |
|
||||
| --- | --- |
|
||||
| `flowchart`, `graph` | TD/TB, BT, LR, RL; rectangle `[label]`, decision `{label}`, and stadium `([label])` nodes; directed and labeled `-->` edges; chains, branches, merges, loops |
|
||||
| `flowchart`, `graph` | TD/TB (default), BT, LR, RL; rectangle `[label]`, decision `{label}`, and stadium `([label])` nodes; solid `-->`/`---`/`<-->` and dashed `-.->`/`-.-`/`<-.->` edges; pipe labels and spaced directed infix labels (`-- text -->`, `-. text .->`); `&` fan-out/fan-in, chains, branches, merges, loops |
|
||||
| `sequenceDiagram` | Implicit participants, `participant`/`actor`, aliases, `->`, `->>`, `-->`, `-->>`, `-x`, `--x`, self-messages, `Note over A[,B]`, nested `loop`/`alt`/`opt`/`critical`/`break`, one labeled `else` per `alt` |
|
||||
| `stateDiagram-v2`, `stateDiagram` | Flat states, `state "label" as ID`, descriptions, directed transitions with optional labels, initial/final `[*]`, direction declarations |
|
||||
| `classDiagram` | `class ID`, multiline member bodies, `ID : member`, solid/dashed links, association, inheritance, composition, aggregation, dependency, realization, quoted endpoint cardinalities, relationship labels, direction declarations |
|
||||
@@ -59,8 +59,8 @@ named participants. States use separate `● initial` and `◎ final` nodes.
|
||||
|
||||
## Limits and evaluation
|
||||
|
||||
All inputs are limited to 16 KiB. Graphs allow 16 nodes, 24 edges, and 16 members per
|
||||
node. Sequences allow 8 participants, 64 events (including fragment boundaries),
|
||||
All inputs are limited to 16 KiB. Graphs allow 16 nodes, 24 edges (after expanding
|
||||
`&` groups), and 16 members per node. Each `&` group allows at most 24 node references. Sequences allow 8 participants, 64 events (including fragment boundaries),
|
||||
and 4 fragment levels. Source labels and identifiers are limited to 40 display
|
||||
cells/ASCII bytes respectively. Rendered canvases are capped at 65,536 cells,
|
||||
independent of the caller's maximum width. The library performs no I/O.
|
||||
|
||||
@@ -156,7 +156,7 @@ fn unicode_labels_and_later_declarations() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_relationship_endpoints() {
|
||||
fn graph_relationship_endpoints() {
|
||||
for (operator, source_tip, target_tip, dashed) in [
|
||||
("<|--", '◁', '─', false),
|
||||
("*--", '◆', '─', false),
|
||||
@@ -192,6 +192,33 @@ fn class_relationship_endpoints() {
|
||||
assert!(ports[1].contains("(many)"));
|
||||
assert_eq!(output.contains('┆'), dashed);
|
||||
}
|
||||
for (operator, source_tip, target_tip, dashed) in [
|
||||
("---", '─', '─', false),
|
||||
("-.->", '─', '◄', true),
|
||||
("-.-", '─', '─', true),
|
||||
("<-->", '◄', '◄', false),
|
||||
("<-.->", '◄', '◄', true),
|
||||
] {
|
||||
let output = render(
|
||||
&format!("flowchart; A{operator}|uses|B"),
|
||||
/*max_width*/ 100,
|
||||
)
|
||||
.unwrap();
|
||||
let ports = output
|
||||
.lines()
|
||||
.filter(|line| line.contains('├'))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(
|
||||
ports[0].contains(&format!("├{source_tip}")),
|
||||
"{operator}: {output}"
|
||||
);
|
||||
assert!(
|
||||
ports[1].contains(&format!("├{target_tip}")),
|
||||
"{operator}: {output}"
|
||||
);
|
||||
assert!(ports[0].contains("uses"));
|
||||
assert_eq!(output.contains('┆'), dashed);
|
||||
}
|
||||
for source in [
|
||||
"classDiagram\nclass A; A --> B : {ok}",
|
||||
"classDiagram\nclass A; class B {\n+id\n}",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Strict parser for a small flowchart grammar; every non-comment byte must be consumed.
|
||||
//! Node groups expand into explicit edges within the graph edge cap.
|
||||
|
||||
use super::Direction;
|
||||
use super::Edge;
|
||||
@@ -13,35 +14,83 @@ use unicode_width::UnicodeWidthStr;
|
||||
|
||||
pub(super) fn parse(header: &str, body: &[&str]) -> Result<Graph, RenderError> {
|
||||
let tokens = header.split_whitespace().collect::<Vec<_>>();
|
||||
let ["flowchart" | "graph", direction] = tokens.as_slice() else {
|
||||
return Err(RenderError::Unsupported);
|
||||
let direction = match tokens.as_slice() {
|
||||
["flowchart" | "graph"] => Direction::Down,
|
||||
["flowchart" | "graph", direction] => Direction::parse(direction)?,
|
||||
_ => return Err(RenderError::Unsupported),
|
||||
};
|
||||
let mut graph = Graph {
|
||||
direction: Direction::parse(direction)?,
|
||||
direction,
|
||||
..Graph::default()
|
||||
};
|
||||
for statement in body {
|
||||
let mut rest = *statement;
|
||||
let mut from = node(&mut rest, &mut graph)?;
|
||||
let mut from = nodes(&mut rest, &mut graph)?;
|
||||
while !rest.trim_start().is_empty() {
|
||||
rest = rest
|
||||
.trim_start()
|
||||
.strip_prefix("-->")
|
||||
.ok_or(RenderError::Unsupported)?;
|
||||
rest = rest.trim_start();
|
||||
let label = if let Some(after) = rest.strip_prefix('|') {
|
||||
let (label, remaining) = delimited_label(after, "|")?;
|
||||
let label = flowchart_label(label)?;
|
||||
rest = remaining;
|
||||
label.to_owned()
|
||||
let mut label = String::new();
|
||||
let (source_tip, target_tip, dashed) = if let Some((after, source, target, dashed)) = [
|
||||
("<-.->", '◄', '◄', true),
|
||||
("<-->", '◄', '◄', false),
|
||||
("-.->", '─', '◄', true),
|
||||
("-.-", '─', '─', true),
|
||||
("-->", '─', '◄', false),
|
||||
("---", '─', '─', false),
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(|(token, source, target, dashed)| {
|
||||
rest.strip_prefix(token)
|
||||
.map(|after| (after, source, target, dashed))
|
||||
}) {
|
||||
// Circle/cross tips are unsupported; without a space they are not node IDs.
|
||||
if target == '─' && after.starts_with(['o', 'x']) {
|
||||
return Err(RenderError::Unsupported);
|
||||
}
|
||||
rest = after.trim_start();
|
||||
if let Some(after) = rest.strip_prefix('|') {
|
||||
let (text, remaining) = delimited_label(after, "|")?;
|
||||
label = flowchart_label(text)?.to_owned();
|
||||
rest = remaining;
|
||||
}
|
||||
(source, target, dashed)
|
||||
} else {
|
||||
String::new()
|
||||
let (after, stem, dashed) = if let Some(after) = rest.strip_prefix("--") {
|
||||
(after, "--", false)
|
||||
} else if let Some(after) = rest.strip_prefix("-.") {
|
||||
(after, ".-", true)
|
||||
} else {
|
||||
return Err(RenderError::Unsupported);
|
||||
};
|
||||
// Spaced labels keep endpoint markers out of the text. Stop at the first
|
||||
// closing stem instead of swallowing an unsupported edge and its target.
|
||||
let (text, remaining) = after.split_once(stem).ok_or(RenderError::Unsupported)?;
|
||||
if !text.starts_with(char::is_whitespace) || !text.ends_with(char::is_whitespace) {
|
||||
return Err(RenderError::Unsupported);
|
||||
}
|
||||
rest = remaining
|
||||
.strip_prefix('>')
|
||||
.ok_or(RenderError::Unsupported)?;
|
||||
label = flowchart_label(text.trim())?.to_owned();
|
||||
('─', '◄', dashed)
|
||||
};
|
||||
let to = node(&mut rest, &mut graph)?;
|
||||
if graph.edges.len() == MAX_EDGES {
|
||||
let to = nodes(&mut rest, &mut graph)?;
|
||||
// Check the Cartesian expansion before allocating edges, including repeated IDs.
|
||||
if from.len() * to.len() > MAX_EDGES - graph.edges.len() {
|
||||
return Err(RenderError::Limit);
|
||||
}
|
||||
graph.edges.push(Edge::directed(from, to, label));
|
||||
for &from in &from {
|
||||
for &to in &to {
|
||||
graph.edges.push(Edge {
|
||||
from,
|
||||
to,
|
||||
label: label.clone(),
|
||||
target_label: String::new(),
|
||||
source_tip,
|
||||
target_tip,
|
||||
dashed,
|
||||
});
|
||||
}
|
||||
}
|
||||
from = to;
|
||||
}
|
||||
}
|
||||
@@ -51,6 +100,18 @@ pub(super) fn parse(header: &str, body: &[&str]) -> Result<Graph, RenderError> {
|
||||
Ok(graph)
|
||||
}
|
||||
|
||||
fn nodes(rest: &mut &str, graph: &mut Graph) -> Result<Vec<usize>, RenderError> {
|
||||
let mut nodes = vec![node(rest, graph)?];
|
||||
while let Some(after) = rest.trim_start().strip_prefix('&') {
|
||||
if nodes.len() == MAX_EDGES {
|
||||
return Err(RenderError::Limit);
|
||||
}
|
||||
*rest = after;
|
||||
nodes.push(node(rest, graph)?);
|
||||
}
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
fn node(rest: &mut &str, graph: &mut Graph) -> Result<usize, RenderError> {
|
||||
let id = identifier(rest)?;
|
||||
// Reserved constructs must not be interpreted as ordinary node declarations.
|
||||
|
||||
+89
-56
@@ -1,73 +1,106 @@
|
||||
---
|
||||
source: mermaid/src/tests.rs
|
||||
assertion_line: 35
|
||||
expression: "cases.join(\"\\n\\n\")"
|
||||
---
|
||||
TD
|
||||
╭─────────╮
|
||||
│ 请求 │
|
||||
│ ├─────┐
|
||||
│ ├◄────╪───┐
|
||||
╰─────────╯ │ │
|
||||
│ │
|
||||
│ │
|
||||
┌─────────┐ │ │
|
||||
│ Work │ │ │
|
||||
│ ├◄────┘ │
|
||||
│ ├───────┐ │
|
||||
└─────────┘ │ │
|
||||
│ │
|
||||
│ │
|
||||
┌─────────┐ │ │
|
||||
│ ◇ Done? │ │ │
|
||||
│ ├◄──────┘ │
|
||||
│ ├─────────┘
|
||||
│ ├──go───┐
|
||||
│ ├──go───╪─┐
|
||||
│ ├◄──────╪─╪───┐
|
||||
│ ├───────╪─╪───╪─┐
|
||||
│ ├◄┄┄┄┄┄┄╪┄╪┄┄┄╪┄╪┄┄┄┐
|
||||
╰─────────╯ │ │ │ │ ┆
|
||||
│ │ │ │ ┆
|
||||
│ │ │ │ ┆
|
||||
┌─────────┐ │ │ │ │ ┆
|
||||
│ Work │ │ │ │ │ ┆
|
||||
│ ├◄──────┘ │ │ │ ┆
|
||||
│ ├─┄no┄┄┄┄┄╪┄┐ │ │ ┆
|
||||
│ ├─────────╪─╪─╪─┘ ┆
|
||||
│ ├─┄┄┄┄┄┄┄┄╪┄╪┄╪┄┄┄┐ ┆
|
||||
│ ├◄┄┄┄┄┄┄┄┄╪┄╪┄╪┄┄┄╪┄┘
|
||||
└─────────┘ │ ┆ │ ┆
|
||||
│ ┆ │ ┆
|
||||
│ ┆ │ ┆
|
||||
┌─────────┐ │ ┆ │ ┆
|
||||
│ ◇ Done? │ │ ┆ │ ┆
|
||||
│ ├◄────────┘ ┆ │ ┆
|
||||
│ ├◄┄┄┄┄┄┄┄┄┄┄┘ │ ┆
|
||||
│ ├◄────────────┘ ┆
|
||||
│ ├─┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘
|
||||
└─────────┘
|
||||
|
||||
BT
|
||||
┌─────────┐
|
||||
│ ◇ Done? │
|
||||
│ ├◄──────┐
|
||||
│ ├───────╪─┐
|
||||
└─────────┘ │ │
|
||||
│ │
|
||||
│ │
|
||||
┌─────────┐ │ │
|
||||
│ Work │ │ │
|
||||
│ ├◄────┐ │ │
|
||||
│ ├─────╪─┘ │
|
||||
└─────────┘ │ │
|
||||
│ │
|
||||
│ │
|
||||
╭─────────╮ │ │
|
||||
│ 请求 │ │ │
|
||||
│ ├─────┘ │
|
||||
│ ├◄────────┘
|
||||
│ ├◄────────┐
|
||||
│ ├◄┄┄┄┄┄┄┄┄╪┄┐
|
||||
│ ├◄────────╪─╪─┐
|
||||
│ ├─┄┄┄┄┄┄┄┄╪┄╪┄╪┄┄┄┐
|
||||
└─────────┘ │ ┆ │ ┆
|
||||
│ ┆ │ ┆
|
||||
│ ┆ │ ┆
|
||||
┌─────────┐ │ ┆ │ ┆
|
||||
│ Work │ │ ┆ │ ┆
|
||||
│ ├◄──────┐ │ ┆ │ ┆
|
||||
│ ├─┄no┄┄┄╪┄╪┄┘ │ ┆
|
||||
│ ├───────╪─╪───╪─┐ ┆
|
||||
│ ├─┄┄┄┄┄┄╪┄╪┄┄┄╪┄╪┄┘
|
||||
│ ├◄┄┄┄┄┄┄╪┄╪┄┄┄╪┄╪┄┄┄┐
|
||||
└─────────┘ │ │ │ │ ┆
|
||||
│ │ │ │ ┆
|
||||
│ │ │ │ ┆
|
||||
╭─────────╮ │ │ │ │ ┆
|
||||
│ 请求 │ │ │ │ │ ┆
|
||||
│ ├──go───┘ │ │ │ ┆
|
||||
│ ├──go─────┘ │ │ ┆
|
||||
│ ├◄────────────┘ │ ┆
|
||||
│ ├───────────────┘ ┆
|
||||
│ ├◄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘
|
||||
╰─────────╯
|
||||
|
||||
LR
|
||||
╭──────╮ ┌──────┐ ┌─────────┐
|
||||
│ 请求 │ │ Work │ │ ◇ Done? │
|
||||
╰┬─┬───╯ └┬─┬───┘ └┬─┬──────┘
|
||||
│ ▲ ▲ │ ▲ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
└─╪───────┘ │ │ │
|
||||
│ │ │ │
|
||||
│ └───────┘ │
|
||||
│ │
|
||||
└───────────────────┘
|
||||
╭────────────────────╮ ┌────────────────────┐ ┌────────────────┐
|
||||
│ 请求 │ │ Work │ │ ◇ Done? │
|
||||
╰┬───┬───┬───┬───┬───╯ └┬───┬───┬───┬───┬───┘ └┬───┬───┬───┬───┘
|
||||
│ │ ▲ │ ▲ ▲ │ │ │ ▲ ▲ ▲ ▲ │
|
||||
│go │go │ │ ┆ │ ┆no │ ┆ ┆ │ ┆ │ ┆
|
||||
│ │ │ │ ┆ │ ┆ │ ┆ ┆ │ ┆ │ ┆
|
||||
│ │ │ │ ┆ │ ┆ │ ┆ ┆ │ ┆ │ ┆
|
||||
└───╪───╪───╪───╪───────┘ ┆ │ ┆ ┆ │ ┆ │ ┆
|
||||
│ │ │ ┆ ┆ │ ┆ ┆ │ ┆ │ ┆
|
||||
└───╪───╪───╪───────────╪───╪───╪───╪───────┘ ┆ │ ┆
|
||||
│ │ ┆ ┆ │ ┆ ┆ ┆ │ ┆
|
||||
│ │ ┆ └┄┄┄╪┄┄┄╪┄┄┄╪┄┄┄┄┄┄┄┄┄┄┄┘ │ ┆
|
||||
│ │ ┆ │ ┆ ┆ │ ┆
|
||||
└───╪───╪───────────────╪───╪───╪───────────────┘ ┆
|
||||
│ ┆ │ ┆ ┆ ┆
|
||||
└───╪───────────────┘ ┆ ┆ ┆
|
||||
┆ ┆ ┆ ┆
|
||||
┆ └┄┄┄╪┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘
|
||||
┆ ┆
|
||||
└┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘
|
||||
|
||||
RL
|
||||
┌─────────┐ ┌──────┐ ╭──────╮
|
||||
│ ◇ Done? │ │ Work │ │ 请求 │
|
||||
└┬─┬──────┘ └┬─┬───┘ ╰┬─┬───╯
|
||||
▲ │ ▲ │ │ ▲
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ └─╪───────┘ │
|
||||
│ │ │ │
|
||||
└─╪────────────┘ │
|
||||
│ │
|
||||
└──────────────────────┘
|
||||
┌────────────────┐ ┌────────────────────┐ ╭────────────────────╮
|
||||
│ ◇ Done? │ │ Work │ │ 请求 │
|
||||
└┬───┬───┬───┬───┘ └┬───┬───┬───┬───┬───┘ ╰┬───┬───┬───┬───┬───╯
|
||||
▲ ▲ ▲ │ ▲ │ │ │ ▲ │ │ ▲ │ ▲
|
||||
│ ┆ │ ┆ │ ┆no │ ┆ ┆ │go │go │ │ ┆
|
||||
│ ┆ │ ┆ │ ┆ │ ┆ ┆ │ │ │ │ ┆
|
||||
│ ┆ │ ┆ │ ┆ │ ┆ ┆ │ │ │ │ ┆
|
||||
│ ┆ │ ┆ └───╪───╪───╪───╪───────┘ │ │ │ ┆
|
||||
│ ┆ │ ┆ ┆ │ ┆ ┆ │ │ │ ┆
|
||||
└───╪───╪───╪───────────╪───╪───╪───╪───────────┘ │ │ ┆
|
||||
┆ │ ┆ ┆ │ ┆ ┆ │ │ ┆
|
||||
└┄┄┄╪┄┄┄╪┄┄┄┄┄┄┄┄┄┄┄┘ │ ┆ ┆ │ │ ┆
|
||||
│ ┆ │ ┆ ┆ │ │ ┆
|
||||
└───╪───────────────╪───╪───╪───────────────┘ │ ┆
|
||||
┆ │ ┆ ┆ │ ┆
|
||||
┆ └───╪───╪───────────────────┘ ┆
|
||||
┆ ┆ ┆ ┆
|
||||
└┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘ ┆ ┆
|
||||
┆ ┆
|
||||
└┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘
|
||||
|
||||
@@ -24,8 +24,9 @@ fn stadium_declarations_and_references() {
|
||||
fn stadiums_with_other_shapes_in_every_direction() {
|
||||
let mut cases = Vec::new();
|
||||
for direction in ["TD", "BT", "LR", "RL"] {
|
||||
let source =
|
||||
format!("flowchart {direction}; A([请求]) --> B[Work] --> C{{Done?}}; C --> A");
|
||||
let source = format!(
|
||||
"flowchart {direction}; A([请求]) -- go --> B[Work] & C{{Done?}}; B -. no .-> C; C <--> A; B --- A; C -.- B; A <-.-> B"
|
||||
);
|
||||
let output = render(&source, /*max_width*/ 100).unwrap();
|
||||
let width = output.lines().map(UnicodeWidthStr::width).max().unwrap();
|
||||
assert_eq!(render(&source, width), Ok(output.clone()));
|
||||
@@ -36,7 +37,7 @@ fn stadiums_with_other_shapes_in_every_direction() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quoted_flowchart_labels_match_unquoted_labels() {
|
||||
fn equivalent_flowchart_forms_preserve_graph() {
|
||||
let quoted = r#"A["Review & confirm;"] -->|"Yes & continue"| B{"Ready?"}
|
||||
B --> C(["Checkout"])
|
||||
A[Review & confirm;]"#;
|
||||
@@ -45,6 +46,48 @@ A[Review & confirm;]"#;
|
||||
super::parse::parse("flowchart TD", "ed.lines().collect::<Vec<_>>()).unwrap(),
|
||||
super::parse::parse("flowchart TD", &unquoted.lines().collect::<Vec<_>>()).unwrap(),
|
||||
);
|
||||
for (infix, pipe) in [
|
||||
("-- Yes -->", "-->|Yes|"),
|
||||
("-- \"Yes & continue\" -->", "-->|\"Yes & continue\"|"),
|
||||
("-. retry .->", "-.->|retry|"),
|
||||
] {
|
||||
assert_eq!(
|
||||
super::parse::parse("flowchart", &[&format!("A {infix} B --> C")]).unwrap(),
|
||||
super::parse::parse("graph TB", &[&format!("A {pipe} B --> C")]).unwrap(),
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
super::parse::parse("graph", &["A[Input & config] & B -- send --> C & D -.-> E"]).unwrap(),
|
||||
super::parse::parse(
|
||||
"flowchart TD",
|
||||
&[
|
||||
"A[Input & config]",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"A -->|send| C",
|
||||
"A -->|send| D",
|
||||
"B -->|send| C",
|
||||
"B -->|send| D",
|
||||
"C -.-> E",
|
||||
"D -.-> E",
|
||||
]
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
super::parse::parse("flowchart", &["A--- oB", "A-.- xB"])
|
||||
.unwrap()
|
||||
.nodes,
|
||||
super::parse::parse("flowchart", &["A", "oB", "xB"])
|
||||
.unwrap()
|
||||
.nodes,
|
||||
);
|
||||
assert_eq!(
|
||||
super::parse::parse("flowchart", &["A --> B & B"]).unwrap(),
|
||||
super::parse::parse("flowchart TD", &["A --> B", "A --> B"]).unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
render(&format!("graph TD; {quoted}"), /*max_width*/ 180).unwrap(),
|
||||
render(&format!("graph TD; {unquoted}"), /*max_width*/ 180).unwrap(),
|
||||
@@ -144,6 +187,7 @@ fn quoted_flowchart_labels_reject_malformed_and_unsafe_text() {
|
||||
for source in [
|
||||
format!("flowchart TD; A[{label}]"),
|
||||
format!("flowchart TD; A -->|{label}| B"),
|
||||
format!("flowchart TD; A -- {label} --> B"),
|
||||
] {
|
||||
assert_eq!(
|
||||
render(&source, /*max_width*/ 100),
|
||||
@@ -176,8 +220,20 @@ fn rejects_partial_or_unsupported_input() {
|
||||
r#"flowchart TD; P --> Q; A -->|"`Caption`"| B"#,
|
||||
"flowchart TD; subgraph X; A; end",
|
||||
"flowchart TD; A --> B; garbage syntax",
|
||||
"flowchart TD; A -.-> B",
|
||||
"flowchart TD; A & B --> C",
|
||||
"flowchart TD; A --> B &",
|
||||
"flowchart TD; A && B",
|
||||
"flowchart TD; A -- Yes B",
|
||||
"flowchart TD; A -. retry --> B",
|
||||
"flowchart TD; A ==> B",
|
||||
"flowchart TD; A -- hello --- B --> C",
|
||||
"flowchart TD; A -. hello .- B -.-> C",
|
||||
"flowchart TD; A -- hello ----> B",
|
||||
"flowchart TD; A -. hello ..-> B",
|
||||
"flowchart TD; A -- hello o--> B",
|
||||
"flowchart TD; A --oB --> C",
|
||||
"flowchart TD; A -. hello -.-> B",
|
||||
"flowchart TD; A---oB",
|
||||
"flowchart TD; A-.-xB",
|
||||
"flowchart TD; A[one]; A[two]",
|
||||
"flowchart TD; A[<b>HTML</b>]",
|
||||
"flowchart TD; A[]",
|
||||
@@ -215,9 +271,14 @@ fn rejects_partial_or_unsupported_input() {
|
||||
|
||||
#[test]
|
||||
fn source_graph_and_width_limits() {
|
||||
let grouped = "A & B & C & D --> E & F & G & H & I & J";
|
||||
assert!(render(&format!("graph; {grouped}"), /*max_width*/ 200).is_ok());
|
||||
for source in [
|
||||
" ".repeat(16 * 1024 + 1),
|
||||
format!("graph TD; A[{}]", "x".repeat(41)),
|
||||
format!("graph; A -- {} --> B", "x".repeat(41)),
|
||||
format!("graph; A --> E; {grouped}"),
|
||||
format!("graph; {} --> B", ["A"; 25].join(" & ")),
|
||||
format!("graph TD; A[\"{}\"]", "[]".repeat(21)),
|
||||
format!("graph TD; A([{}])", "x".repeat(41)),
|
||||
format!(
|
||||
|
||||
@@ -21,6 +21,7 @@ fn mermaid_fences_use_native_renderer_for_every_family() {
|
||||
for source in [
|
||||
"%% heading\nflowchart TD; A --> B",
|
||||
"graph LR; A --> B",
|
||||
"flowchart; A -- send --> B & C; B <-.-> C",
|
||||
"sequenceDiagram; A->>B: request; B-->>A: response",
|
||||
"stateDiagram-v2; [*] --> Active; Active --> [*]",
|
||||
"stateDiagram; [*] --> Active; Active --> [*]",
|
||||
|
||||
Reference in New Issue
Block a user