Wizard tests (#5328)

add integration tests for the ui tutorial
This commit is contained in:
madalynrose 2018-10-12 14:49:06 -04:00 committed by GitHub
parent 4c1c312c65
commit 3ff2ba463a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1940 additions and 80 deletions

View File

@ -0,0 +1,39 @@
import TutorialMachineConfig from 'vault/machines/tutorial-machine';
import SecretsMachineConfig from 'vault/machines/secrets-machine';
import PoliciesMachineConfig from 'vault/machines/policies-machine';
import ReplicationMachineConfig from 'vault/machines/replication-machine';
import ToolsMachineConfig from 'vault/machines/tools-machine';
import AuthMachineConfig from 'vault/machines/auth-machine';
export const STORAGE_KEYS = {
TUTORIAL_STATE: 'vault:ui-tutorial-state',
FEATURE_LIST: 'vault:ui-feature-list',
FEATURE_STATE: 'vault:ui-feature-state',
COMPLETED_FEATURES: 'vault:ui-completed-list',
COMPONENT_STATE: 'vault:ui-component-state',
RESUME_URL: 'vault:ui-tutorial-resume-url',
RESUME_ROUTE: 'vault:ui-tutorial-resume-route',
};
export const MACHINES = {
tutorial: TutorialMachineConfig,
secrets: SecretsMachineConfig,
policies: PoliciesMachineConfig,
replication: ReplicationMachineConfig,
tools: ToolsMachineConfig,
authentication: AuthMachineConfig,
};
export const DEFAULTS = {
currentState: null,
featureList: null,
featureState: null,
currentMachine: null,
tutorialComponent: null,
featureComponent: null,
stepComponent: null,
detailsComponent: null,
componentState: null,
nextFeature: null,
nextStep: null,
};

View File

@ -19,7 +19,6 @@ export default {
},
complete: {
onEntry: ['completeFeature'],
on: { RESET: 'idle' },
},
},
};

View File

@ -43,7 +43,7 @@ export default {
cond: type => ['pki', 'aws', 'ssh'].includes(type),
},
secret: {
cond: type => ['cubbyhole', 'database', 'gcp', 'kv', 'nomad', 'rabbitmq', 'totp'].includes(type),
cond: type => ['kv'].includes(type),
},
encryption: {
cond: type => type === 'transit',
@ -108,7 +108,7 @@ export default {
actions: [{ type: 'routeTransition', params: ['vault.cluster.secrets.backend.create-root'] }],
},
secret: {
cond: type => ['cubbyhole', 'database', 'gcp', 'kv', 'nomad', 'rabbitmq', 'totp'].includes(type),
cond: type => ['kv'].includes(type),
actions: [{ type: 'routeTransition', params: ['vault.cluster.secrets.backend.create-root'] }],
},
encryption: {

View File

@ -55,7 +55,6 @@ export default {
},
complete: {
onEntry: ['completeFeature'],
on: { RESET: 'idle' },
},
},
};

View File

