diff --git a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CssCoreTestSuite.java b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CssCoreTestSuite.java index cb33fdd3c4f..60911cc40ec 100644 --- a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CssCoreTestSuite.java +++ b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CssCoreTestSuite.java @@ -25,6 +25,7 @@ import org.eclipse.e4.ui.tests.css.core.parser.RGBColorImplTest; import org.eclipse.e4.ui.tests.css.core.parser.SelectorTest; import org.eclipse.e4.ui.tests.css.core.parser.StyleRuleTest; +import org.eclipse.e4.ui.tests.css.core.parser.StyleSheetStructureTest; import org.eclipse.e4.ui.tests.css.core.parser.ValueTest; import org.eclipse.e4.ui.tests.css.core.parser.ViewCSSTest; import org.junit.platform.suite.api.SelectClasses; @@ -36,6 +37,7 @@ MediaRulesTest.class, RGBColorImplTest.class, StyleRuleTest.class, + StyleSheetStructureTest.class, ViewCSSTest.class, ValueTest.class, SelectorTest.class, diff --git a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/parser/StyleSheetStructureTest.java b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/parser/StyleSheetStructureTest.java index 21529ae5acd..d08783674ca 100644 --- a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/parser/StyleSheetStructureTest.java +++ b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/parser/StyleSheetStructureTest.java @@ -44,7 +44,7 @@ * parser introduced during the CSS engine rework must produce an AST these * tests still accept. */ -class StyleSheetStructureTest { +public class StyleSheetStructureTest { @Test void testEmptyStyleSheet() throws Exception { diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/CssSwtTestSuite.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/CssSwtTestSuite.java index 9e3546f96d5..dc5a1acadbd 100644 --- a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/CssSwtTestSuite.java +++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/CssSwtTestSuite.java @@ -33,13 +33,16 @@ import org.eclipse.e4.ui.tests.css.swt.ButtonTest; import org.eclipse.e4.ui.tests.css.swt.ButtonTextTransformTest; import org.eclipse.e4.ui.tests.css.swt.CSSSWTWidgetTest; +import org.eclipse.e4.ui.tests.css.swt.CTabFolderActiveClassTest; import org.eclipse.e4.ui.tests.css.swt.CTabFolderTest; +import org.eclipse.e4.ui.tests.css.swt.CTabItemSelectionTest; import org.eclipse.e4.ui.tests.css.swt.CTabItemTest; import org.eclipse.e4.ui.tests.css.swt.ColorDefinitionTest; import org.eclipse.e4.ui.tests.css.swt.CompositeTest; import org.eclipse.e4.ui.tests.css.swt.DescendentTest; import org.eclipse.e4.ui.tests.css.swt.FontDefinitionTest; import org.eclipse.e4.ui.tests.css.swt.GradientTest; +import org.eclipse.e4.ui.tests.css.swt.IEclipsePreferencesPseudoKeyTest; import org.eclipse.e4.ui.tests.css.swt.IEclipsePreferencesTest; import org.eclipse.e4.ui.tests.css.swt.IdClassLabelColorTest; import org.eclipse.e4.ui.tests.css.swt.InheritTest; @@ -65,7 +68,9 @@ SWTResourceRegistryKeyFactoryTest.class, SWTResourcesRegistryTest.class, FontDefinitionTest.class, ColorDefinitionTest.class, ThemesExtensionTest.class, IEclipsePreferencesTest.class, EclipsePreferencesHelperTest.class, CSSSWTWidgetTest.class, LabelTest.class, LinkTest.class, - CTabFolderTest.class, CTabItemTest.class, IdClassLabelColorTest.class, ShellTest.class, ButtonTest.class, + CTabFolderTest.class, CTabFolderActiveClassTest.class, CTabItemTest.class, CTabItemSelectionTest.class, + IEclipsePreferencesPseudoKeyTest.class, + IdClassLabelColorTest.class, ShellTest.class, ButtonTest.class, ToolItemTest.class, GradientTest.class, MarginTest.class, InnerClassElementTest.class, EclipsePreferencesHandlerTest.class, PreferenceOverriddenByCssChangeListenerTest.class, ButtonTextTransformTest.class, LabelTextTransformTest.class, TextTextTransformTest.class, DescendentTest.class, diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabFolderActiveClassTest.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabFolderActiveClassTest.java new file mode 100644 index 00000000000..c06734a3150 --- /dev/null +++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabFolderActiveClassTest.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2026 Lars Vogel and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.e4.ui.tests.css.swt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.eclipse.e4.ui.css.swt.dom.WidgetElement; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabItem; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Shell; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * Locks in how {@code CTabFolder.active { ... }} rules are matched based on + * the CSS class set via {@link WidgetElement#setCSSClass(org.eclipse.swt.widgets.Widget, String)}. + */ +public class CTabFolderActiveClassTest extends CSSSWTTestCase { + + private Shell shell; + + @Override + @AfterEach + public void tearDown() { + if (shell != null && !shell.isDisposed()) { + shell.dispose(); + shell = null; + } + super.tearDown(); + } + + private CTabFolder createFolder() { + shell = new Shell(display, SWT.SHELL_TRIM); + shell.setLayout(new FillLayout()); + + CTabFolder folder = new CTabFolder(shell, SWT.NONE); + CTabItem item = new CTabItem(folder, SWT.NONE); + item.setText("Item 0"); + folder.setSelection(0); + return folder; + } + + @Test + void testActiveClassAppliesStyle() { + CTabFolder folder = createFolder(); + WidgetElement.setCSSClass(folder, "active"); + + engine = createEngine("CTabFolder.active { background-color: #FF0000 }", display); + engine.applyStyles(shell, true); + + assertEquals(RED, folder.getBackground().getRGB()); + } + + @Test + void testWithoutActiveClassRuleDoesNotApply() { + CTabFolder folder = createFolder(); + // no setCSSClass call + + engine = createEngine("CTabFolder.active { background-color: #FF0000 }", display); + engine.applyStyles(shell, true); + + assertNotEquals(RED, folder.getBackground().getRGB()); + } + + @Test + void testClearingActiveClassDoesNotRevertBackground() { + CTabFolder folder = createFolder(); + WidgetElement.setCSSClass(folder, "active"); + + engine = createEngine("CTabFolder.active { background-color: #FF0000 }", display); + engine.applyStyles(shell, true); + assertEquals(RED, folder.getBackground().getRGB()); + + // Surprise: clearing the CSS class and reapplying does NOT undo the + // previously painted background. Once CTabFolder#setBackground has + // been called by the handler, the folder retains that color even + // though the .active rule no longer matches. Pin this real engine + // behaviour so a future change that adds proper revert semantics + // shows up as a test failure. + WidgetElement.setCSSClass(folder, null); + engine.applyStyles(shell, true); + + assertEquals(RED, folder.getBackground().getRGB()); + } +} diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabItemSelectionTest.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabItemSelectionTest.java new file mode 100644 index 00000000000..51734ed82a3 --- /dev/null +++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/CTabItemSelectionTest.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2026 Lars Vogel and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.e4.ui.tests.css.swt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabItem; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Shell; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * Pins {@code CTabItem:selected} pseudo behaviour and the + * {@code CTabFolderElement} selection listener path. + */ +public class CTabItemSelectionTest extends CSSSWTTestCase { + + private Shell shell; + + @Override + @AfterEach + public void tearDown() { + if (shell != null && !shell.isDisposed()) { + shell.dispose(); + shell = null; + } + super.tearDown(); + } + + private void spinEventLoop() { + // Drain queued SWT events. Same pattern as CTabItemTest. + for (int i = 0; i < 3; i++) { + while (display.readAndDispatch()) { + } + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + } + } + + private CTabFolder createFolderWithTwoItems(String styleSheet) { + shell = new Shell(display, SWT.SHELL_TRIM); + shell.setLayout(new FillLayout()); + + CTabFolder folder = new CTabFolder(shell, SWT.NONE); + CTabItem item0 = new CTabItem(folder, SWT.NONE); + item0.setText("Item 0"); + CTabItem item1 = new CTabItem(folder, SWT.NONE); + item1.setText("Item 1"); + folder.setSelection(0); + + engine = createEngine(styleSheet, display); + engine.applyStyles(shell, true); + shell.open(); + return folder; + } + + @Test + void testSelectedTabReceivesStyledSelectionForeground() { + // :selected for CTabItem maps to CTabFolder#getSelectionForeground(), + // not the per-item CTabItem#getForeground(). The matched declaration + // is intentionally observable on the parent folder. + CTabFolder folder = createFolderWithTwoItems("CTabItem:selected { color: #FF0000 }"); + spinEventLoop(); + + assertEquals(RED, folder.getSelectionForeground().getRGB()); + } + + @Test + void testSelectionListenerReappliesStylesOnSelectionEvent() { + CTabFolder folder = createFolderWithTwoItems( + "CTabItem { color: #0000FF }\n" + "CTabItem:selected { color: #FF0000 }"); + spinEventLoop(); + assertEquals(RED, folder.getSelectionForeground().getRGB()); + + // Switch the selection. CTabFolder#setSelection on its own does not + // fire the SWT selection listener, so we explicitly dispatch the + // SWT.Selection event the way an end-user click would. This + // exercises CTabFolderElement#selectionListener which invokes + // applyStyles(folder, true) for us. + CTabItem item1 = folder.getItem(1); + folder.setSelection(item1); + Event event = new Event(); + event.widget = folder; + event.item = item1; + folder.notifyListeners(SWT.Selection, event); + spinEventLoop(); + + // The folder's selection-foreground stays red because the rule still + // matches whichever item is currently selected. + assertEquals(RED, folder.getSelectionForeground().getRGB()); + // Non-selected items inherit the plain CTabItem rule. + assertEquals(new RGB(0, 0, 255), folder.getForeground().getRGB()); + } + + @Test + void testNonSelectedRuleColorIsNotRed() { + CTabFolder folder = createFolderWithTwoItems( + "CTabItem { color: #0000FF }\n" + "CTabItem:selected { color: #FF0000 }"); + spinEventLoop(); + + // The non-selected foreground (plain CTabItem rule) is blue, not red. + assertNotEquals(RED, folder.getForeground().getRGB()); + assertEquals(new RGB(0, 0, 255), folder.getForeground().getRGB()); + } +} diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/IEclipsePreferencesPseudoKeyTest.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/IEclipsePreferencesPseudoKeyTest.java new file mode 100644 index 00000000000..28a584e8d96 --- /dev/null +++ b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/IEclipsePreferencesPseudoKeyTest.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2026 Lars Vogel and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.e4.ui.tests.css.swt; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.core.internal.preferences.EclipsePreferences; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.junit.jupiter.api.Test; + +/** + * Locks in the {@code IEclipsePreferences#node:pseudo} selector form used by + * shipped themes. See Bug 466075. + */ +public class IEclipsePreferencesPseudoKeyTest extends CSSSWTTestCase { + + @Test + void testPseudoSelectorMatchesAndWritesPreferenceValue() { + IEclipsePreferences preferences = new EclipsePreferences(null, "org.eclipse.jdt.ui") {}; + + engine = createEngine( + """ + IEclipsePreferences#org-eclipse-jdt-ui:org-eclipse-ui-themes {\ + preferences: 'semanticHighlighting.abstractClass.color=128,255,0'\ + }""", + display); + engine.applyStyles(preferences, false); + + // Bug 466075 + assertEquals("128,255,0", preferences.get("semanticHighlighting.abstractClass.color", null)); + } + + @Test + void testDifferentPseudosOnSameNodeAllContribute() { + // Mirrors how org.eclipse.ui.editors and org.eclipse.ui.themes both + // contribute to the same preference node via different pseudo tags. + IEclipsePreferences preferences = new EclipsePreferences(null, "org.eclipse.ui.workbench") {}; + + engine = createEngine( + """ + IEclipsePreferences#org-eclipse-ui-workbench:org-eclipse-ui-editors {\ + preferences: 'org.eclipse.ui.editors.inlineAnnotationColor=155,155,155'\ + }\ + IEclipsePreferences#org-eclipse-ui-workbench:org-eclipse-ui-themes {\ + preferences: 'ERROR_COLOR=247,68,117'\ + }""", + display); + engine.applyStyles(preferences, false); + + assertEquals("155,155,155", preferences.get("org.eclipse.ui.editors.inlineAnnotationColor", null)); + assertEquals("247,68,117", preferences.get("ERROR_COLOR", null)); + } + + @Test + void testCascadeOverrideForSameKeyAndSamePseudoLastWins() { + // Two equal-specificity rules under the same pseudo write the same + // preference key with different values. The CSS cascade in viewCSS + // resolves the duplicate "preferences" property to the second rule's + // value before EclipsePreferencesHandler ever sees it, so the LATER + // rule wins. This matches standard CSS source-order tiebreak. + IEclipsePreferences preferences = new EclipsePreferences(null, "org.eclipse.jdt.ui") {}; + + engine = createEngine( + """ + IEclipsePreferences#org-eclipse-jdt-ui:org-eclipse-ui-themes {\ + preferences: 'semanticHighlighting.abstractClass.color=128,255,0'\ + }\ + IEclipsePreferences#org-eclipse-jdt-ui:org-eclipse-ui-themes {\ + preferences: 'semanticHighlighting.abstractClass.color=255,0,0'\ + }""", + display); + engine.applyStyles(preferences, false); + + assertEquals("255,0,0", preferences.get("semanticHighlighting.abstractClass.color", null)); + } +}