优化导航底部工具区

This commit is contained in:
张家训
2026-08-01 20:54:22 +08:00
parent bd959825a3
commit 4c873b3f4f
5 changed files with 111 additions and 86 deletions
@@ -8,10 +8,7 @@ import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
@@ -88,9 +85,9 @@ public class MainWindow {
stage.setOnCloseRequest(event -> embeddedCache.clear()); stage.setOnCloseRequest(event -> embeddedCache.clear());
BorderPane root = new BorderPane(); BorderPane root = new BorderPane();
root.setTop(buildUnifiedHeader(openSettings)); root.setTop(buildUnifiedHeader());
navigationRail = new NavigationRail(navItems, this::navigateTo); navigationRail = new NavigationRail(navItems, this::navigateTo, openSettings);
contentArea = new StackPane(); contentArea = new StackPane();
contentArea.getStyleClass().add(Theme.CSS_CONTENT_AREA); contentArea.getStyleClass().add(Theme.CSS_CONTENT_AREA);
HBox.setHgrow(contentArea, Priority.ALWAYS); HBox.setHgrow(contentArea, Priority.ALWAYS);
@@ -104,7 +101,7 @@ public class MainWindow {
showWelcomePage(); showWelcomePage();
} }
private HBox buildUnifiedHeader(Runnable openSettings) { private HBox buildUnifiedHeader() {
HBox bar = new HBox(Theme.S1); HBox bar = new HBox(Theme.S1);
bar.setId("app-header"); bar.setId("app-header");
bar.setAlignment(Pos.CENTER_LEFT); bar.setAlignment(Pos.CENTER_LEFT);
@@ -114,13 +111,6 @@ public class MainWindow {
bar.setMaxHeight(DesignTokens.HEADER_HEIGHT); bar.setMaxHeight(DesignTokens.HEADER_HEIGHT);
bar.getStyleClass().add(Theme.CSS_HEADER_BAR); bar.getStyleClass().add(Theme.CSS_HEADER_BAR);
Button menuButton = new Button(null, IconFactory.createButtonIcon(AppIcon.MENU, "切换导航栏"));
menuButton.setId("menu-button");
menuButton.setTooltip(new Tooltip("切换导航栏"));
menuButton.setAccessibleText("切换导航栏");
menuButton.setFocusTraversable(true);
menuButton.setOnAction(event -> navigationRail.toggle());
FontIcon brandIcon = IconFactory.create(AppIcon.APP, 18); FontIcon brandIcon = IconFactory.create(AppIcon.APP, 18);
StackPane brandIconContainer = new StackPane(brandIcon); StackPane brandIconContainer = new StackPane(brandIcon);
brandIconContainer.setId("app-brand-icon-container"); brandIconContainer.setId("app-brand-icon-container");
@@ -148,17 +138,7 @@ public class MainWindow {
Region spacerRight = new Region(); Region spacerRight = new Region();
HBox.setHgrow(spacerRight, Priority.ALWAYS); HBox.setHgrow(spacerRight, Priority.ALWAYS);
Button settingsButton = new Button( bar.getChildren().addAll(brand, toolbarChevron, toolbarTitle, spacerRight);
"系统设置", IconFactory.createButtonIcon(AppIcon.SETTINGS, "系统设置"));
settingsButton.setId("settings-button");
settingsButton.setTooltip(new Tooltip("系统设置"));
settingsButton.setAccessibleText("系统设置");
settingsButton.setContentDisplay(ContentDisplay.LEFT);
settingsButton.setFocusTraversable(true);
settingsButton.setOnAction(event -> openSettings.run());
bar.getChildren().addAll(
menuButton, brand, toolbarChevron, toolbarTitle, spacerRight, settingsButton);
return bar; return bar;
} }
@@ -237,7 +217,7 @@ public class MainWindow {
private void switchToEmbedded(String pluginId, EmbeddedEntry entry) { private void switchToEmbedded(String pluginId, EmbeddedEntry entry) {
if ("__settings__".equals(pluginId)) { if ("__settings__".equals(pluginId)) {
navigationRail.clearSelection(); navigationRail.selectSettings();
} }
if (currentEmbedded != null && currentEmbedded != entry) { if (currentEmbedded != null && currentEmbedded != entry) {
contentArea.getChildren().remove(currentEmbedded.wrapper); contentArea.getChildren().remove(currentEmbedded.wrapper);
@@ -39,6 +39,8 @@ public final class NavigationRail extends VBox {
private final List<ItemView> itemViews = new ArrayList<>(); private final List<ItemView> itemViews = new ArrayList<>();
private final Map<String, ItemView> viewsByPluginId = new LinkedHashMap<>(); private final Map<String, ItemView> viewsByPluginId = new LinkedHashMap<>();
private final VBox itemBox = new VBox(); private final VBox itemBox = new VBox();
private final VBox utilityBox = new VBox();
private final ToggleButton settingsButton = new ToggleButton();
private final Button collapseButton = new Button(); private final Button collapseButton = new Button();
private MainWindow.NavItem selectedItem; private MainWindow.NavItem selectedItem;
@@ -46,14 +48,26 @@ public final class NavigationRail extends VBox {
public NavigationRail(List<MainWindow.NavItem> items, public NavigationRail(List<MainWindow.NavItem> items,
Consumer<MainWindow.NavItem> navigator) { Consumer<MainWindow.NavItem> navigator) {
this(items, navigator, () -> { });
}
public NavigationRail(List<MainWindow.NavItem> items,
Consumer<MainWindow.NavItem> navigator,
Runnable openSettings) {
requireFxThread(); requireFxThread();
this.navigator = Objects.requireNonNull(navigator, "navigator must not be null"); this.navigator = Objects.requireNonNull(navigator, "navigator must not be null");
Objects.requireNonNull(openSettings, "openSettings must not be null");
getStyleClass().addAll(Theme.CSS_NAV_BAR, "navigation-rail"); getStyleClass().addAll(Theme.CSS_NAV_BAR, "navigation-rail");
itemBox.getStyleClass().add("navigation-rail-items"); itemBox.getStyleClass().add("navigation-rail-items");
VBox.setVgrow(itemBox, Priority.ALWAYS); VBox.setVgrow(itemBox, Priority.ALWAYS);
buildSettingsEntry(openSettings);
buildBottomToggle(); buildBottomToggle();
utilityBox.setId("navigation-rail-tools");
utilityBox.getStyleClass().add("navigation-rail-tools");
utilityBox.getChildren().addAll(settingsButton, collapseButton);
Separator separator = new Separator(); Separator separator = new Separator();
separator.setId("navigation-rail-separator"); separator.setId("navigation-rail-separator");
separator.getStyleClass().add(Theme.CSS_NAV_SEP); separator.getStyleClass().add(Theme.CSS_NAV_SEP);
@@ -62,7 +76,7 @@ public final class NavigationRail extends VBox {
separator.setPrefHeight(1); separator.setPrefHeight(1);
separator.setMaxHeight(1); separator.setMaxHeight(1);
separator.setFocusTraversable(false); separator.setFocusTraversable(false);
getChildren().addAll(itemBox, separator, collapseButton); getChildren().addAll(itemBox, separator, utilityBox);
expanded.addListener((observable, oldValue, newValue) -> updateAppearance()); expanded.addListener((observable, oldValue, newValue) -> updateAppearance());
setItems(items); setItems(items);
@@ -106,6 +120,18 @@ public final class NavigationRail extends VBox {
requireFxThread(); requireFxThread();
selection.selectToggle(null); selection.selectToggle(null);
itemViews.forEach(view -> view.button().getStyleClass().remove(Theme.CSS_NAV_CELL_ACTIVE)); itemViews.forEach(view -> view.button().getStyleClass().remove(Theme.CSS_NAV_CELL_ACTIVE));
settingsButton.getStyleClass().remove(Theme.CSS_NAV_CELL_ACTIVE);
selectedItem = null;
selectedPluginId = null;
}
public void selectSettings() {
requireFxThread();
selection.selectToggle(settingsButton);
itemViews.forEach(view -> view.button().getStyleClass().remove(Theme.CSS_NAV_CELL_ACTIVE));
if (!settingsButton.getStyleClass().contains(Theme.CSS_NAV_CELL_ACTIVE)) {
settingsButton.getStyleClass().add(Theme.CSS_NAV_CELL_ACTIVE);
}
selectedItem = null; selectedItem = null;
selectedPluginId = null; selectedPluginId = null;
} }
@@ -137,6 +163,24 @@ public final class NavigationRail extends VBox {
return view == null ? null : view.tooltip(); return view == null ? null : view.tooltip();
} }
private void buildSettingsEntry(Runnable openSettings) {
settingsButton.setId("navigation-rail-settings");
settingsButton.getStyleClass().addAll(Theme.CSS_NAV_CELL, "navigation-rail-settings");
settingsButton.setToggleGroup(selection);
settingsButton.setContentDisplay(ContentDisplay.LEFT);
settingsButton.setGraphicTextGap(Theme.S3);
settingsButton.setMinHeight(DesignTokens.ROW_HEIGHT);
settingsButton.setPrefHeight(DesignTokens.ROW_HEIGHT);
settingsButton.setMaxHeight(DesignTokens.ROW_HEIGHT);
settingsButton.setMaxWidth(Double.MAX_VALUE);
settingsButton.setAccessibleText("系统设置");
settingsButton.setFocusTraversable(true);
settingsButton.setOnAction(event -> {
selectSettings();
openSettings.run();
});
}
private void buildBottomToggle() { private void buildBottomToggle() {
collapseButton.setId("navigation-rail-toggle"); collapseButton.setId("navigation-rail-toggle");
collapseButton.getStyleClass().addAll(Theme.CSS_NAV_CELL, "navigation-rail-toggle"); collapseButton.getStyleClass().addAll(Theme.CSS_NAV_CELL, "navigation-rail-toggle");
@@ -182,6 +226,7 @@ public final class NavigationRail extends VBox {
private void activate(MainWindow.NavItem item, ToggleButton button) { private void activate(MainWindow.NavItem item, ToggleButton button) {
selection.selectToggle(button); selection.selectToggle(button);
settingsButton.getStyleClass().remove(Theme.CSS_NAV_CELL_ACTIVE);
itemViews.forEach(view -> view.button().getStyleClass().remove(Theme.CSS_NAV_CELL_ACTIVE)); itemViews.forEach(view -> view.button().getStyleClass().remove(Theme.CSS_NAV_CELL_ACTIVE));
if (!button.getStyleClass().contains(Theme.CSS_NAV_CELL_ACTIVE)) { if (!button.getStyleClass().contains(Theme.CSS_NAV_CELL_ACTIVE)) {
button.getStyleClass().add(Theme.CSS_NAV_CELL_ACTIVE); button.getStyleClass().add(Theme.CSS_NAV_CELL_ACTIVE);
@@ -212,6 +257,10 @@ public final class NavigationRail extends VBox {
collapseButton.setAccessibleText(accessibleText); collapseButton.setAccessibleText(accessibleText);
collapseButton.setTooltip(new Tooltip(accessibleText)); collapseButton.setTooltip(new Tooltip(accessibleText));
collapseButton.setAlignment(isExpanded() ? Pos.CENTER_LEFT : Pos.CENTER); collapseButton.setAlignment(isExpanded() ? Pos.CENTER_LEFT : Pos.CENTER);
settingsButton.setGraphic(IconFactory.createButtonIcon(AppIcon.SETTINGS, "系统设置"));
settingsButton.setText(isExpanded() ? "系统设置" : null);
settingsButton.setTooltip(isExpanded() ? null : new Tooltip("系统设置"));
settingsButton.setAlignment(isExpanded() ? Pos.CENTER_LEFT : Pos.CENTER);
updateItemAppearance(); updateItemAppearance();
} }
@@ -25,12 +25,14 @@ class MainWindowArchitectureTest {
} }
@Test @Test
void settingsIsAUniqueHeaderActionInsteadOfANavigationItem() throws Exception { void settingsUsesTheExplicitShellActionWithoutBecomingABusinessItem() throws Exception {
String application = source("src/main/java/com/example/workutils/WorkUtilsApplication.java"); String application = source("src/main/java/com/example/workutils/WorkUtilsApplication.java");
String buildNavItems = between(application, String buildNavItems = between(application,
"private List<MainWindow.NavItem> buildNavItems()", "private List<MainWindow.NavItem> buildNavItems()",
"// ==================== 内嵌优先打开"); "// ==================== 内嵌优先打开");
String mainWindow = source("src/main/java/com/example/workutils/system/ui/MainWindow.java"); String mainWindow = source("src/main/java/com/example/workutils/system/ui/MainWindow.java");
String navigationRail = source(
"src/main/java/com/example/workutils/system/ui/shell/NavigationRail.java");
assertFalse(buildNavItems.contains("__settings__")); assertFalse(buildNavItems.contains("__settings__"));
assertFalse(buildNavItems.contains("系统设置")); assertFalse(buildNavItems.contains("系统设置"));
@@ -39,8 +41,8 @@ class MainWindowArchitectureTest {
"new MainWindow(navItems, properties, this::openSettingsEmbedded)")); "new MainWindow(navItems, properties, this::openSettingsEmbedded)"));
assertTrue(mainWindow.contains( assertTrue(mainWindow.contains(
"MainWindow(List<NavItem> navItems, WorkUtilsProperties properties, Runnable openSettings)")); "MainWindow(List<NavItem> navItems, WorkUtilsProperties properties, Runnable openSettings)"));
assertTrue(mainWindow.contains("IconFactory.createButtonIcon(AppIcon.SETTINGS")); assertTrue(mainWindow.contains("new NavigationRail(navItems, this::navigateTo, openSettings)"));
assertTrue(mainWindow.contains("openSettings.run()")); assertTrue(navigationRail.contains("openSettings.run()"));
} }
@Test @Test
@@ -71,13 +73,12 @@ class MainWindowArchitectureTest {
String header = between(mainWindow, "private HBox buildUnifiedHeader", "private void navigateTo"); String header = between(mainWindow, "private HBox buildUnifiedHeader", "private void navigateTo");
assertTrue(header.contains("DesignTokens.HEADER_HEIGHT")); assertTrue(header.contains("DesignTokens.HEADER_HEIGHT"));
assertTrue(header.contains("IconFactory.createButtonIcon(AppIcon.MENU"));
assertTrue(header.contains("navigationRail.toggle()"));
assertTrue(header.contains("IconFactory.create(AppIcon.APP")); assertTrue(header.contains("IconFactory.create(AppIcon.APP"));
assertTrue(header.contains("IconFactory.create(AppIcon.CHEVRON_RIGHT")); assertTrue(header.contains("IconFactory.create(AppIcon.CHEVRON_RIGHT"));
assertTrue(header.contains("IconFactory.createButtonIcon(AppIcon.SETTINGS")); assertFalse(header.contains("AppIcon.MENU"));
assertEquals(1, occurrences(header, "AppIcon.SETTINGS")); assertFalse(header.contains("AppIcon.SETTINGS"));
assertEquals(1, occurrences(header, "openSettings.run()")); assertFalse(header.contains("navigationRail.toggle()"));
assertFalse(header.contains("openSettings.run()"));
assertFalse(header.contains("toolbarIcon")); assertFalse(header.contains("toolbarIcon"));
assertFalse(header.contains("SVGPath")); assertFalse(header.contains("SVGPath"));
assertFalse(header.contains("<svg")); assertFalse(header.contains("<svg"));
@@ -19,7 +19,6 @@ import javafx.scene.Scene;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ButtonBase; import javafx.scene.control.ButtonBase;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Labeled;
import javafx.scene.control.ProgressIndicator; import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleButton;
import javafx.scene.image.WritableImage; import javafx.scene.image.WritableImage;
@@ -47,6 +46,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -384,26 +384,17 @@ final class NativeUiVisualAcceptanceFixture {
} }
private void validateSettingsControl(Scene scene, Node header) { private void validateSettingsControl(Scene scene, Node header) {
List<ButtonBase> settingsControls = descendants(scene.getRoot()).stream() ButtonBase settings = (ButtonBase) scene.lookup("#navigation-rail-settings");
.filter(ButtonBase.class::isInstance) assertNotNull(settings);
.map(ButtonBase.class::cast) assertTrue(settings.isVisible());
.filter(Node::isVisible) assertSame(settings, navigationRail.lookup("#navigation-rail-settings"));
.filter(control -> "系统设置".equals(control.getText())) assertFalse(descendants((Parent) header).contains(settings));
.toList(); assertEquals("系统设置", settings.getAccessibleText());
assertEquals(1, settingsControls.size(),
"Exactly one visible system settings control is required");
ButtonBase settings = settingsControls.get(0);
assertSame(scene.lookup("#settings-button"), settings);
assertTrue(descendants((Parent) header).contains(settings));
Bounds position = settings.localToScene(settings.getBoundsInLocal()); Bounds position = settings.localToScene(settings.getBoundsInLocal());
assertTrue(position.getMinX() > scene.getWidth() / 2, assertTrue(position.getMaxX() <= navigationRail.getWidth() + 1,
"System settings must remain at the top-right"); "System settings must remain inside the navigation rail");
assertNull(scene.lookup("#settings-button"));
boolean settingsInNavigation = descendants(navigationRail).stream() assertNull(scene.lookup("#menu-button"));
.filter(Labeled.class::isInstance)
.map(Labeled.class::cast)
.anyMatch(control -> "系统设置".equals(control.getText()));
assertFalse(settingsInNavigation, "System settings must not appear in navigation");
} }
private void ensureRequestedSizeFitsDesktop(CaptureSpec spec) { private void ensureRequestedSizeFitsDesktop(CaptureSpec spec) {
@@ -50,7 +50,7 @@ class NavigationRailTest {
"api-doc"); "api-doc");
@Test @Test
void headerHasOnlyMenuBrandChevronTitleSpacerAndSettings() throws Exception { void headerContainsOnlyBrandBreadcrumbTitleAndSpacer() throws Exception {
fx(() -> { fx(() -> {
MainWindow window = new MainWindow(List.of(), new WorkUtilsProperties(), () -> { MainWindow window = new MainWindow(List.of(), new WorkUtilsProperties(), () -> {
}); });
@@ -60,18 +60,14 @@ class NavigationRailTest {
stage(window).getScene().getRoot().applyCss(); stage(window).getScene().getRoot().applyCss();
stage(window).getScene().getRoot().layout(); stage(window).getScene().getRoot().layout();
assertEquals(6, header.getChildren().size()); assertEquals(4, header.getChildren().size());
Button menu = assertInstanceOf(Button.class, header.getChildren().get(0)); HBox brand = assertInstanceOf(HBox.class, header.getChildren().get(0));
HBox brand = assertInstanceOf(HBox.class, header.getChildren().get(1)); FontIcon chevron = assertInstanceOf(FontIcon.class, header.getChildren().get(1));
FontIcon chevron = assertInstanceOf(FontIcon.class, header.getChildren().get(2)); Label title = assertInstanceOf(Label.class, header.getChildren().get(2));
Label title = assertInstanceOf(Label.class, header.getChildren().get(3)); assertInstanceOf(Region.class, header.getChildren().get(3));
assertInstanceOf(Region.class, header.getChildren().get(4));
Button settings = assertInstanceOf(Button.class, header.getChildren().get(5));
assertEquals(AppIcon.MENU.glyph(), assertNull(header.lookup("#menu-button"));
assertInstanceOf(FontIcon.class, menu.getGraphic()).getIconCode()); assertNull(header.lookup("#settings-button"));
assertEquals("切换导航栏", menu.getAccessibleText());
assertTrue(menu.isFocusTraversable());
assertEquals(2, brand.getChildren().size()); assertEquals(2, brand.getChildren().size());
StackPane brandIconContainer = assertInstanceOf( StackPane brandIconContainer = assertInstanceOf(
StackPane.class, brand.getChildren().get(0)); StackPane.class, brand.getChildren().get(0));
@@ -94,12 +90,6 @@ class NavigationRailTest {
assertEquals("WorkUtils", brandText.getText()); assertEquals("WorkUtils", brandText.getText());
assertEquals(AppIcon.CHEVRON_RIGHT.glyph(), chevron.getIconCode()); assertEquals(AppIcon.CHEVRON_RIGHT.glyph(), chevron.getIconCode());
assertEquals("API", title.getText()); assertEquals("API", title.getText());
assertEquals(AppIcon.SETTINGS.glyph(),
assertInstanceOf(FontIcon.class, settings.getGraphic()).getIconCode());
assertEquals("系统设置", settings.getText());
assertEquals("系统设置", settings.getAccessibleText());
assertEquals(ContentDisplay.LEFT, settings.getContentDisplay());
assertTrue(settings.isFocusTraversable());
}); });
} }
@@ -153,15 +143,27 @@ class NavigationRailTest {
} }
@Test @Test
void railKeepsCollapseEntryAtBottomWithoutDuplicatingTheApplicationBrand() throws Exception { void railBottomToolsKeepSettingsAboveTheLastCollapseEntry() throws Exception {
fx(() -> { fx(() -> {
NavigationRail rail = new NavigationRail(List.of(API_DOC), item -> { MainWindow window = new MainWindow(List.of(API_DOC), new WorkUtilsProperties(), () -> {
}); });
NavigationRail rail = assertInstanceOf(
NavigationRail.class, stage(window).getScene().getRoot().lookup(".navigation-rail"));
VBox tools = assertInstanceOf(VBox.class, rail.lookup("#navigation-rail-tools"));
ToggleButton settings = assertInstanceOf(
ToggleButton.class, rail.lookup("#navigation-rail-settings"));
Button collapse = assertInstanceOf( Button collapse = assertInstanceOf(
Button.class, rail.lookup("#navigation-rail-toggle")); Button.class, rail.lookup("#navigation-rail-toggle"));
assertSame(collapse, rail.getChildren().get(rail.getChildren().size() - 1)); assertEquals(List.of(settings, collapse), tools.getChildren());
assertSame(tools, rail.getChildren().get(rail.getChildren().size() - 1));
assertSame(collapse, tools.getChildren().get(tools.getChildren().size() - 1));
assertTrue(rail.lookupAll(".nav-brand").isEmpty()); assertTrue(rail.lookupAll(".nav-brand").isEmpty());
assertEquals(AppIcon.SETTINGS.glyph(),
assertInstanceOf(FontIcon.class, settings.getGraphic()).getIconCode());
assertEquals("系统设置", settings.getText());
assertEquals("系统设置", settings.getAccessibleText());
assertNull(settings.getTooltip());
assertEquals("收起", collapse.getText()); assertEquals("收起", collapse.getText());
assertEquals("收起导航栏", collapse.getAccessibleText()); assertEquals("收起导航栏", collapse.getAccessibleText());
assertTrue(collapse.isFocusTraversable()); assertTrue(collapse.isFocusTraversable());
@@ -169,6 +171,9 @@ class NavigationRailTest {
collapse.fire(); collapse.fire();
assertFalse(rail.isExpanded()); assertFalse(rail.isExpanded());
assertNull(settings.getText());
assertEquals("系统设置", settings.getAccessibleText());
assertEquals("系统设置", settings.getTooltip().getText());
assertNull(collapse.getText()); assertNull(collapse.getText());
assertEquals("展开导航栏", collapse.getAccessibleText()); assertEquals("展开导航栏", collapse.getAccessibleText());
assertEquals("展开导航栏", collapse.getTooltip().getText()); assertEquals("展开导航栏", collapse.getTooltip().getText());
@@ -212,26 +217,23 @@ class NavigationRailTest {
} }
@Test @Test
void headerMenuAndBottomCollapseEntryDriveTheSameExpandedState() throws Exception { void headerDoesNotDuplicateTheBottomCollapseEntry() throws Exception {
fx(() -> { fx(() -> {
MainWindow window = new MainWindow(List.of(API_DOC), new WorkUtilsProperties(), () -> { MainWindow window = new MainWindow(List.of(API_DOC), new WorkUtilsProperties(), () -> {
}); });
Parent root = stage(window).getScene().getRoot(); Parent root = stage(window).getScene().getRoot();
NavigationRail rail = assertInstanceOf( NavigationRail rail = assertInstanceOf(
NavigationRail.class, root.lookup(".navigation-rail")); NavigationRail.class, root.lookup(".navigation-rail"));
Button menu = assertInstanceOf(Button.class, root.lookup("#menu-button"));
Button collapse = assertInstanceOf( Button collapse = assertInstanceOf(
Button.class, root.lookup("#navigation-rail-toggle")); Button.class, root.lookup("#navigation-rail-toggle"));
menu.fire(); assertNull(root.lookup("#menu-button"));
assertEquals(1, root.lookupAll("#navigation-rail-toggle").size());
assertFalse(rail.isExpanded());
assertEquals("展开导航栏", collapse.getAccessibleText());
collapse.fire(); collapse.fire();
assertTrue(rail.isExpanded()); assertFalse(rail.isExpanded());
assertEquals("收起导航栏", collapse.getAccessibleText()); assertEquals("展开导航栏", collapse.getAccessibleText());
}); });
} }
@@ -256,8 +258,8 @@ class NavigationRailTest {
Parent root = stage(window).getScene().getRoot(); Parent root = stage(window).getScene().getRoot();
ToggleButton pluginButton = assertInstanceOf( ToggleButton pluginButton = assertInstanceOf(
ToggleButton.class, root.lookup("#nav-api-doc")); ToggleButton.class, root.lookup("#nav-api-doc"));
Button settingsButton = assertInstanceOf( ToggleButton settingsButton = assertInstanceOf(
Button.class, root.lookup("#settings-button")); ToggleButton.class, root.lookup("#navigation-rail-settings"));
NavigationRail rail = assertInstanceOf( NavigationRail rail = assertInstanceOf(
NavigationRail.class, root.lookup(".navigation-rail")); NavigationRail.class, root.lookup(".navigation-rail"));
@@ -269,11 +271,13 @@ class NavigationRailTest {
assertEquals("系统设置", toolbarTitle(window).getText()); assertEquals("系统设置", toolbarTitle(window).getText());
assertNull(rail.selectedPluginId()); assertNull(rail.selectedPluginId());
assertFalse(pluginButton.isSelected()); assertFalse(pluginButton.isSelected());
assertTrue(settingsButton.isSelected());
pluginButton.fire(); pluginButton.fire();
assertEquals(2, pluginOpens.get()); assertEquals(2, pluginOpens.get());
assertEquals("API", toolbarTitle(window).getText()); assertEquals("API", toolbarTitle(window).getText());
assertFalse(settingsButton.isSelected());
}); });
} }
@@ -382,7 +386,7 @@ class NavigationRailTest {
assertNull(oldButton.getToggleGroup()); assertNull(oldButton.getToggleGroup());
assertNull(oldButton.getOnAction()); assertNull(oldButton.getOnAction());
assertEquals(1, newButton.getToggleGroup().getToggles().size()); assertEquals(2, newButton.getToggleGroup().getToggles().size());
oldButton.fire(); oldButton.fire();
assertEquals(1, routed.get()); assertEquals(1, routed.get());
}); });