diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 87c6d71..2170c07 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,10 +26,21 @@ jobs: uses: actions/checkout@v7 with: path: app + - name: LocalBase-Referenz bestimmen + id: localbase-ref + env: + CANDIDATE_REF: ${{ github.head_ref || github.ref_name }} + run: | + if git ls-remote --exit-code --heads https://github.com/Filzmann/nextcloud-localbase.git "refs/heads/$CANDIDATE_REF" >/dev/null 2>&1; then + echo "ref=$CANDIDATE_REF" >> "$GITHUB_OUTPUT" + else + echo "ref=main" >> "$GITHUB_OUTPUT" + fi - name: LocalBase auschecken uses: actions/checkout@v7 with: repository: Filzmann/nextcloud-localbase + ref: ${{ steps.localbase-ref.outputs.ref }} path: localbase - name: PHP einrichten uses: shivammathur/setup-php@v2 @@ -50,10 +61,21 @@ jobs: uses: actions/checkout@v7 with: path: app + - name: LocalBase-Referenz bestimmen + id: localbase-ref + env: + CANDIDATE_REF: ${{ github.head_ref || github.ref_name }} + run: | + if git ls-remote --exit-code --heads https://github.com/Filzmann/nextcloud-localbase.git "refs/heads/$CANDIDATE_REF" >/dev/null 2>&1; then + echo "ref=$CANDIDATE_REF" >> "$GITHUB_OUTPUT" + else + echo "ref=main" >> "$GITHUB_OUTPUT" + fi - name: LocalBase auschecken uses: actions/checkout@v7 with: repository: Filzmann/nextcloud-localbase + ref: ${{ steps.localbase-ref.outputs.ref }} path: localbase - name: Node.js einrichten uses: actions/setup-node@v6 diff --git a/AGENTS.md b/AGENTS.md index f42ea0b..8a06835 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,8 @@ Nextcloud-App-ID: - Raumloeschungen loeschen die zugehoerigen Buchungen. Die UI muss diese Auswirkung vor der Aktion deutlich bestaetigen. - Samstage, Sonntage und gesetzliche Feiertage in Berlin werden in der Monatsansicht textlich und optisch gekennzeichnet. - Der WordPress-Raumplaner ist nur fachliche Referenz. WordPress-IDs, Capabilities, Nonces, Shortcodes und Tabellen werden nicht uebernommen. +- WordPress-Bestandsdaten werden nicht importiert. Der app-eigene Adminabschnitt installiert neutrale Räume und Buchungen ausschließlich als manuell bestätigten synthetischen Demo-Pack. +- Beispielbuchungen gehören einem explizit registrierten lokalen Demokonto; ein vorhandenes fremdes oder LDAP-verwaltetes Konto wird niemals dafür wiederverwendet. ## Architektur und Sicherheit @@ -38,9 +40,9 @@ Nextcloud-App-ID: ## Gemeinsame Suite-Navigation -- AD Raumplaner besitzt keinen eigenen Nextcloud-Hauptnavigationseintrag. -- `orgsuite` stellt den Einstieg `AD` und das zentrale Menue bereit. -- Das Template bindet `data-suite="ad"` und `data-current-app="adroom"` ein. +- Ohne aktive OrgSuite registriert AD Raumplaner einen eigenen Nextcloud-Hauptnavigationseintrag. Ab zwei AD-Produkten ersetzt `orgsuite` diesen durch den gemeinsamen Einstieg `AD`. +- Das Template stellt den optionalen Menühost mit `data-suite="ad"` und `data-current-app="adroom"` bereit, lädt aber keine OrgSuite-Assets direkt. +- Ohne Kalender oder Assistenzplanung bleiben Raumbuchungen vollständig manuell nutzbar; optionale Direktbuchungen dürfen nicht als harte Abhängigkeit modelliert werden. - Menuesichtbarkeit ist keine Berechtigung. ## Git, DDEV und Tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 48ef585..9a4c42f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.9.0-rc.1 + +- Eigenständige Navigation ohne OrgSuite ergänzt. +- Raumverfügbarkeits- und Buchungsfähigkeiten über optionale LocalBase-Verträge veröffentlicht. +- Ungültige harte App-Abhängigkeiten aus den Nextcloud-Metadaten entfernt. + ## 0.8.8-rc.1 - Eigener Nextcloud-Adminabschnitt für die ausschließlich appbezogene Raumverwaltung. diff --git a/README.md b/README.md index 4a20f65..427834e 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,14 @@ Monatliche, zeitlich ausgerichtete Raumbelegung mit kollisionsfreien Buchungen, - Nextcloud 34 - PHP 8.3 oder neuer innerhalb des von Nextcloud 34 unterstützten Bereichs -- Abhängigkeiten: `localbase`, `orgsuite` +- Laufzeitbasis: `localbase`; `orgsuite` ist ab zwei AD-Fachprodukten optional aktiv - App-ID und Installationsordner: `adroom` ## Installation -```bash -sudo -u www-data php occ app:enable localbase -sudo -u www-data php occ app:enable orgsuite -sudo -u www-data php occ app:enable adroom -``` +Für Staging und Auslieferung das Produktbundle `ad-product-adroom-.tar.gz` und dessen enthaltenes `install.sh` verwenden. Es prüft und installiert LocalBase automatisch; ab dem zweiten AD-Fachprodukt aktiviert es OrgSuite. + +AD Raumplaner funktioniert einzeln; Buchungen bleiben ohne Kalender oder Assistenzplanung manuell nutzbar. Räume werden nach der Aktivierung im eigenen Nextcloud-Adminabschnitt `AD Raumplaner` eingerichtet. `adroom:demo:seed` ist ausschließlich für synthetische Testdaten bestimmt. diff --git a/appinfo/info.xml b/appinfo/info.xml index 7d2eafe..d7ac4b2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -5,7 +5,7 @@ AD Raumplaner Monatliche Raumbelegung und kollisionsfreie Raumbuchungen. Verwaltet Räume und eigene beziehungsweise administrative Buchungen in einer kompakten Monatsmatrix. - 0.8.8-rc.1 + 0.9.0-rc.2 agpl Simon https://github.com/Filzmann/ad-suite @@ -16,8 +16,6 @@ - localbase - orgsuite OCA\AdRoom\Settings\Admin diff --git a/appinfo/routes.php b/appinfo/routes.php index ae59c7f..1c391fd 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -11,5 +11,5 @@ ['name' => 'api#createRoom', 'url' => '/api/rooms', 'verb' => 'POST'], ['name' => 'api#updateRoom', 'url' => '/api/rooms/{id}', 'verb' => 'PUT'], ['name' => 'api#deleteRoom', 'url' => '/api/rooms/{id}', 'verb' => 'DELETE'], + ['name' => 'demo_admin#install', 'url' => '/api/admin/demo-pack/install', 'verb' => 'POST'], ]]; - diff --git a/css/style.css b/css/style.css index b4125f7..b4eb70a 100644 --- a/css/style.css +++ b/css/style.css @@ -49,6 +49,10 @@ .adr-icon-button:focus-visible { outline:2px solid var(--color-primary-element); outline-offset:2px; } .adr-add { flex:0 0 auto; } .adr-room-form { margin-top:16px; padding:12px; border:1px solid var(--color-border); border-radius:var(--border-radius-large); } +.adr-admin-demo { margin-top:24px; padding-top:18px; border-top:1px solid var(--color-border); } +.adr-demo-confirm { display:block; margin:16px 0 10px; } +.adr-notice.is-success { border:2px solid var(--color-success); color:var(--color-main-text); } +.adr-notice.is-error { border:2px solid var(--color-error); background:var(--color-error-hover); color:var(--color-main-text); } .adr-room-table input { min-width:110px; width:100%; } .adr-dialog { width:min(520px,calc(100vw - 32px)); border:1px solid var(--color-border); border-radius:var(--border-radius-large); padding:16px; background:var(--color-main-background); color:var(--color-main-text); } .adr-dialog:not([open]) { display:none; } diff --git a/js/admin.js b/js/admin.js index a060022..8fdadf4 100644 --- a/js/admin.js +++ b/js/admin.js @@ -15,6 +15,29 @@ onRemove: room => workflow.remove(room), }); + const demoConfirmation = byId('adr-demo-confirm'); + const demoButton = byId('adr-demo-install'); + const demoNotice = byId('adr-demo-notice'); + demoConfirmation.addEventListener('change', () => { demoButton.disabled = !demoConfirmation.checked; }); + demoButton.addEventListener('click', async () => { + if (!demoConfirmation.checked) return; + demoButton.disabled = true; + demoNotice.hidden = false; + demoNotice.className = 'adr-notice'; + demoNotice.textContent = 'Demo-Pack wird geprüft und installiert …'; + try { + const response = await client.request('/api/admin/demo-pack/install', { method: 'POST', body: '{}' }); + demoNotice.classList.add('is-success'); + demoNotice.textContent = `${response.result.rooms} Räume synchronisiert; ${response.result.createdBookings} Buchungen angelegt.`; + demoConfirmation.checked = false; + await load(); + } catch (error) { + demoNotice.classList.add('is-error'); + demoNotice.textContent = error.message || 'Das Demo-Pack konnte nicht installiert werden.'; + demoButton.disabled = false; + } + }); + async function load() { try { const now = new Date(); diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 552b334..356977d 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -4,13 +4,28 @@ namespace OCA\AdRoom\AppInfo; +use OCA\AdRoom\Listener\IntegrationCapabilityQueryListener; +use OCA\AdRoom\Listener\StandaloneNavigationListener; +use OCA\LocalBase\Integration\IntegrationCapabilityQueryEvent; use OCP\AppFramework\App; +use OCP\AppFramework\Bootstrap\IBootContext; +use OCP\AppFramework\Bootstrap\IBootstrap; +use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\Navigation\Events\LoadAdditionalEntriesEvent; -final class Application extends App { +/** Zweck: Registriert Raumfähigkeiten und Standalone-Navigation im Nextcloud-Bootstrap. */ +final class Application extends App implements IBootstrap { public const APP_ID = 'adroom'; public function __construct(array $urlParams = []) { parent::__construct(self::APP_ID, $urlParams); } -} + public function register(IRegistrationContext $context): void { + $context->registerEventListener(IntegrationCapabilityQueryEvent::class, IntegrationCapabilityQueryListener::class); + $context->registerEventListener(LoadAdditionalEntriesEvent::class, StandaloneNavigationListener::class); + } + + public function boot(IBootContext $context): void { + } +} diff --git a/lib/Command/SeedDemoCommand.php b/lib/Command/SeedDemoCommand.php index d8353c3..73cd3dd 100644 --- a/lib/Command/SeedDemoCommand.php +++ b/lib/Command/SeedDemoCommand.php @@ -4,39 +4,18 @@ namespace OCA\AdRoom\Command; -use DateTimeImmutable; -use DateTimeZone; -use OCA\AdRoom\Exception\BookingConflictException; -use OCA\AdRoom\Service\BookingService; -use OCA\AdRoom\Service\RoomService; -use OCP\IUserManager; +use OCA\AdRoom\Service\RoomDemoPackService; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -/** Zweck: Erzeugt idempotent benannte neutrale Räume und Beispielbuchungen. */ +/** Zweck: Stellt den sicheren Raum-Demo-Pack für automatisierte Staging-Setups bereit. */ final class SeedDemoCommand extends Command { - public function __construct(private RoomService $rooms,private BookingService $bookings,private IUserManager $users) { parent::__construct(); } - protected function configure(): void { $this->setName('adroom:demo:seed')->setDescription('Erzeugt neutrale Raumplaner-Demodaten.')->addOption('user',null,InputOption::VALUE_REQUIRED,'UID fuer Beispielbuchungen','admin'); } - protected function execute(InputInterface $input,OutputInterface $output): int { - $uid=(string)$input->getOption('user'); - if ($this->users->get($uid)===null) { $output->writeln("Benutzer {$uid} existiert nicht."); return self::FAILURE; } - $definitions=[ - ['name'=>'Besprechungsraum Nord','description'=>'Kleiner Besprechungsraum','sortOrder'=>10], - ['name'=>'Besprechungsraum Süd','description'=>'Besprechungsraum für Teams','sortOrder'=>20], - ['name'=>'Konferenzraum','description'=>'Großer Raum für Sitzungen und Fortbildungen','sortOrder'=>30], - ]; - $existing=[]; foreach ($this->rooms->all() as $room) $existing[$room->name()]=$room->id(); - foreach ($definitions as $definition) if (!isset($existing[$definition['name']])) $existing[$definition['name']]=$this->rooms->save(null,$definition['name'],$definition['description'],$definition['sortOrder']); - $day=new DateTimeImmutable('next monday',new DateTimeZone('Europe/Berlin')); - $samples=[['Besprechungsraum Nord','10:00','11:00','AT','ASN Nordost'],['Besprechungsraum Süd','12:00','13:30','Sitzung','Büroteam Süd'],['Konferenzraum','14:00','16:00','Fortbildung','Arbeitsschutz']]; - $created=0; - foreach ($samples as [$name,$start,$end,$purpose,$title]) { - try { $this->bookings->create((int)$existing[$name],$day->format('Y-m-d').'T'.$start,$day->format('Y-m-d').'T'.$end,$purpose,$title,$uid); $created++; } - catch (BookingConflictException) {} - } - $output->writeln('Drei benannte Räume synchronisiert; '.$created.' Beispielbuchungen erzeugt.'); + public function __construct(private RoomDemoPackService $demoPack) { parent::__construct(); } + protected function configure(): void { $this->setName('adroom:demo:seed')->setDescription('Erzeugt synthetische Raumplaner-Demodaten.'); } + protected function execute(InputInterface $input, OutputInterface $output): int { + $result = $this->demoPack->install(); + $output->writeln("{$result['rooms']} Räume synchronisiert; {$result['createdBookings']} Beispielbuchungen erzeugt."); return self::SUCCESS; } } diff --git a/lib/Controller/DemoAdminController.php b/lib/Controller/DemoAdminController.php new file mode 100644 index 0000000..32fc486 --- /dev/null +++ b/lib/Controller/DemoAdminController.php @@ -0,0 +1,33 @@ +isAdmin()) return new JSONResponse(['error' => 'Keine Berechtigung.'], Http::STATUS_FORBIDDEN); + try { + return new JSONResponse(['result' => $this->demoPack->install()]); + } catch (\Throwable $error) { + $this->logger->error('Raum-Demo-Pack konnte nicht installiert werden.', ['exception' => $error]); + return new JSONResponse(['error' => $error->getMessage()], Http::STATUS_BAD_REQUEST); + } + } + private function isAdmin(): bool { + $user = $this->session->getUser(); + return $user !== null && $this->groups->isAdmin($user->getUID()); + } +} diff --git a/lib/Listener/IntegrationCapabilityQueryListener.php b/lib/Listener/IntegrationCapabilityQueryListener.php new file mode 100644 index 0000000..5cf7d85 --- /dev/null +++ b/lib/Listener/IntegrationCapabilityQueryListener.php @@ -0,0 +1,22 @@ + */ +final class IntegrationCapabilityQueryListener implements IEventListener { + public function handle(Event $event): void { + if (!$event instanceof IntegrationCapabilityQueryEvent) return; + $event->provide(Application::APP_ID, [ + AdIntegrationCapabilities::ROOM_AVAILABILITY_READ, + AdIntegrationCapabilities::ROOM_BOOKING_WRITE, + ]); + } +} diff --git a/lib/Listener/StandaloneNavigationListener.php b/lib/Listener/StandaloneNavigationListener.php new file mode 100644 index 0000000..68e1ef3 --- /dev/null +++ b/lib/Listener/StandaloneNavigationListener.php @@ -0,0 +1,21 @@ + */ +final class StandaloneNavigationListener implements IEventListener { + public function __construct(private StandaloneAppNavigationService $navigation) { + } + + public function handle(Event $event): void { + if (!$event instanceof LoadAdditionalEntriesEvent) return; + $this->navigation->addWhenStandalone('adroom', 'Räume', 'adroom.page.index', 'app.svg', 83); + } +} diff --git a/lib/Service/RoomDemoPackService.php b/lib/Service/RoomDemoPackService.php new file mode 100644 index 0000000..1b92b29 --- /dev/null +++ b/lib/Service/RoomDemoPackService.php @@ -0,0 +1,50 @@ +accounts->provision('ad-suite-demo', [[ + 'uid' => 'ad-demo-room', 'displayName' => 'Romy Baum (Raumplaner-Demo)', 'groups' => [], + ]]); + $definitions = [ + ['name' => 'Besprechungsraum Nord', 'description' => 'Kleiner Besprechungsraum', 'sortOrder' => 10], + ['name' => 'Besprechungsraum Süd', 'description' => 'Besprechungsraum für Teams', 'sortOrder' => 20], + ['name' => 'Konferenzraum', 'description' => 'Großer Raum für Sitzungen und Fortbildungen', 'sortOrder' => 30], + ]; + $existing = []; + foreach ($this->rooms->all() as $room) $existing[$room->name()] = $room->id(); + foreach ($definitions as $definition) { + if (!isset($existing[$definition['name']])) $existing[$definition['name']] = $this->rooms->save(null, $definition['name'], $definition['description'], $definition['sortOrder']); + } + + $day = new DateTimeImmutable('next monday', new DateTimeZone('Europe/Berlin')); + $samples = [ + ['Besprechungsraum Nord', '10:00', '11:00', 'AT', 'ASN Team A'], + ['Besprechungsraum Süd', '12:00', '13:30', 'Sitzung', 'Büroteam Süd'], + ['Konferenzraum', '14:00', '16:00', 'Fortbildung', 'Arbeitsschutz'], + ]; + $createdBookings = 0; + $skippedBookings = 0; + foreach ($samples as [$name, $start, $end, $purpose, $title]) { + try { + $this->bookings->create((int)$existing[$name], $day->format('Y-m-d') . 'T' . $start, $day->format('Y-m-d') . 'T' . $end, $purpose, $title, 'ad-demo-room'); + $createdBookings++; + } catch (BookingConflictException) { + $skippedBookings++; + } + } + return ['accounts' => $accounts, 'rooms' => count($definitions), 'createdBookings' => $createdBookings, 'skippedBookings' => $skippedBookings]; + } +} diff --git a/templates/admin.php b/templates/admin.php index 5e4de68..248f8c7 100644 --- a/templates/admin.php +++ b/templates/admin.php @@ -27,4 +27,11 @@ +
+

