" is asked, "context:" is taken from the disposition and snapshotted into * the response (DispositionContext::build()). Google's "Bitte ankreuzen:" checkbox list was split * into five single-choice questions, so it maps to five. */ class QuestionnaireCompletenessTest extends TestCase { private const array TEAMER_FEEDBACK = [ 'Name' => ['context:teamerName'], 'Jobprofil' => ['context:jobProfileName'], 'Haus' => ['context:hotel'], 'Einsatzzeitraum (Anreisetag)' => ['context:dateFrom'], 'Wie gut hast du dich vorab vom Büro gebrieft gefühlt?' => ['question:office_briefing_rating'], 'Wenn 1 - 3 Sterne Bewertung, warum?' => ['question:office_briefing_low_reason'], 'War das Wochenteam-Seminar hilfreich für dich? Warst du bzw. wurdest du gut vorbereitet?' => ['question:team_week_seminar'], 'Wie gut lief die Reiseorganisation vor Ort?' => ['question:onsite_organisation_rating'], 'Gab es organisatorische Probleme?' => ['question:had_organisational_problems'], 'Wenn ja, welche?' => ['question:organisational_problems'], 'Wie lief die Kommunikation & Zusammenarbeit mit den E&P Teamer:innen vor Ort während der Reise?' => ['question:teamer_collaboration_rating'], 'Welche Unterstützung hättest du dir zusätzlich gewünscht?' => ['question:support_wished'], 'Wie ist deine Hausleitung mit Problemen vor Ort umgegangen?' => ['question:house_manager_problem_handling'], 'Gab es Momente, in denen du dich überfordert gefühlt hast?' => ['question:felt_overwhelmed'], 'Wenn ja, wodurch?' => ['question:overwhelmed_reason'], 'Was hättest du dir anders oder besser gewünscht?' => ['question:wished_differently'], 'Würdest du wieder als Reiseleitung für E&P arbeiten?' => ['question:would_work_again'], 'Wenn nein, warum nicht?' => ['question:would_not_work_again_reason'], 'Du hast nun schon vieles beantwortet, aber nirgendwo konntest du folgende Aspekte loswerden:' => ['question:anything_else'], ]; private const array BUS_FEEDBACK = [ '(Datum, ohne Titel)' => ['context:escortPickupDate', 'context:dateTo'], 'Hin- oder Rückfahrt' => ['question:direction'], 'Reiseziel:' => ['context:destinationProduct'], 'Bus-Unternehmen:' => ['question:bus_company'], 'Name des Busunternehmens:' => ['question:bus_company_other'], 'Busfahrer-Namen:' => ['question:driver_names'], 'Reiseleitung:' => ['context:teamerName'], '1. Abfahrtsort' => ['context:escortPickup', 'context:hotel'], 'Bus pünktlich am 1. Abfahrtsort?' => ['question:on_time_at_first_pickup'], 'Wieviel Verspätung hatte der Bus am 1. Abfahrtsort?' => ['question:delay_at_first_pickup'], 'Gesamteindruck des Busses' => ['question:overall_impression'], 'Alter des Fahrzeugs' => ['question:vehicle_age'], 'Sitzkomfort im Bus' => ['question:seating_comfort'], 'Freundlichkeit der Fahrer' => ['question:driver_friendliness'], 'Pausenzeiten' => ['question:breaks'], 'Temperatur im Bus' => ['question:temperature'], 'Verhalten der Gäste im Bus' => ['question:guest_behaviour'], 'Waren den Fahrern alle Abfahrtsorte bekannt?' => ['question:drivers_knew_pickups'], 'Welche Abfahrtsorte waren nicht bekannt? Gab es dadurch Probleme?' => ['question:unknown_pickups'], 'War den Busfahrern der Zielort und die Ausladestelle bekannt?' => ['question:drivers_knew_destination'], 'Bitte ankreuzen:' => ['question:video', 'question:wc', 'question:luggage', 'question:smoking', 'question:snow_chains'], 'Entstanden während der Fahrt Schäden am Bus?' => ['question:bus_damage'], 'Was ist passiert?' => ['question:damage_description'], 'Das ging gar nicht / ist besonders hervorzuheben:' => ['question:bus_remarks'], ]; public function testEveryTeamerFeedbackQuestionIsAskedOrDerived(): void { $this->assertComplete(QuestionnaireCatalog::TEAMER_FEEDBACK, self::TEAMER_FEEDBACK); } public function testEveryBusFeedbackQuestionIsAskedOrDerived(): void { $this->assertComplete(QuestionnaireCatalog::BUS_FEEDBACK, self::BUS_FEEDBACK); } /** * The Google forms had 19 and 24 questions; with the checkbox list split there are 43 places * an answer comes from. */ public function testTheMappingCoversBothGoogleForms(): void { $this->assertCount(19, self::TEAMER_FEEDBACK); $this->assertCount(24, self::BUS_FEEDBACK); } /** * A bus escort answers every question of both forms; anyone else every question of the * teamer form. */ public function testABusEscortGetsBothQuestionnairesAndEveryoneTheTeamerOne(): void { $this->assertSame( [QuestionnaireCatalog::TEAMER_FEEDBACK, QuestionnaireCatalog::BUS_FEEDBACK], DispositionContext::questionnaires((new Assignment())->setPickup(4711)), ); $this->assertSame( [QuestionnaireCatalog::TEAMER_FEEDBACK], DispositionContext::questionnaires((new Assignment())->setPickup(0)), ); } /** * @param array> $googleQuestions */ private function assertComplete(string $questionnaire, array $googleQuestions): void { $definition = new QuestionnaireCatalog(__DIR__.'/../../config/questionnaires')->get($questionnaire); $contextFields = array_keys(DispositionContext::build(new Disposition(new Application(new Assignment(), new Teamer())))); $mapped = []; foreach ($googleQuestions as $label => $targets) { foreach ($targets as $target) { [$kind, $key] = explode(':', $target); if ('question' === $kind) { $this->assertNotNull($definition->getQuestion($key), sprintf('"%s" is not asked as "%s"', $label, $key)); $mapped[] = $key; } else { $this->assertContains($key, $contextFields, sprintf('"%s" is not snapshotted as "%s"', $label, $key)); } } } // and nothing is asked that the Google form did not ask $this->assertSame([], array_values(array_diff(array_keys($definition->getQuestions()), $mapped))); } }