test: #3618216 Add kernel and functional tests for the Admin theme

What the MR adds

Kernel tests (core/themes/default_admin/tests/src/Kernel/)

Class Cases Covers
PreprocessHooksTest 84 Every #[Hook('preprocess_*')] method in PreprocessHooks: admin blocks, block, datetime wrapper, details, entity and node add lists, field multiple value form, fieldset, file widget multiple, form element, input (text, boolean, autocomplete, title attribute), links (theme operations, action links, dropbutton, keys as classes), menu local action and tasks, page title, select, status messages, system themes page, table (sortable header, draggable rows), text format wrapper, views view table
FormHooksTest 34 formAlter() branches (entity delete links, views form flex layout, delete form buttons), media and media library add forms, menu link content form layout, node form (sidebar, sticky actions for all three core_admin_theme_use_sticky_action_buttons values, after-build), system modules and theme settings forms, user admin permissions and user form
ThemeHooksTest 22 elementInfoAlter() ordering, container, operations, textFormat (with and without summary, description toggle in and outside content forms), managedFile, messagePlaceholder, themeRegistryAlter(), pageAttachmentsAlter()
HelperTest 19 isContentForm() for shipped routes, module-added routes, altered routes and ignored form IDs, formActions(), isActive(), convertLinkToActionLink(), static caching per method
SettingsTest 16 Defaults from the admin theme, per-user overrides in user.data, userOverrideEnabled(), setAll() and clear(), getSettingsForm() for account and theme settings form
ThemeSuggestionHooksTest 12 Page, form element, region and views suggestions including the page__node de-duplication
ViewsHooksTest 12 viewsPreRender() for the media library and content views, bulk form handling, table markers

A new test module default_admin_test (tests/modules/default_admin_test/) implements hook_default_admin_content_form_routes(), hook_default_admin_content_form_routes_alter() and hook_default_admin_content_form_ignore_form_ids() from default_admin.api.php, so the alter contract is covered from the module side.

Functional tests (core/themes/default_admin/tests/src/Functional/)

Class Cases Covers
NodeFormTest 5 Node edit form layout (two regions, meta sidebar, close trigger), plain page on non-node routes, sticky actions always, content_forms and with navigation installed
ThemeSettingsFormTest 5 The theme settings form alters only default_admin, submit persists and applies, user override section visibility, per-user overrides, disabling overrides clears them
AdminPagesTest 9 Node and block content add lists, admin page blocks (admin-list--panel), appearance page action links, status report, modules page, permissions page, menu link content form layout, sortable and draggable tables
AdminTest (existing) 6 Removed the dead disabledTestUserSettings(); the root user page is loaded through Url::fromRoute('entity.user.edit_form') because $this->rootUser is a UserSession

Functional Javascript tests (core/themes/default_admin/tests/src/FunctionalJavascript/)

Class Tests What it pins
TableDragTest 8 The forked drupal.tabledrag: Drupal.tableDrag.showWeight set on show and removed on hide, toggle wrapper inserted before .table-scroll-wrapper, handle anchor without inner div.handle, svg.tree indentation instead of core's  , changed marker as the handle's next sibling, div.tabledrag-cell-content > __item rewrap, body.drag/drag-y, tree-child classes
TableSelectTest 3 data-drupal-sticky-vbo flips, bulk-operations row marking, tr.selected incl. the shift-click range, select-all title flip, clone sync, and a behavioral guard that the theme's misc/tableselect.js replacement is the file actually served
TableHeaderTest 4 Clone revealed, --is-processed, per-column width parity incl. after resize, scrollLeft mirroring both directions
SidebarTest 5 body[data-meta-sidebar], trigger label/aria-expanded/is-active, both sidebarExpanded.* keys with the 1024 px branch measured, ALT+S, drag-resize clamped to 240–560 px writing --admin-theme-sidebar-width + sidebarWidth
StickyFormActionsTest 5 Cloned buttons bound to the real form id, clicking the copy really saves a node, preview relays, Ajax buttons get the mousedown, and the focus shuttle both ways
AppearanceBootstrapTest 9 window.defaultAdminDarkMode, html.dark-mode, stored sidebar width applied at load, style.sidebar-inline-styles injected/skipped, data-admin-focus, style.custom-focus incl. the exact mixed dark value
SettingsFormTest 8 Live preview on both routes: dark mode, high contrast (on <body>, mismatch commented as a defect), accent/focus picker sync both ways, presets, custom colors
DescriptionToggleTest 3 Both halves: preprocess forces description_display: invisible and prints the help button; the behavior labels it and toggles visually-hidden/aria-expanded/aria-hidden; off-state asserted
Nightwatch Corrected the impossible .messages-list__wrapper container to .messages__wrapper; added is-horizontal, data-width, is-open, data-original-order to the tabs spec; added the positive is-autocompleting path
MediaLibraryTest 3 Counter is the first child of the dialog button pane, select-all drives every item + announcements + sticky bar, and the form-less grid no longer aborts
ThemeOverridesTest 8 checkbox, dropbutton (markup + fixed positioning), vertical-tabs, details, password strength, message dismiss, media embed error markup

Bugs fixed in the MR

The tests fail against the original code for these three, so the fixes are in the MR.

  1. PreprocessHooks::preprocessLinks() iterated $variables['links'] by value. The action-link classes and key-based classes it computes never reached the render array. Fixed with foreach (... as &$links_item) and unset($links_item).
  2. ThemeHooks::elementInfoAlter() registered [ThemeHooks::class, 'textFormat'] twice on text_format (once through the loop, once through an array_key_exists() block). The callback is not idempotent, so the wrapper classes were added twice. The second registration is removed.
  3. templates/dataset/table.html.twig decided between th__item and th__<label> with '<a' in cell.content|render|render. Since #2334319 (2c8c0a2efb2) TwigExtension::renderVar() returns MarkupInterface objects unchanged, and Twig's in operator only stringifies \Twig\Markup, so the comparison was always FALSE. Sortable headers received th__a-href-... classes built from the whole anchor markup. The template now compares against cell.content|render|trim, which yields a plain string (same idiom as olivero/templates/content/page-title.html.twig).
  4. description_toggle library had no dependencies - CI artifact proved description_toggle.js was served before core/misc/drupal.js, so Drupal was undefined and the feature was dead in the shipped theme. Audited every library: 13 missing dependency lines added (core/drupal for accent, settings, sidebar, more_actions, navigation, tableheader, escape_admin; core/drupal.debounce for sidebar, dropbutton, tableheader; core/drupal.displace for navigation). core/drupalSettings does not depend on core/drupal, so those entries were genuinely unguarded.
  5. media_library.view.js dereferenced three nulls - a media library page display without a bulk form throws a TypeError that also kills the per-item click binding. Guarded in the file's own optional-chaining style.

Additional issues fixed in this MR

The issues from this comment have also been fixed.

Disclosure: I used the help of an LLM to spin up the tests.

Edited by Jürgen Haas

Merge request reports

Loading
Loading