Demo-Pack

+

Das Pack legt drei neutrale Räume und Beispielbuchungen unter einem synthetischen lokalen Demokonto an. Es wird nicht automatisch installiert und importiert keine Bestandsdaten.

+ + + +
diff --git a/templates/index.php b/templates/index.php index 0001a55..144a471 100644 --- a/templates/index.php +++ b/templates/index.php @@ -3,7 +3,6 @@ \OCP\Util::addScript('localbase','models/model'); \OCP\Util::addScript('localbase','repositories/repository'); \OCP\Util::addScript('localbase','ui/ui'); -\OCP\Util::addScript('orgsuite','suite-navigation'); \OCP\Util::addScript('adroom','models/room'); \OCP\Util::addScript('adroom','models/booking'); \OCP\Util::addScript('adroom','repositories/room-repository'); @@ -12,7 +11,6 @@ \OCP\Util::addScript('adroom','components/month-calendar'); \OCP\Util::addScript('adroom','components/booking-dialog'); \OCP\Util::addScript('adroom','main'); -\OCP\Util::addStyle('orgsuite','suite-navigation'); \OCP\Util::addStyle('adroom','style'); ?>
diff --git a/tests/IntegrationCapabilityListenerTest.php b/tests/IntegrationCapabilityListenerTest.php new file mode 100644 index 0000000..471dd4f --- /dev/null +++ b/tests/IntegrationCapabilityListenerTest.php @@ -0,0 +1,24 @@ +handle($event); + if ($event->providersFor(AdIntegrationCapabilities::ROOM_AVAILABILITY_READ) !== ['adroom']) throw new RuntimeException('Raum-Verfügbarkeitsfähigkeit fehlt.'); + if ($event->providersFor(AdIntegrationCapabilities::ROOM_BOOKING_WRITE) !== ['adroom']) throw new RuntimeException('Raum-Buchungsfähigkeit fehlt.'); + if ($event->isAvailable(AdIntegrationCapabilities::ABSENCE_READ)) throw new RuntimeException('Raumplaner meldet eine fremde Fähigkeit.'); + + echo "AD Raum capability listener test passed\n"; +} diff --git a/tests/Service/DemoPackContractTest.php b/tests/Service/DemoPackContractTest.php new file mode 100644 index 0000000..d0f7dd3 --- /dev/null +++ b/tests/Service/DemoPackContractTest.php @@ -0,0 +1,22 @@ +provision('ad-suite-demo'", 'ad-demo-room', 'Besprechungsraum Nord', 'BookingConflictException'] as $contract) if (!str_contains($service, $contract)) throw new RuntimeException("Raum-Demo-Vertrag fehlt: {$contract}"); +foreach (['RoomDemoPackService', '->install()'] as $contract) if (!str_contains($command, $contract)) throw new RuntimeException("Demo-Command delegiert nicht: {$contract}"); +foreach (['IUserManager', "addOption('user'", "getOption('user'"] as $unsafe) if (str_contains($command, $unsafe)) throw new RuntimeException("Raum-Demo verwendet ein reales Konto: {$unsafe}"); +foreach (['/api/admin/demo-pack/install', "'verb' => 'POST'"] as $contract) if (!str_contains($routes, $contract)) throw new RuntimeException("Demo-Route fehlt: {$contract}"); +foreach (['private function isAdmin()', '$this->groups->isAdmin(', 'Http::STATUS_FORBIDDEN'] as $contract) if (!str_contains($controller, $contract)) throw new RuntimeException("Adminschutz fehlt: {$contract}"); +if (str_contains($controller, 'NoCSRFRequired')) throw new RuntimeException('Demo-Installation umgeht CSRF.'); +foreach (['id="adr-demo-confirm"', 'id="adr-demo-install"', 'nicht automatisch'] as $contract) if (!str_contains($template, $contract)) throw new RuntimeException("Demo-Adminoberfläche fehlt: {$contract}"); +foreach (['adr-demo-confirm', 'adr-demo-install', "/api/admin/demo-pack/install"] as $contract) if (!str_contains($script, $contract)) throw new RuntimeException("Demo-Admininteraktion fehlt: {$contract}"); + +echo "DemoPackContractTest: OK\n"; diff --git a/tests/StandaloneNavigationListenerTest.php b/tests/StandaloneNavigationListenerTest.php new file mode 100644 index 0000000..e300855 --- /dev/null +++ b/tests/StandaloneNavigationListenerTest.php @@ -0,0 +1,17 @@ +user; } }; $apps = new class implements IAppManager { public function isEnabledForUser($appId, $user = null): bool { return false; } }; $nav = new class implements INavigationManager { public array $entries = []; public function add(callable $entry): void { $this->entries[] = $entry; } }; $url = new class implements IURLGenerator { public function linkToRoute(string $routeName, array $arguments = []): string { return $routeName; } public function imagePath(string $appName, string $file): string { return "$appName/$file"; } }; + (new StandaloneNavigationListener(new StandaloneAppNavigationService($session, $apps, $nav, $url)))->handle(new LoadAdditionalEntriesEvent()); $entry = ($nav->entries[0] ?? static fn(): array => [])(); + if (($entry['id'] ?? '') !== 'adroom' || ($entry['name'] ?? '') !== 'Räume' || ($entry['href'] ?? '') !== 'adroom.page.index') throw new RuntimeException('Standalone-Raumnavigation fehlt.'); + echo "AD Raum standalone navigation test passed\n"; +} diff --git a/tests/Ui/LayoutSmokeTest.php b/tests/Ui/LayoutSmokeTest.php index ba16694..b3280c3 100644 --- a/tests/Ui/LayoutSmokeTest.php +++ b/tests/Ui/LayoutSmokeTest.php @@ -9,5 +9,6 @@ foreach(['id="adroom-admin"','id="adr-admin-room-body"','id="adr-admin-room-form"','

Räume

',"\\OCP\\Util::addScript('localbase', 'repositories/repository')","\\OCP\\Util::addScript('adroom', 'modules/room-workflow')"] as $contract) if(!str_contains($admin,$contract)) throw new RuntimeException("Raum-Adminvertrag fehlt: {$contract}"); foreach ([$template, $admin] as $assetTemplate) if (preg_match('/^\\s*(?:script|style)\\s*\\(/m', $assetTemplate) === 1) throw new RuntimeException('Veralteter globaler Templatehelfer gefunden.'); foreach (['height: 100%','min-height: 0','overflow-y: auto','overflow-x: hidden','background: var(--color-main-background)','overflow-x:auto','width:max-content','focus','.adr-day-schedule { display:grid','.adr-room-lane','isolation:isolate','repeating-linear-gradient'] as $contract) if(!str_contains($css,$contract)) throw new RuntimeException("Layoutvertrag fehlt: {$contract}"); -if(!str_contains($info,'orgsuite')||!str_contains($info,'OCA\AdRoom\Settings\Admin')||str_contains($info,'')) throw new RuntimeException('Suite-Appvertrag fehlt.'); +if(str_contains($info,'')||!str_contains($info,'OCA\AdRoom\Settings\Admin')||str_contains($info,'')) throw new RuntimeException('Standalone-Appvertrag fehlt.'); +if(str_contains($template,"addScript('orgsuite'")||str_contains($template,"addStyle('orgsuite'")) throw new RuntimeException('Direkte OrgSuite-Assetkopplung vorhanden.'); echo "AD Raumplaner layout smoke test passed\n";