Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support [navigateForward]="visited" #228

Merged
merged 3 commits into from Aug 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.md
Expand Up @@ -258,13 +258,22 @@ The only exception to this rule are optional steps, which a user can skip.
Using the navigation bar, the user can navigate back to steps they already visited.

You can alter this behavior by applying to the `<aw-wizard>` element an additional `[awNavigationMode]` directive, which can be used in two ways.
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:
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:

| Parameter name | Possible Values | Default Value |
| ----------------------------- | ---------------------------------------------------------------------------------------------------- | ------------- |
| [navigateBackward] | `'allow'|'deny'` | `'deny'` |
| [navigateForward] | `'allow'|'deny'|'visited'` | `'allow'` |

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:

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

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:
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:

custom-navigation-mode.ts:
```typescript
Expand Down Expand Up @@ -307,7 +316,7 @@ Possible `awNavigationMode` parameters:
| ----------------------------- | ---------------------------------------------------------------------------------------------------- | ------------- |
| [awNavigationMode] | `NavigationMode` | `null` |
| [navigateBackward] | `'allow'|'deny'` | `'deny'` |
| [navigateForward] | `'allow'|'deny'` | `'allow'` |
| [navigateForward] | `'allow'|'deny'|'visited'` | `'allow'` |

### \[awEnableBackLinks\]
In some cases it may be required that the user is allowed to leave an entered `aw-wizard-completion-step`.
Expand Down
4 changes: 2 additions & 2 deletions src/lib/directives/navigation-mode.directive.ts
Expand Up @@ -77,9 +77,10 @@ export class NavigationModeDirective implements OnChanges {
*
* - `navigateForward="deny"` -- the steps are not navigable
* - `navigateForward="allow"` -- the steps are navigable
* - `navigateForward="visited"` -- a step is navigable iff it was already visited before
*/
@Input()
public navigateForward: 'allow'|'deny'|null;
public navigateForward: 'allow'|'deny'|'visited'|null;

constructor(private wizard: WizardComponent) { }

Expand All @@ -95,4 +96,3 @@ export class NavigationModeDirective implements OnChanges {
}

}

7 changes: 5 additions & 2 deletions src/lib/navigation/configurable-navigation-mode.ts
Expand Up @@ -18,6 +18,7 @@ import {WizardCompletionStep} from '../util/wizard-completion-step.interface';
*
* - `"deny"` -- the steps are not navigable
* - `"allow"` -- the steps are navigable
* - `"visited"` -- a step is navigable iff it was already visited before
* - If the corresponding constructor argument is omitted or is `null` or `undefined`,
* then the default value is applied which is `"allow"`
*/
Expand All @@ -31,7 +32,7 @@ export class ConfigurableNavigationMode extends BaseNavigationMode {
*/
constructor(
private navigateBackward: 'allow'|'deny'|null = null,
private navigateForward: 'allow'|'deny'|null = null,
private navigateForward: 'allow'|'deny'|'visited'|null = null,
) {
super();
this.navigateBackward = this.navigateBackward || 'allow';
Expand Down Expand Up @@ -74,7 +75,8 @@ export class ConfigurableNavigationMode extends BaseNavigationMode {
*/
public isNavigable(wizard: WizardComponent, destinationIndex: number): boolean {
// Check if the destination step can be navigated to
if (wizard.getStepAtIndex(destinationIndex) instanceof WizardCompletionStep) {
const destinationStep = wizard.getStepAtIndex(destinationIndex);
if (destinationStep instanceof WizardCompletionStep) {
// A completion step can only be entered, if all previous steps have been completed, are optional, or selected
const previousStepsCompleted = wizard.wizardSteps
.filter((step, index) => index < destinationIndex)
Expand All @@ -98,6 +100,7 @@ export class ConfigurableNavigationMode extends BaseNavigationMode {
switch (this.navigateForward) {
case 'allow': return true;
case 'deny': return false;
case 'visited': return destinationStep.completed;
default:
throw new Error(`Invalid value for navigateForward: ${this.navigateForward}`);
}
Expand Down
197 changes: 197 additions & 0 deletions src/lib/navigation/wizard-navigation-allow-forward-visited.spec.ts
@@ -0,0 +1,197 @@
import {Component, ViewChild} from '@angular/core';
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {ArchwizardModule} from '../archwizard.module';
import {WizardComponent} from '../components/wizard.component';
import {checkWizardState, checkWizardNavigableSteps} from '../util/test-utils';

@Component({
selector: 'aw-test-wizard',
template: `
<aw-wizard [awNavigationMode] navigateForward="visited">
<aw-wizard-step stepTitle='Steptitle 1'>
Step 1
</aw-wizard-step>
<aw-wizard-step stepTitle='Steptitle 2'>
Step 2
</aw-wizard-step>
<aw-wizard-step stepTitle='Steptitle 3'>
Step 3
</aw-wizard-step>
</aw-wizard>
`
})
class WizardTestComponent {
@ViewChild(WizardComponent)
public wizard: WizardComponent;
}

describe('Wizard navigation with navigateForward=visited', () => {
let wizardTestFixture: ComponentFixture<WizardTestComponent>;

let wizardTest: WizardTestComponent;
let wizard: WizardComponent;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [WizardTestComponent],
imports: [ArchwizardModule]
}).compileComponents();
}));

beforeEach(() => {
wizardTestFixture = TestBed.createComponent(WizardTestComponent);
wizardTestFixture.detectChanges();

wizardTest = wizardTestFixture.componentInstance;
wizard = wizardTest.wizard;
});

it('should return correct can go to step', async(() => {
wizard.canGoToStep(-1).then(result => expect(result).toBe(false));
wizard.canGoToStep(0).then(result => expect(result).toBe(true));
wizard.canGoToStep(1).then(result => expect(result).toBe(true));
wizard.canGoToStep(2).then(result => expect(result).toBe(false));
wizard.canGoToStep(3).then(result => expect(result).toBe(false));
}));

it('should go to step', fakeAsync(() => {
checkWizardState(wizard, 0, false, [], false);
checkWizardNavigableSteps(wizard, 0, []);

wizard.goToStep(1);
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 1, false, [0], false);
checkWizardNavigableSteps(wizard, 1, [0]);

wizard.goToStep(2);
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 2, false, [0, 1], false);
checkWizardNavigableSteps(wizard, 2, [0, 1]);

wizard.goToStep(0);
tick();
wizardTestFixture.detectChanges();

// If forward navigation is allowed, visited steps after
// the selected step are still considered completed
checkWizardState(wizard, 0, true, [0, 1, 2], true);
checkWizardNavigableSteps(wizard, 0, [1, 2]);

wizard.goToStep(1);
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 1, true, [0, 1, 2], true);
checkWizardNavigableSteps(wizard, 1, [0, 2]);

wizard.goToStep(2);
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 2, true, [0, 1, 2], true);
checkWizardNavigableSteps(wizard, 2, [0, 1]);

wizard.goToStep(1);
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 1, true, [0, 1, 2], true);
checkWizardNavigableSteps(wizard, 1, [0, 2]);
}));

it('should go to next step', fakeAsync(() => {
wizard.goToNextStep();
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 1, false, [0], false);
checkWizardNavigableSteps(wizard, 1, [0]);
}));

it('should go to previous step', fakeAsync(() => {
checkWizardState(wizard, 0, false, [], false);
checkWizardNavigableSteps(wizard, 0, []);

wizard.goToStep(1);
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 1, false, [0], false);
checkWizardNavigableSteps(wizard, 1, [0]);

wizard.goToPreviousStep();
tick();
wizardTestFixture.detectChanges();

// If forward navigation is allowed, visited steps after
// the selected step are still considered completed
checkWizardState(wizard, 0, true, [0, 1], false);
checkWizardNavigableSteps(wizard, 0, [1]);
}));

it('should stay at the current step', fakeAsync(() => {
expect(wizard.getStepAtIndex(0).completed).toBe(false);

wizard.goToPreviousStep();
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 0, false, [], false);
checkWizardNavigableSteps(wizard, 0, []);

wizard.goToStep(-1);
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 0, false, [], false);
checkWizardNavigableSteps(wizard, 0, []);

wizard.goToStep(0);
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 0, true, [0], false);
checkWizardNavigableSteps(wizard, 0, []);
}));

it('should reset the wizard correctly', fakeAsync(() => {
wizard.goToNextStep();
tick();
wizardTestFixture.detectChanges();

wizard.goToNextStep();
tick();
wizardTestFixture.detectChanges();

checkWizardState(wizard, 2, false, [0, 1], false);
checkWizardNavigableSteps(wizard, 2, [0, 1]);

wizard.reset();

checkWizardState(wizard, 0, false, [], false);
checkWizardNavigableSteps(wizard, 0, []);

wizard.defaultStepIndex = -1;
expect(() => wizard.reset())
.toThrow(new Error(`The wizard doesn't contain a step with index -1`));

checkWizardState(wizard, 0, false, [], false);
checkWizardNavigableSteps(wizard, 0, []);

wizard.defaultStepIndex = 1;
wizard.reset();

checkWizardState(wizard, 1, false, [], false);
checkWizardNavigableSteps(wizard, 1, [0]);

wizard.defaultStepIndex = 2;
wizard.reset();

checkWizardState(wizard, 2, false, [], false);
checkWizardNavigableSteps(wizard, 2, [0, 1]);
}));
});