Skip to content

Commit 70b638d

Browse files
earshinovmadoar
authored andcommitted
Support [navigateForward]="visited" (#228)
* Enhance existing tests with checks for which steps are navigable (`checkWizardNavigableState`) * Implement navigateForward="visited", update README and tests * Rename helper function checkWizardNavigableState → checkWizardNavigableSteps
1 parent 4bb54a6 commit 70b638d

File tree

7 files changed

+281
-9
lines changed

7 files changed

+281
-9
lines changed

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,22 @@ The only exception to this rule are optional steps, which a user can skip.
258258
Using the navigation bar, the user can navigate back to steps they already visited.
259259

260260
You can alter this behavior by applying to the `<aw-wizard>` element an additional `[awNavigationMode]` directive, which can be used in two ways.
261-
The easiest option is to tweak the default navigation mode with `[navigateBackward]` and/or `[navigateForward]` inputs which control the navigation bar. Valid options for these inputs are `'allow'` and `'deny`'. Take notice that the `'allow'` option still respects step exit conditions. Also, the completion step still only becomes enterable after all previous steps are completed. Example usage:
261+
The easiest option is to tweak the default navigation mode with `[navigateBackward]` and/or `[navigateForward]` inputs which control the navigation bar and have the following options:
262+
263+
| Parameter name | Possible Values | Default Value |
264+
| ----------------------------- | ---------------------------------------------------------------------------------------------------- | ------------- |
265+
| [navigateBackward] | `'allow'|'deny'` | `'deny'` |
266+
| [navigateForward] | `'allow'|'deny'|'visited'` | `'allow'` |
267+
268+
Take notice that the `'allow'` and `'visited'` options still respect step exit conditions. Also, the completion step still only becomes enterable after all previous steps are completed. Example usage:
262269

263270
```html
264271
<aw-wizard [awNavigationMode] navigateBackward="allow" navigateForward="allow">...</aw-wizard>
265272
```
266273

267-
If changes you need are more radical, you can define your own navigation mode. In order to do this, create a class implementing the `NavigationMode` interface and pass an instance of this class into the `[awNavigationMode]` directive. This takes priority over `[navigateBackward]` and `[navigateForward]` inputs. Example usage:
274+
If changes you need are more radical, you can define your own navigation mode. In order to do this, create a class implementing the `NavigationMode` interface and pass an instance of this class into the `[awNavigationMode]` directive.
275+
This takes priority over `[navigateBackward]` and `[navigateForward]` inputs.
276+
Example usage:
268277

269278
custom-navigation-mode.ts:
270279
```typescript
@@ -307,7 +316,7 @@ Possible `awNavigationMode` parameters:
307316
| ----------------------------- | ---------------------------------------------------------------------------------------------------- | ------------- |
308317
| [awNavigationMode] | `NavigationMode` | `null` |
309318
| [navigateBackward] | `'allow'|'deny'` | `'deny'` |
310-
| [navigateForward] | `'allow'|'deny'` | `'allow'` |
319+
| [navigateForward] | `'allow'|'deny'|'visited'` | `'allow'` |
311320

312321
### \[awEnableBackLinks\]
313322
In some cases it may be required that the user is allowed to leave an entered `aw-wizard-completion-step`.

src/lib/directives/navigation-mode.directive.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ export class NavigationModeDirective implements OnChanges {
7777
*
7878
* - `navigateForward="deny"` -- the steps are not navigable
7979
* - `navigateForward="allow"` -- the steps are navigable
80+
* - `navigateForward="visited"` -- a step is navigable iff it was already visited before
8081
*/
8182
@Input()
82-
public navigateForward: 'allow'|'deny'|null;
83+
public navigateForward: 'allow'|'deny'|'visited'|null;
8384

8485
constructor(private wizard: WizardComponent) { }
8586

@@ -95,4 +96,3 @@ export class NavigationModeDirective implements OnChanges {
9596
}
9697

9798
}
98-

src/lib/navigation/configurable-navigation-mode.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {WizardCompletionStep} from '../util/wizard-completion-step.interface';
1818
*
1919
* - `"deny"` -- the steps are not navigable
2020
* - `"allow"` -- the steps are navigable
21+
* - `"visited"` -- a step is navigable iff it was already visited before
2122
* - If the corresponding constructor argument is omitted or is `null` or `undefined`,
2223
* then the default value is applied which is `"allow"`
2324
*/
@@ -31,7 +32,7 @@ export class ConfigurableNavigationMode extends BaseNavigationMode {
3132
*/
3233
constructor(
3334
private navigateBackward: 'allow'|'deny'|null = null,
34-
private navigateForward: 'allow'|'deny'|null = null,
35+
private navigateForward: 'allow'|'deny'|'visited'|null = null,
3536
) {
3637
super();
3738
this.navigateBackward = this.navigateBackward || 'allow';
@@ -74,7 +75,8 @@ export class ConfigurableNavigationMode extends BaseNavigationMode {
7475
*/
7576
public isNavigable(wizard: WizardComponent, destinationIndex: number): boolean {
7677
// Check if the destination step can be navigated to
77-
if (wizard.getStepAtIndex(destinationIndex) instanceof WizardCompletionStep) {
78+
const destinationStep = wizard.getStepAtIndex(destinationIndex);
79+
if (destinationStep instanceof WizardCompletionStep) {
7880
// A completion step can only be entered, if all previous steps have been completed, are optional, or selected
7981
const previousStepsCompleted = wizard.wizardSteps
8082
.filter((step, index) => index < destinationIndex)
@@ -98,6 +100,7 @@ export class ConfigurableNavigationMode extends BaseNavigationMode {
98100
switch (this.navigateForward) {
99101
case 'allow': return true;
100102
case 'deny': return false;
103+
case 'visited': return destinationStep.completed;
101104
default:
102105
throw new Error(`Invalid value for navigateForward: ${this.navigateForward}`);
103106
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import {Component, ViewChild} from '@angular/core';
2+
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
3+
import {ArchwizardModule} from '../archwizard.module';
4+
import {WizardComponent} from '../components/wizard.component';
5+
import {checkWizardState, checkWizardNavigableSteps} from '../util/test-utils';
6+
7+
@Component({
8+
selector: 'aw-test-wizard',
9+
template: `
10+
<aw-wizard [awNavigationMode] navigateForward="visited">
11+
<aw-wizard-step stepTitle='Steptitle 1'>
12+
Step 1
13+
</aw-wizard-step>
14+
<aw-wizard-step stepTitle='Steptitle 2'>
15+
Step 2
16+
</aw-wizard-step>
17+
<aw-wizard-step stepTitle='Steptitle 3'>
18+
Step 3
19+
</aw-wizard-step>
20+
</aw-wizard>
21+
`
22+
})
23+
class WizardTestComponent {
24+
@ViewChild(WizardComponent)
25+
public wizard: WizardComponent;
26+
}
27+
28+
describe('Wizard navigation with navigateForward=visited', () => {
29+
let wizardTestFixture: ComponentFixture<WizardTestComponent>;
30+
31+
let wizardTest: WizardTestComponent;
32+
let wizard: WizardComponent;
33+
34+
beforeEach(async(() => {
35+
TestBed.configureTestingModule({
36+
declarations: [WizardTestComponent],
37+
imports: [ArchwizardModule]
38+
}).compileComponents();
39+
}));
40+
41+
beforeEach(() => {
42+
wizardTestFixture = TestBed.createComponent(WizardTestComponent);
43+
wizardTestFixture.detectChanges();
44+
45+
wizardTest = wizardTestFixture.componentInstance;
46+
wizard = wizardTest.wizard;
47+
});
48+
49+
it('should return correct can go to step', async(() => {
50+
wizard.canGoToStep(-1).then(result => expect(result).toBe(false));
51+
wizard.canGoToStep(0).then(result => expect(result).toBe(true));
52+
wizard.canGoToStep(1).then(result => expect(result).toBe(true));
53+
wizard.canGoToStep(2).then(result => expect(result).toBe(false));
54+
wizard.canGoToStep(3).then(result => expect(result).toBe(false));
55+
}));
56+
57+
it('should go to step', fakeAsync(() => {
58+
checkWizardState(wizard, 0, false, [], false);
59+
checkWizardNavigableSteps(wizard, 0, []);
60+
61+
wizard.goToStep(1);
62+
tick();
63+
wizardTestFixture.detectChanges();
64+
65+
checkWizardState(wizard, 1, false, [0], false);
66+
checkWizardNavigableSteps(wizard, 1, [0]);
67+
68+
wizard.goToStep(2);
69+
tick();
70+
wizardTestFixture.detectChanges();
71+
72+
checkWizardState(wizard, 2, false, [0, 1], false);
73+
checkWizardNavigableSteps(wizard, 2, [0, 1]);
74+
75+
wizard.goToStep(0);
76+
tick();
77+
wizardTestFixture.detectChanges();
78+
79+
// If forward navigation is allowed, visited steps after
80+
// the selected step are still considered completed
81+
checkWizardState(wizard, 0, true, [0, 1, 2], true);
82+
checkWizardNavigableSteps(wizard, 0, [1, 2]);
83+
84+
wizard.goToStep(1);
85+
tick();
86+
wizardTestFixture.detectChanges();
87+
88+
checkWizardState(wizard, 1, true, [0, 1, 2], true);
89+
checkWizardNavigableSteps(wizard, 1, [0, 2]);
90+
91+
wizard.goToStep(2);
92+
tick();
93+
wizardTestFixture.detectChanges();
94+
95+
checkWizardState(wizard, 2, true, [0, 1, 2], true);
96+
checkWizardNavigableSteps(wizard, 2, [0, 1]);
97+
98+
wizard.goToStep(1);
99+
tick();
100+
wizardTestFixture.detectChanges();
101+
102+
checkWizardState(wizard, 1, true, [0, 1, 2], true);
103+
checkWizardNavigableSteps(wizard, 1, [0, 2]);
104+
}));
105+
106+
it('should go to next step', fakeAsync(() => {
107+
wizard.goToNextStep();
108+
tick();
109+
wizardTestFixture.detectChanges();
110+
111+
checkWizardState(wizard, 1, false, [0], false);
112+
checkWizardNavigableSteps(wizard, 1, [0]);
113+
}));
114+
115+
it('should go to previous step', fakeAsync(() => {
116+
checkWizardState(wizard, 0, false, [], false);
117+
checkWizardNavigableSteps(wizard, 0, []);
118+
119+
wizard.goToStep(1);
120+
tick();
121+
wizardTestFixture.detectChanges();
122+
123+
checkWizardState(wizard, 1, false, [0], false);
124+
checkWizardNavigableSteps(wizard, 1, [0]);
125+
126+
wizard.goToPreviousStep();
127+
tick();
128+
wizardTestFixture.detectChanges();
129+
130+
// If forward navigation is allowed, visited steps after
131+
// the selected step are still considered completed
132+
checkWizardState(wizard, 0, true, [0, 1], false);
133+
checkWizardNavigableSteps(wizard, 0, [1]);
134+
}));
135+
136+
it('should stay at the current step', fakeAsync(() => {
137+
expect(wizard.getStepAtIndex(0).completed).toBe(false);
138+
139+
wizard.goToPreviousStep();
140+
tick();
141+
wizardTestFixture.detectChanges();
142+
143+
checkWizardState(wizard, 0, false, [], false);
144+
checkWizardNavigableSteps(wizard, 0, []);
145+
146+
wizard.goToStep(-1);
147+
tick();
148+
wizardTestFixture.detectChanges();
149+
150+
checkWizardState(wizard, 0, false, [], false);
151+
checkWizardNavigableSteps(wizard, 0, []);
152+
153+
wizard.goToStep(0);
154+
tick();
155+
wizardTestFixture.detectChanges();
156+
157+
checkWizardState(wizard, 0, true, [0], false);
158+
checkWizardNavigableSteps(wizard, 0, []);
159+
}));
160+
161+
it('should reset the wizard correctly', fakeAsync(() => {
162+
wizard.goToNextStep();
163+
tick();
164+
wizardTestFixture.detectChanges();
165+
166+
wizard.goToNextStep();
167+
tick();
168+
wizardTestFixture.detectChanges();
169+
170+
checkWizardState(wizard, 2, false, [0, 1], false);
171+
checkWizardNavigableSteps(wizard, 2, [0, 1]);
172+
173+
wizard.reset();
174+
175+
checkWizardState(wizard, 0, false, [], false);
176+
checkWizardNavigableSteps(wizard, 0, []);
177+
178+
wizard.defaultStepIndex = -1;
179+
expect(() => wizard.reset())
180+
.toThrow(new Error(`The wizard doesn't contain a step with index -1`));
181+
182+
checkWizardState(wizard, 0, false, [], false);
183+
checkWizardNavigableSteps(wizard, 0, []);
184+
185+
wizard.defaultStepIndex = 1;
186+
wizard.reset();
187+
188+
checkWizardState(wizard, 1, false, [], false);
189+
checkWizardNavigableSteps(wizard, 1, [0]);
190+
191+
wizard.defaultStepIndex = 2;
192+
wizard.reset();
193+
194+
checkWizardState(wizard, 2, false, [], false);
195+
checkWizardNavigableSteps(wizard, 2, [0, 1]);
196+
}));
197+
});

0 commit comments

Comments
 (0)