LG-13346 | FormObject best practices for how_to_verify#10682
Conversation
Normalizes the way we're using FormObjects. changelog: Internal, FormObject normalization, Updates the HowToVerify flow
|
|
||
| analytics.idv_doc_auth_how_to_verify_visited(**analytics_arguments) | ||
| @idv_how_to_verify_form = Idv::HowToVerifyForm.new(selection: @selection) | ||
| @idv_how_to_verify_form = Idv::HowToVerifyForm.new |
There was a problem hiding this comment.
AFAICT this code was a no-op. It was just determining what was passed into the form's constructor, but I don't believe it influenced the form's display.
There was a problem hiding this comment.
Ah so I think this is old code from when the form was a pair radio buttons rather than a set of buttons that submit the form itself. These values would set the radio button to the previous selection if the user went back in the flow. At least that's a guess.
There was a problem hiding this comment.
I think that is accurate. We had a button that would submit for radio button selection. The view was refactored and I think think got missed.
| else | ||
| flash[:error] = result.first_error_message | ||
| redirect_to idv_how_to_verify_url | ||
| render :show, locals: { error: result.first_error_message } |
There was a problem hiding this comment.
By instantiating the form directly, we can just re-render the form.
There was a problem hiding this comment.
This seems like a good idea from an analytics perspective as well, instead of sending folks back and getting a new event, we just get the single event from the first time they arrive. Which I think is closer to what we want to track 🤔
| validates :selection, inclusion: { | ||
| in: [REMOTE, IPP], | ||
| message: proc { I18n.t('errors.doc_auth.how_to_verify_form') }, | ||
| } |
There was a problem hiding this comment.
So it appears that inclusion does not imply presence: with only lines 15-18 here, a nil selection was apparently accepted. So I have both.
There was a problem hiding this comment.
But we had presence already, did it still accept a nil selection?
There was a problem hiding this comment.
presence worked as expected. I wanted to ensure we only accepted known values, so I had initially changed it from presence to inclusion, under the arguably-reasonable expectation that, since [REMOTE, IPP] does not include nil, it wouldn't be accepted. Turns out both are needed.
| <%= error %> | ||
| <% end %> | ||
| <% end %> | ||
|
|
There was a problem hiding this comment.
I am not a UX whiz, but this allows us to conditionally render the error message which was previously not showing up.
There was a problem hiding this comment.
I think this is fine. The semantics are handled by the AlertComponent itself, and you're doing something that looks like what other alert components are already doing. Is there a design to reference, though?
There was a problem hiding this comment.
Oh, you're making me realize I should probably run this by UX explicitly. We were previously setting a flash message to this string, but it wasn't displaying. So I ✨ fixed the glitch ✨, but I should get UX buy-in on this change.
| end | ||
|
|
||
| context 'an invalid selection is submitted' do | ||
| # (This should only be possible if someone alters the form) |
There was a problem hiding this comment.
Do you object to me calling this out? The buttons just select "ipp" or "remote" as options, but a mischievous user could submit something weird. Or is it evident enough why we would care about invalid selections even if the user doesn't have the selections?
There was a problem hiding this comment.
How is this different from the form submission error case that's handled in the spec starting on line 266? Could those two specs be merged into one? I think that might be cleaner?
There was a problem hiding this comment.
Good catch.
My thinking at the time was that 266 tests when no selection is made, while this test is when an invalid selection is made, which wasn't previously handled.
But "invalid" is ambiguous wording used in both tests, and they should result in the same behavior.
Now the question is, do I make this a mini shared example or just duplicate it? 🤔
There was a problem hiding this comment.
OK yeah, we have specific behavior expectations for no selection vs. some selection so now that you say I think we should maybe keep them separate. Mini shared example is my preference, since it's what we'd end up doing if this spec gets larger anyway, right?
| step: 'how_to_verify', | ||
| analytics_id: 'Doc Auth', | ||
| skip_hybrid_handoff: nil, | ||
| 'selection' => selection, |
There was a problem hiding this comment.
This key comes through as a string vs. symbol, which is gross. However, the other tests in here are doing the same thing, so it's... precedented grossness.
| end | ||
| end | ||
|
|
||
| context 'undo/back' do |
There was a problem hiding this comment.
It seems like this is duplicative in intent of https://github.com/18F/identity-idp/blob/main/spec/controllers/idv/how_to_verify_controller_spec.rb#L171
|
|
||
| context 'undo/back' do | ||
| it 'sets skip_doc_auth to nil and does not redirect' do | ||
| put :update, params: { undo_step: true } |
There was a problem hiding this comment.
This is doing an HTTP PUT with undo_step: true as a form parameter, which does not make sense to me. There is no apparent way for the user to do this, and no code in the controller to act on this—it appears that this is equivalent to what happens when you submit a blank form, which, again, we already test. So I removed this whole case.
Please let me know if I'm misinterpreting.
There was a problem hiding this comment.
I am not sure why put vs calling undo_step.call with args. Aside from that, this test was to show that when you move backwards in the flow that some of the values on idv_session get set back to nil (the values before making any selection on this page.)
There was a problem hiding this comment.
Ah-ha, this is helpful context; thanks!
I'm inclined to think that this provides equivalent coverage: https://github.com/18F/identity-idp/blob/main/spec/controllers/idv/how_to_verify_controller_spec.rb#L259-L266
I think the only way to get undo_step to fire are to fail the validation. Are you on board with this?
There was a problem hiding this comment.
Ummm, so I think the form submission error that you link to is checking that those items are nil because they are not set yet, not because they are set back to nil on undo. In other words, that test is viewing it as if it is someone's first time hitting this page, not that they are moving backwards. The undo step only fires on a form submission, not when folks hit the back button in the browser. So it's only called when an update action happens on a controller.
There was a problem hiding this comment.
I am not sure it captures it but think you can delete this test. I am not seeing tests for each step in our flow (address, ssn, etc.) that test undo_step but there is a spec testing undo_step so it is covered.
There was a problem hiding this comment.
The bit I linked to is testing the update action. However, I went ahead and pulled all this out into a shared context which I think will make this all easier to follow.
Just waiting on ruby 3.3.1 to build because I pulled in main. 🤦♂️
There was a problem hiding this comment.
That change is 58fa3b8 if that makes it easier to read.
| expect(subject.idv_session.skip_doc_auth).to be_nil | ||
| expect(subject.idv_session.opted_in_to_in_person_proofing).to be_nil | ||
| expect(response).to redirect_to(idv_how_to_verify_url) | ||
| expect(response).to render_template :show |
There was a problem hiding this comment.
We now let the form handle this for us, so we no longer need to set a flash message, and we just re-render the form (show template) vs. redirecting.
There was a problem hiding this comment.
Same comment as above: does it make sense to combine this test case with the 'an invalid selection is submitted' spec?
JackRyan1989
left a comment
There was a problem hiding this comment.
Looks good! Tested and works as expected. Just have some qs!
|
|
||
| analytics.idv_doc_auth_how_to_verify_visited(**analytics_arguments) | ||
| @idv_how_to_verify_form = Idv::HowToVerifyForm.new(selection: @selection) | ||
| @idv_how_to_verify_form = Idv::HowToVerifyForm.new |
There was a problem hiding this comment.
Ah so I think this is old code from when the form was a pair radio buttons rather than a set of buttons that submit the form itself. These values would set the radio button to the previous selection if the user went back in the flow. At least that's a guess.
| else | ||
| flash[:error] = result.first_error_message | ||
| redirect_to idv_how_to_verify_url | ||
| render :show, locals: { error: result.first_error_message } |
There was a problem hiding this comment.
This seems like a good idea from an analytics perspective as well, instead of sending folks back and getting a new event, we just get the single event from the first time they arrive. Which I think is closer to what we want to track 🤔
| validates :selection, inclusion: { | ||
| in: [REMOTE, IPP], | ||
| message: proc { I18n.t('errors.doc_auth.how_to_verify_form') }, | ||
| } |
There was a problem hiding this comment.
But we had presence already, did it still accept a nil selection?
| <%= error %> | ||
| <% end %> | ||
| <% end %> | ||
|
|
There was a problem hiding this comment.
I think this is fine. The semantics are handled by the AlertComponent itself, and you're doing something that looks like what other alert components are already doing. Is there a design to reference, though?
| end | ||
|
|
||
| context 'an invalid selection is submitted' do | ||
| # (This should only be possible if someone alters the form) |
There was a problem hiding this comment.
How is this different from the form submission error case that's handled in the spec starting on line 266? Could those two specs be merged into one? I think that might be cleaner?
|
|
||
| context 'undo/back' do | ||
| it 'sets skip_doc_auth to nil and does not redirect' do | ||
| put :update, params: { undo_step: true } |
| expect(subject.idv_session.skip_doc_auth).to be_nil | ||
| expect(subject.idv_session.opted_in_to_in_person_proofing).to be_nil | ||
| expect(response).to redirect_to(idv_how_to_verify_url) | ||
| expect(response).to render_template :show |
There was a problem hiding this comment.
Same comment as above: does it make sense to combine this test case with the 'an invalid selection is submitted' spec?
| end | ||
| let(:analytics_name) { :idv_doc_auth_how_to_verify_submitted } | ||
|
|
||
| shared_examples_for 'invalid form submissions' do |
JackRyan1989
left a comment
There was a problem hiding this comment.
I think you still need UX to check the alert but the code LGTM.

🎫 Ticket
Link to the relevant ticket:
LG-13346
🛠 Summary of changes
Applies some of the best practices described in this Google doc to bring this into line with how FormObject is used elsewhere.
There are two changes to behavior here:
selectionparameter, we validate that it's an accepted value. It should be impossible for this to happen without someone manipulating the form, though.👀 Screenshots
The red error condition is now shown if an invalid submission (somehow) occurs. It was previously not showing up.