Skip to content

Commit

Permalink
Add StepId and StepIndex interfaces (#102)
Browse files Browse the repository at this point in the history
- add two new interfaces: StepId and StepIndex
- change the way the awGoToStep directive takes a step index
- add a way to navigate to a step by its unique id
- refactor the tests for the awGoToStep directive
- split awGoToStep directive tests into different test case files for for the StepOffset, StepIndex and StepId interfaces
- improve the explanation of the awGoToStep directive
- improve comments in the awGoToStep directive
- add StepId and StepIndex to the index.ts file
  • Loading branch information
madoar committed Feb 19, 2018
1 parent 760dd83 commit cd08116
Show file tree
Hide file tree
Showing 11 changed files with 681 additions and 194 deletions.
69 changes: 49 additions & 20 deletions README.md
Expand Up @@ -133,6 +133,10 @@ Possible `<aw-wizard>` parameters:
`ng2-archwizard` contains two ways to define a wizard step.
One of these two ways is by using the `<aw-wizard-step>` component.

#### \[stepId\]
A wizard step can have its own unique id.
This id can then be used to navigate to the step.

#### \[stepTitle\]
A wizard step needs to contain a title, which is shown in the navigation bar of the wizard.
To set the title of a step, add the `stepTitle` input attribute, with the choosen step title, to the definition of your wizard step.
Expand Down Expand Up @@ -206,6 +210,7 @@ Possible `<aw-wizard-step>` parameters:

| Parameter name | Possible Values | Default Value |
| ----------------------------- | ---------------------------------------------------------------------------------------------------- | ------------- |
| [stepId] | `string` | null |
| [stepTitle] | `string` | null |
| [navigationSymbol] | `string` | '' |
| [navigationSymbolFontFamily] | `string` | null |
Expand All @@ -229,6 +234,7 @@ Possible `<aw-wizard-completion-step>` parameters:

| Parameter name | Possible Values | Default Value |
| ----------------------------- | ---------------------------------------------------------------------------------------------------- | ------------- |
| [stepId] | `string` | null |
| [stepTitle] | `string` | null |
| [navigationSymbol] | `string` | '' |
| [navigationSymbolFontFamily] | `string` | null |
Expand Down Expand Up @@ -284,27 +290,48 @@ When attaching the `awSelectedStep` directive to an arbitrary wizard step, it wi
which is shown directly after the wizard startup.

### \[awGoToStep\]
`ng2-archwizard` has three directives, that allow moving between steps.
`ng2-archwizard` has three directives, which allow moving between steps.
These directives are the `awPreviousStep`, `asNextStep` and `awGoToStep` directives.
The `awGoToStep` directive needs to receive an argument, that tells the wizard to which step it should change,
when the element with the `awGoToStep` directive has been clicked.
This argument has to be the zero-based index of the destination step:

```html
<button awGoToStep="2" (finalize)="finalizeStep()">Go directly to the third Step</button>
```

In the previous example the button moves the user automatically to the third step, after the user pressed onto it.
This makes it possible to directly jump to all already completed steps and to the first not completed optional or default (not optional) next step,
which will set the current as completed and makes it possible to jump over steps defined as optional steps.
The `awGoToStep` directive needs to receive an input, which tells the wizard, to which step it should navigate,
when the element with the `awGoToStep` directive has been clicked.

Alternatively to an absolute step index, it's also possible to set the destination wizard step as an offset to the source step:
```html
<button [awGoToStep]="{stepOffset: 1}" (finalize)="finalizeStep()">Go to the third Step</button>
```
In this example a click on the "Go to the third Step" button will move the user to the next step compared to the step the button belongs to.
If the button is for example part of the second step, a click on it will move the user to the third step.
When using offsets it's important to use `[]` around the `awGoToStep` directive to tell angular that the argument is to be interpreted as javascript.
This input accepts different arguments:

- a destination **step index**:
One possible argument for the input is a destination step index.
A destination step index is always zero-based, i.e. the index of the first step inside the wizard
is always zero.

To pass a destination step index to an `awGoToStep` directive,
you need to pass the following json object to the directive:

```html
<button awGoToStep="{ stepIndex: 2 }" (finalize)="finalizeStep()">Go directly to the third Step</button>
```
- a destination **step id**:
Another possible argument for the input is a the unique step id of the destination step.
This step id can be set for all wizard steps through their input `[stepId]`.

To pass a unique destination step id to an `awGoToStep` directive,
you need to pass the following json object to the directive:

```html
<button awGoToStep="{ stepId: 'unique id of the third step' }" (finalize)="finalizeStep()">Go directly to the third Step</button>
```
- a **step offset** between the current step and the destination step:
Alternatively to an absolute step index or an unique step id,
it's also possible to set the destination wizard step as an offset to the source step:

```html
<button [awGoToStep]="{ stepOffset: 1 }" (finalize)="finalizeStep()">Go to the third Step</button>
```

In all above examples a click on the "Go to the third Step" button will move
the user to the next step (the third step) compared to the step the button belongs to (the second step).
If the button is part of the second step, a click on it will move the user to the third step.

In all above cases it's important to use `[]` around the `awGoToStep` directive to tell angular that the argument is to be interpreted as javascript.

In addition to a static value you can also pass a local variable from your component typescript class,
that contains to which step a click on the element should change the current step of the wizard.
Expand All @@ -329,7 +356,7 @@ Possible parameters:

| Parameter name | Possible Values | Default Value |
| ----------------- | ----------------------------------------------------------------- | ------------- |
| [goToStep] | `WizardStep | StepOffset | number | string` | null |
| [goToStep] | `WizardStep | StepOffset | StepIndex | StepId` | null |
| (preFinalize) | `function(): void` | null |
| (postFinalize) | `function(): void` | null |
| (finalize) | `function(): void` | null |
Expand Down Expand Up @@ -399,6 +426,7 @@ Possible `awWizardStep` parameters:

| Parameter name | Possible Values | Default Value |
| ----------------------------- | ---------------------------------------------------------------------------------------------------- | ------------- |
| [stepId] | `string` | null |
| [stepTitle] | `string` | null |
| [navigationSymbol] | `string` | '' |
| [navigationSymbolFontFamily] | `string` | null |
Expand All @@ -424,10 +452,11 @@ that contains the wizard completion step.
```

#### Parameter overview
Possible `wizardCompletionStep` parameters:
Possible `awWizardCompletionStep` parameters:

| Parameter name | Possible Values | Default Value |
| ----------------------------- | ---------------------------------------------------------------------------------------------------- | ------------- |
| [stepId] | `string` | null |
| [stepTitle] | `string` | null |
| [navigationSymbol] | `string` | '' |
| [navigationSymbolFontFamily] | `string` | null |
Expand Down
166 changes: 5 additions & 161 deletions src/directives/go-to-step.directive.spec.ts
Expand Up @@ -3,7 +3,7 @@
*/
import {GoToStepDirective} from './go-to-step.directive';
import {Component} from '@angular/core';
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {async, ComponentFixture, fakeAsync, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {ArchwizardModule} from '../archwizard.module';
import {WizardState} from '../navigation/wizard-state.model';
Expand All @@ -15,13 +15,13 @@ import {NavigationMode} from '../navigation/navigation-mode.interface';
<aw-wizard>
<aw-wizard-step stepTitle='Steptitle 1' [canExit]="canExit">
Step 1
<button type="button" awGoToStep="0" (preFinalize)="finalizeStep(1)">Stay at this step</button>
<button type="button" [awGoToStep]="goToSecondStep" (preFinalize)="finalizeStep(1)">Go to second step</button>
<button type="button" [awGoToStep]="{stepIndex: 0}" (preFinalize)="finalizeStep(1)">Stay at this step</button>
<button type="button" [awGoToStep]="{stepIndex: goToSecondStep}" (preFinalize)="finalizeStep(1)">Go to second step</button>
<button type="button" [awGoToStep]="{stepOffset: 2}" (preFinalize)="finalizeStep(1)">Go to third step</button>
</aw-wizard-step>
<aw-wizard-step stepTitle='Steptitle 2' awOptionalStep>
Step 2
<button type="button" [awGoToStep]="'2'" (finalize)="finalizeStep(2)">Go to third step</button>
<button type="button" [awGoToStep]="{stepIndex: 2}" (finalize)="finalizeStep(2)">Go to third step</button>
<button type="button" [awGoToStep]="{incorrectKey: 3}" (finalize)="finalizeStep(2)">Invalid Button</button>
</aw-wizard-step>
<aw-wizard-step stepTitle='Steptitle 3'>
Expand Down Expand Up @@ -79,168 +79,12 @@ describe('GoToStepDirective', () => {
expect(wizardTestFixture.debugElement.queryAll(By.directive(GoToStepDirective)).length).toBe(9);
});

it('should move to step correctly', fakeAsync(() => {
const firstStepGoToButton = wizardTestFixture.debugElement.query(
By.css('aw-wizard-step[stepTitle="Steptitle 1"] > button:nth-child(2)')).nativeElement;
const secondStepGoToButton = wizardTestFixture.debugElement.query(
By.css('aw-wizard-step[stepTitle="Steptitle 2"] > button')).nativeElement;

const wizardSteps = wizardState.wizardSteps;

expect(wizardState.currentStepIndex).toBe(0);
expect(wizardSteps[0].selected).toBe(true);
expect(wizardSteps[1].selected).toBe(false);
expect(wizardSteps[2].selected).toBe(false);

// click button
firstStepGoToButton.click();
tick();
wizardTestFixture.detectChanges();

expect(wizardState.currentStepIndex).toBe(1);
expect(wizardSteps[0].selected).toBe(false);
expect(wizardSteps[1].selected).toBe(true);
expect(wizardSteps[2].selected).toBe(false);

// click button
secondStepGoToButton.click();
tick();
wizardTestFixture.detectChanges();

expect(wizardState.currentStepIndex).toBe(2);
expect(wizardSteps[0].selected).toBe(false);
expect(wizardSteps[1].selected).toBe(false);
expect(wizardSteps[2].selected).toBe(true);
}));

it('should jump over an optional step correctly', fakeAsync(() => {
const firstStepGoToButton = wizardTestFixture.debugElement.query(
By.css('aw-wizard-step[stepTitle="Steptitle 1"] > button:nth-child(3)')).nativeElement;
const thirdStepGoToButton = wizardTestFixture.debugElement.query(
By.css('aw-wizard-step[stepTitle="Steptitle 3"] > button')).nativeElement;

const wizardSteps = wizardState.wizardSteps;

expect(wizardState.currentStepIndex).toBe(0);
expect(wizardSteps[0].selected).toBe(true);
expect(wizardSteps[1].selected).toBe(false);
expect(wizardSteps[2].selected).toBe(false);

// click button
firstStepGoToButton.click();
tick();
wizardTestFixture.detectChanges();

expect(wizardState.currentStepIndex).toBe(2);
expect(wizardSteps[0].selected).toBe(false);
expect(wizardSteps[1].selected).toBe(false);
expect(wizardSteps[2].selected).toBe(true);

// click button
thirdStepGoToButton.click();
tick();
wizardTestFixture.detectChanges();

expect(wizardState.currentStepIndex).toBe(0);
expect(wizardSteps[0].selected).toBe(true);
expect(wizardSteps[1].selected).toBe(false);
expect(wizardSteps[2].selected).toBe(false);
}));

it('should stay at current step correctly', fakeAsync(() => {
const firstStepGoToButton = wizardTestFixture.debugElement.query(
By.css('aw-wizard-step[stepTitle="Steptitle 1"] > button:nth-child(1)')).nativeElement;

const wizardSteps = wizardState.wizardSteps;

expect(wizardState.currentStepIndex).toBe(0);
expect(wizardSteps[0].selected).toBe(true);
expect(wizardSteps[1].selected).toBe(false);
expect(wizardSteps[2].selected).toBe(false);

// click button
firstStepGoToButton.click();
tick();
wizardTestFixture.detectChanges();

expect(wizardState.currentStepIndex).toBe(0);
expect(wizardSteps[0].selected).toBe(true);
expect(wizardSteps[1].selected).toBe(false);
expect(wizardSteps[2].selected).toBe(false);
}));

it('should finalize step correctly', fakeAsync(() => {
const firstStepGoToButton = wizardTestFixture.debugElement.query(
By.css('aw-wizard-step[stepTitle="Steptitle 1"] > button:nth-child(3)')).nativeElement;
const thirdStepGoToButton = wizardTestFixture.debugElement.query(
By.css('aw-wizard-step[stepTitle="Steptitle 3"] > button')).nativeElement;

expect(wizardState.currentStepIndex).toBe(0);
expect(wizardTest.eventLog).toEqual([]);

// click button
firstStepGoToButton.click();
tick();
wizardTestFixture.detectChanges();

expect(wizardState.currentStepIndex).toBe(2);
expect(wizardTest.eventLog).toEqual(['finalize 1']);

// click button
thirdStepGoToButton.click();
tick();
wizardTestFixture.detectChanges();

expect(wizardState.currentStepIndex).toBe(0);
expect(wizardTest.eventLog).toEqual(['finalize 1', 'finalize 3']);
}));

it('should throw an error when using an invalid targetStep value', fakeAsync(() => {
const invalidGoToAttribute = wizardTestFixture.debugElement
.query(By.css('aw-wizard-step[stepTitle="Steptitle 2"]'))
.queryAll(By.directive(GoToStepDirective))[1].injector.get(GoToStepDirective) as GoToStepDirective;

expect(() => invalidGoToAttribute.destinationStep)
.toThrow(new Error(`Input 'targetStep' is neither a WizardStep, StepOffset, number or string`));
}));

it('should return correct destination step for correct targetStep values', fakeAsync(() => {
const firstGoToAttribute = wizardTestFixture.debugElement
.query(By.css('aw-wizard-navigation-bar'))
.queryAll(By.directive(GoToStepDirective))[0].injector.get(GoToStepDirective) as GoToStepDirective;

const secondGoToAttribute = wizardTestFixture.debugElement
.query(By.css('aw-wizard-step[stepTitle="Steptitle 1"]'))
.queryAll(By.directive(GoToStepDirective))[1].injector.get(GoToStepDirective) as GoToStepDirective;

const thirdGoToAttribute = wizardTestFixture.debugElement
.query(By.css('aw-wizard-step[stepTitle="Steptitle 2"]'))
.queryAll(By.directive(GoToStepDirective))[0].injector.get(GoToStepDirective) as GoToStepDirective;

const fourthGoToAttribute = wizardTestFixture.debugElement
.query(By.css('aw-wizard-step[stepTitle="Steptitle 3"]'))
.queryAll(By.directive(GoToStepDirective))[0].injector.get(GoToStepDirective) as GoToStepDirective;

expect(firstGoToAttribute.destinationStep).toBe(0);
expect(secondGoToAttribute.destinationStep).toBe(1);
expect(thirdGoToAttribute.destinationStep).toBe(2);
expect(fourthGoToAttribute.destinationStep).toBe(0);
}));

it('should not leave current step if it the destination step can not be entered', fakeAsync(() => {
expect(wizardState.currentStepIndex).toBe(0);

wizardTest.canExit = false;
wizardTestFixture.detectChanges();

const secondGoToAttribute = wizardTestFixture.debugElement
.query(By.css('aw-wizard-navigation-bar'))
.queryAll(By.directive(GoToStepDirective))[1].nativeElement;

secondGoToAttribute.click();
tick();
wizardTestFixture.detectChanges();

expect(wizardState.currentStepIndex).toBe(0);
.toThrow(new Error(`Input 'targetStep' is neither a WizardStep, StepOffset, StepIndex or StepId`));
}));
});

0 comments on commit cd08116

Please sign in to comment.