diff --git a/api/app/Enums/FormBuilder/FormFieldDisplayWidth.php b/api/app/Enums/FormBuilder/FormFieldDisplayWidth.php new file mode 100644 index 00000000..bab9138d --- /dev/null +++ b/api/app/Enums/FormBuilder/FormFieldDisplayWidth.php @@ -0,0 +1,19 @@ + + */ + public static function values(): array + { + return array_map(fn (self $case): string => $case->value, self::cases()); + } +} diff --git a/api/app/Enums/FormBuilder/FormFieldType.php b/api/app/Enums/FormBuilder/FormFieldType.php new file mode 100644 index 00000000..dcedcfe4 --- /dev/null +++ b/api/app/Enums/FormBuilder/FormFieldType.php @@ -0,0 +1,98 @@ + + */ + public static function values(): array + { + return array_map(fn (self $case): string => $case->value, self::cases()); + } + + /** + * Recommended storage hint per ARCH §5.2. Suggestive, organiser may override. + */ + public function recommendedValueStorageHint(): FormValueStorageHint + { + return match ($this) { + self::TEXT, + self::TEXTAREA, + self::EMAIL, + self::PHONE, + self::URL, + self::RADIO, + self::SELECT => FormValueStorageHint::STRING, + + self::NUMBER => FormValueStorageHint::NUMBER, + self::DATE, + self::DATETIME => FormValueStorageHint::DATE, + self::BOOLEAN => FormValueStorageHint::BOOL, + + default => FormValueStorageHint::JSON, + }; + } + + /** + * Per ARCH §7.1. Presentational types and complex payloads are not filterable. + */ + public function isFilterable(): bool + { + return match ($this) { + self::TEXTAREA, + self::FILE_UPLOAD, + self::IMAGE_UPLOAD, + self::SIGNATURE, + self::HEADING, + self::PARAGRAPH, + self::TABLE_ROWS, + self::SECTION_PRIORITY, + self::AVAILABILITY_PICKER => false, + default => true, + }; + } + + /** + * HEADING and PARAGRAPH are presentational only — no stored value. + */ + public function hasValue(): bool + { + return match ($this) { + self::HEADING, self::PARAGRAPH => false, + default => true, + }; + } +} diff --git a/api/app/Enums/FormBuilder/FormPurpose.php b/api/app/Enums/FormBuilder/FormPurpose.php new file mode 100644 index 00000000..6410947a --- /dev/null +++ b/api/app/Enums/FormBuilder/FormPurpose.php @@ -0,0 +1,119 @@ + + */ + public static function values(): array + { + return array_map(fn (self $case): string => $case->value, self::cases()); + } + + /** + * Default subject_type for each purpose per ARCH §3.1. + * null = subject may be null (public or ambiguous). + */ + public function defaultSubjectType(): ?string + { + return match ($this) { + self::EVENT_REGISTRATION, + self::POST_EVENT_EVALUATION, + self::SIGNATURE_RECEIPT, + self::ABSENCE_REPORT, + self::CHECK_OUT_INVENTORY => 'person', + + self::USER_PROFILE, + self::SIGNATURE_CODE_OF_CONDUCT => 'user', + + self::ARTIST_PROFILE, + self::ARTIST_ADVANCE, + self::ARTIST_CUSTOM => 'artist', + + self::COMPANY_PROFILE, + self::SUPPLIER_INTAKE, + self::COMPANY_CUSTOM => 'company', + + self::ONBOARDING_WIZARD => 'organisation', + self::EVENT_SETUP_WIZARD => 'event', + + self::INCIDENT_REPORT, + self::FEEDBACK, + self::SIGNATURE_CONTRACT => 'user', + + self::PUBLIC_COMPLAINT, + self::PUBLIC_PRESS_REQUEST, + self::PUBLIC_RSVP, + self::CUSTOM => null, + }; + } + + public function defaultSubmissionMode(): FormSubmissionMode + { + return match ($this) { + self::EVENT_REGISTRATION, + self::ARTIST_ADVANCE, + self::SUPPLIER_INTAKE => FormSubmissionMode::DRAFT_SINGLE, + + self::INCIDENT_REPORT, + self::FEEDBACK, + self::SIGNATURE_RECEIPT, + self::ABSENCE_REPORT, + self::CHECK_OUT_INVENTORY, + self::PUBLIC_COMPLAINT, + self::PUBLIC_PRESS_REQUEST => FormSubmissionMode::MULTIPLE, + + self::USER_PROFILE, + self::ARTIST_PROFILE, + self::COMPANY_PROFILE, + self::POST_EVENT_EVALUATION, + self::SIGNATURE_CONTRACT, + self::SIGNATURE_CODE_OF_CONDUCT, + self::PUBLIC_RSVP, + self::ONBOARDING_WIZARD, + self::EVENT_SETUP_WIZARD, + self::COMPANY_CUSTOM, + self::ARTIST_CUSTOM, + self::CUSTOM => FormSubmissionMode::SINGLE, + }; + } + + public function allowsPublicAccess(): bool + { + return match ($this) { + self::PUBLIC_COMPLAINT, + self::PUBLIC_PRESS_REQUEST, + self::PUBLIC_RSVP, + self::ARTIST_ADVANCE, + self::SUPPLIER_INTAKE => true, + default => false, + }; + } +} diff --git a/api/app/Enums/FormBuilder/FormSchemaSnapshotMode.php b/api/app/Enums/FormBuilder/FormSchemaSnapshotMode.php new file mode 100644 index 00000000..5580efe9 --- /dev/null +++ b/api/app/Enums/FormBuilder/FormSchemaSnapshotMode.php @@ -0,0 +1,20 @@ + + */ + public static function values(): array + { + return array_map(fn (self $case): string => $case->value, self::cases()); + } +} diff --git a/api/app/Enums/FormBuilder/FormSubmissionMode.php b/api/app/Enums/FormBuilder/FormSubmissionMode.php new file mode 100644 index 00000000..fe32ef87 --- /dev/null +++ b/api/app/Enums/FormBuilder/FormSubmissionMode.php @@ -0,0 +1,20 @@ + + */ + public static function values(): array + { + return array_map(fn (self $case): string => $case->value, self::cases()); + } +} diff --git a/api/app/Enums/FormBuilder/FormSubmissionReviewStatus.php b/api/app/Enums/FormBuilder/FormSubmissionReviewStatus.php new file mode 100644 index 00000000..1310e4c1 --- /dev/null +++ b/api/app/Enums/FormBuilder/FormSubmissionReviewStatus.php @@ -0,0 +1,21 @@ + + */ + public static function values(): array + { + return array_map(fn (self $case): string => $case->value, self::cases()); + } +} diff --git a/api/app/Enums/FormBuilder/FormSubmissionStatus.php b/api/app/Enums/FormBuilder/FormSubmissionStatus.php new file mode 100644 index 00000000..60a78fd8 --- /dev/null +++ b/api/app/Enums/FormBuilder/FormSubmissionStatus.php @@ -0,0 +1,20 @@ + + */ + public static function values(): array + { + return array_map(fn (self $case): string => $case->value, self::cases()); + } +} diff --git a/api/app/Enums/FormBuilder/FormValueStorageHint.php b/api/app/Enums/FormBuilder/FormValueStorageHint.php new file mode 100644 index 00000000..4300e3fc --- /dev/null +++ b/api/app/Enums/FormBuilder/FormValueStorageHint.php @@ -0,0 +1,22 @@ + + */ + public static function values(): array + { + return array_map(fn (self $case): string => $case->value, self::cases()); + } +} diff --git a/api/app/Enums/FormBuilder/FormWebhookDeliveryStatus.php b/api/app/Enums/FormBuilder/FormWebhookDeliveryStatus.php new file mode 100644 index 00000000..fbfb5d50 --- /dev/null +++ b/api/app/Enums/FormBuilder/FormWebhookDeliveryStatus.php @@ -0,0 +1,21 @@ + + */ + public static function values(): array + { + return array_map(fn (self $case): string => $case->value, self::cases()); + } +} diff --git a/api/tests/Unit/Enums/FormBuilder/FormFieldDisplayWidthTest.php b/api/tests/Unit/Enums/FormBuilder/FormFieldDisplayWidthTest.php new file mode 100644 index 00000000..8f85d80b --- /dev/null +++ b/api/tests/Unit/Enums/FormBuilder/FormFieldDisplayWidthTest.php @@ -0,0 +1,23 @@ +assertSame('half', FormFieldDisplayWidth::HALF->value); + $this->assertSame('full', FormFieldDisplayWidth::FULL->value); + $this->assertCount(2, FormFieldDisplayWidth::cases()); + } + + public function test_values_returns_string_array(): void + { + $this->assertSame(['half', 'full'], FormFieldDisplayWidth::values()); + } +} diff --git a/api/tests/Unit/Enums/FormBuilder/FormFieldTypeTest.php b/api/tests/Unit/Enums/FormBuilder/FormFieldTypeTest.php new file mode 100644 index 00000000..d0c15f27 --- /dev/null +++ b/api/tests/Unit/Enums/FormBuilder/FormFieldTypeTest.php @@ -0,0 +1,90 @@ +assertCount(22, FormFieldType::cases()); + } + + public function test_values_returns_string_array(): void + { + $values = FormFieldType::values(); + $this->assertContains('TEXT', $values); + $this->assertContains('SIGNATURE', $values); + $this->assertContains('TABLE_ROWS', $values); + } + + public function test_storage_hint_string_types(): void + { + $this->assertSame(FormValueStorageHint::STRING, FormFieldType::TEXT->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::STRING, FormFieldType::TEXTAREA->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::STRING, FormFieldType::EMAIL->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::STRING, FormFieldType::PHONE->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::STRING, FormFieldType::URL->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::STRING, FormFieldType::RADIO->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::STRING, FormFieldType::SELECT->recommendedValueStorageHint()); + } + + public function test_storage_hint_typed(): void + { + $this->assertSame(FormValueStorageHint::NUMBER, FormFieldType::NUMBER->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::DATE, FormFieldType::DATE->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::DATE, FormFieldType::DATETIME->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::BOOL, FormFieldType::BOOLEAN->recommendedValueStorageHint()); + } + + public function test_storage_hint_json_default(): void + { + $this->assertSame(FormValueStorageHint::JSON, FormFieldType::MULTISELECT->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::JSON, FormFieldType::CHECKBOX_LIST->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::JSON, FormFieldType::FILE_UPLOAD->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::JSON, FormFieldType::SIGNATURE->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::JSON, FormFieldType::TAG_PICKER->recommendedValueStorageHint()); + $this->assertSame(FormValueStorageHint::JSON, FormFieldType::TABLE_ROWS->recommendedValueStorageHint()); + } + + public function test_is_filterable_true(): void + { + $this->assertTrue(FormFieldType::TEXT->isFilterable()); + $this->assertTrue(FormFieldType::SELECT->isFilterable()); + $this->assertTrue(FormFieldType::NUMBER->isFilterable()); + $this->assertTrue(FormFieldType::BOOLEAN->isFilterable()); + $this->assertTrue(FormFieldType::MULTISELECT->isFilterable()); + $this->assertTrue(FormFieldType::TAG_PICKER->isFilterable()); + } + + public function test_is_filterable_false(): void + { + $this->assertFalse(FormFieldType::TEXTAREA->isFilterable()); + $this->assertFalse(FormFieldType::FILE_UPLOAD->isFilterable()); + $this->assertFalse(FormFieldType::IMAGE_UPLOAD->isFilterable()); + $this->assertFalse(FormFieldType::SIGNATURE->isFilterable()); + $this->assertFalse(FormFieldType::HEADING->isFilterable()); + $this->assertFalse(FormFieldType::PARAGRAPH->isFilterable()); + $this->assertFalse(FormFieldType::TABLE_ROWS->isFilterable()); + $this->assertFalse(FormFieldType::SECTION_PRIORITY->isFilterable()); + $this->assertFalse(FormFieldType::AVAILABILITY_PICKER->isFilterable()); + } + + public function test_has_value_true_for_inputs(): void + { + $this->assertTrue(FormFieldType::TEXT->hasValue()); + $this->assertTrue(FormFieldType::SIGNATURE->hasValue()); + $this->assertTrue(FormFieldType::TABLE_ROWS->hasValue()); + } + + public function test_has_value_false_for_presentational(): void + { + $this->assertFalse(FormFieldType::HEADING->hasValue()); + $this->assertFalse(FormFieldType::PARAGRAPH->hasValue()); + } +} diff --git a/api/tests/Unit/Enums/FormBuilder/FormPurposeTest.php b/api/tests/Unit/Enums/FormBuilder/FormPurposeTest.php new file mode 100644 index 00000000..438ab96f --- /dev/null +++ b/api/tests/Unit/Enums/FormBuilder/FormPurposeTest.php @@ -0,0 +1,101 @@ +assertCount(22, FormPurpose::cases()); + } + + public function test_values_contains_expected_strings(): void + { + $values = FormPurpose::values(); + $expected = [ + 'event_registration', 'user_profile', 'artist_profile', 'company_profile', + 'artist_advance', 'supplier_intake', 'incident_report', 'feedback', + 'post_event_evaluation', 'signature_contract', 'signature_code_of_conduct', + 'signature_receipt', 'absence_report', 'check_out_inventory', + 'public_complaint', 'public_press_request', 'public_rsvp', + 'onboarding_wizard', 'event_setup_wizard', 'company_custom', + 'artist_custom', 'custom', + ]; + foreach ($expected as $v) { + $this->assertContains($v, $values); + } + } + + public function test_default_subject_type_for_person_purposes(): void + { + $this->assertSame('person', FormPurpose::EVENT_REGISTRATION->defaultSubjectType()); + $this->assertSame('person', FormPurpose::POST_EVENT_EVALUATION->defaultSubjectType()); + $this->assertSame('person', FormPurpose::SIGNATURE_RECEIPT->defaultSubjectType()); + $this->assertSame('person', FormPurpose::ABSENCE_REPORT->defaultSubjectType()); + $this->assertSame('person', FormPurpose::CHECK_OUT_INVENTORY->defaultSubjectType()); + } + + public function test_default_subject_type_for_entity_purposes(): void + { + $this->assertSame('user', FormPurpose::USER_PROFILE->defaultSubjectType()); + $this->assertSame('artist', FormPurpose::ARTIST_PROFILE->defaultSubjectType()); + $this->assertSame('company', FormPurpose::COMPANY_PROFILE->defaultSubjectType()); + $this->assertSame('artist', FormPurpose::ARTIST_ADVANCE->defaultSubjectType()); + $this->assertSame('company', FormPurpose::SUPPLIER_INTAKE->defaultSubjectType()); + $this->assertSame('organisation', FormPurpose::ONBOARDING_WIZARD->defaultSubjectType()); + $this->assertSame('event', FormPurpose::EVENT_SETUP_WIZARD->defaultSubjectType()); + } + + public function test_default_subject_type_is_null_for_public_and_custom(): void + { + $this->assertNull(FormPurpose::PUBLIC_COMPLAINT->defaultSubjectType()); + $this->assertNull(FormPurpose::PUBLIC_PRESS_REQUEST->defaultSubjectType()); + $this->assertNull(FormPurpose::PUBLIC_RSVP->defaultSubjectType()); + $this->assertNull(FormPurpose::CUSTOM->defaultSubjectType()); + } + + public function test_default_submission_mode_draft_single(): void + { + $this->assertSame(FormSubmissionMode::DRAFT_SINGLE, FormPurpose::EVENT_REGISTRATION->defaultSubmissionMode()); + $this->assertSame(FormSubmissionMode::DRAFT_SINGLE, FormPurpose::ARTIST_ADVANCE->defaultSubmissionMode()); + $this->assertSame(FormSubmissionMode::DRAFT_SINGLE, FormPurpose::SUPPLIER_INTAKE->defaultSubmissionMode()); + } + + public function test_default_submission_mode_multiple(): void + { + $this->assertSame(FormSubmissionMode::MULTIPLE, FormPurpose::INCIDENT_REPORT->defaultSubmissionMode()); + $this->assertSame(FormSubmissionMode::MULTIPLE, FormPurpose::FEEDBACK->defaultSubmissionMode()); + $this->assertSame(FormSubmissionMode::MULTIPLE, FormPurpose::PUBLIC_COMPLAINT->defaultSubmissionMode()); + } + + public function test_default_submission_mode_single(): void + { + $this->assertSame(FormSubmissionMode::SINGLE, FormPurpose::USER_PROFILE->defaultSubmissionMode()); + $this->assertSame(FormSubmissionMode::SINGLE, FormPurpose::ARTIST_PROFILE->defaultSubmissionMode()); + $this->assertSame(FormSubmissionMode::SINGLE, FormPurpose::ONBOARDING_WIZARD->defaultSubmissionMode()); + $this->assertSame(FormSubmissionMode::SINGLE, FormPurpose::CUSTOM->defaultSubmissionMode()); + } + + public function test_allows_public_access(): void + { + $this->assertTrue(FormPurpose::PUBLIC_COMPLAINT->allowsPublicAccess()); + $this->assertTrue(FormPurpose::PUBLIC_PRESS_REQUEST->allowsPublicAccess()); + $this->assertTrue(FormPurpose::PUBLIC_RSVP->allowsPublicAccess()); + $this->assertTrue(FormPurpose::ARTIST_ADVANCE->allowsPublicAccess()); + $this->assertTrue(FormPurpose::SUPPLIER_INTAKE->allowsPublicAccess()); + } + + public function test_disallows_public_access(): void + { + $this->assertFalse(FormPurpose::EVENT_REGISTRATION->allowsPublicAccess()); + $this->assertFalse(FormPurpose::USER_PROFILE->allowsPublicAccess()); + $this->assertFalse(FormPurpose::INCIDENT_REPORT->allowsPublicAccess()); + $this->assertFalse(FormPurpose::CUSTOM->allowsPublicAccess()); + } +} diff --git a/api/tests/Unit/Enums/FormBuilder/FormSchemaSnapshotModeTest.php b/api/tests/Unit/Enums/FormBuilder/FormSchemaSnapshotModeTest.php new file mode 100644 index 00000000..9ca58d83 --- /dev/null +++ b/api/tests/Unit/Enums/FormBuilder/FormSchemaSnapshotModeTest.php @@ -0,0 +1,24 @@ +assertSame('never', FormSchemaSnapshotMode::NEVER->value); + $this->assertSame('on_submit', FormSchemaSnapshotMode::ON_SUBMIT->value); + $this->assertSame('always', FormSchemaSnapshotMode::ALWAYS->value); + $this->assertCount(3, FormSchemaSnapshotMode::cases()); + } + + public function test_values_returns_string_array(): void + { + $this->assertSame(['never', 'on_submit', 'always'], FormSchemaSnapshotMode::values()); + } +} diff --git a/api/tests/Unit/Enums/FormBuilder/FormSubmissionModeTest.php b/api/tests/Unit/Enums/FormBuilder/FormSubmissionModeTest.php new file mode 100644 index 00000000..1d23be17 --- /dev/null +++ b/api/tests/Unit/Enums/FormBuilder/FormSubmissionModeTest.php @@ -0,0 +1,24 @@ +assertSame('single', FormSubmissionMode::SINGLE->value); + $this->assertSame('multiple', FormSubmissionMode::MULTIPLE->value); + $this->assertSame('draft_single', FormSubmissionMode::DRAFT_SINGLE->value); + $this->assertCount(3, FormSubmissionMode::cases()); + } + + public function test_values_returns_string_array(): void + { + $this->assertSame(['single', 'multiple', 'draft_single'], FormSubmissionMode::values()); + } +} diff --git a/api/tests/Unit/Enums/FormBuilder/FormSubmissionReviewStatusTest.php b/api/tests/Unit/Enums/FormBuilder/FormSubmissionReviewStatusTest.php new file mode 100644 index 00000000..61222bf1 --- /dev/null +++ b/api/tests/Unit/Enums/FormBuilder/FormSubmissionReviewStatusTest.php @@ -0,0 +1,28 @@ +assertCount(4, FormSubmissionReviewStatus::cases()); + $this->assertSame('pending_review', FormSubmissionReviewStatus::PENDING_REVIEW->value); + $this->assertSame('approved', FormSubmissionReviewStatus::APPROVED->value); + $this->assertSame('rejected', FormSubmissionReviewStatus::REJECTED->value); + $this->assertSame('changes_requested', FormSubmissionReviewStatus::CHANGES_REQUESTED->value); + } + + public function test_values_returns_string_array(): void + { + $this->assertSame( + ['pending_review', 'approved', 'rejected', 'changes_requested'], + FormSubmissionReviewStatus::values() + ); + } +} diff --git a/api/tests/Unit/Enums/FormBuilder/FormSubmissionStatusTest.php b/api/tests/Unit/Enums/FormBuilder/FormSubmissionStatusTest.php new file mode 100644 index 00000000..466736f9 --- /dev/null +++ b/api/tests/Unit/Enums/FormBuilder/FormSubmissionStatusTest.php @@ -0,0 +1,24 @@ +assertSame('draft', FormSubmissionStatus::DRAFT->value); + $this->assertSame('submitted', FormSubmissionStatus::SUBMITTED->value); + $this->assertSame('archived', FormSubmissionStatus::ARCHIVED->value); + $this->assertCount(3, FormSubmissionStatus::cases()); + } + + public function test_values_returns_array_of_strings(): void + { + $this->assertSame(['draft', 'submitted', 'archived'], FormSubmissionStatus::values()); + } +} diff --git a/api/tests/Unit/Enums/FormBuilder/FormValueStorageHintTest.php b/api/tests/Unit/Enums/FormBuilder/FormValueStorageHintTest.php new file mode 100644 index 00000000..69f62855 --- /dev/null +++ b/api/tests/Unit/Enums/FormBuilder/FormValueStorageHintTest.php @@ -0,0 +1,29 @@ +assertSame('json', FormValueStorageHint::JSON->value); + $this->assertSame('string', FormValueStorageHint::STRING->value); + $this->assertSame('number', FormValueStorageHint::NUMBER->value); + $this->assertSame('date', FormValueStorageHint::DATE->value); + $this->assertSame('bool', FormValueStorageHint::BOOL->value); + $this->assertCount(5, FormValueStorageHint::cases()); + } + + public function test_values_returns_string_array(): void + { + $this->assertSame( + ['json', 'string', 'number', 'date', 'bool'], + FormValueStorageHint::values() + ); + } +} diff --git a/api/tests/Unit/Enums/FormBuilder/FormWebhookDeliveryStatusTest.php b/api/tests/Unit/Enums/FormBuilder/FormWebhookDeliveryStatusTest.php new file mode 100644 index 00000000..f4825dfe --- /dev/null +++ b/api/tests/Unit/Enums/FormBuilder/FormWebhookDeliveryStatusTest.php @@ -0,0 +1,28 @@ +assertSame('pending', FormWebhookDeliveryStatus::PENDING->value); + $this->assertSame('delivered', FormWebhookDeliveryStatus::DELIVERED->value); + $this->assertSame('failed', FormWebhookDeliveryStatus::FAILED->value); + $this->assertSame('dead_letter', FormWebhookDeliveryStatus::DEAD_LETTER->value); + $this->assertCount(4, FormWebhookDeliveryStatus::cases()); + } + + public function test_values_returns_string_array(): void + { + $this->assertSame( + ['pending', 'delivered', 'failed', 'dead_letter'], + FormWebhookDeliveryStatus::values() + ); + } +}