This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch camel-jbang-diagram-zoom in repository https://gitbox.apache.org/repos/asf/camel.git
commit cce85a296b70ce3b1b107b18c9ec56667171302e Author: Claus Ibsen <[email protected]> AuthorDate: Sun Oct 25 14:11:32 2026 +0100 chore: camel-jbang - route diagram: zoom level position, fold all and expand all, step count The zoom of the view bar says where the level is (compact 2/3). f folds all the blocks of the route, the blocks in other blocks too, and F expands them all again. The title of the route diagram says how many steps the route has and how many are folded (38 steps · 12 folded), so a large route is told at a glance. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../ROOT/pages/camel-jbang-tui-diagram.adoc | 11 ++- .../core/commands/tui/AbstractDiagramTab.java | 8 +- .../dsl/jbang/core/commands/tui/DiagramTab.java | 8 +- .../jbang/core/commands/tui/RouteDiagramZoom.java | 104 +++++++++++++++++---- .../dsl/jbang/core/commands/tui/RoutesTab.java | 5 +- .../core/commands/tui/diagram/RouteDensity.java | 5 + .../src/main/resources/tui/help/diagram.md | 10 +- .../src/main/resources/tui/help/routes.md | 10 +- .../core/commands/tui/RouteDiagramZoomTest.java | 41 +++++++- 9 files changed, 164 insertions(+), 38 deletions(-) diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-diagram.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-diagram.adoc index 185de8ae945f..f38793542a4e 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-diagram.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-diagram.adoc @@ -64,14 +64,16 @@ A large route takes a lot of screen. Press *-* to zoom out and *+* to zoom in, t * *bare*: no boxes, each step is a mark and its text on one row (`▶` the from, `◆` a step with steps in it, `◇` a branch, `●` a step), with the counts beside the lines between them -Press *z* for fit: the most detailed level in which the whole route fits the screen. The view bar shows the level -(`zoom: compact`, or `zoom: fit bare`). Zooming keeps the selected step selected, on the same row of the screen, and +Press *z* for fit: the most detailed level in which the whole route fits the screen. The view bar shows the level and +where it is among the three (`zoom: compact 2/3`, or `zoom: fit bare 3/3`), and the title of the diagram how many steps +the route has, so you can tell a large route at a glance, and how many of them are folded (`38 steps · 12 folded`). Zooming keeps the selected step selected, on the same row of the screen, and the Info panel has the whole text of the selected step at every level. Press *Space* to fold a block (a choice, one of its branches, a split, a doTry, a filter...) into its first step, which then says how many steps are folded (`▸ 12 steps folded`); *Space* on a step inside a block folds that block, and *Space* on a folded step unfolds it. A folded block gives its rows and its columns back to the rest of the route. -Press *f* to fold the outermost blocks of the route, and *f* again to unfold them all. +Press *f* to fold all the blocks of the route, the blocks in other blocks too: *Space* on a folded block then shows +its own steps, with the blocks in it still folded. Press *F* to expand all the blocks again. === Diagram levels @@ -268,7 +270,8 @@ camel-xml-io, while a Spring Boot or Quarkus application may not. | *+* / *-* | In a route: zoom in or out (detailed, compact, bare) | *z* | In a route: fit, the most detail in which the whole route fits the screen | *Space* | In a route: fold the block of the selected step, or unfold a folded step -| *f* | In a route: fold the outermost blocks, or unfold all +| *f* | In a route: fold all the blocks +| *F* | In a route: expand all the blocks | *e* | External systems: in the topology off, edges, all; in the architecture off or edges | *a* | AI-assisted parts shown, or the facts alone; without a `camel-summary.md` yet, has the AI write one | *b* (or *n*) | Business view (labels, what capabilities achieve) or technical view (route ids, endpoints) diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java index f28394a96b9a..b409939287a8 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java @@ -193,8 +193,8 @@ abstract class AbstractDiagramTab extends AbstractTab { /** * The zoom of the route diagram: + and - give the steps more or less room, z the most detail that fits the screen, - * space folds the block of the selected step (or unfolds it), f folds the outermost blocks or unfolds all. The - * selected step stays selected, on the same row of the screen. + * space folds the block of the selected step (or unfolds it), f folds all the blocks and F expands them all. The + * selected step stays selected (or the folded block it is in), on the same row of the screen. */ private boolean handleZoomKeys(KeyEvent ke) { RouteDiagramZoom zoom = diagram.zoom; @@ -217,7 +217,9 @@ abstract class AbstractDiagramTab extends AbstractTab { } anchorId = folded; } else if (ke.isChar('f')) { - zoom.toggleFoldAll(drillDownRouteId, diagram.getRouteLayout(drillDownRouteId)); + anchorId = zoom.foldAll(drillDownRouteId, selected != null ? selected.layoutNode() : null); + } else if (ke.isChar('F')) { + zoom.expandAll(drillDownRouteId); } else { return false; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java index c4926553ef51..697a1dea978e 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java @@ -582,10 +582,11 @@ class DiagramTab extends AbstractDiagramTab { return; } else if (!topologyMode && drillDownRouteId != null && diagram.getRouteLayout(drillDownRouteId) != null) { - Line title = withLevels(DiagramDetailSupport.withRouteContext( - DiagramDetailSupport.buildBreadcrumbTitle(routeNavigationStack, drillDownRouteId), info, - drillDownRouteId, selectedSourceDirectory(), isShowGroups(), diagram.isShowErrorPaths())); var routeLayout = diagram.getRouteLayout(drillDownRouteId); + Line title = withLevels(diagram.zoom.withStepCount(DiagramDetailSupport.withRouteContext( + DiagramDetailSupport.buildBreadcrumbTitle(routeNavigationStack, drillDownRouteId), info, + drillDownRouteId, selectedSourceDirectory(), isShowGroups(), diagram.isShowErrorPaths()), + drillDownRouteId, routeLayout)); if (area.width() > 60) { infoPanelWidth = Math.max(10, Math.min(infoPanelWidth, area.width() - 20)); List<Rect> hChunks = Layout.horizontal() @@ -664,6 +665,7 @@ class DiagramTab extends AbstractDiagramTab { hint(spans, "g", "go to"); hint(spans, "+/-", "zoom"); hint(spans, "Space", "fold"); + hint(spans, "f/F", "fold/expand all"); hint(spans, "s", "summary"); } else if (!topologyMode) { hint(spans, "Esc", "back"); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteDiagramZoom.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteDiagramZoom.java index 781c57633f12..f4e6428f56bf 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteDiagramZoom.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteDiagramZoom.java @@ -16,6 +16,7 @@ */ package org.apache.camel.dsl.jbang.core.commands.tui; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -26,6 +27,8 @@ import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; +import dev.tamboui.text.Line; +import dev.tamboui.text.Span; import org.apache.camel.diagram.RouteDiagramLayoutEngine; import org.apache.camel.diagram.RouteDiagramLayoutEngine.LayoutNode; import org.apache.camel.diagram.RouteDiagramLayoutEngine.LayoutRoute; @@ -57,6 +60,8 @@ final class RouteDiagramZoom { private volatile boolean fit; /** The folded blocks of each route: the ids of their first steps. */ private final Map<String, Set<String>> folded = new ConcurrentHashMap<>(); + /** The steps of each route as last laid out, without its folds: fold all folds the blocks hidden in others too. */ + private final Map<String, RouteInfo> unfolded = new ConcurrentHashMap<>(); /** What each layout was made with: its density, and the steps folded into each folded step. */ private final Map<LayoutRoute, RouteDensity> densities = Collections.synchronizedMap(new WeakHashMap<>()); private final Map<LayoutRoute, Map<String, Integer>> foldedSteps @@ -126,10 +131,14 @@ final class RouteDiagramZoom { fit = !fit; } - /** What the zoom toggle of the view bar says: the density, and fit when it is chosen for the screen. */ + /** + * What the zoom toggle of the view bar says: the density and where it is among the levels (compact 2/3), and fit + * when it is chosen for the screen. + */ String label(LayoutRoute shown) { RouteDensity d = shown != null ? densityOf(shown) : density; - return fit ? "fit " + d.label() : d.label(); + String label = d.label() + " " + d.position(); + return fit ? "fit " + label : label; } RouteDensity densityOf(LayoutRoute lr) { @@ -167,32 +176,92 @@ final class RouteDiagramZoom { return block.info.id; } - /** Folds the outermost blocks of the route, or unfolds all of them when some are folded already. */ - void toggleFoldAll(String routeId, LayoutRoute lr) { + /** + * Folds every block of the route, the blocks in other blocks too: unfolding a block shows its own steps, with the + * blocks in it folded. + * + * @return the id of the outermost block the step is in, which is the step to select; the step itself when it is in + * no block + */ + String foldAll(String routeId, LayoutNode selected) { + RouteInfo route = unfolded.get(routeId); + if (route == null) { + return selected != null ? selected.id : null; + } Set<String> ids = folded.computeIfAbsent(routeId, k -> ConcurrentHashMap.newKeySet()); - if (!ids.isEmpty()) { - ids.clear(); - return; + List<NodeInfo> nodes = route.nodes; + for (int i = 0; i + 1 < nodes.size(); i++) { + NodeInfo n = nodes.get(i); + if (n.id != null && nodes.get(i + 1).level > n.level && foldable(n.type)) { + ids.add(n.id); + } + } + String answer = selected != null ? selected.id : null; + TreeNode block = selected != null && selected.treeNode != null ? selected.treeNode.parent : null; + for (; block != null; block = block.parent) { + if (block.info.id != null && ids.contains(block.info.id)) { + answer = block.info.id; + } } - if (lr != null && !lr.nodes.isEmpty() && lr.nodes.get(0).treeNode != null) { - foldOutermost(lr.nodes.get(0).treeNode, ids); + return answer; + } + + /** Unfolds every block of the route. */ + void expandAll(String routeId) { + Set<String> ids = folded.get(routeId); + if (ids != null) { + ids.clear(); } } - private static void foldOutermost(TreeNode node, Set<String> ids) { - for (TreeNode child : node.children) { - if (foldable(child) && child.info.id != null) { - ids.add(child.info.id); - } else { - foldOutermost(child, ids); + /** The steps of the route (not its from), as last laid out: how big the route is. */ + int stepCount(String routeId) { + RouteInfo route = routeId != null ? unfolded.get(routeId) : null; + if (route == null) { + return 0; + } + int count = 0; + for (NodeInfo n : route.nodes) { + if (!"route".equals(n.type) && !"from".equals(n.type)) { + count++; } } + return count; + } + + /** + * The title of a route's diagram with how many steps it has, and how many of them are folded: 38 steps · 12 folded. + */ + Line withStepCount(Line title, String routeId, LayoutRoute shown) { + int steps = stepCount(routeId); + if (steps == 0) { + return title; + } + int hidden = 0; + for (int n : foldedStepsOf(shown).values()) { + hidden += n; + } + String text = steps + (steps == 1 ? " step" : " steps") + (hidden > 0 ? " · " + hidden + " folded" : "") + " "; + List<Span> spans = new ArrayList<>(title.spans()); + spans.add(Span.styled(text, Theme.muted())); + return Line.from(spans); + } + + /** How many blocks of the route are folded. */ + int foldedCount(String routeId) { + Set<String> ids = folded.get(routeId); + return ids != null ? ids.size() : 0; } /** A step with steps in it, which is not the route or its from. */ private static boolean foldable(TreeNode node) { - return !node.children.isEmpty() && !node.table && node.info.type != null - && !"route".equals(node.info.type) && !"from".equals(node.info.type); + return !node.children.isEmpty() && !node.table && foldable(node.info.type); + } + + /** A step of this type with steps in it folds; a Switch is a table, its cases are not steps after it. */ + private static boolean foldable(String type) { + return type != null && !"route".equals(type) && !"from".equals(type) + && !RouteDiagramLayoutEngine.TABLE_EIPS.contains(type); } /** Remembers the selected step and its row on the screen, to keep it there once the route is laid out anew. */ @@ -238,6 +307,7 @@ final class RouteDiagramZoom { for (RouteInfo r : routes) { // the happy path; the onException clauses are not its steps, and are shown in the error frame RouteErrorFrame.Split split = RouteErrorFrame.split(r); + unfolded.put(r.routeId, split.happy()); Map<String, Integer> steps = new HashMap<>(); LayoutRoute lr = engine.layoutRoute(fold(split.happy(), steps), 0); RouteDensity d = fit ? RouteDensity.DETAILED : density; diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java index 04248a6e0591..1d664d690c27 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java @@ -392,8 +392,10 @@ class RoutesTab extends AbstractDiagramTab { return; } else if (!topologyMode && drillDownRouteId != null && diagram.getRouteLayout(drillDownRouteId) != null) { - Line title = DiagramDetailSupport.buildBreadcrumbTitle(routeNavigationStack, drillDownRouteId); var routeLayout = diagram.getRouteLayout(drillDownRouteId); + Line title = diagram.zoom.withStepCount( + DiagramDetailSupport.buildBreadcrumbTitle(routeNavigationStack, drillDownRouteId), drillDownRouteId, + routeLayout); // Split for detail panel when active Rect diagramArea = area; @@ -760,6 +762,7 @@ class RoutesTab extends AbstractDiagramTab { hint(spans, "g", "go to"); hint(spans, "+/-", "zoom"); hint(spans, "Space", "fold"); + hint(spans, "f/F", "fold/expand all"); hint(spans, "PgUp/PgDn", detailMode ? "detail" : "page"); hint(spans, "c", "source"); } else if (!topologyMode) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDensity.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDensity.java index c671d9c9eb9f..b7665c3132d3 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDensity.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDensity.java @@ -65,6 +65,11 @@ public enum RouteDensity { return label; } + /** Where the density is among the levels, from the most detail: 2/3. */ + public String position() { + return (ordinal() + 1) + "/" + values().length; + } + /** One level more detail, or this one when it is the most. */ public RouteDensity zoomIn() { return this == DETAILED ? DETAILED : values()[ordinal() - 1]; diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md index 450d0137a208..7c9c1f8702cd 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md @@ -243,13 +243,17 @@ you an at-a-glance view of where you are in the route. - `g` — go to node (fuzzy search popup) - `+` / `-` — zoom in or out: detailed (boxes with up to three lines), compact (boxes with one line, the steps closer together) and bare (a - mark and the text of each step on one row, no boxes); the selected - step stays where it is on the screen + mark and the text of each step on one row, no boxes); the view bar + shows the level and where it is (`compact 2/3`), the title how many + steps the route has and how many are folded (`38 steps · 12 folded`); + the selected step stays where it is on the screen - `z` — fit: the most detail in which the whole route fits the screen - `Space` — fold the block of the selected step (a choice, a branch, a split, a doTry...) into its first step, which says how many steps are folded; `Space` on a folded step unfolds it -- `f` — fold the outermost blocks of the route, or unfold all +- `f` — fold all the blocks of the route, the blocks in other blocks too + (`Space` on one then shows its steps with the blocks in it folded) +- `F` — expand all the blocks of the route - `Esc` — go back (previous route or topology) - `t` — jump back to topology view diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md index 073e5346e231..cf3300829696 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md @@ -164,13 +164,17 @@ in the diagram. - `g` — go to node (fuzzy search popup) - `+` / `-` — zoom in or out: detailed (boxes with up to three lines), compact (boxes with one line, the steps closer together) and bare (a - mark and the text of each step on one row, no boxes); the selected - step stays where it is on the screen + mark and the text of each step on one row, no boxes); the view bar + shows the level and where it is (`compact 2/3`), the title how many + steps the route has and how many are folded (`38 steps · 12 folded`); + the selected step stays where it is on the screen - `z` — fit: the most detail in which the whole route fits the screen - `Space` — fold the block of the selected step (a choice, a branch, a split, a doTry...) into its first step, which says how many steps are folded; `Space` on a folded step unfolds it -- `f` — fold the outermost blocks of the route, or unfold all +- `f` — fold all the blocks of the route, the blocks in other blocks too + (`Space` on one then shows its steps with the blocks in it folded) +- `F` — expand all the blocks of the route - `c` — show source code at selected node - `Esc` — go back (previous route or topology) - `t` — jump back to topology view diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteDiagramZoomTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteDiagramZoomTest.java index 35e42b1daf7a..74f6e64aeee4 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteDiagramZoomTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteDiagramZoomTest.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import dev.tamboui.text.Line; import org.apache.camel.diagram.RouteDiagramLayoutEngine.LayoutNode; import org.apache.camel.diagram.RouteDiagramLayoutEngine.LayoutRoute; import org.apache.camel.diagram.RouteDiagramLayoutEngine.NodeInfo; @@ -99,14 +100,46 @@ class RouteDiagramZoomTest { } @Test - void foldAllFoldsTheOutermostBlocksThenUnfoldsAll() { + void foldAllFoldsEveryBlockAndExpandAllOpensThemAgain() { RouteDiagramZoom zoom = new RouteDiagramZoom(); - zoom.toggleFoldAll("orders", layout(zoom, 0)); LayoutRoute lr = layout(zoom, 0); + // the selected step is in the when, in the choice: the choice is selected once folded + assertThat(zoom.foldAll("orders", find(lr, "log1"))).isEqualTo("choice1"); + lr = layout(zoom, 0); assertThat(zoom.foldedStepsOf(lr)).isEqualTo(Map.of("choice1", 5, "split1", 1)); + assertThat(zoom.foldedCount("orders")).isEqualTo(4); + + // the blocks in the choice are folded too: opening it shows its branches folded + zoom.toggleFold("orders", find(lr, "choice1")); + lr = layout(zoom, 0); + assertThat(zoom.foldedStepsOf(lr)).isEqualTo(Map.of("when1", 2, "otherwise1", 1, "split1", 1)); - zoom.toggleFoldAll("orders", lr); + zoom.expandAll("orders"); assertThat(zoom.foldedStepsOf(layout(zoom, 0))).isEmpty(); + // a step in no block stays selected + assertThat(zoom.foldAll("orders", find(lr, "to4"))).isEqualTo("to4"); + } + + @Test + void theTitleSaysHowManyStepsTheRouteHasAndHowManyAreFolded() { + RouteDiagramZoom zoom = new RouteDiagramZoom(); + LayoutRoute lr = layout(zoom, 0); + assertThat(zoom.stepCount("orders")).isEqualTo(9); + assertThat(text(zoom.withStepCount(Line.from(" Route [orders] "), "orders", lr))) + .isEqualTo(" Route [orders] 9 steps "); + + zoom.toggleFold("orders", find(lr, "choice1")); + lr = layout(zoom, 0); + assertThat(text(zoom.withStepCount(Line.from(" Route [orders] "), "orders", lr))) + .isEqualTo(" Route [orders] 9 steps · 5 folded "); + // a route not laid out yet: the title as it is + assertThat(text(zoom.withStepCount(Line.from(" Route [other] "), "other", lr))).isEqualTo(" Route [other] "); + } + + private static String text(Line line) { + StringBuilder sb = new StringBuilder(); + line.spans().forEach(span -> sb.append(span.content())); + return sb.toString(); } @Test @@ -130,7 +163,7 @@ class RouteDiagramZoomTest { assertThat(zoom.densityOf(fitted)).isEqualTo(RouteDensity.BARE); // the route starts at the top, as without fit assertThat(find(fitted, "from1").y).isEqualTo(40); - assertThat(zoom.label(layout(zoom, bare))).isEqualTo("fit bare"); + assertThat(zoom.label(layout(zoom, bare))).isEqualTo("fit bare 3/3"); // a zoom key leaves fit zoom.zoomIn(); assertThat(zoom.isFit()).isFalse();