@ -4,44 +4,9 @@ import Service, { inject as service } from '@ember/service';
import { Machine } from 'xstate';
import getStorage from 'vault/lib/token-storage';
import TutorialMachineConfig from 'vault/machines/tutorial-machine';
import SecretsMachineConfig from 'vault/machines/secrets-machine';
import PoliciesMachineConfig from 'vault/machines/policies-machine';
import ReplicationMachineConfig from 'vault/machines/replication-machine';
import ToolsMachineConfig from 'vault/machines/tools-machine';
import AuthMachineConfig from 'vault/machines/auth-machine';
const TutorialMachine = Machine(TutorialMachineConfig);
import { STORAGE_KEYS, DEFAULTS, MACHINES } from 'vault/helpers/wizard-constants';
const TutorialMachine = Machine(MACHINES.tutorial);
let FeatureMachine = null;
const TUTORIAL_STATE = 'vault:ui-tutorial-state';
const FEATURE_LIST = 'vault:ui-feature-list';
const FEATURE_STATE = 'vault:ui-feature-state';
const COMPLETED_FEATURES = 'vault:ui-completed-list';
const COMPONENT_STATE = 'vault:ui-component-state';
const RESUME_URL = 'vault:ui-tutorial-resume-url';
const RESUME_ROUTE = 'vault:ui-tutorial-resume-route';
const MACHINES = {
secrets: SecretsMachineConfig,
policies: PoliciesMachineConfig,
replication: ReplicationMachineConfig,
tools: ToolsMachineConfig,
authentication: AuthMachineConfig,
};
const DEFAULTS = {
currentState: null,
featureList: null,
featureState: null,
currentMachine: null,
tutorialComponent: null,
featureComponent: null,
stepComponent: null,
detailsComponent: null,
componentState: null,
nextFeature: null,
nextStep: null,
};
export default Service.extend(DEFAULTS, {
router: service(),
@ -53,25 +18,25 @@ export default Service.extend(DEFAULTS, {
},
initializeMachines() {
if (!this.storageHasKey(TUTORIAL_STATE)) {
if (!this.storageHasKey(STORAGE_KEYS.TUTORIAL_STATE)) {
let state = TutorialMachine.initialState;
this.saveState('currentState', state.value);
this.saveExtState(TUTORIAL_STATE, state.value);
this.saveExtState(STORAGE_KEYS.TUTORIAL_STATE, state.value);
}
this.saveState('currentState', this.getExtState(TUTORIAL_STATE));
if (this.storageHasKey(COMPONENT_STATE)) {
this.set('componentState', this.getExtState(COMPONENT_STATE));
this.saveState('currentState', this.getExtState(STORAGE_KEYS.TUTORIAL_STATE));
if (this.storageHasKey(STORAGE_KEYS.COMPONENT_STATE)) {
this.set('componentState', this.getExtState(STORAGE_KEYS.COMPONENT_STATE));
}
let stateNodes = TutorialMachine.getStateNodes(this.get('currentState'));
this.executeActions(stateNodes.reduce((acc, node) => acc.concat(node.onEntry), []), null, 'tutorial');
if (this.storageHasKey(FEATURE_LIST)) {
this.set('featureList', this.getExtState(FEATURE_LIST));
if (this.storageHasKey(FEATURE_STATE)) {
this.saveState('featureState', this.getExtState(FEATURE_STATE));
if (this.storageHasKey(STORAGE_KEYS.FEATURE_LIST)) {
this.set('featureList', this.getExtState(STORAGE_KEYS.FEATURE_LIST));
if (this.storageHasKey(STORAGE_KEYS.FEATURE_STATE)) {
this.saveState('featureState', this.getExtState(STORAGE_KEYS.FEATURE_STATE));
} else {
if (FeatureMachine != null) {
this.saveState('featureState', FeatureMachine.initialState);
this.saveExtState(FEATURE_STATE, this.get('featureState'));
this.saveExtState(STORAGE_KEYS.FEATURE_STATE, this.get('featureState'));
}
}
this.buildFeatureMachine();
@ -82,13 +47,13 @@ export default Service.extend(DEFAULTS, {
let storage = this.storage();
// empty storage
[
TUTORIAL_STATE,
FEATURE_LIST,
FEATURE_STATE,
COMPLETED_FEATURES,
COMPONENT_STATE,
RESUME_URL,
RESUME_ROUTE,
STORAGE_KEYS.TUTORIAL_STATE,
STORAGE_KEYS.FEATURE_LIST,
STORAGE_KEYS.FEATURE_STATE,
STORAGE_KEYS.COMPLETED_FEATURES,
STORAGE_KEYS.COMPONENT_STATE,
STORAGE_KEYS.RESUME_URL,
STORAGE_KEYS.RESUME_ROUTE,
].forEach(key => storage.removeItem(key));
// reset wizard state
this.setProperties(DEFAULTS);
@ -115,11 +80,11 @@ export default Service.extend(DEFAULTS, {
transitionTutorialMachine(currentState, event, extendedState) {
if (extendedState) {
this.set('componentState', extendedState);
this.saveExtState(COMPONENT_STATE, extendedState);
this.saveExtState(STORAGE_KEYS.COMPONENT_STATE, extendedState);
}
let { actions, value } = TutorialMachine.transition(currentState, event);
this.saveState('currentState', value);
this.saveExtState(TUTORIAL_STATE, this.get('currentState'));
this.saveExtState(STORAGE_KEYS.TUTORIAL_STATE, this.get('currentState'));
this.executeActions(actions, event, 'tutorial');
},
@ -129,12 +94,12 @@ export default Service.extend(DEFAULTS, {
}
if (extendedState) {
this.set('componentState', extendedState);
this.saveExtState(COMPONENT_STATE, extendedState);
this.saveExtState(STORAGE_KEYS.COMPONENT_STATE, extendedState);
}
let { actions, value } = FeatureMachine.transition(currentState, event, this.get('componentState'));
this.saveState('featureState', value);
this.saveExtState(FEATURE_STATE, value);
this.saveExtState(STORAGE_KEYS.FEATURE_STATE, value);
this.executeActions(actions, event, 'feature');
// if all features were completed, the FeatureMachine gets nulled
// out and won't exist here as there is no next step
@ -229,33 +194,33 @@ export default Service.extend(DEFAULTS, {
handlePaused() {
let expected = this.get('expectedURL');
if (expected) {
this.saveExtState(RESUME_URL, this.get('expectedURL'));
this.saveExtState(RESUME_ROUTE, this.get('expectedRouteName'));
this.saveExtState(STORAGE_KEYS.RESUME_URL, this.get('expectedURL'));
this.saveExtState(STORAGE_KEYS.RESUME_ROUTE, this.get('expectedRouteName'));
}
},
handleResume() {
let resumeURL = this.storage().getItem(RESUME_URL);
let resumeURL = this.storage().getItem(STORAGE_KEYS.RESUME_URL);
if (!resumeURL) {
return;
}
this.get('router').transitionTo(resumeURL).followRedirects().then(() => {
this.set('expectedRouteName', this.storage().getItem(RESUME_ROUTE));
this.set('expectedRouteName', this.storage().getItem(STORAGE_KEYS.RESUME_ROUTE));
this.set('expectedURL', resumeURL);
this.initializeMachines();
this.storage().removeItem(RESUME_URL);
this.storage().removeItem(STORAGE_KEYS.RESUME_URL);
});
},
handleDismissed() {
this.storage().removeItem(FEATURE_STATE);
this.storage().removeItem(FEATURE_LIST);
this.storage().removeItem(COMPONENT_STATE);
this.storage().removeItem(STORAGE_KEYS.FEATURE_STATE);
this.storage().removeItem(STORAGE_KEYS.FEATURE_LIST);
this.storage().removeItem(STORAGE_KEYS.COMPONENT_STATE);
},
saveFeatures(features) {
this.set('featureList', features);
this.saveExtState(FEATURE_LIST, this.get('featureList'));
this.saveExtState(STORAGE_KEYS.FEATURE_LIST, this.get('featureList'));
this.buildFeatureMachine();
},
@ -264,10 +229,10 @@ export default Service.extend(DEFAULTS, {
return;
}
this.startFeature();
if (this.storageHasKey(FEATURE_STATE)) {
this.saveState('featureState', this.getExtState(FEATURE_STATE));
if (this.storageHasKey(STORAGE_KEYS.FEATURE_STATE)) {
this.saveState('featureState', this.getExtState(STORAGE_KEYS.FEATURE_STATE));
}
this.saveExtState(FEATURE_STATE, this.get('featureState'));
this.saveExtState(STORAGE_KEYS.FEATURE_STATE, this.get('featureState'));
let nextFeature =
this.get('featureList').length > 1 ? this.get('featureList').objectAt(1).capitalize() : 'Finish';
this.set('nextFeature', nextFeature);
@ -292,20 +257,23 @@ export default Service.extend(DEFAULTS, {
completeFeature() {
let features = this.get('featureList');
let done = features.shift();
if (!this.getExtState(COMPLETED_FEATURES)) {
if (!this.getExtState(STORAGE_KEYS.COMPLETED_FEATURES)) {
let completed = [];
completed.push(done);
this.saveExtState(COMPLETED_FEATURES, completed);
this.saveExtState(STORAGE_KEYS.COMPLETED_FEATURES, completed);
} else {
this.saveExtState(COMPLETED_FEATURES, this.getExtState(COMPLETED_FEATURES).toArray().addObject(done));
this.saveExtState(
STORAGE_KEYS.COMPLETED_FEATURES,
this.getExtState(STORAGE_KEYS.COMPLETED_FEATURES).toArray().addObject(done)
);
}
this.saveExtState(FEATURE_LIST, features.length ? features : null);
this.storage().removeItem(FEATURE_STATE);
this.saveExtState(STORAGE_KEYS.FEATURE_LIST, features.length ? features : null);
this.storage().removeItem(STORAGE_KEYS.FEATURE_STATE);
if (features.length > 0) {
this.buildFeatureMachine();
} else {
this.storage().removeItem(FEATURE_LIST);
this.storage().removeItem(STORAGE_KEYS.FEATURE_LIST);
FeatureMachine = null;
this.transitionTutorialMachine(this.get('currentState'), 'DONE');
}

View File

@ -0,0 +1,87 @@
import { module, test } from 'qunit';
import { Machine } from 'xstate';
import AuthMachineConfig from 'vault/machines/auth-machine';
module('Unit | Machine | auth-machine', function() {
const authMachine = Machine(AuthMachineConfig);
const testCases = [
{
currentState: authMachine.initialState,
event: 'CONTINUE',
params: null,
expectedResults: {
value: 'enable',
actions: [
{ component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
{ component: 'wizard/auth-enable', level: 'step', type: 'render' },
],
},
},
{
currentState: 'enable',
event: 'CONTINUE',
params: null,
expectedResults: {
value: 'list',
actions: [
{ component: 'wizard/auth-list', level: 'step', type: 'render' },
{ component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
],
},
},
{
currentState: 'list',
event: 'DETAILS',
expectedResults: {
value: 'details',
actions: [
{ component: 'wizard/auth-details', level: 'step', type: 'render' },
{ component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
],
},
},
{
currentState: 'details',
event: 'CONTINUE',
expectedResults: {
value: 'complete',
actions: ['completeFeature'],
},
},
{
currentState: 'details',
event: 'RESET',
params: null,
expectedResults: {
value: 'idle',
actions: [
{
params: ['vault.cluster.settings.auth.enable'],
type: 'routeTransition',
},
{
component: 'wizard/mounts-wizard',
level: 'feature',
type: 'render',
},
{
component: 'wizard/auth-idle',
level: 'step',
type: 'render',
},
],
},
},
];
testCases.forEach(testCase => {
test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${
testCase.params
}`, function(assert) {
let result = authMachine.transition(testCase.currentState, testCase.event, testCase.params);
assert.equal(result.value, testCase.expectedResults.value);
assert.deepEqual(result.actions, testCase.expectedResults.actions);
});
});
});

View File

@ -0,0 +1,62 @@
import { module, test } from 'qunit';
import { Machine } from 'xstate';
import PoliciesMachineConfig from 'vault/machines/policies-machine';
module('Unit | Machine | policies-machine', function() {
const policiesMachine = Machine(PoliciesMachineConfig);
const testCases = [
{
currentState: policiesMachine.initialState,
event: 'CONTINUE',
params: null,
expectedResults: {
value: 'create',
actions: [{ component: 'wizard/policies-create', level: 'feature', type: 'render' }],
},
},
{
currentState: 'create',
event: 'CONTINUE',
params: null,
expectedResults: {
value: 'details',
actions: [{ component: 'wizard/policies-details', level: 'feature', type: 'render' }],
},
},
{
currentState: 'details',
event: 'CONTINUE',
expectedResults: {
value: 'delete',
actions: [{ component: 'wizard/policies-delete', level: 'feature', type: 'render' }],
},
},
{
currentState: 'delete',
event: 'CONTINUE',
expectedResults: {
value: 'others',
actions: [{ component: 'wizard/policies-others', level: 'feature', type: 'render' }],
},
},
{
currentState: 'others',
event: 'CONTINUE',
expectedResults: {
value: 'complete',
actions: ['completeFeature'],
},
},
];
testCases.forEach(testCase => {
test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${
testCase.params
}`, function(assert) {
let result = policiesMachine.transition(testCase.currentState, testCase.event, testCase.params);
assert.equal(result.value, testCase.expectedResults.value);
assert.deepEqual(result.actions, testCase.expectedResults.actions);
});
});
});

View File

@ -0,0 +1,38 @@
import { module, test } from 'qunit';
import { Machine } from 'xstate';
import ReplicationMachineConfig from 'vault/machines/replication-machine';
module('Unit | Machine | replication-machine', function() {
const replicationMachine = Machine(ReplicationMachineConfig);
const testCases = [
{
currentState: replicationMachine.initialState,
event: 'ENABLEREPLICATION',
params: null,
expectedResults: {
value: 'details',
actions: [{ type: 'render', level: 'feature', component: 'wizard/replication-details' }],
},
},
{
currentState: 'details',
event: 'CONTINUE',
params: null,
expectedResults: {
value: 'complete',
actions: ['completeFeature'],
},
},
];
testCases.forEach(testCase => {
test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${
testCase.params
}`, function(assert) {
let result = replicationMachine.transition(testCase.currentState, testCase.event, testCase.params);
assert.equal(result.value, testCase.expectedResults.value);
assert.deepEqual(result.actions, testCase.expectedResults.actions);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,91 @@
import { module, test } from 'qunit';
import { Machine } from 'xstate';
import ToolsMachineConfig from 'vault/machines/tools-machine';
module('Unit | Machine | tools-machine', function() {
const toolsMachine = Machine(ToolsMachineConfig);
const testCases = [
{
currentState: toolsMachine.initialState,
event: 'CONTINUE',
params: null,
expectedResults: {
value: 'wrapped',
actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-wrapped' }],
},
},
{
currentState: 'wrapped',
event: 'LOOKUP',
params: null,
expectedResults: {
value: 'lookup',
actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-lookup' }],
},
},
{
currentState: 'lookup',
event: 'CONTINUE',
params: null,
expectedResults: {
value: 'info',
actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-info' }],
},
},
{
currentState: 'info',
event: 'REWRAP',
params: null,
expectedResults: {
value: 'rewrap',
actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-rewrap' }],
},
},
{
currentState: 'rewrap',
event: 'CONTINUE',
params: null,
expectedResults: {
value: 'rewrapped',
actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-rewrapped' }],
},
},
{
currentState: 'rewrapped',
event: 'UNWRAP',
params: null,
expectedResults: {
value: 'unwrap',
actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-unwrap' }],
},
},
{
currentState: 'unwrap',
event: 'CONTINUE',
params: null,
expectedResults: {
value: 'unwrapped',
actions: [{ type: 'render', level: 'feature', component: 'wizard/tools-unwrapped' }],
},
},
{
currentState: 'unwrapped',
event: 'CONTINUE',
expectedResults: {
value: 'complete',
actions: ['completeFeature'],
},
},
];
testCases.forEach(testCase => {
test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${
testCase.params
}`, function(assert) {
let result = toolsMachine.transition(testCase.currentState, testCase.event, testCase.params);
assert.equal(result.value, testCase.expectedResults.value);
assert.deepEqual(result.actions, testCase.expectedResults.actions);
});
});
});

View File

@ -0,0 +1,247 @@
import { module, test } from 'qunit';
import { Machine } from 'xstate';
import TutorialMachineConfig from 'vault/machines/tutorial-machine';
module('Unit | Machine | tutorial-machine', function() {
const tutorialMachine = Machine(TutorialMachineConfig);
const testCases = [
{
currentState: 'init',
event: 'START',
params: null,
expectedResults: {
value: {
init: {
active: 'setup',
},
},
actions: [
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
{ type: 'render', level: 'feature', component: 'wizard/init-setup' },
],
},
},
{
currentState: 'init.active.setup',
event: 'TOSAVE',
params: null,
expectedResults: {
value: {
init: {
active: 'save',
},
},
actions: [{ type: 'render', level: 'feature', component: 'wizard/init-save-keys' }],
},
},
{
currentState: 'init',
event: 'SAVE',
params: null,
expectedResults: {
value: {
init: {
active: 'save',
},
},
actions: [
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
{ type: 'render', level: 'feature', component: 'wizard/init-save-keys' },
],
},
},
{
currentState: 'init.active.save',
event: 'TOUNSEAL',
params: null,
expectedResults: {
value: {
init: {
active: 'unseal',
},
},
actions: [{ type: 'render', level: 'feature', component: 'wizard/init-unseal' }],
},
},
{
currentState: 'init',
event: 'UNSEAL',
params: null,
expectedResults: {
value: {
init: {
active: 'unseal',
},
},
actions: [
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
{ type: 'render', level: 'feature', component: 'wizard/init-unseal' },
],
},
},
{
currentState: 'init.active.unseal',
event: 'TOLOGIN',
params: null,
expectedResults: {
value: {
init: {
active: 'login',
},
},
actions: [{ type: 'render', level: 'feature', component: 'wizard/init-login' }],
},
},
{
currentState: 'init',
event: 'LOGIN',
params: null,
expectedResults: {
value: {
init: {
active: 'login',
},
},
actions: [
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
{ type: 'render', level: 'feature', component: 'wizard/init-login' },
],
},
},
{
currentState: 'init.active.login',
event: 'INITDONE',
params: null,
expectedResults: {
value: {
active: 'select',
},
actions: [
'showTutorialWhenAuthenticated',
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
{ type: 'render', level: 'feature', component: 'wizard/features-selection' },
],
},
},
{
currentState: 'active.select',
event: 'CONTINUE',
params: null,
expectedResults: {
value: {
active: 'feature',
},
actions: [],
},
},
{
currentState: 'active.feature',
event: 'DISMISS',
params: null,
expectedResults: {
value: 'dismissed',
actions: [
{ type: 'render', level: 'tutorial', component: null },
{ type: 'render', level: 'feature', component: null },
{ type: 'render', level: 'step', component: null },
{ type: 'render', level: 'detail', component: null },
'handleDismissed',
],
},
},
{
currentState: 'active.feature',
event: 'DONE',
params: null,
expectedResults: {
value: 'complete',
actions: [
{ type: 'render', level: 'feature', component: null },
{ type: 'render', level: 'step', component: null },
{ type: 'render', level: 'detail', component: null },
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-complete' },
],
},
},
{
currentState: 'active.feature',
event: 'PAUSE',
params: null,
expectedResults: {
value: 'paused',
actions: [
{ type: 'render', level: 'feature', component: null },
{ type: 'render', level: 'step', component: null },
{ type: 'render', level: 'detail', component: null },
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-paused' },
'handlePaused',
],
},
},
{
currentState: 'paused',
event: 'CONTINUE',
params: null,
expectedResults: {
value: {
active: 'feature',
},
actions: ['handleResume', { type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' }],
},
},
{
currentState: 'idle',
event: 'INIT',
params: null,
expectedResults: {
value: {
init: 'idle',
},
actions: [
'showTutorialAlways',
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-idle' },
{ type: 'render', level: 'feature', component: null },
],
},
},
{
currentState: 'idle',
event: 'AUTH',
params: null,
expectedResults: {
value: {
active: 'select',
},
actions: [
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
{ type: 'render', level: 'feature', component: 'wizard/features-selection' },
],
},
},
{
currentState: 'idle',
event: 'CONTINUE',
params: null,
expectedResults: {
value: {
active: 'select',
},
actions: [
{ type: 'render', level: 'tutorial', component: 'wizard/tutorial-active' },
{ type: 'render', level: 'feature', component: 'wizard/features-selection' },
],
},
},
];
testCases.forEach(testCase => {
test(`transition: ${testCase.event} for currentState ${testCase.currentState} and componentState ${
testCase.params
}`, function(assert) {
let result = tutorialMachine.transition(testCase.currentState, testCase.event, testCase.params);
assert.deepEqual(result.value, testCase.expectedResults.value);
assert.deepEqual(result.actions, testCase.expectedResults.actions);
});
});
});

View File

@ -0,0 +1,258 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import Service from '@ember/service';
import sinon from 'sinon';
import { STORAGE_KEYS, DEFAULTS } from 'vault/helpers/wizard-constants';
let routerStub = Service.extend({
transitionTo: sinon.stub().returns({
followRedirects: function() {
return {
then: function(callback) {
callback();
},
};
},
}),
urlFor: sinon.stub().returns('/ui/vault/foo'),
});
module('Unit | Service | wizard', function(hooks) {
setupTest(hooks);
hooks.beforeEach(function() {
this.owner.register('service:router', routerStub);
this.router = this.owner.lookup('service:router');
});
function storage() {
return {
items: {},
getItem(key) {
var item = this.items[key];
return item && JSON.parse(item);
},
setItem(key, val) {
return (this.items[key] = JSON.stringify(val));
},
removeItem(key) {
delete this.items[key];
},
keys() {
return Object.keys(this.items);
},
};
}
let testCases = [
{
method: 'getExtState',
args: [STORAGE_KEYS.TUTORIAL_STATE],
expectedResults: {
storage: [{ key: STORAGE_KEYS.TUTORIAL_STATE, value: 'idle' }],
},
},
{
method: 'saveExtState',
args: [STORAGE_KEYS.TUTORIAL_STATE, 'test'],
expectedResults: {
storage: [{ key: STORAGE_KEYS.TUTORIAL_STATE, value: 'test' }],
},
},
{
method: 'storageHasKey',
args: ['fake-key'],
expectedResults: { value: false },
},
{
method: 'storageHasKey',
args: [STORAGE_KEYS.TUTORIAL_STATE],
expectedResults: { value: true },
},
{
method: 'handleDismissed',
args: [],
expectedResults: {
storage: [
{ key: STORAGE_KEYS.FEATURE_STATE, value: undefined },
{ key: STORAGE_KEYS.FEATURE_LIST, value: undefined },
{ key: STORAGE_KEYS.COMPONENT_STATE, value: undefined },
],
},
},
{
method: 'handlePaused',
args: [],
properties: {
expectedURL: 'this/is/a/url',
expectedRouteName: 'this.is.a.route',
},
expectedResults: {
storage: [
{ key: STORAGE_KEYS.RESUME_URL, value: 'this/is/a/url' },
{ key: STORAGE_KEYS.RESUME_ROUTE, value: 'this.is.a.route' },
],
},
},
{
method: 'handlePaused',
args: [],
expectedResults: {
storage: [
{ key: STORAGE_KEYS.RESUME_URL, value: undefined },
{ key: STORAGE_KEYS.RESUME_ROUTE, value: undefined },
],
},
},
{
method: 'handleResume',
storage: [
{ key: STORAGE_KEYS.RESUME_URL, value: 'this/is/a/url' },
{ key: STORAGE_KEYS.RESUME_ROUTE, value: 'this.is.a.route' },
],
args: [],
expectedResults: {
props: [
{ prop: 'expectedURL', value: 'this/is/a/url' },
{ prop: 'expectedRouteName', value: 'this.is.a.route' },
],
storage: [
{ key: STORAGE_KEYS.RESUME_URL, value: undefined },
{ key: STORAGE_KEYS.RESUME_ROUTE, value: 'this.is.a.route' },
],
},
},
{
method: 'handleResume',
args: [],
expectedResults: {
storage: [
{ key: STORAGE_KEYS.RESUME_URL, value: undefined },
{ key: STORAGE_KEYS.RESUME_ROUTE, value: undefined },
],
},
},
{
method: 'restartGuide',
args: [],
expectedResults: {
props: [
{ prop: 'currentState', value: 'active.select' },
{ prop: 'featureComponent', value: 'wizard/features-selection' },
{ prop: 'tutorialComponent', value: 'wizard/tutorial-active' },
],
storage: [
{ key: STORAGE_KEYS.FEATURE_STATE, value: undefined },
{ key: STORAGE_KEYS.FEATURE_LIST, value: undefined },
{ key: STORAGE_KEYS.COMPONENT_STATE, value: undefined },
{ key: STORAGE_KEYS.TUTORIAL_STATE, value: 'active.select' },
{ key: STORAGE_KEYS.COMPLETED_FEATURES, value: undefined },
{ key: STORAGE_KEYS.RESUME_URL, value: undefined },
{ key: STORAGE_KEYS.RESUME_ROUTE, value: undefined },
],
},
},
{
method: 'saveState',
args: [
'currentState',
{
value: {
init: {
active: 'login',
},
},
actions: [{ type: 'render', level: 'feature', component: 'wizard/init-login' }],
},
],
expectedResults: {
props: [{ prop: 'currentState', value: 'init.active.login' }],
},
},
{
method: 'saveState',
args: [
'currentState',
{
value: {
active: 'login',
},
actions: [{ type: 'render', level: 'feature', component: 'wizard/init-login' }],
},
],
expectedResults: {
props: [{ prop: 'currentState', value: 'active.login' }],
},
},
{
method: 'saveState',
args: ['currentState', 'login'],
expectedResults: {
props: [{ prop: 'currentState', value: 'login' }],
},
},
{
method: 'startFeature',
args: [],
properties: { featureList: ['secrets', 'tools'] },
expectedResults: {
props: [{ prop: 'featureState', value: 'idle' }, { prop: 'currentMachine', value: 'secrets' }],
},
},
{
method: 'saveFeatures',
args: [['secrets', 'tools']],
expectedResults: {
props: [{ prop: 'featureList', value: ['secrets', 'tools'] }],
storage: [{ key: STORAGE_KEYS.FEATURE_LIST, value: ['secrets', 'tools'] }],
},
},
];
testCases.forEach(testCase => {
let store = storage();
test(`${testCase.method}`, function(assert) {
let wizard = this.owner.factoryFor('service:wizard').create({
storage() {
return store;
},
});
if (testCase.properties) {
wizard.setProperties(testCase.properties);
} else {
wizard.setProperties(DEFAULTS);
}
if (testCase.storage) {
testCase.storage.forEach(item => wizard.storage().setItem(item.key, item.value));
}
let result = wizard[testCase.method](...testCase.args);
if (testCase.expectedResults.props) {
testCase.expectedResults.props.forEach(property => {
assert.deepEqual(
wizard.get(property.prop),
property.value,
`${testCase.method} creates correct value for ${property.prop}`
);
});
}
if (testCase.expectedResults.storage) {
testCase.expectedResults.storage.forEach(item => {
assert.deepEqual(
wizard.storage().getItem(item.key),
item.value,
`${testCase.method} creates correct storage state for ${item.key}`
);
});
}
if (testCase.expectedResults.value !== null && testCase.expectedResults.value !== undefined) {
assert.equal(result, testCase.expectedResults.value, `${testCase.method} gives correct value`);
}
});
});
});