OBPIH-6395 Add reusable useWizard hook#4642
Conversation
kchelstowski
commented
May 28, 2024

| const isCurrentStep = (iteratedStep) => iteratedStep.key === currentStepKey; | ||
| return ( | ||
| <div className="steps-main-box"> | ||
| <div className="steps-inside-wrapper"> |
There was a problem hiding this comment.
this className sounds slightly different to me: Can it be just "steps" or "steps-container"?
There was a problem hiding this comment.
Those are the class names used in the current WizardSteps.jsx. I think we want to keep the same styling for now, so the only difference in this component and the current WizardSteps is that I eventually call the step.title at the end, not the step. I didn't want to do some workarounds in the existing WizardSteps, this is why I created a brand new compoment, as it will be more maintainable if we decide to change anything there and not break the existing one.
src/js/hooks/useWizard.js
Outdated
| }; | ||
|
|
||
| const next = () => { | ||
| const nextStepIdx = steps.findIndex((s) => s.key === key) + 1; |
There was a problem hiding this comment.
Maybe it would be better to store the index of the key instead of recalculating the ID here and in the next 2 following functions?
There was a problem hiding this comment.
To be honest I was doing that, but when I saw the complexity (or rather the readability) after such refactor, I decided that it would be better to do it this way, because otherwise, I would have to return both: step and index from the const Step, and imho this would be ugly to access the step then via, e.g. Step.step, if in the useMemo we returned:
return {
step: foundStep,
currentStepIdx: idx
}if you guys strongly feel it's worth, then I can do that again.
There was a problem hiding this comment.
I also think I would prefer Alan's suggestion.
… wrapper components
| describe('Changing steps', () => { | ||
| const steps = [ | ||
| { | ||
| key: 1, | ||
| Component: () => (<div>First compoment</div>), | ||
| title: 'First', | ||
| }, | ||
| { | ||
| key: 2, | ||
| Component: () => (<div>Second compoment</div>), | ||
| title: 'Second', | ||
| }, | ||
| { | ||
| key: 3, | ||
| Component: () => (<div>Third compoment</div>), | ||
| title: 'Third', | ||
| }, | ||
| { | ||
| key: 4, | ||
| Component: () => (<div>Fourth compoment</div>), | ||
| title: 'Fourth', | ||
| }, | ||
| ]; | ||
|
|
||
| it('wizard will start with the initial key', () => { | ||
| const { result } = renderHook(() => useWizard({ initialKey: 3, steps })); | ||
|
|
||
| expect(result.current[0].key).toEqual(3); | ||
| }); | ||
|
|
||
| it('wizard goes to next step', () => { | ||
| const { result } = renderHook(() => useWizard({ initialKey: 2, steps })); | ||
|
|
||
| act(() => { | ||
| result.current[1].next(); | ||
| }); | ||
|
|
||
| expect(result.current[0].key).toEqual(3); | ||
| }); | ||
|
|
||
| it('wizard goes to previous step', () => { | ||
| const { result } = renderHook(() => useWizard({ initialKey: 4, steps })); | ||
|
|
||
| act(() => { | ||
| result.current[1].previous(); | ||
| }); |
There was a problem hiding this comment.
one nitpicky thing - the naming of tests (look into this comment) #3701 (comment)
There was a problem hiding this comment.
Where is that convention documented? I can't find any resource opting for the "should" convention.
There was a problem hiding this comment.
do we have any conventions documented? 😆 😆 😆 😆
There was a problem hiding this comment.
When Alan was implementing tests a while ago, this was a request from me, "It should..." is a very common pattern for test name that explains the behavior and expected result like
"should return initial key"
"should advance to the next step when next is called"
"should go to the previous step when previous is called'"
"should go to the specific step when set is called"
| })); | ||
|
|
||
| const [ | ||
| Step, |
There was a problem hiding this comment.
Can you get Component and key by destructuring?
I would also like to make the object returned from the hook flat. It doesn't make sense to add an additional object.
return [
Step,
set,
first,
next,
previous,
last,
];
There was a problem hiding this comment.
imho it's cleaner to distinguish the step and the util hook functions, this is why I made it like this.
grails-app/conf/runtime.groovy
Outdated
| defaultLabel: "Stock Movement", | ||
| menuItems: [ | ||
| [label: "outbound.create.label", defaultLabel: "Create Outbound Movements", href: "/stockMovement/createOutbound?direction=OUTBOUND"], | ||
| [label: "outbound.fullOutbound.import.label", defaultLabel: "Import Completed Outbound", href: "/stockMovement/fullOutboundImport"], |
There was a problem hiding this comment.
Perhaps it could be a simple: outbound.import.label?
| render(view: "/common/react", params: params) | ||
| } | ||
|
|
||
| def fullOutboundImport() { |
There was a problem hiding this comment.
Do we have to have a "full" keyword everywhere?
There was a problem hiding this comment.
Yeah I was going to say we really should keep things simple. Try not to use PIH terminology if there's a way to make it simple. Imagine some old Greek guys having a conversation and then distill their response.
Socrates: What does it do?
Kacperian: It provides a way to import a stock movement including all of its dependent objects like requests, picklists, etc.
✨
importStockMovement()
✨
There was a problem hiding this comment.
or
importOutboundStockMovement()
There was a problem hiding this comment.
I would go with the 2nd option, especially if we are supposed to distinguish the SMs in the future for inbound, outbound etc.
|
|
||
| import 'components/wizard/WizardSteps.scss'; | ||
|
|
||
| const WizardStepsV2 = ({ steps, currentStepKey }) => { |
There was a problem hiding this comment.
What has changed here compared to WizardSteps?
There was a problem hiding this comment.
I answered that question in one of the comments below: https://github.com/openboxes/openboxes/pull/4642/files#r1617300455
| import useWizard from 'hooks/useWizard'; | ||
|
|
||
| const FullOutboundImport = () => { | ||
| const FullOutboundImportStep = { |
There was a problem hiding this comment.
FullOutboundImportSteps
| title: 'Confirm', | ||
| }, | ||
| }; | ||
| const steps = useMemo(() => [ |
There was a problem hiding this comment.
This feels a bit weird. Can we just have an object with full steps structure that would be transformed to list of values? Feels like this part could be simplified
src/js/hooks/useWizard.js
Outdated
| }; | ||
|
|
||
| const next = () => { | ||
| const nextStepIdx = steps.findIndex((s) => s.key === key) + 1; |
There was a problem hiding this comment.
I also think I would prefer Alan's suggestion.
| <div | ||
| key={step.key} | ||
| className={`step-container ${isCurrentStep(step) ? 'active' : ''}`} | ||
| data-testid={isCurrentStep(step) ? 'active' : 'inactive'} |
There was a problem hiding this comment.
data-testid should not change conditionally I believe this should data-testid="wizzard-step" and if you need to specify the state of the step we should use a different attribute like a custom
data-stepstate={isCurrentStep(step) ? 'active' : 'inactive'}
or
aria-label={isCurrentStep(step) ? 'active' : 'inactive'}
There was a problem hiding this comment.
It was copied from the existing WizardSteps, so I did not even realize and thought about it, but since you are testing ninja, I trust you 😆
src/js/hooks/useWizard.js
Outdated
| const set = (val) => { | ||
| setKey(val); | ||
| }; |
There was a problem hiding this comment.
this looks unnecessary we can just return the setKey function in the hook like
return {
set: setKey
}
| const first = () => { | ||
| setKey(steps[0]?.key); | ||
| }; | ||
|
|
||
| const last = () => { | ||
| const lastIdx = steps.length - 1; | ||
| setKey(steps[lastIdx]?.key); | ||
| }; |
There was a problem hiding this comment.
It's cool that you added these but are we ever going to use them?
I don't care if they stay or they go, I am just not familiar with any use case where we will want to jump to first and last steps.
There was a problem hiding this comment.
We already have such use cases - e.g. if we have an SM in progress and it is supposed to be on "X" step, we go to step "X".
There was a problem hiding this comment.
yes, but I am not talking about function set but last and first
There was a problem hiding this comment.
maybe now we don't have the use case, but they felt cute and I also wanted to have a complete hook, that would cover any of the future cases, so I kept them :sadsmile:
Let me know if I should remove them in your opinion guys.
| describe('Changing steps', () => { | ||
| const steps = [ | ||
| { | ||
| key: 1, | ||
| Component: () => (<div>First compoment</div>), | ||
| title: 'First', | ||
| }, | ||
| { | ||
| key: 2, | ||
| Component: () => (<div>Second compoment</div>), | ||
| title: 'Second', | ||
| }, | ||
| { | ||
| key: 3, | ||
| Component: () => (<div>Third compoment</div>), | ||
| title: 'Third', | ||
| }, | ||
| { | ||
| key: 4, | ||
| Component: () => (<div>Fourth compoment</div>), | ||
| title: 'Fourth', | ||
| }, | ||
| ]; | ||
|
|
||
| it('wizard will start with the initial key', () => { | ||
| const { result } = renderHook(() => useWizard({ initialKey: 3, steps })); | ||
|
|
||
| expect(result.current[0].key).toEqual(3); | ||
| }); | ||
|
|
||
| it('wizard goes to next step', () => { | ||
| const { result } = renderHook(() => useWizard({ initialKey: 2, steps })); | ||
|
|
||
| act(() => { | ||
| result.current[1].next(); | ||
| }); | ||
|
|
||
| expect(result.current[0].key).toEqual(3); | ||
| }); | ||
|
|
||
| it('wizard goes to previous step', () => { | ||
| const { result } = renderHook(() => useWizard({ initialKey: 4, steps })); | ||
|
|
||
| act(() => { | ||
| result.current[1].previous(); | ||
| }); |
There was a problem hiding this comment.
When Alan was implementing tests a while ago, this was a request from me, "It should..." is a very common pattern for test name that explains the behavior and expected result like
"should return initial key"
"should advance to the next step when next is called"
"should go to the previous step when previous is called'"
"should go to the specific step when set is called"
jmiranda
left a comment
There was a problem hiding this comment.
@kchelstowski Wait does this mean I can finally click on the wizard steps to go through the flow? I'm so confused. And excited.
|
@jmiranda yes, I've just done the "background" for such behavior, but to be honest we've already had such a behavior, but it was kinda "freezed" (it's been prepared but it has been disabled throughout all of the current wizards) |
|
@jmiranda clicking on a step is supported, but disabled/not used on "old" wizards. |