test(webui): cover contextual help in the browser

This commit is contained in:
2026-08-21 17:36:06 +02:00
parent fbea74a74b
commit 5a9e8f79f9
7 changed files with 124 additions and 12 deletions
+21
View File
@@ -48,6 +48,12 @@ module and server restrictions, lower-scope visibility, activation, save, and
irreversible deletion. This ensures F1 explains secret custody and the effect on
dependent connections from system, tenant, group, user, and personal surfaces.
The source inventory treats literal `helpContextId` and
`data-help-context-id` declarations as authored help associations, including a
native control nested in `FormField`. Dynamic context expressions remain
separate evidence and generic derived fallbacks remain in the richer-help
candidate queue.
The generated `help_review_candidates` list is therefore a content-depth queue,
not a list of controls on which F1 cannot work. It should prioritize:
@@ -57,6 +63,14 @@ not a list of controls on which F1 cannot work. It should prioritize:
4. provider authority, synchronization, conflict, and outcome unknown;
5. fields whose consequences are not evident from their label.
The shared browser conformance journey mounts the production Help menu and
resolver. It proves that F1 uses the focused control rather than only the page,
maps an exact retention action to Policy-owned administrator documentation,
retains the page context as fallback for derived actions, exposes an accessible
modal at narrow widths, closes with Escape, and restores focus to the triggering
control. Module journeys should add their own exact high-risk mappings; they do
not need to reimplement the keyboard or dialog mechanics.
## Verification
```bash
@@ -74,3 +88,10 @@ The check must report:
- no duplicate stable IDs;
- no undeclared public WebUI surface;
- no stale runtime route or endpoint declaration.
Browser acceptance is part of the focused workspace gate and can be run alone:
```bash
cd /mnt/DATA/git/govoplan-core/webui
npm run test:conformance
```
+28
View File
@@ -26,6 +26,7 @@ import WorkspaceFrame from "../src/components/WorkspaceFrame";
import WorkspaceLayout from "../src/components/WorkspaceLayout";
import WorkspaceActionBar from "../src/components/WorkspaceActionBar";
import BreadcrumbBar from "../src/layout/BreadcrumbBar";
import HelpMenu from "../src/layout/HelpMenu";
import { useGuardedNavigate } from "../src/components/UnsavedChangesGuard";
import {
createQuickAccessLaunchContext,
@@ -149,6 +150,7 @@ export default function ConformanceApp() {
</section>
<LaunchContextScenario />
{new URLSearchParams(location.search).has("help") ? <HelpConformanceScenario /> : null}
</PageLayout>
<Dialog
@@ -170,6 +172,32 @@ export default function ConformanceApp() {
);
}
function HelpConformanceScenario() {
return (
<section className="conformance-section" aria-labelledby="help-heading">
<h2 id="help-heading">Kontextsensitive Hilfe</h2>
<p>F1 löst den Hilfekontext des fokussierten Bedienelements auf und kehrt nach dem Schließen dorthin zurück.</p>
<ActionToolbar surface="subtle">
<Button
variant="danger"
data-testid="f1-retention-action"
helpContextId="policy.retention.action.apply"
helpModuleId="policy"
>
Aufbewahrungsregeln anwenden
</Button>
<Button
data-testid="f1-fallback-action"
interfaceId="core.conformance.action.review"
>
Allgemeine Aktion prüfen
</Button>
<HelpMenu auth={null} />
</ActionToolbar>
</section>
);
}
function QuickAccessScenario() {
const location = useLocation();
const mode = new URLSearchParams(location.search).get("quick-access");
+3
View File
@@ -0,0 +1,3 @@
const installedModules: Array<never> = [];
export default installedModules;
+22 -3
View File
@@ -3,6 +3,9 @@ import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router";
import ConformanceApp from "./ConformanceApp";
import { UnsavedChangesProvider } from "../src/components/UnsavedChangesGuard";
import { PlatformLanguageProvider } from "../src/i18n/LanguageContext";
import { PlatformModulesProvider } from "../src/platform/ModuleContext";
import type { PlatformWebModule } from "../src/types";
import "../src/styles/tokens.css";
import "../src/styles/layout.css";
import "../src/styles/forms.css";
@@ -16,12 +19,28 @@ import "./conformance.css";
const theme = new URLSearchParams(window.location.search).get("theme");
if (theme === "dark" || theme === "light") document.documentElement.dataset.theme = theme;
const CONFORMANCE_MODULES: PlatformWebModule[] = [{
id: "policy",
label: "Richtlinien",
version: "1",
helpContexts: [{
id: "policy.retention.action.apply",
topic_id: "policy.admin.retention-and-disposition",
title: "Aufbewahrungsregeln sicher anwenden",
documentation_types: ["admin"]
}]
}];
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<BrowserRouter>
<UnsavedChangesProvider>
<ConformanceApp />
</UnsavedChangesProvider>
<PlatformModulesProvider modules={CONFORMANCE_MODULES}>
<PlatformLanguageProvider preferredLanguageCode="de">
<UnsavedChangesProvider>
<ConformanceApp />
</UnsavedChangesProvider>
</PlatformLanguageProvider>
</PlatformModulesProvider>
</BrowserRouter>
</React.StrictMode>
);
@@ -110,6 +110,44 @@ test("Quick Access preserves focus and adapts to a narrow viewport", async ({ pa
await expect(filesTrigger).toBeFocused();
});
test("F1 resolves focused high-risk help and restores focus accessibly", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto("/?theme=light&help=1");
const retentionAction = page.getByTestId("f1-retention-action");
await retentionAction.focus();
await page.keyboard.press("F1");
const dialog = page.getByRole("dialog", { name: "Kontexthilfe" });
await expect(dialog).toBeVisible();
await expect(dialog.locator("[data-help-context]"))
.toHaveAttribute("data-help-context", "policy.retention.action.apply");
await expect(dialog.getByRole("heading", { level: 3 }))
.toHaveText("Aufbewahrungsregeln anwenden");
await expect(dialog).toContainText("topic=policy.admin.retention-and-disposition");
await expect(dialog.getByRole("button", { name: "Administrator-Dokumentation öffnen" }))
.toBeVisible();
await expectNoAccessibilityViolations(page);
const bounds = await dialog.boundingBox();
expect(bounds?.x).toBeGreaterThanOrEqual(0);
expect((bounds?.x ?? 0) + (bounds?.width ?? 0)).toBeLessThanOrEqual(390);
await page.keyboard.press("Escape");
await expect(dialog).toBeHidden();
await expect(retentionAction).toBeFocused();
const fallbackAction = page.getByTestId("f1-fallback-action");
await fallbackAction.focus();
await page.keyboard.press("F1");
const fallbackDialog = page.getByRole("dialog", { name: "Kontexthilfe" });
await expect(fallbackDialog.locator("[data-help-context]"))
.toHaveAttribute("data-help-context", "core.conformance.action.review");
await expect(fallbackDialog).toContainText("fallback_context=campaigns.list");
await page.keyboard.press("Escape");
await expect(fallbackAction).toBeFocused();
});
test("View focus has a deliberate permission-derived all-tools escape", async ({ page }) => {
await page.route("**/api/v1/quick-access/effective*", async (route) => {
await route.fulfill({
+11 -9
View File
@@ -26,6 +26,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.restore_safe_defaults": "Restore safe defaults",
"i18n:govoplan-core.import_overrides": "Import JSON",
"i18n:govoplan-core.export_overrides": "Export JSON",
"i18n:govoplan-core.explanation_subject.6ff46d22": "Explanation subject",
"i18n:govoplan-core.remove_overrides": "Remove overrides",
"i18n:govoplan-core.appearance_override_not_configured_help": "No personal token overrides are stored. Preset and policy inheritance remain active.",
"i18n:govoplan-core.appearance_override_policy_disabled": "The governing appearance policy currently blocks creating or editing personal overrides. Existing overrides may still be exported or removed.",
@@ -761,6 +762,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.restore_safe_defaults": "Sichere Standardwerte wiederherstellen",
"i18n:govoplan-core.import_overrides": "JSON importieren",
"i18n:govoplan-core.export_overrides": "JSON exportieren",
"i18n:govoplan-core.explanation_subject.6ff46d22": "Person für die Zugriffserklärung",
"i18n:govoplan-core.remove_overrides": "Anpassungen entfernen",
"i18n:govoplan-core.appearance_override_not_configured_help": "Es sind keine persönlichen Token-Anpassungen gespeichert. Vorgaben und Richtlinienvererbung bleiben aktiv.",
"i18n:govoplan-core.appearance_override_policy_disabled": "Die geltende Darstellungsrichtlinie sperrt derzeit das Anlegen oder Bearbeiten persönlicher Anpassungen. Bestehende Anpassungen können weiterhin exportiert oder entfernt werden.",
@@ -936,7 +938,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.choose_date.e7877fd9": "Choose date",
"i18n:govoplan-core.clear_filter.b667d6f9": "Clear filter",
"i18n:govoplan-core.close_filter.3a281c3f": "Close filter",
"i18n:govoplan-core.close.bbfa773e": "Close",
"i18n:govoplan-core.close.bbfa773e": "Schließen",
"i18n:govoplan-core.collapse.9cf188d3": "Collapse",
"i18n:govoplan-core.color_picker.91dee962": "Color picker",
"i18n:govoplan-core.comfortable.2313707a": "Comfortable",
@@ -950,7 +952,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.connection_security_mode_expected_by_the_server_.4348e12e": "Connection security mode expected by the server: plain, TLS or STARTTLS.",
"i18n:govoplan-core.connection_successful_the_backend_health_endpoin.14eb9295": "Connection successful. The backend health endpoint responded.",
"i18n:govoplan-core.contains.33e15d00": "Contains",
"i18n:govoplan-core.context_help.7fc44ea1": "Context help",
"i18n:govoplan-core.context_help.7fc44ea1": "Kontexthilfe",
"i18n:govoplan-core.controls_contextual_ui_help_markers_once_persist.0eaef9c4": "Controls contextual UI help markers once persisted user preferences are available.",
"i18n:govoplan-core.core_only.d46ce7d9": "Core only",
"i18n:govoplan-core.core_shell_only.4949b5d5": "Core shell only",
@@ -1047,9 +1049,9 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.group.171a0606": "Group",
"i18n:govoplan-core.groups": "Gruppen",
"i18n:govoplan-core.headers.520de744": "Headers",
"i18n:govoplan-core.help_context.61aed3b9": "Help context:",
"i18n:govoplan-core.help_context.61aed3b9": "Hilfekontext:",
"i18n:govoplan-core.help_f1": "Hilfe (F1)",
"i18n:govoplan-core.help.c47ae153": "Help",
"i18n:govoplan-core.help.c47ae153": "Hilfe",
"i18n:govoplan-core.hh_mm.a4c7ee9b": "HH:MM",
"i18n:govoplan-core.hide_password.e40123b4": "Hide password",
"i18n:govoplan-core.high_level_campaign_pattern_used_by_guided_setup.c5ec6652": "High-level campaign pattern used by guided setup to prefill sensible defaults.",
@@ -1174,9 +1176,9 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.on.e0049a66": "On",
"i18n:govoplan-core.only_users_with_maintenance_access_can_use_the_s.5566264f": "Only users with maintenance access can use the system right now.",
"i18n:govoplan-core.open_address_form.f8ee560f": "Open address form",
"i18n:govoplan-core.open_admin_documentation.6adbdae3": "Open admin documentation",
"i18n:govoplan-core.open_admin_documentation.6adbdae3": "Administrator-Dokumentation öffnen",
"i18n:govoplan-core.open_maintenance_mode_settings.99b41249": "Open maintenance mode settings",
"i18n:govoplan-core.open_user_documentation.084af515": "Open user documentation",
"i18n:govoplan-core.open_user_documentation.084af515": "Benutzerdokumentation öffnen",
"i18n:govoplan-core.optional_backend_url_leave_empty_to_use_the_curr.52bd2bb4": "Optional backend URL. Leave empty to use the current origin and the configured Vite/backend proxy.",
"i18n:govoplan-core.optional_html_version_of_the_email_body_for_rich.3f01db97": "Optional HTML version of the email body for richer formatting.",
"i18n:govoplan-core.optional_local_alias_for_the_active_tenant_it_do.edbb7b14": "Optional local alias for the active tenant. It does not replace the global account name in the title bar.",
@@ -1184,7 +1186,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.owner.89ff3122": "Eigentümer",
"i18n:govoplan-core.owner_policy.1e8df143": "Owner policy",
"i18n:govoplan-core.p_no_html_body_content_p.c305bfc6": "<p>No HTML body content.</p>",
"i18n:govoplan-core.page.fb06270f": "Page",
"i18n:govoplan-core.page.fb06270f": "Seite",
"i18n:govoplan-core.password_for_the_selected_account_or_mail_server.d9681f16": "Password for the selected account or mail server. Stored and transmitted according to the configured backend flow.",
"i18n:govoplan-core.password_protected.09d9e174": "Password protected",
"i18n:govoplan-core.password.8be3c943": "Passwort",
@@ -1346,12 +1348,12 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.theme_and_language.60a8cf59": "Design und Sprache",
"i18n:govoplan-core.theme.a797e309": "Theme",
"i18n:govoplan-core.this_address_is_already_listed.0f6d0ff2": "This address is already listed.",
"i18n:govoplan-core.this_area_is_prepared_for_context_sensitive_help.57665877": "This area is prepared for context-sensitive help. Future help content can use this context identifier, or the equivalent help query parameter",
"i18n:govoplan-core.this_area_is_prepared_for_context_sensitive_help.57665877": "Das fokussierte Bedienelement verweist auf den folgenden dokumentierten Hilfekontext:",
"i18n:govoplan-core.this_global_name_is_shown_in_the_title_bar_and_f.1d46240e": "This global name is shown in the title bar and follows you across tenant memberships.",
"i18n:govoplan-core.this_module_is_prepared_but_not_implemented_yet.6e6449ee": "Dieses Modul ist vorbereitet, aber noch nicht umgesetzt.",
"i18n:govoplan-core.this_page_has_unsaved_changes_save_them_before_l.419a9d8b": "This page has unsaved changes. Save them before leaving, or discard the changes and continue.",
"i18n:govoplan-core.timeout_seconds.0bfc6553": "Timeout seconds",
"i18n:govoplan-core.to_open_the_right_page_or_section.5ecf4fd2": "to open the right page or section.",
"i18n:govoplan-core.to_open_the_right_page_or_section.5ecf4fd2": "Öffnen Sie die verknüpfte Dokumentation für Berechtigungen, Folgen und Hinweise zur Wiederherstellung.",
"i18n:govoplan-core.to.ae79ea1e": "To",
"i18n:govoplan-core.tu.b44892b7": "Tu",
"i18n:govoplan-core.type_a_name_and_email_address_then_press_enter.7c8d43f0": "Type a name and email address, then press Enter",
+1
View File
@@ -8,6 +8,7 @@ export default defineConfig({
resolve: {
alias: {
"@govoplan/core-webui": resolve(import.meta.dirname, "conformance/QuickAccessCoreFacade.ts"),
"virtual:govoplan-installed-modules": resolve(import.meta.dirname, "conformance/installedModules.ts"),
react: resolve(import.meta.dirname, "node_modules/react"),
"react-router": resolve(import.meta.dirname, "node_modules/react-router"),
"lucide-react": resolve(import.meta.dirname, "node_modules/lucide-react")