├── .deepsource.toml ├── .gitattributes ├── .github ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── pre-release.yml │ ├── publish-snapshot.yml │ ├── release.yml │ └── run-tests.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle ├── fakeFillerConfig.txt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── intellij-settings ├── LiveTemplates.xml ├── PlatformFlavoredGoogleStyle.xml └── flows-config-schema.json ├── readme-assets ├── FFB-Version-1-Gift.png ├── intellij-json-schema-mappings.png ├── money-input-with-postfix.png ├── number-input-with-postfix.png └── subflow-stickies.png ├── settings.gradle ├── setup.sh └── src ├── CODEOWNERS ├── main ├── java │ └── formflow │ │ └── library │ │ ├── DevController.java │ │ ├── FileController.java │ │ ├── FormFlowController.java │ │ ├── FormFlowErrorController.java │ │ ├── PdfController.java │ │ ├── ScreenController.java │ │ ├── ValidationService.java │ │ ├── address_validation │ │ ├── AddressValidationService.java │ │ ├── ClientFactory.java │ │ ├── ValidatedAddress.java │ │ └── ValidationRequestFactory.java │ │ ├── config │ │ ├── ActionManager.java │ │ ├── ConditionManager.java │ │ ├── DisabledFlowInterceptorConfiguration.java │ │ ├── FlowConfiguration.java │ │ ├── FlowsConfigurationFactory.java │ │ ├── FlowsConfigurationFactoryConfig.java │ │ ├── FormFlowConfigurationProperties.java │ │ ├── LandmarkConfiguration.java │ │ ├── NextScreen.java │ │ ├── PdfMapFactory.java │ │ ├── PdfMapFactoryConfig.java │ │ ├── ScreenNavigationConfiguration.java │ │ ├── SecurityConfigurationBase.java │ │ ├── SessionContinuityInterceptorConfiguration.java │ │ ├── SubflowConfiguration.java │ │ ├── SubflowManager.java │ │ ├── SubflowRelationship.java │ │ ├── ThymeleafConfiguration.java │ │ └── submission │ │ │ ├── Action.java │ │ │ ├── Condition.java │ │ │ └── ShortCodeConfig.java │ │ ├── data │ │ ├── FlowInputs.java │ │ ├── FormSubmission.java │ │ ├── Submission.java │ │ ├── SubmissionEncryptionService.java │ │ ├── SubmissionRepository.java │ │ ├── SubmissionRepositoryService.java │ │ ├── UserFile.java │ │ ├── UserFileRepository.java │ │ ├── UserFileRepositoryService.java │ │ ├── annotations │ │ │ ├── DynamicField.java │ │ │ ├── Money.java │ │ │ ├── Phone.java │ │ │ └── SSN.java │ │ └── validators │ │ │ ├── MoneyValidator.java │ │ │ ├── PhoneValidator.java │ │ │ └── SSNValidator.java │ │ ├── email │ │ ├── EmailClient.java │ │ └── MailgunEmailClient.java │ │ ├── exceptions │ │ └── FlowConfigurationException.java │ │ ├── file │ │ ├── ClammitVirusScanner.java │ │ ├── CloudFile.java │ │ ├── CloudFileRepository.java │ │ ├── FileConversionService.java │ │ ├── FileValidationService.java │ │ ├── FileVirusScanner.java │ │ ├── NoOpCloudFileRepository.java │ │ ├── NoOpVirusScanner.java │ │ └── S3CloudFileRepository.java │ │ ├── filters │ │ └── MDCInsertionFilter.java │ │ ├── inputs │ │ ├── AddressParts.java │ │ ├── Encrypted.java │ │ └── FieldNameMarkers.java │ │ ├── interceptors │ │ ├── DisabledFlowInterceptor.java │ │ └── SessionContinuityInterceptor.java │ │ ├── pdf │ │ ├── CheckboxField.java │ │ ├── DatabaseField.java │ │ ├── DatabaseFieldPreparer.java │ │ ├── DefaultSubmissionFieldPreparer.java │ │ ├── OneToManyPreparer.java │ │ ├── OneToOnePreparer.java │ │ ├── PDFFormFiller.java │ │ ├── PdfField.java │ │ ├── PdfFieldMapper.java │ │ ├── PdfMap.java │ │ ├── PdfMapConfiguration.java │ │ ├── PdfMapSubflow.java │ │ ├── PdfService.java │ │ ├── SingleField.java │ │ ├── SubflowFieldPreparer.java │ │ ├── SubmissionField.java │ │ ├── SubmissionFieldPreparer.java │ │ └── SubmissionFieldPreparers.java │ │ └── utils │ │ ├── InputUtils.java │ │ ├── RegexUtils.java │ │ └── UserFileMap.java └── resources │ ├── META-INF │ └── resources │ │ └── webjars │ │ └── form-flow │ │ └── 0.0.1 │ │ ├── css │ │ ├── customDropzone.css │ │ ├── honeycrisp.min.css │ │ └── libraryStyles.css │ │ ├── fonts │ │ ├── MaterialIcons-Regular.eot │ │ ├── MaterialIcons-Regular.svg │ │ ├── MaterialIcons-Regular.ttf │ │ └── MaterialIcons-Regular.woff │ │ ├── images │ │ ├── code-for-america-logo_black.svg │ │ ├── code-for-america-logo_white.svg │ │ ├── emoji_pairs │ │ │ ├── emoji_dependent_care.png │ │ │ ├── emoji_elderly_disabled.png │ │ │ ├── emoji_jobs.png │ │ │ ├── emoji_older_man_and_woman.png │ │ │ ├── emoji_self_employed.png │ │ │ ├── emoji_ssi_fast_ssp.png │ │ │ ├── emoji_ssi_ssp.png │ │ │ └── emoji_students.png │ │ ├── emojis │ │ │ ├── 1f30e.png │ │ │ ├── 1f31f.png │ │ │ ├── 1f35a.png │ │ │ ├── 1f373.png │ │ │ ├── 1f392.png │ │ │ ├── 1f393.png │ │ │ ├── 1f3e0.png │ │ │ ├── 1f447.png │ │ │ ├── 1f44c.png │ │ │ ├── 1f44d.png │ │ │ ├── 1f464.png │ │ │ ├── 1f468-1f393.png │ │ │ ├── 1f469-1f33e.png │ │ │ ├── 1f469-1f393.png │ │ │ ├── 1f475.png │ │ │ ├── 1f476.png │ │ │ ├── 1f477.png │ │ │ ├── 1f48a.png │ │ │ ├── 1f4b0.png │ │ │ ├── 1f4b2.png │ │ │ ├── 1f4b3.png │ │ │ ├── 1f4b5.png │ │ │ ├── 1f4bb.png │ │ │ ├── 1f4bc.png │ │ │ ├── 1f4c4.png │ │ │ ├── 1f4cb.png │ │ │ ├── 1f4dd.png │ │ │ ├── 1f4de.png │ │ │ ├── 1f4eb.png │ │ │ ├── 1f4ec.png │ │ │ ├── 1f4f2.png │ │ │ ├── 1f512.png │ │ │ ├── 1f575-1f3fe-2640.png │ │ │ ├── 1f5fa.png │ │ │ ├── 1f600.png │ │ │ ├── 1f60a.png │ │ │ ├── 1f610.png │ │ │ ├── 1f641.png │ │ │ ├── 1f691.png │ │ │ ├── 1f697.png │ │ │ ├── 1f6b0.png │ │ │ ├── 1f6e1.png │ │ │ ├── 1f914.png │ │ │ ├── 1f951.png │ │ │ ├── 1f955.png │ │ │ ├── 1f957.png │ │ │ ├── 1f958.png │ │ │ ├── 260e.png │ │ │ ├── 267f.png │ │ │ ├── 26a0.png │ │ │ ├── 2705.png │ │ │ ├── 270a.png │ │ │ ├── 270f.png │ │ │ ├── 2712.png │ │ │ ├── checkmark.png │ │ │ ├── crossmark.png │ │ │ ├── fast_disability.png │ │ │ ├── older-man.png │ │ │ ├── older-woman.png │ │ │ └── pen.png │ │ ├── icon_accordion_close.svg │ │ └── icon_accordion_open.svg │ │ └── js │ │ ├── formFlowDisableMultipleFormSubmit.js │ │ ├── formFlowDropZone.js │ │ ├── formFlowFollowUpQuestion.js │ │ ├── formFlowLanguageSelector.js │ │ ├── honeycrisp.min.js │ │ └── qunit-upload-tests.js │ ├── application-form-flow-library.yaml │ ├── cfa-uswds-templates │ ├── .keep │ └── fragments │ │ ├── .keep │ │ └── libraryHead.html │ ├── db │ └── migration │ │ ├── V1__create_submissions_table.sql │ │ ├── V2023.08.24.15.00.27__add_virus_scanned_column.sql │ │ ├── V2023.10.25.08.53.44__create_doc_type_label_for_user_files.sql │ │ ├── V2023.12.15.16_51__use_timestamps_with_timezones.sql │ │ ├── V2024.09.18.15.44__submission_add_column_short_code.sql │ │ ├── V2025.01.29.12.58__user_files_add_column_conversion_source_file_id.sql │ │ ├── V2__create_files_table.sql │ │ ├── V3__rename_extension_column_in_user_files_table.sql │ │ ├── V4__user_files_change_primary_key_type.sql │ │ ├── V5__submission_id_change_primary_key_to_uuid.sql │ │ ├── V6__submission_add_column_url_params.sql │ │ └── V7__submission_add_primary_key.sql │ ├── default-thumbnail.txt │ ├── logback-spring.xml │ ├── messages-form-flow.properties │ ├── messages-form-flow_es.properties │ ├── messages-form-flow_vi.properties │ ├── pdf-fonts │ ├── NotoSans-Regular.ttf │ └── NotoSansSC-Regular.ttf │ └── templates │ ├── disabledFeature.html │ ├── errors │ ├── devError.html │ └── genericError.html │ └── fragments │ ├── aboveCard.html │ ├── cardHeader.html │ ├── cardHeaderForSingleInputScreen.html │ ├── continueButton.html │ ├── demoBanner.html │ ├── fileUploader.html │ ├── footer.html │ ├── form.html │ ├── goBack.html │ ├── head.html │ ├── honeycrisp │ ├── accordion.html │ └── reveal.html │ ├── icons.html │ ├── inputError.html │ ├── inputs │ ├── address.html │ ├── checkbox.html │ ├── checkboxFieldset.html │ ├── checkboxInSet.html │ ├── date.html │ ├── money.html │ ├── number.html │ ├── phone.html │ ├── radio.html │ ├── radioFieldset.html │ ├── select.html │ ├── selectOption.html │ ├── selectOptionPlaceholder.html │ ├── ssn.html │ ├── state.html │ ├── submitButton.html │ ├── submitButtonSecondary.html │ ├── text.html │ ├── textArea.html │ └── yesOrNo.html │ ├── languageSelector.html │ ├── libraryHead.html │ ├── screens │ ├── addressSelectionScreen.html │ ├── addressSuggestionFound.html │ ├── addressSuggestionNotFound.html │ ├── screenWithOneInput.html │ └── screenWithYesAndNoButtons.html │ └── toolbar.html └── test ├── java └── formflow │ └── library │ ├── StarterApplicationTest.java │ ├── ValidationServiceTest.java │ ├── address_validation │ ├── AddressValidationJourneyTest.java │ ├── AddressValidationServiceTest.java │ ├── AddressValidationServiceTestConfiguration.java │ └── ValidationRequestFactoryTest.java │ ├── client │ └── DisableMultipleFormSubmitTest.java │ ├── config │ ├── FlowsConfigurationFactoryTest.java │ ├── FlywayConfiguration.java │ ├── InterceptorMockMvcTest.java │ ├── MessageSourceConfig.java │ ├── ShortCodeConfigAlphaTest.java │ ├── ShortCodeConfigNumericTest.java │ ├── ShortCodeConfigUpperAlphaTest.java │ ├── ThymeleafConfigurationLoadedTest.java │ └── ThymeleafConfigurationUnloadedTest.java │ ├── controllers │ ├── DataInterceptorJourneyTest.java │ ├── DisabledFlowInterceptorTest.java │ ├── FileControllerTest.java │ ├── LockedSubmissionRedirectTest.java │ ├── NoOpVirusScannerTest.java │ ├── PdfControllerTest.java │ ├── ScreenControllerJourneyTest.java │ ├── ScreenControllerShortCodeCreationPointTest.java │ ├── ScreenControllerTest.java │ ├── ShowCustomErrorPageTest.java │ ├── ShowErrorStackTraceTest.java │ └── UploadBlockedIfVirusScanUnreachableTest.java │ ├── data │ ├── SubmissionEncryptionServiceTest.java │ ├── SubmissionTests.java │ └── validators │ │ ├── MoneyValidatorTest.java │ │ ├── PhoneValidatorTest.java │ │ └── SSNValidatorTest.java │ ├── email │ └── MailgunEmailClientTest.java │ ├── file │ ├── ClammitVirusScannerTest.java │ ├── FileValidationServiceTest.java │ ├── SecurityConfigurationJourneyTest.java │ ├── UploadJourneyTests.java │ └── UploadUnitTests.java │ ├── framework │ ├── AfterSaveActionTest.java │ ├── BeforeDisplayActionTest.java │ ├── BeforeSaveActionTest.java │ ├── ConditionalNavigationTest.java │ ├── CrossValidationTest.java │ ├── InputsTest.java │ └── OnPostActionTest.java │ ├── inputs │ ├── ConditionsTestFlow.java │ ├── OtherTestFlow.java │ ├── TestFlow.java │ ├── TestFlowAddressValidation.java │ ├── TestLandmarkFlow.java │ ├── TestSubflowLogic.java │ ├── UploadFlowA.java │ ├── UploadFlowB.java │ └── YetAnotherTestFlow.java │ ├── interceptors │ └── SpyInterceptorConfig.java │ ├── languages │ └── LanguagesTest.java │ ├── pdf │ ├── DatabaseFieldPreparerTest.java │ ├── OneToManyPreparerTest.java │ ├── OneToOnePreparerTest.java │ ├── PDFFormFillerTest.java │ ├── PdfFieldMapperTest.java │ ├── PdfMapConfigurationTest.java │ ├── PdfMapTest.java │ ├── PdfServiceTest.java │ ├── SubflowFieldPreparersTest.java │ ├── SubmissionFieldPreparersTest.java │ └── TestCustomPreparer.java │ ├── repository │ ├── SubmissionRepositoryServiceTest.java │ └── UserFileRepositoryServiceTests.java │ ├── submission │ └── conditions │ │ ├── DidNotFindAddressSuggestion.java │ │ ├── DidNotFindSubflowAddressSuggestion.java │ │ ├── FalseCondition.java │ │ ├── FoundAddressSuggestion.java │ │ ├── FoundSubflowAddressSuggestion.java │ │ ├── SubflowCondition.java │ │ └── TrueCondition.java │ ├── submissions │ └── actions │ │ ├── AggregateDatesInPI.java │ │ ├── CalculateTotalBeforeSave.java │ │ ├── CheckIndicatedContactMethodIsProvided.java │ │ ├── DecryptSSN.java │ │ ├── EncryptSSN.java │ │ ├── FormatDateBeforeSave.java │ │ ├── SendEmailAfterSave.java │ │ └── VerifyValidDate.java │ └── utilities │ ├── AbstractBasePageTest.java │ ├── AbstractMockMvcTest.java │ ├── DatePart.java │ ├── EmailRegexTest.java │ ├── FormScreen.java │ ├── Page.java │ ├── SeleniumFactory.java │ ├── TestUtils.java │ └── WebDriverConfiguration.java └── resources ├── another-test-heic.heic ├── application-test.yaml ├── flows-config ├── test-after-save-action.yaml ├── test-before-display-action.yaml ├── test-before-save-action.yaml ├── test-conditional-navigation.yaml ├── test-cross-validation-action.yaml ├── test-flow-address-validation.yaml ├── test-flow.yaml ├── test-landmark-flow.yaml ├── test-on-post-action.yaml └── test-upload-flow.yaml ├── i-am-not-a-png.txt.png ├── password-protected.pdf ├── pdf-map.yaml ├── pdfs ├── blankPdf.pdf ├── testFlow │ └── Multipage-UBI-Form.pdf └── testPdf.pdf ├── templates ├── conditionsTestFlow │ ├── skipFirst.html │ └── viewThird.html ├── index.html ├── otherTestFlow │ ├── inputs.html │ ├── subflowAddItem.html │ ├── subflowAddItemPage2.html │ ├── success.html │ └── test.html ├── testFlow │ ├── contactInfoPreference.html │ ├── first.html │ ├── inputs.html │ ├── last.html │ ├── next.html │ ├── other.html │ ├── pageWithCheckboxSetInput.html │ ├── pageWithCustomSubmitButton.html │ ├── pageWithDefaultSubmitButton.html │ ├── pageWithMultipleValidationInput.html │ ├── pageWithOptionalValidation.html │ ├── pageWithSSNInput.html │ ├── second.html │ ├── subflowAddItem.html │ ├── subflowAddItemPage2.html │ ├── success.html │ ├── test.html │ ├── testAddressValidation.html │ ├── testAddressValidationFound.html │ ├── testAddressValidationNotFound.html │ ├── testDeleteConfirmationScreen.html │ ├── testEntryScreen.html │ ├── testReviewScreen.html │ └── testUpload.html ├── testFlowAddressValidation │ ├── subflowAddItem.html │ ├── success.html │ ├── testAddressValidation.html │ ├── testAddressValidationFound.html │ ├── testAddressValidationNotFound.html │ ├── testAddressVerification.html │ ├── testSubflowAddressValidation.html │ ├── testSubflowAddressValidationFound.html │ └── testSubflowAddressValidationNotFound.html ├── testLandmarkFlow │ ├── first.html │ ├── nonFormPage.html │ └── success.html ├── testSubflowLogic │ ├── getScreen.html │ ├── otherGetScreen.html │ ├── subflowAddItem.html │ └── testEntryScreen.html ├── uploadFlowA │ ├── docUploadJourney.html │ └── docUploadUnit.html ├── uploadFlowB │ ├── docUploadJourney.html │ └── docUploadUnit.html └── yetAnotherTestFlow │ ├── getScreen.html │ ├── otherGetScreen.html │ ├── subflowAddItem.html │ ├── subflowAddItemPage2.html │ └── testEntryScreen.html ├── test-archive.zip ├── test-platypus.gif ├── test.jpeg ├── test.png ├── test.tif ├── testA.jpeg └── testB.jpeg /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | test_patterns = ["src/test"] 4 | 5 | [[analyzers]] 6 | name = "test-coverage" 7 | 8 | [[analyzers]] 9 | name = "java" 10 | 11 | [analyzers.meta] 12 | runtime_version = "21" -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "daily" 7 | ignore: 8 | - dependency-name: "com.amazonaws:aws-java-sdk-s3" 9 | update-types: ["version-update:semver-patch"] 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | #### Issue tracking number 🔗 2 | 3 | 4 | 5 | #### Description of change ✍️ 6 | 7 | 8 | 9 | #### Priority 🥇 10 | 11 | 12 | 13 | 14 | #### Effect on other applications using FFB 🌊 15 | 16 | 18 | 19 | #### Testing 20 | 21 | 22 | 23 | #### ✅ Checklist before requesting a review 24 | 25 | - [ ] Does the new code follow [our preferred coding 26 | style](/intellij-settings/PlatformFlavoredGoogleStyle.xml)? 27 | - [ ] Does the code include javadocs, where necessary? 28 | - [ ] Have tests for this feature been added / updated? 29 | - [ ] Has the readme been updated? 30 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: Create Pre-Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: 'Version to release' 7 | type: string 8 | required: true 9 | refToRelease: 10 | description: 'Ref to release (branch, sha, tag, etc)' 11 | type: string 12 | required: true 13 | default: 'main' 14 | 15 | jobs: 16 | run-tests: 17 | name: Run tests 18 | uses: ./.github/workflows/run-tests.yml 19 | create-pre-release: 20 | name: Create Pre-Release 21 | needs: run-tests 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | with: 27 | ref: ${{ inputs.refToRelease }} 28 | - name: Tag Version 29 | run: | 30 | echo Creating pre-release version: ${{inputs.version}} 31 | git config --local user.email "platforms-robot@codeforamerica.org" 32 | git config --local user.name "CfA Platforms Robot" 33 | git tag -a ${{inputs.version}} -m "Pre-release version ${{inputs.version}}" 34 | git push origin ${{inputs.version}} 35 | - name: Draft Release 36 | uses: softprops/action-gh-release@v2 37 | with: 38 | tag_name: ${{inputs.version}} 39 | generate_release_notes: true 40 | body: | 41 | FILL IN DETAILS ABOUT THIS RELEASE! 42 | draft: true 43 | prerelease: true 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | HELP.md 3 | .gradle 4 | gradle.properties 5 | build/ 6 | !gradle/wrapper/gradle-wrapper.jar 7 | !**/src/main/**/build/ 8 | !**/src/test/**/build/ 9 | src/test/resources/qunit-results.png 10 | 11 | ### STS ### 12 | .apt_generated 13 | .classpath 14 | .factorypath 15 | .project 16 | .settings 17 | .springBeans 18 | .sts4-cache 19 | bin/ 20 | !**/src/main/**/bin/ 21 | !**/src/test/**/bin/ 22 | 23 | ### IntelliJ IDEA ### 24 | .idea 25 | *.iws 26 | *.iml 27 | *.ipr 28 | out/ 29 | !**/src/main/**/out/ 30 | !**/src/test/**/out/ 31 | 32 | ### NetBeans ### 33 | /nbproject/private/ 34 | /nbbuild/ 35 | /dist/ 36 | /nbdist/ 37 | /.nb-gradle/ 38 | 39 | ### VS Code ### 40 | .vscode/ 41 | 42 | ### Mac OS ### 43 | .DS_Store -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Code for America 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /readme-assets/FFB-Version-1-Gift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/readme-assets/FFB-Version-1-Gift.png -------------------------------------------------------------------------------- /readme-assets/intellij-json-schema-mappings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/readme-assets/intellij-json-schema-mappings.png -------------------------------------------------------------------------------- /readme-assets/money-input-with-postfix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/readme-assets/money-input-with-postfix.png -------------------------------------------------------------------------------- /readme-assets/number-input-with-postfix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/readme-assets/number-input-with-postfix.png -------------------------------------------------------------------------------- /readme-assets/subflow-stickies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/readme-assets/subflow-stickies.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/7.3.3/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "formflow-library" 11 | include("lib") 12 | 13 | buildCache { 14 | local { 15 | directory = new File(rootDir, 'build-cache') 16 | removeUnusedEntriesAfterDays = 30 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @codeforamerica/platform -------------------------------------------------------------------------------- /src/main/java/formflow/library/DevController.java: -------------------------------------------------------------------------------- 1 | package formflow.library; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | 10 | /** 11 | * Controller responsible for handling development environment specific requests. It is active only when the application is 12 | * running in the `dev` profile. 13 | */ 14 | @Controller 15 | @EnableAutoConfiguration 16 | @Slf4j 17 | @Profile("dev") 18 | @RequestMapping("dev") 19 | public class DevController { 20 | 21 | /** 22 | * Handles the GET request to `dev/icons`. This method renders a page that displays the current icons available in the 23 | * development environment. 24 | * 25 | * @return The name of the HTML page that shows the icons. 26 | */ 27 | @GetMapping("/icons") 28 | String getIcons() { 29 | return "fragments/icons"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/address_validation/ClientFactory.java: -------------------------------------------------------------------------------- 1 | package formflow.library.address_validation; 2 | 3 | import com.smartystreets.api.ClientBuilder; 4 | import com.smartystreets.api.StaticCredentials; 5 | import com.smartystreets.api.us_street.Client; 6 | import java.util.List; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * Factory component for creating SmartyStreets API clients. This class provides a method to instantiate a new SmartyStreets 11 | * client with given authentication and license information. 12 | */ 13 | @Component 14 | public class ClientFactory { 15 | 16 | /** 17 | * Default constructor for ClientFactory. 18 | */ 19 | public ClientFactory() { 20 | } 21 | 22 | /** 23 | * Creates and configures a new SmartyStreets API client. The client is configured with the provided authentication ID, token, 24 | * and license. 25 | * 26 | * @param authId The authentication ID for the SmartyStreets API. 27 | * @param authToken The authentication token for the SmartyStreets API. 28 | * @param license The license key for using the SmartyStreets API. 29 | * @return A configured instance of the SmartyStreets Client. 30 | */ 31 | public Client create(String authId, String authToken, String license) { 32 | return new ClientBuilder(new StaticCredentials(authId, authToken)).withLicenses(List.of(license)).buildUsStreetApiClient(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/address_validation/ValidatedAddress.java: -------------------------------------------------------------------------------- 1 | package formflow.library.address_validation; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * Represents a validated address. This class stores information about an address, including street address, apartment number, 8 | * city, state, and zip code. 9 | */ 10 | @Data 11 | @AllArgsConstructor 12 | public class ValidatedAddress { 13 | 14 | String streetAddress; 15 | String apartmentNumber; 16 | String city; 17 | String state; 18 | String zipCode; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/ConditionManager.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import formflow.library.config.submission.Condition; 4 | import formflow.library.data.Submission; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Slf4j 11 | @Component 12 | public class ConditionManager { 13 | 14 | private final HashMap conditions = new HashMap<>(); 15 | 16 | public ConditionManager(List conditionsList) { 17 | conditionsList.forEach(condition -> this.conditions.put(condition.getClass().getSimpleName(), condition)); 18 | } 19 | 20 | public Condition getCondition(String name) { 21 | return conditions.get(name); 22 | } 23 | 24 | public Boolean conditionExists(String name) { 25 | return conditions.containsKey(name); 26 | } 27 | 28 | public Boolean runCondition(String conditionName, Submission submission) { 29 | Condition condition = getCondition(conditionName); 30 | if (condition == null) { 31 | log.warn("Condition not found: " + conditionName); 32 | return false; 33 | } 34 | return condition.run(submission); 35 | } 36 | 37 | public Boolean runCondition(String conditionName, Submission submission, String uuid) { 38 | Condition condition = getCondition(conditionName); 39 | if (condition == null) { 40 | log.warn("Condition not found: " + conditionName); 41 | return false; 42 | } 43 | return condition.run(submission, uuid); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/DisabledFlowInterceptorConfiguration.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | 4 | import static formflow.library.interceptors.DisabledFlowInterceptor.PATH_FORMAT; 5 | 6 | import formflow.library.interceptors.DisabledFlowInterceptor; 7 | import java.util.List; 8 | 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 12 | 13 | /*** 14 | * Adds DisabledFlowInterceptorConfiguration to the Interceptor registry. 15 | */ 16 | @Configuration 17 | public class DisabledFlowInterceptorConfiguration implements WebMvcConfigurer { 18 | 19 | FormFlowConfigurationProperties formFlowConfigurationProperties; 20 | 21 | /** 22 | * Default constructor for DisabledFlowInterceptorConfiguration. Sets {@code formFlowConfigurationProperties}. 23 | * 24 | * @param formFlowConfigurationProperties The configuration properties for form flow. 25 | */ 26 | public DisabledFlowInterceptorConfiguration(FormFlowConfigurationProperties formFlowConfigurationProperties) { 27 | this.formFlowConfigurationProperties = formFlowConfigurationProperties; 28 | } 29 | 30 | /** 31 | * Adds the DisabledFlowInterceptor to the Interceptor registry. 32 | * 33 | * @param registry The Interceptor registry. 34 | */ 35 | @Override 36 | public void addInterceptors(InterceptorRegistry registry) { 37 | registry.addInterceptor(new DisabledFlowInterceptor(formFlowConfigurationProperties)) 38 | .addPathPatterns(List.of(PATH_FORMAT)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/FlowConfiguration.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import java.util.Map; 4 | import java.util.stream.Collectors; 5 | 6 | import lombok.Data; 7 | import lombok.Getter; 8 | 9 | /** 10 | * Represents the configuration for a certain flow. 11 | */ 12 | @Data 13 | @Getter 14 | public class FlowConfiguration { 15 | 16 | private String name; 17 | 18 | private Map flow; 19 | 20 | private Map subflows; 21 | 22 | private LandmarkConfiguration landmarks; 23 | 24 | /** 25 | * Returns the screen navigation for a particular screen. 26 | * 27 | * @param screenName name of the screen to get the flow for, not null 28 | * @return the navigation configuration for the particular screen 29 | */ 30 | public ScreenNavigationConfiguration getScreenNavigation(String screenName) { 31 | return flow.get(screenName); 32 | } 33 | 34 | public void setFlow(Map screenMap) { 35 | flow = screenMap.entrySet().stream() 36 | .map(entry -> { 37 | entry.getValue().setName(entry.getKey()); 38 | return entry; 39 | }) 40 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/FlowsConfigurationFactoryConfig.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import java.io.IOException; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Factory for FlowsConfiguration configuration. 13 | */ 14 | @Configuration 15 | public class FlowsConfigurationFactoryConfig { 16 | 17 | @Autowired(required = false) 18 | FormFlowConfigurationProperties formFlowConfigurationProperties; 19 | 20 | /** 21 | * Bean to get a FlowsConfigurationFactory object. 22 | * 23 | * @return flow configuration factory 24 | */ 25 | @Bean 26 | public FlowsConfigurationFactory flowsConfigurationFactory() { 27 | if (this.formFlowConfigurationProperties == null) { 28 | return new FlowsConfigurationFactory(); 29 | } 30 | return new FlowsConfigurationFactory(formFlowConfigurationProperties); 31 | } 32 | 33 | /** 34 | * Bean to get a list of FlowConfiguration objects. 35 | * 36 | * @return list of flow configuration objects 37 | */ 38 | @Bean 39 | public List flowsConfiguration() throws IOException { 40 | return flowsConfigurationFactory().getObject(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/LandmarkConfiguration.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import java.util.ArrayList; 4 | import lombok.Data; 5 | 6 | /** 7 | * Class which contains configuration for the landmark screen(s) in a flow. 8 | */ 9 | @Data 10 | public class LandmarkConfiguration { 11 | String firstScreen; 12 | ArrayList afterSubmitPages; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/NextScreen.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | 4 | import lombok.Data; 5 | 6 | 7 | /** 8 | * NextScreen represents what the next screen in a flow is and any conditions that need to be tested before one can go to that 9 | * screen. 10 | */ 11 | @Data 12 | public class NextScreen { 13 | 14 | private String name; 15 | private String condition; 16 | } -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/PdfMapFactoryConfig.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import formflow.library.pdf.PdfMap; 4 | import java.util.List; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | /** 10 | * Factory for PdfMapConfiguration configuration. 11 | */ 12 | @Configuration 13 | @ConditionalOnProperty(name = "form-flow.pdf.map-file") 14 | public class PdfMapFactoryConfig { 15 | 16 | @Bean 17 | public PdfMapFactory pdfMapFactory() { 18 | return new PdfMapFactory(); 19 | } 20 | 21 | /** 22 | * Bean to get a list of FlowConfiguration objects. 23 | * 24 | * @return list of flow configuration objects 25 | */ 26 | @Bean 27 | public List pdfMaps() { 28 | return pdfMapFactory().getObject(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/ScreenNavigationConfiguration.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import lombok.Data; 7 | 8 | /** 9 | * Screen navigation configuration class used to store navigation information about a specific screen. 10 | */ 11 | @Data 12 | public class ScreenNavigationConfiguration { 13 | 14 | private String name; 15 | 16 | private List nextScreens = Collections.emptyList(); 17 | private String subflow; 18 | 19 | private String onPostAction; 20 | private String crossFieldValidationAction; 21 | private String beforeSaveAction; 22 | private String beforeDisplayAction; 23 | private String afterSaveAction; 24 | private String condition; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/SessionContinuityInterceptorConfiguration.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import formflow.library.interceptors.SessionContinuityInterceptor; 4 | import java.util.List; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 10 | 11 | /*** 12 | * Adds SessionContinuityInterceptor to the Interceptor registry. 13 | */ 14 | @Configuration 15 | @ConditionalOnProperty(name = "form-flow.session-continuity-interceptor.enabled", havingValue = "true") 16 | public class SessionContinuityInterceptorConfiguration implements WebMvcConfigurer { 17 | 18 | @Autowired 19 | List flowConfigurations; 20 | 21 | 22 | /** 23 | * Adds the SessionContinuityInterceptor to the Interceptor registry. 24 | * @param registry the Interceptor registry. 25 | */ 26 | @Override 27 | public void addInterceptors(InterceptorRegistry registry) { 28 | registry.addInterceptor(new SessionContinuityInterceptor(flowConfigurations)) 29 | .addPathPatterns(List.of(SessionContinuityInterceptor.FLOW_PATH_FORMAT, 30 | SessionContinuityInterceptor.NAVIGATION_FLOW_PATH_FORMAT)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/SubflowConfiguration.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * Class which contains configuration for a sub flow. 7 | */ 8 | @Data 9 | public class SubflowConfiguration { 10 | 11 | SubflowRelationship relationship; 12 | String entryScreen; 13 | String iterationStartScreen; 14 | String reviewScreen; 15 | String deleteConfirmationScreen; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/SubflowRelationship.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class SubflowRelationship { 7 | 8 | private String relatesTo; 9 | private String relationAlias; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/submission/Condition.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config.submission; 2 | 3 | import formflow.library.data.Submission; 4 | 5 | /** 6 | * An interface that defines a function to run a Condition 7 | * 8 | *

