client = static::createClient(server: ['HTTPS' => true]); $this->client->disableReboot(); self::getContainer()->set(RequiredTeamerCheckRegistry::class, $this->createStub(RequiredTeamerCheckRegistry::class)); $this->entityManager = self::getContainer()->get(EntityManagerInterface::class); $this->entityManager->getConnection()->beginTransaction(); $this->teamer = (new Teamer())->setFirstName('Kim')->setLastName('Muster')->setGender('d'); $user = (new User()) ->setFirstName('Kim') ->setLastName('Muster') ->setEmail('kim.muster.questionnaire@example.com') ->setRoles(['ROLE_TEAMER']) ->setBusProAddressId(1) ->setBusProPersonId(1) ->setTeamer($this->teamer) ; $this->teamer->setUser($user); $this->entityManager->persist($this->teamer); $this->entityManager->persist($user); $this->entityManager->flush(); $this->client->loginUser($user); } protected function tearDown(): void { $this->entityManager->getConnection()->rollBack(); parent::tearDown(); } public function testABusEscortGetsBothQuestionnairesOnOnePage(): void { $crawler = $this->open($this->placement(busEscort: true)); $this->assertResponseIsSuccessful(); // the placement in one line above the form $this->assertStringContainsString('Reiseleitung', $crawler->filter('h1 + p')->text()); $this->assertStringContainsString('Busbegleitung ab Stuttgart', $crawler->filter('h1 + p')->text()); $this->assertCount(1, $crawler->filter('[data-question-key="teamer_feedback.office_briefing_rating"]')); $this->assertCount(1, $crawler->filter('[data-question-key="bus_feedback.bus_company"]')); } public function testAPlacementWithoutEscortGetsTheTeamerFeedbackOnly(): void { $crawler = $this->open($this->placement(busEscort: false)); $this->assertResponseIsSuccessful(); $this->assertCount(1, $crawler->filter('[data-question-key="teamer_feedback.office_briefing_rating"]')); $this->assertCount(0, $crawler->filter('[data-question-key^="bus_feedback."]')); } public function testADraftSavesOneResponsePerQuestionnaire(): void { $disposition = $this->placement(busEscort: true); $form = $this->open($disposition)->selectButton('Entwurf speichern')->form(); $form['questionnaire[bus_feedback][bus_team][driver_names]'] = 'Hans'; $this->client->submit($form); $this->assertResponseRedirects(sprintf('/teamer/questionnaire/%s', $disposition->getUuid())); $this->assertSame([ QuestionnaireCatalog::BUS_FEEDBACK => QuestionnaireResponseStatus::DRAFT, QuestionnaireCatalog::TEAMER_FEEDBACK => QuestionnaireResponseStatus::DRAFT, ], $this->statuses($disposition)); // reopened, the draft is back in the form $crawler = $this->open($disposition); $this->assertSame('Hans', $crawler->filter('input[name="questionnaire[bus_feedback][bus_team][driver_names]"]')->attr('value')); } public function testOneSubmitSubmitsBoth(): void { $disposition = $this->placement(busEscort: true); $form = $this->open($disposition)->selectButton('Absenden')->form(); $this->answerAll($form, [QuestionnaireCatalog::TEAMER_FEEDBACK, QuestionnaireCatalog::BUS_FEEDBACK]); $this->client->submit($form); $this->assertResponseRedirects(sprintf('/teamer/questionnaire/%s/detail', $disposition->getUuid())); $this->assertSame([ QuestionnaireCatalog::BUS_FEEDBACK => QuestionnaireResponseStatus::SUBMITTED, QuestionnaireCatalog::TEAMER_FEEDBACK => QuestionnaireResponseStatus::SUBMITTED, ], $this->statuses($disposition)); // the read-only view shows both, and the form is closed $crawler = $this->client->followRedirect(); $this->assertStringContainsString('Wochenteamer:innen-Feedback', $crawler->text()); $this->assertStringContainsString('Bus-Feedback', $crawler->text()); $this->open($disposition); $this->assertResponseRedirects(sprintf('/teamer/questionnaire/%s/detail', $disposition->getUuid())); } /** * Nothing is half-submitted: a bus question left open keeps both questionnaires unsubmitted. */ public function testAnIncompleteBusFeedbackSubmitsNeither(): void { $disposition = $this->placement(busEscort: true); $form = $this->open($disposition)->selectButton('Absenden')->form(); $this->answerAll($form, [QuestionnaireCatalog::TEAMER_FEEDBACK]); $this->client->submit($form); $this->assertResponseStatusCodeSame(422); $this->assertSame([], $this->statuses($disposition)); } public function testWithoutEscortOneResponseIsSubmitted(): void { $disposition = $this->placement(busEscort: false); $form = $this->open($disposition)->selectButton('Absenden')->form(); $this->answerAll($form, [QuestionnaireCatalog::TEAMER_FEEDBACK]); $this->client->submit($form); $this->assertSame([QuestionnaireCatalog::TEAMER_FEEDBACK => QuestionnaireResponseStatus::SUBMITTED], $this->statuses($disposition)); } private function open(Disposition $disposition): Crawler { return $this->client->request('GET', sprintf('/teamer/questionnaire/%s', $disposition->getUuid())); } /** * Answers every required question that is shown without a follow-up: first option, rating 5. * * @param list $questionnaires */ private function answerAll(Form $form, array $questionnaires): void { $catalog = self::getContainer()->get(QuestionnaireCatalog::class); foreach ($questionnaires as $key) { foreach ($catalog->get($key)->sections as $section) { foreach ($section->questions as $question) { if (!$question->required || null !== $question->showIf) { continue; } $form[sprintf('questionnaire[%s][%s][%s]', $key, $section->key, $question->key)] = match ($question->type) { QuestionType::CHOICE => $question->options[0]->key, QuestionType::RATING => '5', default => 'Antwort', }; } } } } /** * @return array keyed by questionnaire, sorted */ private function statuses(Disposition $disposition): array { $this->entityManager->clear(); $statuses = []; foreach ($this->entityManager->getRepository(QuestionnaireResponse::class)->findBy(['disposition' => $disposition->getId()]) as $response) { $statuses[$response->getQuestionnaire()] = $response->getStatus(); } ksort($statuses); return $statuses; } private function placement(bool $busEscort): Disposition { $destination = (new Destination()) ->setProduct('Davos Klosters - Schwendi') ->setHotel('Schwendi') ->setHotelCode('DKI') ->setHotelBusProId(1) ->setDateFrom(new \DateTimeImmutable('-8 days')) ->setDateTo(new \DateTimeImmutable('yesterday')) ->setPickups([['busProId' => 4711, 'city' => 'Stuttgart', 'street' => 'Busbahnhof', 'time' => '21:30']]) ; $jobProfile = (new JobProfile())->setName('Reiseleitung')->setCategory(JobProfile::CATEGORY_GUIDE); $assignment = (new Assignment()) ->setDestination($destination) ->setJobProfile($jobProfile) ->setPickup($busEscort ? 4711 : 0) ->setStatus(Assignment::STATUS_STAFFED) ; $disposition = (new Disposition(new Application($assignment, $this->teamer)))->setStatus(Disposition::STATUS_ENDED); foreach ([$destination, $jobProfile, $assignment, $disposition] as $entity) { $this->entityManager->persist($entity); } $this->entityManager->flush(); return $disposition; } }