docs(calendar): complete German reference coverage
Module Package Release / publish-packages (push) Successful in 12s

This commit is contained in:
2026-08-23 20:19:13 +02:00
parent a2a9e8e814
commit 4f02425a28
6 changed files with 336 additions and 51 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@govoplan/calendar-webui",
"version": "0.1.18",
"version": "0.1.20",
"private": true,
"type": "module",
"main": "webui/src/index.ts",
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "govoplan-calendar"
version = "0.1.18"
version = "0.1.20"
description = "GovOPlaN calendar module with VEVENT storage and WebUI integration."
readme = "README.md"
requires-python = ">=3.12"
+1 -1
View File
@@ -2,4 +2,4 @@
__all__ = ["__version__"]
__version__ = "0.1.18"
__version__ = "0.1.20"
+289 -35
View File
@@ -6,7 +6,10 @@ from pathlib import Path
from sqlalchemy import inspect
from govoplan_calendar.backend.db import models as calendar_models # noqa: F401 - populate Calendar ORM metadata
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
from govoplan_core.core.access import (
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
)
from govoplan_core.core.calendar import (
CALENDAR_AVAILABILITY_READ_SCOPE,
CALENDAR_EVENT_WRITE_SCOPE,
@@ -15,7 +18,10 @@ from govoplan_core.core.calendar import (
CAPABILITY_CALENDAR_OUTBOX,
CAPABILITY_CALENDAR_SCHEDULING,
)
from govoplan_core.core.module_guards import drop_table_retirement_provider, persistent_table_uninstall_guard
from govoplan_core.core.module_guards import (
drop_table_retirement_provider,
persistent_table_uninstall_guard,
)
from govoplan_core.core.modules import (
CapabilityDocumentation,
DocumentationCondition,
@@ -110,6 +116,7 @@ CALENDAR_ARCHITECTURE = ModuleArchitectureDeclaration(
),
)
def _read_only_calendar_provider(
*,
provider_id: str,
@@ -249,13 +256,27 @@ CALENDAR_EXTERNAL_PROVIDERS = (
ProviderObjectDeclaration(
object_type="calendar_collection",
field_groups=("identity", "display", "sync_state", "resource_mapping"),
authority_modes=("external_authoritative", "external_mirror", "governed_sync"),
authority_modes=(
"external_authoritative",
"external_mirror",
"governed_sync",
),
default_authority_mode="governed_sync",
),
ProviderObjectDeclaration(
object_type="calendar_event",
field_groups=("identity", "schedule", "recurrence", "participants", "content"),
authority_modes=("external_authoritative", "external_mirror", "governed_sync"),
field_groups=(
"identity",
"schedule",
"recurrence",
"participants",
"content",
),
authority_modes=(
"external_authoritative",
"external_mirror",
"governed_sync",
),
default_authority_mode="governed_sync",
),
),
@@ -272,19 +293,37 @@ CALENDAR_EXTERNAL_PROVIDERS = (
outcome_unknown="A timed-out remote write is outcome-unknown and is reconciled before retry.",
outcome_unknown_supported=True,
evidence="The Open-Xchange profile reference, mapping references, operation intent, ETag, and reconciliation result are retained.",
audit_event_types=("calendar.sync.requested", "calendar.sync.completed", "calendar.sync.conflict", "calendar.sync.reconciled"),
audit_event_types=(
"calendar.sync.requested",
"calendar.sync.completed",
"calendar.sync.conflict",
"calendar.sync.reconciled",
),
correction="A later conditional update or tombstone corrects external state after reconciliation.",
rollback="Remote effects are not treated as transactionally rollback-safe.",
compensation="A compensating event update or delete may be queued after remote state is known.",
reconciliation="Read by CalDAV href and VEVENT UID, compare ETag and content, then classify the result.",
outage="The local projection remains available with stale markers and durable pending writes.",
classifications=("internal", "confidential", "restricted"),
purposes=("calendar collaboration", "availability", "resource booking", "meeting coordination"),
purposes=(
"calendar collaboration",
"availability",
"resource booking",
"meeting coordination",
),
retention="Calendar event, outbox, audit, profile-binding, and credential retention remain independent policies.",
secret_handling="Calendar stores only scoped credential references or encrypted Calendar-owned credentials.",
),
capability_names=(CAPABILITY_CALENDAR_EXTERNAL_PROFILES, CAPABILITY_CALENDAR_OUTBOX, CAPABILITY_CALENDAR_SCHEDULING),
interface_names=("calendar.external_profiles", "calendar.outbox", "calendar.scheduling"),
capability_names=(
CAPABILITY_CALENDAR_EXTERNAL_PROFILES,
CAPABILITY_CALENDAR_OUTBOX,
CAPABILITY_CALENDAR_SCHEDULING,
),
interface_names=(
"calendar.external_profiles",
"calendar.outbox",
"calendar.scheduling",
),
documentation_topic_ids=("calendar.external-sources-and-sync",),
),
_read_only_calendar_provider(
@@ -325,10 +364,18 @@ def _calendar_retirement_provider(session: object | None, module_id: str):
return plan
def executor(execute_session: object, execute_module_id: str) -> None:
if not hasattr(execute_session, "get_bind") or not hasattr(execute_session, "query"):
raise RuntimeError("No database session is available for Calendar credential retirement.")
if inspect(execute_session.get_bind()).has_table(calendar_models.CalendarSyncCredential.__tablename__):
from govoplan_calendar.backend.service import delete_calendar_credentials_for_retirement
if not hasattr(execute_session, "get_bind") or not hasattr(
execute_session, "query"
):
raise RuntimeError(
"No database session is available for Calendar credential retirement."
)
if inspect(execute_session.get_bind()).has_table(
calendar_models.CalendarSyncCredential.__tablename__
):
from govoplan_calendar.backend.service import (
delete_calendar_credentials_for_retirement,
)
delete_calendar_credentials_for_retirement(execute_session)
base_executor(execute_session, execute_module_id)
@@ -358,15 +405,51 @@ def _permission(scope: str, label: str, description: str) -> PermissionDefinitio
PERMISSIONS = (
_permission("calendar:calendar:read", "View calendars", "List tenant calendar collections and metadata."),
_permission("calendar:calendar:write", "Manage calendars", "Create and edit tenant calendar collections."),
_permission("calendar:calendar:admin", "Administer calendars", "Delete calendars and manage tenant-level calendar settings."),
_permission("calendar:event:read", "View calendar events", "List and inspect calendar events."),
_permission(CALENDAR_EVENT_WRITE_SCOPE, "Manage calendar events", "Create and edit calendar events."),
_permission("calendar:event:delete", "Delete calendar events", "Delete or cancel calendar events where policy allows it."),
_permission("calendar:event:import", "Import iCalendar events", "Import VEVENT data from iCalendar sources."),
_permission("calendar:event:export", "Export iCalendar events", "Export events as text/calendar VEVENT data."),
_permission(CALENDAR_AVAILABILITY_READ_SCOPE, "Read availability", "Read free/busy and availability data for integrations."),
_permission(
"calendar:calendar:read",
"View calendars",
"List tenant calendar collections and metadata.",
),
_permission(
"calendar:calendar:write",
"Manage calendars",
"Create and edit tenant calendar collections.",
),
_permission(
"calendar:calendar:admin",
"Administer calendars",
"Delete calendars and manage tenant-level calendar settings.",
),
_permission(
"calendar:event:read",
"View calendar events",
"List and inspect calendar events.",
),
_permission(
CALENDAR_EVENT_WRITE_SCOPE,
"Manage calendar events",
"Create and edit calendar events.",
),
_permission(
"calendar:event:delete",
"Delete calendar events",
"Delete or cancel calendar events where policy allows it.",
),
_permission(
"calendar:event:import",
"Import iCalendar events",
"Import VEVENT data from iCalendar sources.",
),
_permission(
"calendar:event:export",
"Export iCalendar events",
"Export events as text/calendar VEVENT data.",
),
_permission(
CALENDAR_AVAILABILITY_READ_SCOPE,
"Read availability",
"Read free/busy and availability data for integrations.",
),
)
ROLE_TEMPLATES = (
@@ -410,11 +493,32 @@ def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
)
return {
"calendars": session.query(CalendarCollection).filter(CalendarCollection.tenant_id == tenant_id, CalendarCollection.deleted_at.is_(None)).count(),
"calendar_events": session.query(CalendarEvent).filter(CalendarEvent.tenant_id == tenant_id, CalendarEvent.deleted_at.is_(None)).count(),
"calendar_sync_sources": session.query(CalendarSyncSource).filter(CalendarSyncSource.tenant_id == tenant_id, CalendarSyncSource.deleted_at.is_(None)).count(),
"calendar_sync_credentials": session.query(CalendarSyncCredential).filter(CalendarSyncCredential.tenant_id == tenant_id, CalendarSyncCredential.deleted_at.is_(None)).count(),
"calendar_view_preferences": session.query(CalendarViewPreference).filter(CalendarViewPreference.tenant_id == tenant_id).count(),
"calendars": session.query(CalendarCollection)
.filter(
CalendarCollection.tenant_id == tenant_id,
CalendarCollection.deleted_at.is_(None),
)
.count(),
"calendar_events": session.query(CalendarEvent)
.filter(
CalendarEvent.tenant_id == tenant_id, CalendarEvent.deleted_at.is_(None)
)
.count(),
"calendar_sync_sources": session.query(CalendarSyncSource)
.filter(
CalendarSyncSource.tenant_id == tenant_id,
CalendarSyncSource.deleted_at.is_(None),
)
.count(),
"calendar_sync_credentials": session.query(CalendarSyncCredential)
.filter(
CalendarSyncCredential.tenant_id == tenant_id,
CalendarSyncCredential.deleted_at.is_(None),
)
.count(),
"calendar_view_preferences": session.query(CalendarViewPreference)
.filter(CalendarViewPreference.tenant_id == tenant_id)
.count(),
"calendar_outbox_pending": session.query(CalendarOutboxOperation)
.filter(
CalendarOutboxOperation.tenant_id == tenant_id,
@@ -464,7 +568,9 @@ def _calendar_outbox_provider(context: ModuleContext) -> object:
def _calendar_external_profile_provider(context: ModuleContext) -> object:
del context
from govoplan_calendar.backend.capabilities import SqlCalendarExternalProfileProvider
from govoplan_calendar.backend.capabilities import (
SqlCalendarExternalProfileProvider,
)
return SqlCalendarExternalProfileProvider()
@@ -509,9 +615,22 @@ def _open_xchange_provider_states(context):
manifest = ModuleManifest(
id="calendar",
name="Calendar",
version="0.1.19",
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
optional_dependencies=("mail", "tasks", "scheduling", "appointments", "workflow_engine", "notifications", "dms", "connectors", "search"),
version="0.1.20",
required_capabilities=(
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
),
optional_dependencies=(
"mail",
"tasks",
"scheduling",
"appointments",
"workflow_engine",
"notifications",
"dms",
"connectors",
"search",
),
provides_interfaces=(
ModuleInterfaceProvider(name="calendar.outbox", version="0.1.8"),
ModuleInterfaceProvider(name="calendar.scheduling", version="0.1.9"),
@@ -578,7 +697,12 @@ manifest = ModuleManifest(
),
layer="configured",
documentation_types=("admin",),
audience=("privacy_officer", "calendar_manager", "records_manager", "operator"),
audience=(
"privacy_officer",
"calendar_manager",
"records_manager",
"operator",
),
order=17,
conditions=(
DocumentationCondition(
@@ -603,6 +727,29 @@ manifest = ModuleManifest(
),
),
related_modules=("access", "audit", "ops", "scheduling"),
translations={
"de": {
"title": "Calendar-Daten in einer Betroffenenanfrage prüfen",
"summary": (
"Mandantenbezogene Ereignis-, Teilnahme-, Präferenz-, Synchronisations- und Migrationsmetadaten erfassen, ohne "
"Connector-Geheimnisse offenzulegen."
),
"body": (
"Der DSAR-Provider von Calendar durchsucht den wirksamen Mandanten anhand normalisierter E-Mail-Adressen von "
"Organisierenden oder Teilnehmenden, direkter Mitgliedschaftsverweise und namensraumgebundener Calendar-Sammlungs- oder "
"Ereignisverweise. Er liefert nur den passenden Organisierenden- oder Teilnehmendenausschnitt zusammen mit berechtigtem "
"Ereignis- und Sammlungskontext, persönlichen Ansichtspräferenzen, eigener Synchronisationskonfiguration, dauerhaften "
"Outbox-Ergebnissen und Migrationsnachweisen. Ausgeschlossen sind rohe ICS- und vollständige iCalendar-Daten, "
"Sammlungs-URLs, entfernte hrefs und ETags, Synchronisationstoken, Zugangsdaten, verschlüsselte Geheimnisse, Worker-Leases, "
"Idempotenzmaterial, Provider-Fehlertexte sowie unbeteiligte Personen oder Ereignisse. Synchronisierte, korrelierte, "
"gelöschte, eingereihte und migrierte Zustände bleiben mit Begründung erhalten, weil sie institutionelle Liefer-, Abgleich- "
"oder Wiederherstellungsnachweise sind. Lokale Ereignis-, Sammlungs- und aktive Zugangsdatenmetadaten erfordern eine "
"koordinierte manuelle Prüfung. Nach erneuter Prüfung von Mandant und Eigentum darf der Provider die persönliche "
"Calendar-Ansichtspräferenz idempotent löschen; Ereignisse, Teilnehmerlisten, synchronisierte Ressourcen, Zugangsdaten "
"oder Migrationsdatensätze verändert er niemals direkt."
),
}
},
metadata={
"kind": "workflow",
"route": "/admin?section=tenant-data-subject-requests",
@@ -653,7 +800,10 @@ manifest = ModuleManifest(
),
}
},
metadata={"kind": "reference", "help_contexts": ["calendar.quick_access.agenda"]},
metadata={
"kind": "reference",
"help_contexts": ["calendar.quick_access.agenda"],
},
order=18,
),
DocumentationTopic(
@@ -670,6 +820,20 @@ manifest = ModuleManifest(
documentation_types=("admin", "user"),
audience=("user", "calendar_manager", "administrator"),
related_modules=("search",),
translations={
"de": {
"title": "Berechtigte Kalenderereignisse durchsuchen",
"summary": (
"Ereignistitel, Zeitpläne, Orte und Beschreibungen für die berechtigungsbewusste Plattform-Suche bereitstellen."
),
"body": (
"Ist Search installiert, liefert Calendar nicht gelöschte Ereignisse aus sichtbaren Kalendern. Jedes Ergebnis bleibt "
"mandantengebunden und prüft vor der Ausgabe erneut die aktuelle Leseberechtigung für Ereignisse sowie bei privaten "
"Kalendern Eigentum oder Gruppenmitgliedschaft. Festgeschriebene Ereignisänderungen aktualisieren den abgeleiteten Index "
"über die Plattform-Event-Outbox; ein Neuaufbau von Search verändert niemals Calendar-Daten."
),
}
},
order=19,
),
DocumentationTopic(
@@ -680,6 +844,24 @@ manifest = ModuleManifest(
documentation_types=("user",),
audience=("user", "calendar_manager"),
related_modules=("scheduling", "notifications"),
translations={
"de": {
"title": "Kalender und Ereignisse verwenden",
"summary": (
"Kalendersammlungen anlegen und ganztägige oder zeitgebundene Ereignisse in fortlaufender, Monats-, Wochen-, "
"Arbeitswochen- und Tagesansicht bearbeiten."
),
"body": (
"Calendar merkt sich gewählte Ansicht und Präferenzen. Ereignisse können mit der entsprechenden Berechtigung angelegt, "
"bearbeitet, verschoben, in ihrer Dauer geändert, wiederholt, importiert, exportiert oder gelöscht werden. Ganztägige "
"Ereignisse verwenden Datumswerte statt lokaler Uhrzeiten; zeitgebundene Ereignisse bewahren zeitzonenbezogene Start- und "
"Endwerte. Scheduling darf eine ausgewählte vorläufige Vormerkung direkt bestätigen und nicht verwendete Vormerkungen "
"über die neutrale Calendar-Fähigkeit idempotent freigeben. Calendar besitzt den entstehenden lokalen Löschmarker, die "
"synchronisierte Outbox-Operation sowie Wiederholungs- und Abgleichsnachweise; ein bereits freigegebenes oder fehlendes "
"Ereignis gilt bei Wiederholung als akzeptiert und nicht als doppelter Fehler."
),
}
},
metadata={
"kind": "reference",
"help_contexts": [
@@ -706,6 +888,29 @@ manifest = ModuleManifest(
documentation_types=("admin", "user"),
audience=("user", "calendar_manager", "operator"),
related_modules=("connectors", "audit", "ops"),
translations={
"de": {
"title": "Externe Kalender verbinden und synchronisieren",
"summary": (
"Calendar unterstützt lokale Sammlungen, bidirektionale CalDAV- und Open-Xchange-Profile sowie schreibgeschützte "
"ICS-/webcal-, Microsoft-Graph- und Exchange-Web-Services-Quellen."
),
"body": (
"Jede externe Quelle hält URL, Synchronisationsrichtung, Status und Zugangsdatenverweis gemeinsam mit der "
"Calendar-Sammlung. Open-Xchange verwendet den bewährten CalDAV-Transport und bewahrt dabei Connector-Profil-, "
"Identitäts-/Gruppenzuordnungs- und Ressourcenkalenderverweise. Manuelle und geplante Synchronisation zeichnet begrenzte "
"Ergebnisse auf. Geplante Quellen- und Outbox-Worker teilen Arbeit nach Mandantenberechtigung; wird Calendar deaktiviert, "
"bleiben angenommene Vorgänge erhalten und verlangen Betriebshandeln, statt einen entfernten Provider anzusprechen. "
"CalDAV-Schreibvorgänge verwenden bedingte Anfragen und dauerhaften Outbox-Zustand. Konflikte und unbekannte Ergebnisse "
"erfordern Synchronisation oder ausdrücklichen Abgleich statt blinder Wiederholung. Das Verschieben zwischen zwei "
"bidirektionalen CalDAV-Kalendern ist ein administrativ genehmigter Migrationsstapel: Alle Zielressourcen müssen kopiert "
"sein, bevor eine Quellressource bedingt mit ihrem aufgezeichneten ETag gelöscht wird. Änderungen an Kalender und "
"Ereignissen bleiben gesperrt, während Fortschritt, Konflikte, Abbruchmöglichkeit und Nachweise sichtbar sind. Das "
"Entfernen einer externen Quelle entfernt die Verbindung; das Löschen eines lokalen Kalenders löscht dessen Ereignisse "
"nach Bestätigung oder Übertragung."
),
}
},
metadata={
"kind": "reference",
"help_contexts": [
@@ -742,6 +947,23 @@ manifest = ModuleManifest(
),
),
related_modules=("campaigns", "mail", "audit"),
translations={
"de": {
"title": "Campaign-Einladungen und Antworten nachverfolgen",
"summary": (
"Akzeptierte Campaign-Einladungslieferungen als korrelierte VEVENTs spiegeln und den Teilnahmestatus in Calendar "
"maßgeblich halten."
),
"body": (
"Campaign kann vor der Zustellung einen METHOD:REQUEST-Anhang erzeugen und das korrelierte Calendar-Ereignis erst nach "
"Annahme der Lieferung anlegen oder aktualisieren. Calendar speichert Korrelation, PARTSTAT der Teilnehmenden, "
"Antwortzeit, begrenzte Nachweise, Synchronisationszustand und etwaiges eingeschränktes Verhalten. Beobachtet Mail einen "
"berechtigten IMAP-Ordner, werden METHOD:REPLY-Teile idempotent an Calendar weitergeleitet. Campaign-Berichte fragen den "
"aktuellen Zustand gesammelt ab, statt ihn in Campaign-Datensätze zu kopieren. Wiederkehrende Campaign-Einladungsserien "
"bleiben ein eigener Ablauf."
),
}
},
metadata={"kind": "workflow"},
),
DocumentationTopic(
@@ -759,6 +981,22 @@ manifest = ModuleManifest(
documentation_types=("admin", "user"),
audience=("calendar_manager", "operator", "tenant_admin"),
related_modules=("ops", "audit"),
translations={
"de": {
"title": "Synchronisierte Kalenderschreibvorgänge wiederherstellen",
"summary": (
"Ungelöste CalDAV-Schreibvorgänge prüfen und ausschließlich die neueste sichere Ressourcengeneration wiederherstellen."
),
"body": (
"Calendar-Administrierende öffnen Ausgehende Änderungen in den Einstellungen eines synchronisierten Kalenders. Die "
"begrenzte Historie erläutert Versuche, Konflikte, endgültig fehlgeschlagene Arbeit, veraltete Generationen, deaktivierte "
"Quellen und aktive Worker-Leases. Wiederholen reiht einen fehlgeschlagenen Sollzustand erneut ein, Abgleichen vergleicht "
"ihn mit der entfernten Ressource und Verwerfen gibt den lokalen Sollzustand nach einer getrennten Warnung auf, sodass die "
"nächste vollständige Synchronisation den entfernten Zustand übernehmen kann. Automatische Auslieferung und fällige "
"Quellenausführung bleiben reine Worker-Operationen für Dienstkonten und werden nicht als interaktive Steuerungen angeboten."
),
}
},
metadata={
"kind": "runbook",
"help_contexts": [
@@ -787,7 +1025,15 @@ manifest = ModuleManifest(
audience=("privacy_officer", "calendar_manager", "records_manager"),
),
},
nav_items=(NavItem(path="/calendar", label="Calendar", icon="calendar", required_any=("calendar:event:read",), order=55),),
nav_items=(
NavItem(
path="/calendar",
label="Calendar",
icon="calendar",
required_any=("calendar:event:read",),
order=55,
),
),
frontend=FrontendModule(
module_id="calendar",
package_name="@govoplan/calendar-webui",
@@ -799,7 +1045,15 @@ manifest = ModuleManifest(
order=55,
),
),
nav_items=(NavItem(path="/calendar", label="Calendar", icon="calendar", required_any=("calendar:event:read",), order=55),),
nav_items=(
NavItem(
path="/calendar",
label="Calendar",
icon="calendar",
required_any=("calendar:event:read",),
order=55,
),
),
view_surfaces=(
ViewSurface(
id="calendar.navigation",
+43 -12
View File
@@ -10,6 +10,14 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
class CalendarInterfaceDocumentationContractTests(unittest.TestCase):
def test_all_static_topics_have_complete_german_content(self) -> None:
for topic in get_manifest().documentation:
german = (topic.translations or {}).get("de", {})
self.assertEqual({"title", "summary", "body"}, set(german), topic.id)
self.assertTrue(
all(str(value).strip() for value in german.values()), topic.id
)
def test_backend_surfaces_and_hierarchy_remain_declared(self) -> None:
frontend = get_manifest().frontend
self.assertIsNotNone(frontend)
@@ -31,13 +39,21 @@ class CalendarInterfaceDocumentationContractTests(unittest.TestCase):
}
self.assertEqual(expected, set(surfaces))
self.assertEqual("calendar.page", surfaces["calendar.page.sidebar"].parent_id)
self.assertEqual("calendar.page.sidebar", surfaces["calendar.page.agenda"].parent_id)
self.assertEqual(
"calendar.page.sidebar", surfaces["calendar.page.agenda"].parent_id
)
self.assertEqual("calendar.page", surfaces["calendar.page.workspace"].parent_id)
self.assertEqual("calendar.page", surfaces["calendar.event-editor"].parent_id)
self.assertEqual("calendar.page.sidebar", surfaces["calendar.collection-editor"].parent_id)
self.assertEqual("calendar.collection-editor", surfaces["calendar.sync-status"].parent_id)
self.assertEqual(
"calendar.page.sidebar", surfaces["calendar.collection-editor"].parent_id
)
self.assertEqual(
"calendar.collection-editor", surfaces["calendar.sync-status"].parent_id
)
self.assertEqual("calendar.sync-status", surfaces["calendar.outbox"].parent_id)
self.assertEqual("calendar.sync-status", surfaces["calendar.migration"].parent_id)
self.assertEqual(
"calendar.sync-status", surfaces["calendar.migration"].parent_id
)
def test_help_and_consequence_metadata_remain_published(self) -> None:
topics = {topic.id: topic for topic in get_manifest().documentation}
@@ -63,9 +79,9 @@ class CalendarInterfaceDocumentationContractTests(unittest.TestCase):
quick_access = (
REPO_ROOT / "webui/src/features/calendar/CalendarQuickAccess.tsx"
).read_text(encoding="utf-8")
page = (
REPO_ROOT / "webui/src/features/calendar/CalendarPage.tsx"
).read_text(encoding="utf-8")
page = (REPO_ROOT / "webui/src/features/calendar/CalendarPage.tsx").read_text(
encoding="utf-8"
)
self.assertIn("const AGENDA_LIMIT = 7", quick_access)
self.assertIn("launchContext.temporalContext", quick_access)
@@ -75,13 +91,28 @@ class CalendarInterfaceDocumentationContractTests(unittest.TestCase):
self.assertIn('parameters.get("quickAction") !== "create-event"', page)
def test_webui_uses_shared_help_guard_and_confirmation_components(self) -> None:
event_dialog = (REPO_ROOT / "webui/src/features/calendar/CalendarEventDialog.tsx").read_text(encoding="utf-8")
collection_dialog = (REPO_ROOT / "webui/src/features/calendar/CalendarCollectionDialogs.tsx").read_text(encoding="utf-8")
settings_panel = (REPO_ROOT / "webui/src/features/calendar/CalendarSettingsPanel.tsx").read_text(encoding="utf-8")
event_dialog = (
REPO_ROOT / "webui/src/features/calendar/CalendarEventDialog.tsx"
).read_text(encoding="utf-8")
collection_dialog = (
REPO_ROOT / "webui/src/features/calendar/CalendarCollectionDialogs.tsx"
).read_text(encoding="utf-8")
settings_panel = (
REPO_ROOT / "webui/src/features/calendar/CalendarSettingsPanel.tsx"
).read_text(encoding="utf-8")
for component in ("ActionBlockerHint", "ConfirmDialog", "DocumentationHelpLink", "useUnsavedDraftGuard"):
for component in (
"ActionBlockerHint",
"ConfirmDialog",
"DocumentationHelpLink",
"useUnsavedDraftGuard",
):
self.assertIn(component, event_dialog)
for component in ("ActionBlockerHint", "DocumentationHelpLink", "useUnsavedDraftGuard"):
for component in (
"ActionBlockerHint",
"DocumentationHelpLink",
"useUnsavedDraftGuard",
):
self.assertIn(component, collection_dialog)
for component in ("DocumentationHelpLink", "useUnsavedDraftGuard"):
self.assertIn(component, settings_panel)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@govoplan/calendar-webui",
"version": "0.1.18",
"version": "0.1.20",
"private": true,
"type": "module",
"main": "src/index.ts",