9 | * Conditions are applied to screen flow, so each screen may have a condition (or multiple) attached to it. 10 | *

11 | */ 12 | public interface Condition { 13 | 14 | /** 15 | * Runs a condition check on a submission. 16 | * 17 | * @param submission submission object the condition is associated with, not null 18 | * @return true if the condition check passes, else false 19 | */ 20 | default Boolean run(Submission submission) { 21 | throw new UnsupportedOperationException("Method not implemented in " + this.getClass().getName()); 22 | } 23 | 24 | /** 25 | * Runs a condition check on a submission's subflow iteration. 26 | * 27 | * @param submission submission object the condition is associated with, not null 28 | * @param subflowUuid uuid of the subflow iteration this should operate on 29 | * @return true if the condition check passes, else false 30 | */ 31 | default Boolean run(Submission submission, String subflowUuid) { 32 | throw new UnsupportedOperationException("Method not implemented in " + this.getClass().getName()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/config/submission/ShortCodeConfig.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config.submission; 2 | 3 | import java.util.Map; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | @ConfigurationProperties(prefix = "form-flow.short-code") 11 | public class ShortCodeConfig { 12 | 13 | private Map shortCodeConfigs; 14 | 15 | public Config getConfig(String flowName) { 16 | return shortCodeConfigs != null ? shortCodeConfigs.get(flowName) : null; 17 | } 18 | 19 | public void setShortCodeConfigs(Map shortCodeConfigs) { 20 | this.shortCodeConfigs = shortCodeConfigs; 21 | } 22 | 23 | @Setter 24 | @Getter 25 | public static class Config { 26 | 27 | public enum ShortCodeType { 28 | alphanumeric, alpha, numeric; 29 | } 30 | 31 | public enum ShortCodeCreationPoint { 32 | creation, submission 33 | } 34 | 35 | private int codeLength = 6; 36 | 37 | private ShortCodeType codeType = ShortCodeType.alphanumeric; 38 | 39 | private boolean uppercase = true; 40 | 41 | private ShortCodeCreationPoint creationPoint = ShortCodeCreationPoint.submission; 42 | 43 | private String prefix = null; 44 | 45 | private String suffix = null; 46 | 47 | public boolean isCreateShortCodeAtCreation() { 48 | return ShortCodeCreationPoint.creation.equals(creationPoint); 49 | } 50 | 51 | public boolean isCreateShortCodeAtSubmission() { 52 | return ShortCodeCreationPoint.submission.equals(creationPoint); 53 | } 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/data/FlowInputs.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data; 2 | 3 | 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | public class FlowInputs { 7 | 8 | @NotBlank 9 | private String _csrf; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/data/SubmissionRepository.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data; 2 | 3 | import java.util.Optional; 4 | import java.util.UUID; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | /** 9 | * Repository interface for the SubmissionRepository. 10 | */ 11 | @Repository 12 | public interface SubmissionRepository extends JpaRepository { 13 | 14 | boolean existsByShortCode(String shortCode); 15 | 16 | Optional findSubmissionByShortCode(String shortCode); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/data/annotations/DynamicField.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data.annotations; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 6 | 7 | import jakarta.validation.Payload; 8 | import java.lang.annotation.Documented; 9 | import java.lang.annotation.ElementType; 10 | import java.lang.annotation.Retention; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * The {@code DynamicField} annotation is used to mark a field as dynamic, as in we don't know how many of the fields will come 15 | * in. 16 | */ 17 | @Target({ElementType.FIELD, TYPE, ANNOTATION_TYPE}) 18 | @Retention(RUNTIME) 19 | @Documented 20 | public @interface DynamicField { 21 | 22 | /** 23 | * The default message that will be used in validation messages. 24 | * 25 | * @return the default validation message 26 | */ 27 | String message() default ""; 28 | 29 | /** 30 | * Defines the group(s) the constraint belongs to. This is used for grouped validation. 31 | * 32 | * @return the groups for which the constraint is applicable 33 | */ 34 | Class[] groups() default {}; 35 | 36 | /** 37 | * Can be used by clients of the Bean Validation API to assign custom payload objects to a constraint. 38 | * 39 | * @return the payload associated with the constraint 40 | */ 41 | Class[] payload() default {}; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/data/annotations/Money.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data.annotations; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import formflow.library.data.validators.MoneyValidator; 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | import jakarta.validation.Constraint; 11 | import jakarta.validation.Payload; 12 | 13 | /** 14 | * Custom annotation for validating monetary values. This annotation can be used on fields to ensure that they contain a valid 15 | * representation of a monetary amount. It uses {@link MoneyValidator} for the validation logic. 16 | */ 17 | @Target({FIELD}) 18 | @Retention(RUNTIME) 19 | @Constraint(validatedBy = MoneyValidator.class) 20 | @Documented 21 | public @interface Money { 22 | 23 | /** 24 | * Default message to be used in validation failure. 25 | * 26 | * @return The default error message. 27 | */ 28 | String message() default "Please make sure to enter a valid dollar amount. Example: 1.50."; 29 | 30 | /** 31 | * Defines the group(s) the constraint belongs to. This is used for grouped validation. 32 | * 33 | * @return The groups to which this constraint belongs. 34 | */ 35 | Class[] groups() default {}; 36 | 37 | /** 38 | * Can be used by clients of the Bean Validation API to assign custom payload objects to a constraint. 39 | * 40 | * @return The payload associated with the constraint. 41 | */ 42 | Class[] payload() default {}; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/data/annotations/Phone.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data.annotations; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import formflow.library.data.validators.PhoneValidator; 7 | import jakarta.validation.Constraint; 8 | import jakarta.validation.Payload; 9 | import java.lang.annotation.Documented; 10 | import java.lang.annotation.Retention; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * Custom annotation for validating phone numbers in form submissions. This annotation ensures that the annotated field contains a 15 | * valid phone number in a specified format. It uses {@link PhoneValidator} for the validation logic. 16 | */ 17 | @Target({FIELD}) 18 | @Retention(RUNTIME) 19 | @Constraint(validatedBy = PhoneValidator.class) 20 | @Documented 21 | public @interface Phone { 22 | 23 | /** 24 | * Default message that will be used when the phone number validation fails. 25 | * 26 | * @return The default error message. 27 | */ 28 | String message() default "Please make sure you enter a ten digit phone number: (999) 999-9999"; 29 | 30 | /** 31 | * Optional groups for categorizing validation constraints. 32 | * 33 | * @return An array of group classes. 34 | */ 35 | Class[] groups() default {}; 36 | 37 | /** 38 | * Can be used by clients of the Bean Validation API to assign custom payload objects to a constraint. 39 | * 40 | * @return An array of payload classes. 41 | */ 42 | Class[] payload() default {}; 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/data/annotations/SSN.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data.annotations; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import formflow.library.data.validators.SSNValidator; 7 | import jakarta.validation.Constraint; 8 | import jakarta.validation.Payload; 9 | import java.lang.annotation.Documented; 10 | import java.lang.annotation.Retention; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * Custom annotation for validating social security number values. This annotation can be used on fields to ensure that they 15 | * contain a valid representation of a SSN. It uses {@link formflow.library.data.validators.SSNValidator} for the validation 16 | * logic. 17 | */ 18 | @Target({FIELD}) 19 | @Retention(RUNTIME) 20 | @Constraint(validatedBy = SSNValidator.class) 21 | @Documented 22 | public @interface SSN { 23 | 24 | /** 25 | * Default message to be used in validation failure. 26 | * 27 | * @return The default error message. 28 | */ 29 | String message() default "Make sure the SSN is valid and 9 digits"; 30 | 31 | /** 32 | * Defines the group(s) the constraint belongs to. This is used for grouped validation. 33 | * 34 | * @return The groups to which this constraint belongs. 35 | */ 36 | Class[] groups() default {}; 37 | 38 | /** 39 | * Can be used by clients of the Bean Validation API to assign custom payload objects to a constraint. 40 | * 41 | * @return The payload associated with the constraint. 42 | */ 43 | Class[] payload() default {}; 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/data/validators/MoneyValidator.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data.validators; 2 | 3 | import formflow.library.data.annotations.Money; 4 | import java.util.regex.Pattern; 5 | import jakarta.validation.ConstraintValidator; 6 | import jakarta.validation.ConstraintValidatorContext; 7 | 8 | /** 9 | * Validates that a given string is in the correct money format. The format is defined as one or more digits, optionally followed 10 | * by a dot and exactly two digits after the dot. This validator is used in conjunction with the {@link Money} annotation to 11 | * ensure that the string representation of money values adheres to this format. 12 | */ 13 | public class MoneyValidator implements ConstraintValidator { 14 | 15 | /** 16 | * Checks if the provided {@code String} value matches the expected money format. The format is validated against the regex 17 | * pattern which ensures that the value consists of one or more digits, optionally followed by a dot and two decimal places. 18 | * 19 | * @param value the {@code String} value to be validated 20 | * @param context context in which the constraint is evaluated 21 | * @return {@code true} if the value matches the money format, otherwise {@code false} 22 | */ 23 | @Override 24 | public boolean isValid(String value, ConstraintValidatorContext context) { 25 | if (value != null && !value.isBlank()){ 26 | return Pattern.matches("(^(0|([1-9]\\d*))?(\\.\\d{1,2})?$)?", value); 27 | } 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/data/validators/PhoneValidator.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data.validators; 2 | 3 | import formflow.library.data.annotations.Phone; 4 | import jakarta.validation.ConstraintValidator; 5 | import jakarta.validation.ConstraintValidatorContext; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * Validator class for custom {@link Phone} annotation. This validator checks if the input string is a valid phone number matching 10 | * a specific format. 11 | */ 12 | public class PhoneValidator implements ConstraintValidator { 13 | 14 | /** 15 | * Validates the given phone number against a predefined pattern. This pattern corresponds to a standard US phone number. 16 | * format. 17 | * 18 | * @param value The phone number to validate. 19 | * @param context Context in which the constraint is evaluated. 20 | * @return {@code true} if the phone number is valid. 21 | */ 22 | @Override 23 | public boolean isValid(String value, ConstraintValidatorContext context) { 24 | if (value != null && !value.isBlank()){ 25 | return Pattern.matches("(\\([2-9][0-8][0-9]\\)\\s\\d{3}-\\d{4})", value); 26 | } 27 | return true; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/data/validators/SSNValidator.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data.validators; 2 | 3 | import formflow.library.data.annotations.SSN; 4 | import jakarta.validation.ConstraintValidator; 5 | import jakarta.validation.ConstraintValidatorContext; 6 | import java.util.regex.Pattern; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * Validates that a given string is in the correct social security number format. This validator is used in conjunction with the 12 | * {@link formflow.library.data.annotations.SSN} annotation to ensure that the string representation of SSN values adheres to this format. 13 | *

14 | * The default format is defined as ###-##-####, but with the following constraints: 15 | *

16 | * does not begin with 000, 666, or 900-999 17 | * does not have 00 in the middle group 18 | * does not end with 0000 19 | */ 20 | @Component 21 | public class SSNValidator implements ConstraintValidator { 22 | 23 | @Value("${form-flow.validation.ssn-pattern:^(?!000|666|9\\d{2})\\d{3}-(?!00)\\d{2}-(?!0000)\\d{4}$}") 24 | private String pattern; 25 | 26 | /** 27 | * Checks if the provided {@code String} value matches the expected SSN format. T 28 | * 29 | * @param value the {@code String} value to be validated 30 | * @param context context in which the constraint is evaluated 31 | * @return {@code true} if the value matches the SSN format, otherwise {@code false} 32 | */ 33 | @Override 34 | public boolean isValid(String value, ConstraintValidatorContext context) { 35 | if (value != null && !value.isBlank()) { 36 | return Pattern.matches(pattern, value); 37 | } 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/exceptions/FlowConfigurationException.java: -------------------------------------------------------------------------------- 1 | package formflow.library.exceptions; 2 | 3 | /** 4 | * Custom exception for handling configuration errors related to form flows. This exception is thrown when there are issues in the 5 | * configuration of the form flows, such as missing or invalid configurations that are essential for the flow's operation. 6 | */ 7 | public class FlowConfigurationException extends RuntimeException { 8 | 9 | /** 10 | * Constructs a new FlowConfigurationException with the specified message. 11 | * 12 | * @param message The message that is sent on exception 13 | */ 14 | public FlowConfigurationException(String message) { 15 | super(message); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/file/CloudFile.java: -------------------------------------------------------------------------------- 1 | package formflow.library.file; 2 | 3 | 4 | import java.util.Map; 5 | 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | 9 | @AllArgsConstructor 10 | @Getter 11 | public class CloudFile { 12 | private Long fileSize; 13 | private byte[] fileBytes; 14 | private Map metadata; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/file/CloudFileRepository.java: -------------------------------------------------------------------------------- 1 | package formflow.library.file; 2 | 3 | import java.io.IOException; 4 | import org.springframework.web.multipart.MultipartFile; 5 | 6 | 7 | public interface CloudFileRepository { 8 | 9 | void upload(String filePath, MultipartFile file) throws IOException, InterruptedException; 10 | 11 | CloudFile get(String filepath); 12 | 13 | void delete(String filepath); 14 | } -------------------------------------------------------------------------------- /src/main/java/formflow/library/file/FileVirusScanner.java: -------------------------------------------------------------------------------- 1 | package formflow.library.file; 2 | 3 | import org.springframework.web.multipart.MultipartFile; 4 | 5 | /** 6 | * Interface that defines the abilities any File Virus Scanner should implement to work in this setup 7 | */ 8 | public interface FileVirusScanner { 9 | 10 | /** 11 | * This method will send the passed in `file` to a virus scanner service defined in the implementation and return a boolean 12 | * value indicating if the file contains a virus. 13 | * 14 | * @param file file to check for virus in 15 | * @return true if virus is found, false otherwise 16 | * @throws Exception 17 | */ 18 | boolean virusDetected(MultipartFile file) throws Exception; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/file/NoOpCloudFileRepository.java: -------------------------------------------------------------------------------- 1 | package formflow.library.file; 2 | 3 | import java.io.IOException; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.multipart.MultipartFile; 8 | 9 | /** 10 | * This implementation of CloudFileRepository does nothing useful at all. 11 | * It logs the methods being called. 12 | */ 13 | @Service 14 | @Profile("test") 15 | @Slf4j 16 | public class NoOpCloudFileRepository implements CloudFileRepository { 17 | 18 | @Override 19 | public void upload(String filepath, MultipartFile file) throws IOException, InterruptedException { 20 | log.info("Pretending to upload file {} to s3 with filepath {}", file.getOriginalFilename(), filepath); 21 | } 22 | 23 | @Override 24 | public CloudFile get(String filepath) { 25 | log.info("Pretending to get file {}", filepath); 26 | return null; 27 | } 28 | 29 | @Override 30 | public void delete(String filepath) { 31 | log.info("Pretending to delete file from s3: {}", filepath); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/file/NoOpVirusScanner.java: -------------------------------------------------------------------------------- 1 | package formflow.library.file; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.web.multipart.MultipartFile; 7 | 8 | /** 9 | * This implementation of FileVirusScanner does nothing useful at all. 10 | * It logs the methods being called. 11 | */ 12 | @Service 13 | @ConditionalOnProperty(name = "form-flow.uploads.virus-scanning.enabled", havingValue = "false", matchIfMissing = true) 14 | @Slf4j 15 | public class NoOpVirusScanner implements FileVirusScanner { 16 | 17 | @Override 18 | public boolean virusDetected(MultipartFile file) { 19 | log.info("Virus scanning disabled. Returning false."); 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/inputs/AddressParts.java: -------------------------------------------------------------------------------- 1 | package formflow.library.inputs; 2 | 3 | /** 4 | * Fields used to define an address fragment 5 | */ 6 | public enum AddressParts { 7 | /** 8 | * Street component of an address 9 | */ 10 | STREET_ADDRESS_1("StreetAddress1"), 11 | /** 12 | * Apartment, suite, or office number 13 | */ 14 | STREET_ADDRESS_2("StreetAddress2"), 15 | /** 16 | * City name 17 | */ 18 | CITY("City"), 19 | /** 20 | * The State value stored as the state code, a two character String, for example, "IL", "CA", etc. 21 | */ 22 | STATE("State"), 23 | /** 24 | * Five or nine digit value used by the post office to facilitate the delivery of mail. 25 | */ 26 | ZIPCODE("ZipCode"); 27 | private final String value; 28 | 29 | @Override 30 | public String toString() { 31 | return value; 32 | } 33 | 34 | AddressParts(String value) { 35 | this.value = value; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/inputs/Encrypted.java: -------------------------------------------------------------------------------- 1 | package formflow.library.inputs; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface Encrypted { 11 | String key() default ""; 12 | } -------------------------------------------------------------------------------- /src/main/java/formflow/library/inputs/FieldNameMarkers.java: -------------------------------------------------------------------------------- 1 | package formflow.library.inputs; 2 | 3 | public class FieldNameMarkers { 4 | 5 | public static final String UNVALIDATED_FIELD_MARKER_CSRF = "_csrf"; 6 | public static final String UNVALIDATED_FIELD_MARKER_VALIDATE_ADDRESS = "validate_"; 7 | public static final String UNVALIDATED_FIELD_MARKER_VALIDATED = "_validated"; 8 | public static final String DYNAMIC_FIELD_MARKER = "_wildcard_"; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/pdf/CheckboxField.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import java.util.List; 4 | import lombok.AccessLevel; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.ToString; 8 | import lombok.experimental.FieldDefaults; 9 | 10 | @Getter 11 | @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) 12 | @ToString(callSuper = true, onlyExplicitlyIncluded = true) 13 | @EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) 14 | public class CheckboxField extends SubmissionField { 15 | 16 | @ToString.Include 17 | @EqualsAndHashCode.Include 18 | List value; 19 | 20 | public CheckboxField(String name, List value, Integer iteration) { 21 | super(name, iteration); 22 | this.value = value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/pdf/DatabaseField.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | import lombok.experimental.FieldDefaults; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | @Getter 11 | @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) 12 | @ToString(callSuper = true, onlyExplicitlyIncluded = true) 13 | @EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) 14 | public class DatabaseField extends SubmissionField { 15 | 16 | @ToString.Include 17 | @EqualsAndHashCode.Include 18 | @NotNull String value; 19 | 20 | public DatabaseField(String name, @NotNull String value) { 21 | super(name, null); 22 | this.value = value; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/pdf/DefaultSubmissionFieldPreparer.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import formflow.library.data.Submission; 4 | import java.util.Map; 5 | 6 | public interface DefaultSubmissionFieldPreparer { 7 | 8 | Map prepareSubmissionFields(Submission submission, PdfMap pdfMap); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/pdf/OneToManyPreparer.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import formflow.library.data.Submission; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class OneToManyPreparer implements DefaultSubmissionFieldPreparer { 11 | 12 | @Override 13 | public Map prepareSubmissionFields(Submission submission, PdfMap pdfMap) { 14 | Map fieldMap = pdfMap.getAllFields(); 15 | Map preppedFields = new HashMap<>(); 16 | 17 | fieldMap.keySet().stream() 18 | .filter(field -> fieldMap.get(field) instanceof Map && submission.getInputData().get(field + "[]") != null) 19 | .forEach(field -> 20 | preppedFields.put(field, new CheckboxField( 21 | field, 22 | (List) submission.getInputData().get(field + "[]"), 23 | null 24 | ) 25 | ) 26 | ); 27 | return preppedFields; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/pdf/OneToOnePreparer.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import formflow.library.data.Submission; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class OneToOnePreparer implements DefaultSubmissionFieldPreparer { 10 | 11 | @Override 12 | public Map prepareSubmissionFields(Submission submission, PdfMap pdfMap) { 13 | Map preppedFields = new HashMap<>(); 14 | Map fieldMap = pdfMap.getAllFields(); 15 | 16 | fieldMap.keySet().stream() 17 | .filter(field -> (fieldMap.get(field) instanceof String) && (submission.getInputData().get(field) != null)) 18 | .forEach(field -> 19 | preppedFields.put(field, new SingleField( 20 | field, 21 | submission.getInputData().get(field).toString(), 22 | null 23 | ) 24 | ) 25 | ); 26 | 27 | return preppedFields; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/pdf/PdfField.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import java.util.Optional; 4 | 5 | public record PdfField(String name, String value) { 6 | 7 | public PdfField(String name, String value) { 8 | this.name = name; 9 | this.value = Optional.ofNullable(value).orElse(""); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/pdf/PdfMapConfiguration.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.List; 6 | 7 | @Component 8 | public class PdfMapConfiguration { 9 | 10 | private final List maps; 11 | 12 | List getMaps() { 13 | return maps; 14 | } 15 | 16 | public PdfMapConfiguration(List maps) { 17 | this.maps = maps; 18 | } 19 | 20 | /** 21 | * Based on a specific form flow, get the PDF file path (and name) associated with that flow. 22 | * 23 | * @param flow The form flow to get the PDF file path for 24 | * @return path to PDF file (including filename), prefixed with '/' 25 | */ 26 | public String getPdfPathFromFlow(String flow) { 27 | String pdf = getPdfMap(flow).getPdf(); 28 | return pdf.startsWith("/") ? pdf : "/" + pdf; 29 | } 30 | 31 | /** 32 | * Based on a specific form flow, get the PdfMap associated with that flow. 33 | * 34 | * @param flow The form flow to get the PdfMap for 35 | * @return PdfMap object for the form flow 36 | */ 37 | public PdfMap getPdfMap(String flow) { 38 | return maps.stream().filter(config -> config.getFlow().equals(flow)) 39 | .findFirst().orElseThrow(RuntimeException::new); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/pdf/SingleField.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | import lombok.experimental.FieldDefaults; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | @Getter 11 | @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) 12 | @ToString(callSuper = true, onlyExplicitlyIncluded = true) 13 | @EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) 14 | public class SingleField extends SubmissionField { 15 | 16 | @ToString.Include 17 | @EqualsAndHashCode.Include 18 | @NotNull String value; 19 | 20 | public SingleField(String name, @NotNull String value, Integer iteration) { 21 | super(name, iteration); 22 | this.value = value; 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/pdf/SubmissionField.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | 8 | @AllArgsConstructor 9 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 10 | @ToString(onlyExplicitlyIncluded = true) 11 | public abstract class SubmissionField { 12 | 13 | @ToString.Include 14 | @EqualsAndHashCode.Include 15 | public String name = null; 16 | 17 | @ToString.Include 18 | @EqualsAndHashCode.Include 19 | @Getter 20 | public Integer iteration = null; 21 | 22 | /** 23 | * Returns the name of the field. If the field is part of a subflow, the name will be the field name suffixed with a "_" and 24 | * iteration number. 25 | *
26 | * Given this data: 27 | *

28 |    *    name = "incomeJob"
29 |    *    iteration = 2
30 |    * 
31 | * This method would return: `incomeJob_2`. 32 | *
33 | * If no iteration value is set, then just the name is returned: 34 | *
35 |    *    name = "firstName"
36 |    *    iteration = null
37 |    * 
38 | * This method would return: `firstName` 39 | * 40 | * @return name of field 41 | */ 42 | public String getName() { 43 | return iteration != null ? name + "_" + iteration : name; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/pdf/SubmissionFieldPreparer.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import formflow.library.data.Submission; 4 | import java.util.Map; 5 | 6 | public interface SubmissionFieldPreparer { 7 | 8 | Map prepareSubmissionFields(Submission submission, PdfMap pdfMap); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/utils/InputUtils.java: -------------------------------------------------------------------------------- 1 | package formflow.library.utils; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * Utility class to help with parsing input from forms 7 | */ 8 | public class InputUtils { 9 | 10 | /** 11 | * Static function to help determine if a String or ArrayList contains the {@code target} value. 12 | * 13 | *

Currently, this performs a comparison between two data types:

14 | *
    15 | *
  • String - checks if the {@code value} equals the {@code target}
  • 16 | *
  • ArrayList of Strings - checks if the {@code target} is in the ArrayList
  • 17 | *
18 | * 19 | * @param value Object to check if it equals or contains the {@code target} string 20 | * @param target string to find in {@code target} 21 | * @return true if the {@code target} is equal to or contained in {@code value}, else false 22 | */ 23 | public static boolean arrayOrStringContains(Object value, String target) { 24 | if (value instanceof String) { 25 | return value.equals(target); 26 | } 27 | 28 | if (value instanceof ArrayList) { 29 | return ((ArrayList) value).contains(target); 30 | } 31 | 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/formflow/library/utils/RegexUtils.java: -------------------------------------------------------------------------------- 1 | package formflow.library.utils; 2 | 3 | /** 4 | * Utility class containing regular expressions for various patterns. This class serves as a centralized repository for regex 5 | * patterns used throughout the application. 6 | */ 7 | public class RegexUtils { 8 | 9 | /** 10 | * Regular expression pattern for validating email addresses. This pattern conforms to the general email format, allowing a wide 11 | * range of email addresses. It includes support for various characters in the local part and domain of the email address. 12 | * 13 | * @see Email 15 | * regex is from Shiba. 16 | */ 17 | public static final String EMAIL_REGEX = "[a-zA-Z0-9!#$%&'*+=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?"; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/fonts/MaterialIcons-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/fonts/MaterialIcons-Regular.eot -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/fonts/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/fonts/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/fonts/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/fonts/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_dependent_care.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_dependent_care.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_elderly_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_elderly_disabled.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_jobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_jobs.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_older_man_and_woman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_older_man_and_woman.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_self_employed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_self_employed.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_ssi_fast_ssp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_ssi_fast_ssp.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_ssi_ssp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_ssi_ssp.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_students.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emoji_pairs/emoji_students.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f30e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f30e.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f31f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f31f.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f35a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f35a.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f373.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f373.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f392.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f392.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f393.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f393.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f3e0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f3e0.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f447.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f447.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f44c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f44c.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f44d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f44d.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f464.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f464.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f468-1f393.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f468-1f393.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f469-1f33e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f469-1f33e.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f469-1f393.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f469-1f393.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f475.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f475.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f476.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f476.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f477.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f477.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f48a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f48a.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4b0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4b0.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4b2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4b2.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4b3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4b3.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4b5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4b5.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4bb.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4bc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4bc.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4c4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4c4.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4cb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4cb.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4dd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4dd.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4de.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4de.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4eb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4eb.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4ec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4ec.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4f2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f4f2.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f512.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f575-1f3fe-2640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f575-1f3fe-2640.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f5fa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f5fa.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f600.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f60a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f60a.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f610.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f610.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f641.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f641.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f691.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f691.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f697.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f697.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f6b0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f6b0.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f6e1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f6e1.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f914.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f914.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f951.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f951.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f955.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f955.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f957.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f957.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f958.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/1f958.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/260e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/260e.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/267f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/267f.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/26a0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/26a0.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/2705.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/2705.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/270a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/270a.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/270f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/270f.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/2712.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/2712.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/checkmark.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/crossmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/crossmark.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/fast_disability.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/fast_disability.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/older-man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/older-man.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/older-woman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/older-woman.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/emojis/pen.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/icon_accordion_close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | + 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/images/icon_accordion_open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | + 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/js/formFlowDisableMultipleFormSubmit.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", function () { 2 | const formSubmitButton = document.getElementById("form-submit-button"); 3 | if (formSubmitButton) { 4 | const form = formSubmitButton.form; 5 | if (form) { 6 | form.addEventListener("submit", function () { 7 | formSubmitButton.classList.add("button--disabled"); 8 | formSubmitButton.disabled = true; 9 | }); 10 | } 11 | } 12 | }); 13 | 14 | // This prevents the button from being disabled when the user navigates back to 15 | // the page after submitting the form using back button. 16 | // (Safari caches the page in it's back-forward cache). 17 | window.addEventListener('pageshow', function (event) { 18 | if (event.persisted) { 19 | const formSubmitButton = document.getElementById("form-submit-button"); 20 | if (formSubmitButton) { 21 | formSubmitButton.classList.remove("button--disabled"); 22 | formSubmitButton.disabled = false; 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/js/formFlowDropZone.js: -------------------------------------------------------------------------------- 1 | FormFlowDZ = { 2 | hideContinueIfNoFiles: function (inputName, idToHide) { 3 | window[dropzonePrefix + inputName].on('success', function () { 4 | $(document.getElementById(idToHide)).removeClass("display-none"); 5 | }); 6 | 7 | window[dropzonePrefix + inputName].on('removedfile', function () { 8 | if (window[dropzonePrefix + inputName].files.length === 0) { 9 | $(document.getElementById(idToHide)).addClass("display-none"); 10 | } 11 | }); 12 | 13 | if (window[dropzonePrefix + inputName].files.length > 0) { 14 | $(document.getElementById(idToHide)).removeClass("display-none"); 15 | } 16 | }, 17 | disableIfNoFiles: function (inputName, idToDisable) { 18 | window[dropzonePrefix + inputName].on('success', function () { 19 | $(document.getElementById(idToDisable)).removeClass("disabled"); 20 | }); 21 | 22 | window[dropzonePrefix + inputName].on('removedfile', function () { 23 | if (window[dropzonePrefix + inputName].files.length === 0) { 24 | $(document.getElementById(idToDisable)).addClass("disabled"); 25 | } 26 | }); 27 | 28 | if (window[dropzonePrefix + inputName].files.length > 0) { 29 | $(document.getElementById(idToDisable)).removeClass("disabled"); 30 | } 31 | } 32 | } 33 | 34 | window.FormFlowDZ = FormFlowDZ; -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/webjars/form-flow/0.0.1/js/formFlowFollowUpQuestion.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", function () { 2 | const allFollowUpTriggers = document.querySelectorAll('[data-follow-up]'); 3 | 4 | allFollowUpTriggers.forEach(followUpTrigger => { 5 | if (followUpTrigger.checked === false) { 6 | const followUpInputs = document.querySelectorAll( 7 | `${document.querySelector( 8 | '[data-follow-up]').dataset.followUp} input`); 9 | followUpInputs.forEach(input => { 10 | input.setAttribute("disabled", ""); 11 | }); 12 | } 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/main/resources/application-form-flow-library.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | flyway: 3 | placeholders: 4 | uuid_function: "gen_random_uuid" 5 | user_file_doc_type_default_label: ${form-flow.uploads.default-doc-type-label:#{null}} 6 | messages: 7 | encoding: ISO-8859-1 8 | basename: messages, messages-form-flow 9 | session: 10 | store-type: jdbc 11 | timeout: 72h 12 | jdbc: 13 | initialize-schema: always 14 | jpa: 15 | open-in-view: false -------------------------------------------------------------------------------- /src/main/resources/cfa-uswds-templates/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/cfa-uswds-templates/.keep -------------------------------------------------------------------------------- /src/main/resources/cfa-uswds-templates/fragments/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/cfa-uswds-templates/fragments/.keep -------------------------------------------------------------------------------- /src/main/resources/cfa-uswds-templates/fragments/libraryHead.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1__create_submissions_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS submissions 2 | ( 3 | id SERIAL PRIMARY KEY, 4 | flow VARCHAR NOT NULL, 5 | input_data JSONB NOT NULL, 6 | created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, 7 | updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, 8 | submitted_at TIMESTAMP WITHOUT TIME ZONE 9 | ); -------------------------------------------------------------------------------- /src/main/resources/db/migration/V2023.08.24.15.00.27__add_virus_scanned_column.sql: -------------------------------------------------------------------------------- 1 | -- Add the 'virus_scanned' column to the 'user_files' table 2 | ALTER TABLE user_files ADD COLUMN virus_scanned BOOLEAN DEFAULT FALSE; 3 | 4 | -- Set the 'virus_scanned' value to false for existing rows 5 | UPDATE user_files SET virus_scanned = FALSE; 6 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V2023.10.25.08.53.44__create_doc_type_label_for_user_files.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE user_files 2 | ADD COLUMN doc_type_label VARCHAR(255) DEFAULT '${user_file_doc_type_default_label}'; -------------------------------------------------------------------------------- /src/main/resources/db/migration/V2023.12.15.16_51__use_timestamps_with_timezones.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE submissions 2 | ALTER created_at SET DATA TYPE TIMESTAMP WITH TIME ZONE, 3 | ALTER updated_at SET DATA TYPE TIMESTAMP WITH TIME ZONE, 4 | ALTER submitted_at SET DATA TYPE TIMESTAMP WITH TIME ZONE 5 | ; 6 | 7 | ALTER TABLE user_files 8 | ALTER created_at SET DATA TYPE TIMESTAMP WITH TIME ZONE; 9 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V2024.09.18.15.44__submission_add_column_short_code.sql: -------------------------------------------------------------------------------- 1 | alter table submissions 2 | add column short_code VARCHAR NULL UNIQUE; 3 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V2025.01.29.12.58__user_files_add_column_conversion_source_file_id.sql: -------------------------------------------------------------------------------- 1 | alter table user_files 2 | add column conversion_source_file_id uuid NULL; 3 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V2__create_files_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS user_files 2 | ( 3 | file_id SERIAL PRIMARY KEY , 4 | submission_id INT REFERENCES submissions(id), 5 | created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, 6 | original_name VARCHAR NOT NULL, 7 | repository_path VARCHAR NOT NULL, 8 | extension VARCHAR NOT NULL, 9 | filesize REAL NOT NULL 10 | ); 11 | 12 | CREATE INDEX idx_submission_id on user_files (submission_id); 13 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V3__rename_extension_column_in_user_files_table.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE user_files 2 | RENAME COLUMN extension TO mime_type; -------------------------------------------------------------------------------- /src/main/resources/db/migration/V4__user_files_change_primary_key_type.sql: -------------------------------------------------------------------------------- 1 | -- This will make the original file_id a column a uuid column 2 | 3 | alter table user_files add column new_id uuid DEFAULT ${uuid_function}(); 4 | alter table user_files drop column file_id; 5 | alter table user_files rename column new_id to file_id; 6 | alter table user_files alter column file_id set not null; 7 | alter table user_files add primary key (file_id); -------------------------------------------------------------------------------- /src/main/resources/db/migration/V5__submission_id_change_primary_key_to_uuid.sql: -------------------------------------------------------------------------------- 1 | -- This will make the original id a column a uuid column 2 | alter table submissions add column new_id uuid UNIQUE DEFAULT gen_random_uuid(); 3 | update submissions set new_id = gen_random_uuid() where new_id is null; 4 | alter table user_files add column new_submission_id uuid; 5 | 6 | update user_files uf set new_submission_id = s.new_id from submissions s where s.id = uf.submission_id; 7 | alter table user_files drop constraint user_files_submission_id_fkey; 8 | 9 | alter table submissions drop column id; 10 | alter table submissions rename column new_id to id; 11 | 12 | alter table user_files drop column submission_id; 13 | alter table user_files rename column new_submission_id to submission_id; 14 | alter table user_files add constraint user_files_submission_id_fkey FOREIGN KEY (submission_id) REFERENCES submissions (id); 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V6__submission_add_column_url_params.sql: -------------------------------------------------------------------------------- 1 | alter table submissions 2 | add column url_params JSONB; -------------------------------------------------------------------------------- /src/main/resources/db/migration/V7__submission_add_primary_key.sql: -------------------------------------------------------------------------------- 1 | alter table submissions 2 | add primary key (id); 3 | alter table submissions 4 | alter column id set not null; 5 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %boldCyan(%d{HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %boldMagenta(%logger{36}) - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/messages-form-flow.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/messages-form-flow.properties -------------------------------------------------------------------------------- /src/main/resources/messages-form-flow_es.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/messages-form-flow_es.properties -------------------------------------------------------------------------------- /src/main/resources/pdf-fonts/NotoSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/pdf-fonts/NotoSans-Regular.ttf -------------------------------------------------------------------------------- /src/main/resources/pdf-fonts/NotoSansSC-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/main/resources/pdf-fonts/NotoSansSC-Regular.ttf -------------------------------------------------------------------------------- /src/main/resources/templates/disabledFeature.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 11 | 12 |

13 | 14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/errors/devError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

Internal 6 | Error!

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
KeyValue
Timestamp:
Status:
Error:
Message:
Path:
Exception:
41 | 42 |

Stacktrace

44 |
45 |
46 |
47 | 48 | -------------------------------------------------------------------------------- /src/main/resources/templates/errors/genericError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 12 | 14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/aboveCard.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/cardHeader.html: -------------------------------------------------------------------------------- 1 |
6 |

7 |

10 |
11 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/cardHeaderForSingleInputScreen.html: -------------------------------------------------------------------------------- 1 |
9 |

10 | 11 |

12 |

15 |
16 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/continueButton.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/demoBanner.html: -------------------------------------------------------------------------------- 1 |
5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

6 |
8 | 10 |
11 | 18 |
19 |
20 |
-------------------------------------------------------------------------------- /src/main/resources/templates/fragments/form.html: -------------------------------------------------------------------------------- 1 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/goBack.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/honeycrisp/accordion.html: -------------------------------------------------------------------------------- 1 | 7 |
8 | 11 |
12 | 13 |
14 |
15 |
-------------------------------------------------------------------------------- /src/main/resources/templates/fragments/honeycrisp/reveal.html: -------------------------------------------------------------------------------- 1 | 7 |
8 | 12 |
13 | 14 |
15 |
16 | 26 |
27 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/inputError.html: -------------------------------------------------------------------------------- 1 |

5 | 6 | 7 | 9 |
10 |
11 |

-------------------------------------------------------------------------------- /src/main/resources/templates/fragments/inputs/checkboxInSet.html: -------------------------------------------------------------------------------- 1 | 13 | 35 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/inputs/radio.html: -------------------------------------------------------------------------------- 1 | 8 | 28 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/inputs/selectOption.html: -------------------------------------------------------------------------------- 1 | 6 | 10 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/inputs/selectOptionPlaceholder.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/inputs/submitButton.html: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/inputs/submitButtonSecondary.html: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/inputs/yesOrNo.html: -------------------------------------------------------------------------------- 1 | 7 |
8 |

11 |
12 | 20 | 27 |
-------------------------------------------------------------------------------- /src/main/resources/templates/fragments/libraryHead.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/screens/addressSuggestionNotFound.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

5 |

7 |
8 |
9 | 25 |
26 | 28 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/toolbar.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 7 | 13 |
14 |
15 | 16 |
17 |
18 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/StarterApplicationTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 | 7 | @SpringBootApplication 8 | @EnableConfigurationProperties 9 | public class StarterApplicationTest { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(StarterApplicationTest.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/address_validation/AddressValidationServiceTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package formflow.library.address_validation; 2 | 3 | import org.mockito.Mockito; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Primary; 7 | import org.springframework.context.annotation.Profile; 8 | 9 | @Profile("test") 10 | @Configuration 11 | public class AddressValidationServiceTestConfiguration { 12 | 13 | @Bean 14 | @Primary 15 | public AddressValidationService addressValidationService() { 16 | return Mockito.mock(AddressValidationService.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/config/FlywayConfiguration.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class FlywayConfiguration { 9 | 10 | @Bean 11 | public FlywayMigrationStrategy clean() { 12 | return flyway -> { 13 | flyway.clean(); 14 | flyway.migrate(); 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/config/MessageSourceConfig.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import static java.util.Locale.ENGLISH; 4 | 5 | import org.springframework.context.MessageSource; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.support.ResourceBundleMessageSource; 9 | 10 | @Configuration 11 | public class MessageSourceConfig { 12 | 13 | @Bean 14 | public MessageSource messageSource() { 15 | ResourceBundleMessageSource rs = new ResourceBundleMessageSource(); 16 | rs.setBasenames("messages-form-flow"); 17 | rs.setDefaultLocale(ENGLISH); 18 | rs.setUseCodeAsDefaultMessage(true); 19 | return rs; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/config/ThymeleafConfigurationLoadedTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.Objects; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 8 | import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.thymeleaf.TemplateEngine; 11 | import org.junit.jupiter.api.Test; 12 | 13 | @ImportAutoConfiguration(ThymeleafAutoConfiguration.class) 14 | @SpringBootTest(classes = {ThymeleafConfiguration.class}, properties = {"form-flow.design-system.name=cfa-uswds"}) 15 | public class ThymeleafConfigurationLoadedTest { 16 | 17 | @Autowired 18 | private TemplateEngine templateEngine; 19 | 20 | @Test 21 | void checkThatUSWDSTemplateResolverIsLoaded() { 22 | assertThat(templateEngine.getTemplateResolvers().size()).isEqualTo(2); 23 | var uswdsResolver = templateEngine.getTemplateResolvers().stream() 24 | .filter(x -> Objects.equals(x.getName(), "org.thymeleaf.templateresolver.ClassLoaderTemplateResolver")).findFirst(); 25 | assertThat(uswdsResolver).isPresent(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/config/ThymeleafConfigurationUnloadedTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.config; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.Objects; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 9 | import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.thymeleaf.TemplateEngine; 12 | 13 | @ImportAutoConfiguration(ThymeleafAutoConfiguration.class) 14 | @SpringBootTest(classes = {ThymeleafConfiguration.class}, properties = {"form-flow.design-system.name=honeycrisp"}) 15 | public class ThymeleafConfigurationUnloadedTest { 16 | 17 | @Autowired 18 | private TemplateEngine templateEngine; 19 | 20 | @Test 21 | void checkThatUSWDSTemplateResolverIsLoaded() { 22 | assertThat(templateEngine.getTemplateResolvers().size()).isEqualTo(1); 23 | var uswdsResolver = templateEngine.getTemplateResolvers().stream() 24 | .filter(x -> Objects.equals(x.getName(), "org.thymeleaf.templateresolver.ClassLoaderTemplateResolver")).findFirst(); 25 | assertThat(uswdsResolver).isNotPresent(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/controllers/ShowCustomErrorPageTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.controllers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 5 | 6 | import formflow.library.utilities.AbstractBasePageTest; 7 | import java.io.IOException; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | 12 | @SpringBootTest(properties = {"form-flow.path=flows-config/test-flow.yaml", "form-flow.error.show-stack-trace=false"}, webEnvironment = RANDOM_PORT) 13 | public class ShowCustomErrorPageTest extends AbstractBasePageTest { 14 | 15 | @Override 16 | @BeforeEach 17 | public void setUp() throws IOException { 18 | startingPage = "flow/inputs/asdf"; 19 | super.setUp(); 20 | } 21 | 22 | @Test 23 | void showCustomErrorPageToHideInternalErrorWhenPropertyIsDisabled() { 24 | assertThat(testPage.getBody()).contains("We're sorry, but something went wrong. Please try again later."); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/data/SubmissionTests.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.UUID; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | 12 | public class SubmissionTests { 13 | 14 | private Submission submission; 15 | private final String iterationUuid = UUID.randomUUID().toString(); 16 | 17 | @BeforeEach 18 | public void setUp() { 19 | Map inputData = new HashMap<>(); 20 | ArrayList> subflowArr = new ArrayList<>(); 21 | 22 | Map subflowMap = new HashMap<>(); 23 | subflowMap.put("_csrf", "a1fd9167-9d6d-4298-b9x4-2fc6c75ff3ab"); 24 | subflowMap.put("firstName", "Rosie"); 25 | subflowMap.put("uuid", iterationUuid); 26 | 27 | subflowArr.add(subflowMap); 28 | inputData.put("household", subflowArr); 29 | submission = Submission.builder() 30 | .inputData(inputData) 31 | .flow("testFlow") 32 | .build(); 33 | } 34 | 35 | @Test 36 | public void shouldMarkSubmissionIterationComplete() { 37 | submission.setIterationIsCompleteToTrue("household", iterationUuid); 38 | Map subflowData = submission.getSubflowEntryByUuid("household", iterationUuid); 39 | assertThat(subflowData.containsKey(Submission.ITERATION_IS_COMPLETE_KEY)).isTrue(); 40 | assertThat(subflowData.get(Submission.ITERATION_IS_COMPLETE_KEY)).isEqualTo(true); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/data/validators/MoneyValidatorTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data.validators; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.ValueSource; 7 | 8 | class MoneyValidatorTest { 9 | 10 | @ValueSource(strings = { 11 | "100", "100.05", "0.05", "0.15", "0.1", "100.1", "1", "9", ".1", "" 12 | }) 13 | @ParameterizedTest 14 | void validMoneyAmountsShouldReturnTrue(String value) { 15 | assertThat(new MoneyValidator().isValid(value, null)).isTrue(); 16 | } 17 | 18 | @ValueSource(strings = { 19 | "012.34", 20 | "0999", 21 | "-100", 22 | "100.123", "1.2.3.4", "01.5" 23 | }) 24 | @ParameterizedTest 25 | void invalidMoneyAmountsShouldReturnFalse(String value) { 26 | assertThat(new MoneyValidator().isValid(value, null)).isFalse(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/java/formflow/library/data/validators/PhoneValidatorTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data.validators; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.ValueSource; 7 | 8 | public class PhoneValidatorTest { 9 | 10 | @ValueSource(strings = { 11 | "(333) 333-3333", 12 | "(923) 456-7890", 13 | "(823) 456-7890", 14 | "(707) 987-5266", 15 | "(829) 622-9048" 16 | }) 17 | @ParameterizedTest 18 | void validPhoneNumberShouldReturnTrue(String validPhoneNumber) { 19 | assertThat(new PhoneValidator().isValid(validPhoneNumber, null)).isTrue(); 20 | } 21 | 22 | @ValueSource(strings = { 23 | "+1(111)-1111", 24 | "(111)222-3333", 25 | "(111)111-11", 26 | "999999999", 27 | "(123) 456-7890", 28 | "(077) 987-5266", 29 | "(892) 622-9048", 30 | "22", 31 | "der" 32 | }) 33 | @ParameterizedTest 34 | void invalidPhoneNumberShouldReturnFalse(String invalidPhoneNumber) { 35 | assertThat(new PhoneValidator().isValid(invalidPhoneNumber, null)).isFalse(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/data/validators/SSNValidatorTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.data.validators; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.ValueSource; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.ActiveProfiles; 10 | 11 | @ActiveProfiles("test") 12 | @SpringBootTest(properties = {"form-flow.path=flows-config/test-flow.yaml"}) 13 | class SSNValidatorTest { 14 | 15 | @Autowired 16 | private SSNValidator ssnValidator; 17 | 18 | // valid SSNs do not begin with 000, 666, or 900-999, do not have 00 in the group position (middle two digits), or end in 0000 19 | @ValueSource(strings = {"123-12-1234", "782-98-5200", "665-01-0001", "899-10-0030"}) 20 | @ParameterizedTest 21 | void validSSNShouldReturnTrue(String value) { 22 | assertThat(ssnValidator.isValid(value, null)).isTrue(); 23 | } 24 | 25 | @ValueSource(strings = {"000-12-1234", "666-98-5200", "900-01-0001", "934-10-0030", "123-00-0030", "123-10-000"}) 26 | @ParameterizedTest 27 | void invalidSSNShouldReturnFalse(String value) { 28 | assertThat(ssnValidator.isValid(value, null)).isFalse(); 29 | } 30 | } -------------------------------------------------------------------------------- /src/test/java/formflow/library/file/SecurityConfigurationJourneyTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.file; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import formflow.library.utilities.AbstractBasePageTest; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 8 | 9 | @SpringBootTest(properties = "form-flow.path=flows-config/test-landmark-flow.yaml", webEnvironment = WebEnvironment.RANDOM_PORT) 10 | public class SecurityConfigurationJourneyTest extends AbstractBasePageTest { 11 | 12 | @Test 13 | void sessionCookieContainsHttpOnlyAndSecureHeaders(){ 14 | assertThat(testPage.getTitle()).isEqualTo("Test index page"); 15 | testPage.clickButton("Start the test flow"); 16 | assertThat(testPage.getTitle()).isEqualTo("First Page"); 17 | assertThat(driver.manage().getCookieNamed("SESSION").isSecure()).isEqualTo(true); 18 | assertThat(driver.manage().getCookieNamed("SESSION").isHttpOnly()).isEqualTo(true); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/file/UploadUnitTests.java: -------------------------------------------------------------------------------- 1 | package formflow.library.file; 2 | 3 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 4 | import static org.awaitility.Awaitility.await; 5 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 6 | 7 | import formflow.library.utilities.AbstractBasePageTest; 8 | import java.io.IOException; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.openqa.selenium.By; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | 14 | @SpringBootTest(properties = {"form-flow.path=flows-config/test-upload-flow.yaml"}, webEnvironment = RANDOM_PORT) 15 | public class UploadUnitTests extends AbstractBasePageTest { 16 | 17 | @Override 18 | @BeforeEach 19 | public void setUp() throws IOException { 20 | startingPage = "flow/uploadFlowA/docUploadUnit"; 21 | super.setUp(); 22 | } 23 | 24 | @Test 25 | void runQunitTests() { 26 | takeSnapShot("src/test/resources/qunit-results.png"); 27 | await().until( 28 | () -> driver.findElements(By.id("qunit-testresult-display")).get(0).getAttribute("innerHTML") 29 | .contains("tests completed in")); 30 | assertThat(testPage.getElementText("qunit-testresult-display")).doesNotContain("global failure"); 31 | assertThat(testPage.getElementText("qunit-testresult-display")).contains("0 failed"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/inputs/ConditionsTestFlow.java: -------------------------------------------------------------------------------- 1 | package formflow.library.inputs; 2 | 3 | import formflow.library.data.FlowInputs; 4 | import jakarta.validation.constraints.NotBlank; 5 | import org.springframework.boot.test.context.TestConfiguration; 6 | 7 | @TestConfiguration 8 | @SuppressWarnings("unused") 9 | public class ConditionsTestFlow extends FlowInputs { 10 | 11 | @NotBlank(message = "{validations.make-sure-to-provide-a-first-name}") 12 | String firstName; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/inputs/TestFlowAddressValidation.java: -------------------------------------------------------------------------------- 1 | package formflow.library.inputs; 2 | 3 | import formflow.library.data.FlowInputs; 4 | import jakarta.validation.constraints.NotBlank; 5 | import org.springframework.boot.test.context.TestConfiguration; 6 | 7 | @TestConfiguration 8 | @SuppressWarnings("unused") 9 | public class TestFlowAddressValidation extends FlowInputs { 10 | 11 | @NotBlank 12 | String validationOffStreetAddress1; 13 | String validationOffStreetAddress2; 14 | @NotBlank 15 | String validationOffCity; 16 | @NotBlank 17 | String validationOffState; 18 | @NotBlank 19 | String validationOffZipCode; 20 | 21 | @NotBlank 22 | String validationOnStreetAddress1; 23 | String validationOnStreetAddress2; 24 | @NotBlank 25 | String validationOnCity; 26 | @NotBlank 27 | String validationOnState; 28 | @NotBlank 29 | String validationOnZipCode; 30 | Boolean useValidatedValidationOn; 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/inputs/TestLandmarkFlow.java: -------------------------------------------------------------------------------- 1 | package formflow.library.inputs; 2 | 3 | import formflow.library.data.FlowInputs; 4 | import jakarta.validation.constraints.NotBlank; 5 | import org.springframework.boot.test.context.TestConfiguration; 6 | 7 | @TestConfiguration 8 | @SuppressWarnings("unused") 9 | public class TestLandmarkFlow extends FlowInputs { 10 | 11 | @NotBlank(message = "{validations.make-sure-to-provide-a-first-name}") 12 | String firstName; 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/inputs/UploadFlowA.java: -------------------------------------------------------------------------------- 1 | package formflow.library.inputs; 2 | 3 | import formflow.library.data.FlowInputs; 4 | import org.springframework.boot.test.context.TestConfiguration; 5 | 6 | @TestConfiguration 7 | public class UploadFlowA extends FlowInputs { 8 | 9 | String uploadTest; 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/inputs/UploadFlowB.java: -------------------------------------------------------------------------------- 1 | package formflow.library.inputs; 2 | 3 | import formflow.library.data.FlowInputs; 4 | import org.springframework.boot.test.context.TestConfiguration; 5 | 6 | @TestConfiguration 7 | public class UploadFlowB extends FlowInputs { 8 | 9 | String uploadTest; 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/languages/LanguagesTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.languages; 2 | 3 | import formflow.library.utilities.AbstractMockMvcTest; 4 | 5 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 6 | 7 | import java.util.Locale; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.MessageSource; 10 | import org.junit.jupiter.api.Test; 11 | 12 | 13 | public class LanguagesTest extends AbstractMockMvcTest { 14 | 15 | @Autowired 16 | private MessageSource messageSource; 17 | 18 | @Test 19 | void shouldGetEnglishTranslation() { 20 | String continueText = messageSource.getMessage("general.inputs.continue", null, Locale.ENGLISH); 21 | 22 | assertThat(continueText).isEqualTo("Continue"); 23 | } 24 | 25 | @Test 26 | void shouldGetSpanishTranslation() { 27 | String continueText = messageSource.getMessage("general.inputs.continue", null, Locale.forLanguageTag("es")); 28 | 29 | assertThat(continueText).isEqualTo("Continuar"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/pdf/OneToManyPreparerTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import formflow.library.data.Submission; 6 | import java.util.List; 7 | import java.util.Map; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | 11 | class OneToManyPreparerTest { 12 | 13 | PdfMap pdfMap; 14 | Submission submission; 15 | 16 | @BeforeEach 17 | void setUp() { 18 | pdfMap = new PdfMap(); 19 | pdfMap.setFlow("flow1"); 20 | submission = Submission.builder().flow("flow1").build(); 21 | } 22 | 23 | @Test 24 | void preparesSubmissionFieldsForCheckboxInputs() { 25 | pdfMap.setInputFields(Map.of( 26 | "checkbox", Map.of( 27 | "option1", "CHECKBOX_OPTION_1", 28 | "option2", "CHECKBOX_OPTION_2", 29 | "option3", "CHECKBOX_OPTION_3" 30 | ))); 31 | submission.setInputData(Map.of( 32 | "checkbox[]", List.of("option1", "option3") 33 | )); 34 | OneToManyPreparer oneToManyPreparer = new OneToManyPreparer(); 35 | 36 | assertThat(oneToManyPreparer.prepareSubmissionFields(submission, pdfMap)).containsExactly( 37 | Map.entry("checkbox", new CheckboxField("checkbox", List.of("option1", "option3"), null)) 38 | ); 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/java/formflow/library/pdf/PdfMapConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | import org.junit.jupiter.api.Test; 9 | 10 | class PdfMapConfigurationTest { 11 | 12 | @Test 13 | void getPdfFromFlowReturnsPdfWithMatchingFlowName() { 14 | String testPdfFilename = "/pdfs/testPdf.pdf"; 15 | PdfMapConfiguration config = new PdfMapConfiguration(List.of( 16 | new PdfMap("flow1", testPdfFilename, Map.of(), Map.of(), Map.of(), Map.of()), 17 | new PdfMap("flow2", "/pdfs/testFlow/Multipage-UBI-Form.pdf", Map.of(), Map.of(), Map.of(), Map.of()) 18 | )); 19 | assertThat(config.getPdfPathFromFlow("flow1")).isEqualTo(testPdfFilename); 20 | } 21 | 22 | @Test 23 | void getPdfFromFlowThrowsExceptionIfConfigMatchingFlowDoesntExist() { 24 | PdfMapConfiguration config = new PdfMapConfiguration(List.of( 25 | new PdfMap("flow1", "pdf", Map.of(), Map.of(), Map.of(), Map.of()) 26 | )); 27 | assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> config.getPdfPathFromFlow("flow2")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/pdf/TestCustomPreparer.java: -------------------------------------------------------------------------------- 1 | package formflow.library.pdf; 2 | 3 | import formflow.library.data.Submission; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class TestCustomPreparer implements SubmissionFieldPreparer { 9 | 10 | @Override 11 | public Map prepareSubmissionFields(Submission submission, PdfMap pdfMap) { 12 | HashMap testFieldMap = new HashMap<>(); 13 | testFieldMap.put("fieldThatGetsOverwritten", new SingleField("fieldThatGetsOverwritten", "OVERWRITTEN", null)); 14 | return testFieldMap; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submission/conditions/DidNotFindAddressSuggestion.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submission.conditions; 2 | 3 | import formflow.library.config.submission.Condition; 4 | import formflow.library.data.Submission; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @SuppressWarnings("unused") 9 | public class DidNotFindAddressSuggestion implements Condition { 10 | 11 | @Override 12 | public Boolean run(Submission submission) { 13 | return !submission.getInputData().containsKey("validationOnStreetAddress1_validated"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submission/conditions/DidNotFindSubflowAddressSuggestion.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submission.conditions; 2 | 3 | import formflow.library.config.submission.Condition; 4 | import formflow.library.data.Submission; 5 | import java.util.List; 6 | import java.util.Map; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class DidNotFindSubflowAddressSuggestion implements Condition { 11 | 12 | @Override 13 | public Boolean run(Submission submission, String uuid) { 14 | List> subflowDataList = (List>) submission.getInputData().get("testSubflow"); 15 | 16 | Map theEntry = subflowDataList.get(0); 17 | return !theEntry.containsKey("validationOnStreetAddress1_validated"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submission/conditions/FalseCondition.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submission.conditions; 2 | 3 | import formflow.library.config.submission.Condition; 4 | import formflow.library.data.Submission; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @SuppressWarnings("unused") 9 | public class FalseCondition implements Condition { 10 | 11 | @Override 12 | public Boolean run(Submission submission) { 13 | return false; 14 | } 15 | 16 | @Override 17 | public Boolean run(Submission submission, String uuid) { 18 | return false; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submission/conditions/FoundAddressSuggestion.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submission.conditions; 2 | 3 | import formflow.library.config.submission.Condition; 4 | import formflow.library.data.Submission; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @SuppressWarnings("unused") 9 | public class FoundAddressSuggestion implements Condition { 10 | 11 | @Override 12 | public Boolean run(Submission submission) { 13 | return submission.getInputData().containsKey("validationOnStreetAddress1_validated"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submission/conditions/FoundSubflowAddressSuggestion.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submission.conditions; 2 | 3 | import formflow.library.config.submission.Condition; 4 | import formflow.library.data.Submission; 5 | import java.util.List; 6 | import java.util.Map; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class FoundSubflowAddressSuggestion implements Condition { 11 | 12 | @Override 13 | public Boolean run(Submission submission, String uuid) { 14 | List> subflowDataList = (List>) submission.getInputData().get("testSubflow"); 15 | Map theEntry = subflowDataList.get(0); 16 | return theEntry.containsKey("validationOnStreetAddress1_validated"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submission/conditions/SubflowCondition.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submission.conditions; 2 | 3 | import formflow.library.config.submission.Condition; 4 | import formflow.library.data.Submission; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class SubflowCondition implements Condition { 9 | 10 | @Override 11 | public Boolean run(Submission submission, String subflowUuid) { 12 | return true; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submission/conditions/TrueCondition.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submission.conditions; 2 | 3 | import formflow.library.config.submission.Condition; 4 | import formflow.library.data.Submission; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @SuppressWarnings("unused") 9 | public class TrueCondition implements Condition { 10 | 11 | @Override 12 | public Boolean run(Submission submission) { 13 | return true; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submissions/actions/AggregateDatesInPI.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submissions.actions; 2 | 3 | import formflow.library.config.submission.Action; 4 | import formflow.library.data.FormSubmission; 5 | import formflow.library.data.Submission; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Map; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class AggregateDatesInPI implements Action { 13 | 14 | public void run(FormSubmission formSubmission, Submission submission) { 15 | Map inputData = formSubmission.getFormData(); 16 | 17 | String prefix = "date"; 18 | List dateComponents = new ArrayList<>(3); 19 | if (formSubmission.formData.containsKey(prefix + "Month") && formSubmission.formData.get(prefix + "Month") != "") { 20 | dateComponents.add((String) formSubmission.formData.get(prefix + "Month")); 21 | dateComponents.add((String) formSubmission.formData.get(prefix + "Day")); 22 | dateComponents.add((String) formSubmission.formData.get(prefix + "Year")); 23 | formSubmission.formData.put(prefix + "Full", String.join("/", dateComponents)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submissions/actions/CalculateTotalBeforeSave.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submissions.actions; 2 | 3 | import formflow.library.config.submission.Action; 4 | import formflow.library.data.Submission; 5 | import java.util.List; 6 | import java.util.Map; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | @SuppressWarnings("unused") 11 | public class CalculateTotalBeforeSave implements Action { 12 | 13 | public void run(Submission submission, String id) { 14 | List> subflow = (List>) submission.getInputData() 15 | .get("income"); 16 | var totalIncome = subflow.stream() 17 | .map(e -> Double.parseDouble((String) e.get("textInput"))) 18 | .reduce(Double::sum) 19 | .get(); 20 | 21 | submission.getInputData().put("totalIncome", totalIncome); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submissions/actions/DecryptSSN.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submissions.actions; 2 | 3 | import formflow.library.config.submission.Action; 4 | import formflow.library.data.FormSubmission; 5 | import formflow.library.data.Submission; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Map; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @SuppressWarnings("unused") 13 | public class DecryptSSN implements Action { 14 | 15 | public void run(Submission submission) { 16 | String ssnEncrypted = (String) submission.getInputData().remove("ssnInputEncrypted"); 17 | if (ssnEncrypted != null) { 18 | String decrypted = decrypt(ssnEncrypted); 19 | submission.getInputData().put("ssnInput", decrypted); 20 | } 21 | } 22 | 23 | public void run(Submission submission, String id) { 24 | Map subflowEntry = submission.getSubflowEntryByUuid("householdMembers", id); 25 | if (id.equals(subflowEntry.get("uuid"))) { 26 | String ssnInput = (String) subflowEntry.remove("ssnInputEncrypted"); 27 | if (ssnInput != null) { 28 | String decrypted = decrypt(ssnInput); 29 | subflowEntry.put("ssnInput", decrypted); 30 | } 31 | } 32 | } 33 | 34 | private String decrypt(String input) { 35 | return input 36 | .replace('A', '0') 37 | .replace('B', '1') 38 | .replace('C', '2') 39 | .replace('D', '3') 40 | .replace('E', '4') 41 | .replace('F', '5'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submissions/actions/EncryptSSN.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submissions.actions; 2 | 3 | import formflow.library.config.submission.Action; 4 | import formflow.library.data.Submission; 5 | import java.util.Map; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @SuppressWarnings("unused") 10 | public class EncryptSSN implements Action { 11 | 12 | public void run(Submission submission) { 13 | String ssnInput = (String) submission.getInputData().remove("ssnInput"); 14 | if (ssnInput != null) { 15 | String encrypted = encrypt(ssnInput); 16 | submission.getInputData().put("ssnInputEncrypted", encrypted); 17 | } 18 | } 19 | 20 | public void run(Submission submission, String id) { 21 | Map subflowEntry = submission.getSubflowEntryByUuid("householdMembers", id); 22 | if (id.equals(subflowEntry.get("uuid"))) { 23 | String ssnInput = (String) subflowEntry.remove("ssnInput"); 24 | if (ssnInput != null) { 25 | String encrypted = encrypt(ssnInput); 26 | subflowEntry.put("ssnInputEncrypted", encrypted); 27 | } 28 | } 29 | } 30 | 31 | private String encrypt(String input) { 32 | return input 33 | .replace('0', 'A') 34 | .replace('1', 'B') 35 | .replace('2', 'C') 36 | .replace('3', 'D') 37 | .replace('4', 'E') 38 | .replace('5', 'F'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submissions/actions/FormatDateBeforeSave.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submissions.actions; 2 | 3 | import formflow.library.config.submission.Action; 4 | import formflow.library.data.Submission; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | @SuppressWarnings("unused") 11 | public class FormatDateBeforeSave implements Action { 12 | 13 | public void run(Submission submission) { 14 | List dateComponents = new ArrayList<>(3); 15 | dateComponents.add((String) submission.getInputData().get("dateMonth")); 16 | dateComponents.add((String) submission.getInputData().get("dateDay")); 17 | dateComponents.add((String) submission.getInputData().get("dateYear")); 18 | submission.getInputData().put("formattedDate", String.join("/", dateComponents)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/submissions/actions/SendEmailAfterSave.java: -------------------------------------------------------------------------------- 1 | package formflow.library.submissions.actions; 2 | 3 | import formflow.library.config.submission.Action; 4 | import formflow.library.data.Submission; 5 | import formflow.library.email.MailgunEmailClient; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | @SuppressWarnings("unused") 11 | public class SendEmailAfterSave implements Action { 12 | 13 | @Autowired 14 | private MailgunEmailClient mailgunEmailClient; 15 | 16 | public void run(Submission submission) { 17 | mailgunEmailClient.sendEmail( 18 | "Subject", 19 | "test@example.com", 20 | "This is a test email" 21 | ); 22 | } 23 | 24 | public void run(Submission submission, String id) { 25 | mailgunEmailClient.sendEmail( 26 | "Subject", 27 | "test@example.com", 28 | "This is a test email" 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/utilities/DatePart.java: -------------------------------------------------------------------------------- 1 | package formflow.library.utilities; 2 | 3 | public enum DatePart { 4 | YEAR(3), MONTH(1), DAY(2); 5 | 6 | private final Integer position; 7 | 8 | DatePart(Integer position) { 9 | this.position = position; 10 | } 11 | 12 | public Integer getPosition() { 13 | return this.position; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/utilities/EmailRegexTest.java: -------------------------------------------------------------------------------- 1 | package formflow.library.utilities; 2 | 3 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 4 | 5 | import formflow.library.utils.RegexUtils; 6 | import java.util.regex.Pattern; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.ValueSource; 9 | 10 | class EmailRegexTest { 11 | 12 | @ValueSource(strings = { 13 | "formflow@gmail.com", 14 | "fce343@whis.edu", 15 | }) 16 | @ParameterizedTest 17 | void shouldReturnTrueIfEmailIsValid(String email) { 18 | String test = RegexUtils.EMAIL_REGEX; 19 | assertThat(Pattern.matches(test, email)).isTrue(); 20 | } 21 | 22 | @ValueSource(strings = { 23 | "test..book@gmail.com", 24 | ".hello@yahoo.org", 25 | "wha?tsssss23@.reatd?.com", 26 | "@book.com", 27 | "wakeup.com", 28 | }) 29 | @ParameterizedTest 30 | void shouldReturnFalseIfEmailIsNotValid(String email) { 31 | String emailRegex = RegexUtils.EMAIL_REGEX; 32 | assertThat(Pattern.matches(emailRegex, email)).isFalse(); 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/java/formflow/library/utilities/TestUtils.java: -------------------------------------------------------------------------------- 1 | package formflow.library.utilities; 2 | 3 | import java.io.File; 4 | import java.net.URL; 5 | import java.time.LocalDate; 6 | import java.time.OffsetDateTime; 7 | import java.time.OffsetTime; 8 | 9 | public class TestUtils { 10 | 11 | public static String getAbsoluteFilepathString(String resourceFilename) { 12 | URL resource = TestUtils.class.getClassLoader().getResource(resourceFilename); 13 | if (resource != null) { 14 | return (new File(resource.getFile())).getAbsolutePath(); 15 | } 16 | return ""; 17 | } 18 | 19 | public static OffsetDateTime makeOffsetDateTime(String isoDate) { 20 | return LocalDate.parse(isoDate).atTime(OffsetTime.parse("00:00-08:00")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/formflow/library/utilities/WebDriverConfiguration.java: -------------------------------------------------------------------------------- 1 | package formflow.library.utilities; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import org.springframework.boot.test.context.TestConfiguration; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Scope; 9 | 10 | @TestConfiguration 11 | public class WebDriverConfiguration { 12 | 13 | @Bean(initMethod = "start", destroyMethod = "stop") 14 | @Scope("singleton") 15 | public SeleniumFactory seleniumComponent() throws IOException { 16 | return new SeleniumFactory(tempDir()); 17 | } 18 | 19 | @Bean 20 | public Path tempDir() throws IOException { 21 | return Files.createTempDirectory(""); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/resources/another-test-heic.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/another-test-heic.heic -------------------------------------------------------------------------------- /src/test/resources/flows-config/test-after-save-action.yaml: -------------------------------------------------------------------------------- 1 | name: testFlow 2 | flow: 3 | inputs: 4 | afterSaveAction: SendEmailAfterSave 5 | nextScreens: 6 | - name: subflowEntry 7 | subflowEntry: 8 | nextScreens: 9 | - name: subflowIterationStart 10 | subflowIterationStart: 11 | afterSaveAction: SendEmailAfterSave 12 | subflow: income 13 | nextScreens: 14 | - name: next 15 | next: 16 | subflow: income 17 | nextScreens: 18 | - name: subflowReview 19 | subflowReview: 20 | nextScreens: 21 | - name: subflowComplete 22 | subflowDeleteConfirmation: 23 | nextScreens: null 24 | subflowComplete: 25 | nextScreens: 26 | - name: last 27 | last: 28 | nextScreens: null 29 | subflows: 30 | income: 31 | entryScreen: subflowEntry 32 | iterationStartScreen: subflowIterationStart 33 | reviewScreen: subflowReview 34 | deleteConfirmationScreen: subflowDeleteConfirmation 35 | -------------------------------------------------------------------------------- /src/test/resources/flows-config/test-before-display-action.yaml: -------------------------------------------------------------------------------- 1 | name: testFlow 2 | flow: 3 | inputs: 4 | beforeSaveAction: EncryptSSN 5 | beforeDisplayAction: DecryptSSN 6 | nextScreens: 7 | - name: subflowEntry 8 | subflowEntry: 9 | nextScreens: 10 | - name: subflowIterationStart 11 | subflowIterationStart: 12 | subflow: householdMembers 13 | nextScreens: 14 | - name: pageWithSSNInput 15 | pageWithSSNInput: 16 | beforeSaveAction: EncryptSSN 17 | beforeDisplayAction: DecryptSSN 18 | subflow: householdMembers 19 | nextScreens: 20 | - name: subflowReview 21 | subflowReview: 22 | nextScreens: 23 | - name: subflowComplete 24 | subflowDeleteConfirmation: 25 | nextScreens: ~ 26 | subflowComplete: 27 | nextScreens: 28 | - name: last 29 | last: 30 | nextScreens: ~ 31 | subflows: 32 | householdMembers: 33 | entryScreen: subflowEntry 34 | iterationStartScreen: subflowIterationStart 35 | reviewScreen: subflowReview 36 | deleteConfirmationScreen: subflowDeleteConfirmation 37 | -------------------------------------------------------------------------------- /src/test/resources/flows-config/test-before-save-action.yaml: -------------------------------------------------------------------------------- 1 | name: testFlow 2 | flow: 3 | inputs: 4 | beforeSaveAction: FormatDateBeforeSave 5 | nextScreens: 6 | - name: subflowEntry 7 | subflowEntry: 8 | nextScreens: 9 | - name: subflowIterationStart 10 | subflowIterationStart: 11 | subflow: income 12 | nextScreens: 13 | - name: next 14 | next: 15 | beforeSaveAction: CalculateTotalBeforeSave 16 | subflow: income 17 | nextScreens: 18 | - name: subflowReview 19 | subflowReview: 20 | nextScreens: 21 | - name: subflowComplete 22 | subflowDeleteConfirmation: 23 | nextScreens: null 24 | subflowComplete: 25 | nextScreens: 26 | - name: last 27 | last: 28 | nextScreens: null 29 | subflows: 30 | income: 31 | entryScreen: subflowEntry 32 | iterationStartScreen: subflowIterationStart 33 | reviewScreen: subflowReview 34 | deleteConfirmationScreen: subflowDeleteConfirmation 35 | -------------------------------------------------------------------------------- /src/test/resources/flows-config/test-cross-validation-action.yaml: -------------------------------------------------------------------------------- 1 | name: testFlow 2 | flow: 3 | contactInfoPreference: 4 | crossFieldValidationAction: CheckIndicatedContactMethodIsProvided 5 | nextScreens: 6 | - name: subflowEntry 7 | subflowEntry: 8 | nextScreens: 9 | - name: subflowIterationStart 10 | subflowIterationStart: 11 | subflow: income 12 | nextScreens: 13 | - name: next 14 | next: 15 | beforeSaveAction: CalculateTotalBeforeSave 16 | subflow: income 17 | nextScreens: 18 | - name: subflowReview 19 | subflowReview: 20 | nextScreens: 21 | - name: subflowComplete 22 | subflowDeleteConfirmation: 23 | nextScreens: null 24 | subflowComplete: 25 | nextScreens: 26 | - name: last 27 | last: 28 | nextScreens: null 29 | subflows: 30 | income: 31 | entryScreen: subflowEntry 32 | iterationStartScreen: subflowIterationStart 33 | reviewScreen: subflowReview 34 | deleteConfirmationScreen: subflowDeleteConfirmation -------------------------------------------------------------------------------- /src/test/resources/flows-config/test-flow-address-validation.yaml: -------------------------------------------------------------------------------- 1 | name: testFlowAddressValidation 2 | flow: 3 | testAddressValidation: 4 | nextScreens: 5 | - name: testAddressValidationFound 6 | condition: FoundAddressSuggestion 7 | - name: testAddressValidationNotFound 8 | condition: DidNotFindAddressSuggestion 9 | testAddressValidationFound: 10 | nextScreens: 11 | - name: subflowAddItem 12 | testAddressValidationNotFound: 13 | nextScreens: 14 | - name: subflowAddItem 15 | subflowAddItem: 16 | subflow: testSubflow 17 | nextScreens: 18 | - name: testSubflowAddressValidation 19 | testSubflowAddressValidation: 20 | subflow: testSubflow 21 | nextScreens: 22 | - name: testSubflowAddressValidationFound 23 | condition: FoundSubflowAddressSuggestion 24 | - name: testSubflowAddressValidationNotFound 25 | condition: DidNotFindSubflowAddressSuggestion 26 | testSubflowAddressValidationFound: 27 | subflow: testSubflow 28 | nextScreens: 29 | - name: testReviewScreen 30 | testSubflowAddressValidationNotFound: 31 | subflow: testSubflow 32 | nextScreens: 33 | - name: testReviewScreen 34 | testReviewScreen: 35 | nextScreens: 36 | - name: success 37 | testDeleteConfirmationScreen: 38 | nextScreens: 39 | - name: success 40 | success: 41 | nextScreens: 42 | subflows: 43 | testSubflow: 44 | entryScreen: subflowAddItem 45 | iterationStartScreen: testSubflowAddressValidation 46 | reviewScreen: testReviewScreen 47 | deleteConfirmationScreen: testDeleteConfirmationScreen 48 | -------------------------------------------------------------------------------- /src/test/resources/flows-config/test-landmark-flow.yaml: -------------------------------------------------------------------------------- 1 | name: testLandmarkFlow 2 | flow: 3 | first: 4 | nextScreens: 5 | - name: nonFormPage 6 | nonFormPage: 7 | nextScreens: 8 | - name: success 9 | success: 10 | nextScreens: 11 | - name: null 12 | landmarks: 13 | firstScreen: first -------------------------------------------------------------------------------- /src/test/resources/flows-config/test-on-post-action.yaml: -------------------------------------------------------------------------------- 1 | name: testFlow 2 | flow: 3 | inputs: 4 | onPostAction: AggregateDatesInPI 5 | nextScreens: 6 | - name: subflowEntry 7 | subflowEntry: 8 | nextScreens: 9 | - name: subflowIterationStart 10 | subflowIterationStart: 11 | subflow: income 12 | nextScreens: 13 | - name: next 14 | next: 15 | subflow: income 16 | nextScreens: 17 | - name: subflowReview 18 | subflowReview: 19 | nextScreens: 20 | - name: subflowComplete 21 | subflowDeleteConfirmation: 22 | nextScreens: null 23 | subflowComplete: 24 | nextScreens: 25 | - name: last 26 | last: 27 | nextScreens: null 28 | subflows: 29 | income: 30 | entryScreen: subflowEntry 31 | iterationStartScreen: subflowIterationStart 32 | reviewScreen: subflowReview 33 | deleteConfirmationScreen: subflowDeleteConfirmation -------------------------------------------------------------------------------- /src/test/resources/flows-config/test-upload-flow.yaml: -------------------------------------------------------------------------------- 1 | name: uploadFlowA 2 | flow: 3 | docUploadUnit: 4 | nextScreens: null 5 | docUploadJourney: 6 | nextScreens: null 7 | --- 8 | name: uploadFlowB 9 | flow: 10 | docUploadUnit: 11 | nextScreens: null 12 | docUploadJourney: 13 | nextScreens: null -------------------------------------------------------------------------------- /src/test/resources/i-am-not-a-png.txt.png: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /src/test/resources/password-protected.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/password-protected.pdf -------------------------------------------------------------------------------- /src/test/resources/pdf-map.yaml: -------------------------------------------------------------------------------- 1 | flow: ubi 2 | pdf: pdfs/testFlow/Multipage-UBI-Form.pdf 3 | inputFields: 4 | firstName: APPLICANT_LEGAL_NAME_FIRST 5 | city: APPLICANT_CITY 6 | howToContactYou: 7 | phone: PHONE 8 | email: EMAIL 9 | dbFields: 10 | submittedAt: DATE 11 | -------------------------------------------------------------------------------- /src/test/resources/pdfs/blankPdf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/pdfs/blankPdf.pdf -------------------------------------------------------------------------------- /src/test/resources/pdfs/testFlow/Multipage-UBI-Form.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/pdfs/testFlow/Multipage-UBI-Form.pdf -------------------------------------------------------------------------------- /src/test/resources/pdfs/testPdf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/pdfs/testPdf.pdf -------------------------------------------------------------------------------- /src/test/resources/templates/conditionsTestFlow/skipFirst.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/resources/templates/conditionsTestFlow/viewThird.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 |
9 |
10 |
11 |

Test Index Page

12 |
13 |
14 |
15 |
16 | Start the test flow 17 |
18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/resources/templates/otherTestFlow/success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 | 14 |
15 |

Congratulations, you did it! 🎉

16 |
17 | 19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/templates/otherTestFlow/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/first.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 14 | 15 |
16 | 19 |
20 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/last.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 | 16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/next.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 14 | 15 |
16 | 19 |
20 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/other.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 14 | 15 |
16 | 19 |
20 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/pageWithCustomSubmitButton.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 14 | 15 | 20 | 21 | 22 |
23 |
24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/pageWithDefaultSubmitButton.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 14 | 15 | 19 | 20 | 21 |
22 |
23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/pageWithMultipleValidationInput.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 14 | 15 |
16 | 19 | 20 | 23 |
24 | 27 |
28 |
29 |
30 |
31 |
32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/pageWithOptionalValidation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 14 | 15 |
16 | 19 |
20 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/pageWithSSNInput.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 14 | 15 |
16 | 20 |
21 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/second.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 15 | 16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 | 14 |
15 |

Congratulations, you did it! 🎉

16 |
17 | 19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/testAddressValidation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 7 |
8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/testAddressValidationFound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/testAddressValidationNotFound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 | 11 | 12 | 17 | 18 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/testEntryScreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 12 | 14 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlow/testUpload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 |
7 |
8 |
9 |
10 |
11 |
12 | 14 | 16 | 17 |
18 | 20 |
21 | 25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlowAddressValidation/subflowAddItem.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 13 |
14 | 17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlowAddressValidation/success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 | 14 |
15 |

Congratulations, you did it! 🎉

16 |
17 | 19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlowAddressValidation/testAddressValidation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 7 |
8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlowAddressValidation/testAddressValidationFound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlowAddressValidation/testAddressValidationNotFound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 | 11 | 12 | 17 | 18 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlowAddressValidation/testAddressVerification.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 | 11 | 12 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlowAddressValidation/testSubflowAddressValidation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 8 |
9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlowAddressValidation/testSubflowAddressValidationFound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/templates/testFlowAddressValidation/testSubflowAddressValidationNotFound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 |
7 |
8 |
9 |
10 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/templates/testLandmarkFlow/first.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 12 | 14 | 15 |
16 | 19 |
20 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/resources/templates/testLandmarkFlow/nonFormPage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 |
14 | 15 |
16 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/templates/testLandmarkFlow/success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 | 14 |
15 |

Congratulations, you did it! 🎉

16 |
17 | 19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/templates/testSubflowLogic/getScreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 |
14 | This is a GET Screen. 15 |
16 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/templates/testSubflowLogic/otherGetScreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 |
14 | This is another GET Screen. 15 |
16 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/templates/testSubflowLogic/testEntryScreen.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/templates/uploadFlowA/docUploadJourney.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 | 15 | 16 |
17 | 19 |
20 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/test/resources/templates/uploadFlowB/docUploadJourney.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 | 15 | 16 |
17 | 19 |
20 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/test/resources/templates/yetAnotherTestFlow/getScreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 |
14 | This is a GET Screen. 15 |
16 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/templates/yetAnotherTestFlow/otherGetScreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 |
14 | This is another GET Screen. 15 |
16 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/templates/yetAnotherTestFlow/testEntryScreen.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/test-archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/test-archive.zip -------------------------------------------------------------------------------- /src/test/resources/test-platypus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/test-platypus.gif -------------------------------------------------------------------------------- /src/test/resources/test.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/test.jpeg -------------------------------------------------------------------------------- /src/test/resources/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/test.png -------------------------------------------------------------------------------- /src/test/resources/test.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/test.tif -------------------------------------------------------------------------------- /src/test/resources/testA.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/testA.jpeg -------------------------------------------------------------------------------- /src/test/resources/testB.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/form-flow/24c59166c771f49c1da659a5401469d8e40eb1e6/src/test/resources/testB.jpeg --------------------------------------------------------------------------------