UI kmip acceptance (#7129)

* change the story blueprint so that using -ir will generate a story in the expected place for in-repo addons and engines

* update gen-story-md script to do output md to stories folder inside of in-repo addons and engines

* update storybook config to look for story files in /lib

* add story for list-view component

* add list view page object

* add kmip page objects and tests

* update storybook commands in the README

* split tests up more

* update var name in storybook
This commit is contained in:
Matthew Irish
2019-08-01 16:04:59 -05:00
committed by GitHub
parent 027148692c
commit 783bb2b598
27 changed files with 496 additions and 43 deletions

View File

@@ -5,8 +5,10 @@ import { assign } from '@ember/polyfills';
function loadStories() {
// automatically import all files ending in *.stories.js
const req = require.context('../stories/', true, /.stories.js$/);
req.keys().forEach(filename => req(filename));
const appStories = require.context('../stories', true, /.stories.js$/);
const addonAndRepoStories = require.context('../lib', true, /.stories.js$/);
appStories.keys().forEach(filename => appStories(filename));
addonAndRepoStories.keys().forEach(filename => addonAndRepoStories(filename));
}
addParameters({

View File

@@ -111,14 +111,16 @@ The Vault UI uses Storybook to catalog all of its components. Below are details
### Storybook Commands at a Glance
| Command | Description |
| ------------------------------------------ | ------------------------- |
| `yarn storybook` | run storybook |
| `ember generate story [name-of-component]` | generate a new story |
| `yarn gen-story-md [name-of-component]` | update a story notes file |
| ------------------------------------------------------------------------ | ---------------------------------------------------------- |
| `yarn storybook` | run storybook |
| `ember generate story [name-of-component]` | generate a new story |
| `ember generate story [name-of-component] -ir [name-of-engine-or-addon]` | generate a new story in the specified engine or addon |
| `yarn gen-story-md [name-of-component]` | update a story notes file |
| `yarn gen-story-md [name-of-component] [name-of-engine-or-addon]` | update a story notes file in the specified engine or addon |
### Writing Stories
Each component in `vault/ui/app/components` should have a corresponding `[component-name].stories.js` and `[component-name].md` files within `vault/ui/stories`.
Each component in `vault/ui/app/components` should have a corresponding `[component-name].stories.js` and `[component-name].md` files within `vault/ui/stories`. Components in the `core` addon located at `vault/ui/lib/core/addon/components` have corresponding stories and markdown files in `vault/ui/lib/core/stories`.
#### Adding a new story

View File

@@ -7,8 +7,8 @@ storiesOf('<%= classifiedModuleName %>/', module)
.addParameters({ options: { showPanel: true } })
.add(`<%= classifiedModuleName %>`, () => ({
template: hbs`
<h5 class="title is-5"><%= header %></h5>
<<%= classifiedModuleName %>/>
<h5 class="title is-5"><%= header %></h5>
<<%= classifiedModuleName %>/>
`,
context: {},
}),

View File

@@ -1,12 +1,31 @@
'use strict';
const getPathOption = require('ember-cli-get-component-path-option');
const stringUtil = require('ember-cli-string-utils');
const path = require('path');
function findAddonByName(addonOrProject, name) {
let addon = addonOrProject.addons.find(addon => addon.name === name);
if (addon) {
return addon;
}
return addonOrProject.addons.find(addon => findAddonByName(addon, name));
}
module.exports = {
description: 'generates a story for storybook',
fileMapTokens: function() {
let { project } = this;
return {
__path__: function(options) {
if (options.inRepoAddon) {
let addon = findAddonByName(project, options.inRepoAddon);
return path.relative(project.root, addon.root);
}
return path.relative(project.root, project.root);
},
__markdownname__: function(options) {
return options.dasherizedModuleName;
},

View File

@@ -3,19 +3,44 @@ import { computed } from '@ember/object';
import { pluralize } from 'ember-inflector';
import layout from '../templates/components/list-view';
/**
* @module ListView
* `ListView` components are used in conjuction with `ListItem` for rendering a list.
*
* @example
* ```js
* <ListView @items={{model}} @itemNoun="role" @paginationRouteName="scope.roles" as |list|>
* {{#if list.empty}}
* <list.empty @title="No roles here" />
* {{else}}
* <div>
* {{list.item.id}}
* </div>
* {{/if}}
* </ListView>
* ```
*
* @param items=null {Array} - An array of items to render as a list
* @param [itemNoun=null {String}] - A noun to use in the empty state of message and title.
* @param [message=null {String}] - The message to display within the banner.
* @yields Object with `item` that is the current item in the loop.
* @yields If there are no objects in items, then `empty` will be yielded - this is an instance of
* the EmptyState component.
* @yields If `item` or `empty` isn't present on the object, the component can still yield a block - this is
* useful for showing states where there are items but there may be a filter applied that returns an
* empty set.
*
*/
export default Component.extend({
layout,
tagName: '',
items: null,
itemNoun: 'item',
// the dasherized name of a component to render
// in the EmptyState component if there are no items in items.length
emptyActions: '',
showPagination: computed('paginationRouteName', 'items.meta{lastPage,total}', function() {
return this.paginationRouteName && this.items.meta.lastPage > 1 && this.items.meta.total > 0;
}),
paginationRouteName: '',
showPagination: computed('paginationRouteName', 'items.meta{lastPage,total}', function() {
let meta = this.items.meta;
return this.paginationRouteName && meta && meta.lastPage > 1 && meta.total > 0;
}),
emptyTitle: computed('itemNoun', function() {
let items = pluralize(this.get('itemNoun'));

View File

@@ -1,4 +1,4 @@
<div class="empty-state" ...attributes>
<div data-test-component="empty-state" class="empty-state" ...attributes>
<div class="empty-state-content">
<h3 class="empty-state-title" data-test-empty-state-title>
{{title}}

View File

@@ -1,15 +1,15 @@
{{#if componentName}}
{{component componentName item=item}}
{{else if linkParams}}
<LinkedBlock @params={{linkParams}} @linkPrefix={{@linkPrefix}} @class="list-item-row">
<LinkedBlock @params={{linkParams}} @linkPrefix={{@linkPrefix}} @class="list-item-row" data-test-list-item-link>
<div class="level is-mobile">
<div class="level-left is-flex-1">
<div class="level-left is-flex-1" data-test-list-item-content>
{{#link-to params=linkParams class="has-text-weight-semibold has-text-black is-display-flex is-flex-1 is-no-underline"}}
{{yield (hash content=(component "list-item/content"))}}
{{/link-to}}
</div>
<div class="level-right">
<div class="level-item">
<div class="level-item" data-test-list-item-popup>
{{yield (hash callMethod=callMethod menu=(component "list-item/popup-menu" item=item hasMenu=hasMenu))}}
</div>
</div>
@@ -18,11 +18,11 @@
{{else}}
<div class="list-item-row">
<div class="level is-mobile">
<div class="level-left is-flex-1 has-text-weight-semibold">
<div class="level-left is-flex-1 has-text-weight-semibold" data-test-list-item>
{{yield (hash content=(component "list-item/content"))}}
</div>
<div class="level-right">
<div class="level-item">
<div class="level-item" data-test-list-item-popup>
{{yield (hash callMethod=callMethod menu=(component "list-item/popup-menu" item=item hasMenu=hasMenu))}}
</div>
</div>

View File

@@ -4,8 +4,8 @@
items.length
)
}}
<div class="box is-fullwidth is-bottomless is-sideless is-paddingless">
{{#each items as |item|}}
<div class="box is-fullwidth is-bottomless is-sideless is-paddingless" data-test-list-view-list>
{{#each (or items.content items) as |item|}}
{{yield (hash item=item)}}
{{else}}
{{yield}}
@@ -15,6 +15,7 @@
@page={{items.meta.currentPage}}
@lastPage={{items.meta.lastPage}}
@link={{@paginationRouteName}}
data-test-list-view-pagination
/>
{{/if}}
</div>

View File

@@ -6,6 +6,7 @@
value={{@filter}}
placeholder={{ or @placeholder "Filter keys" }}
type="text"
data-test-comoponent="navigate-input"
oninput={{action "handleInput" value="target.value"}}
onkeyup={{action "handleKeyUp" }}

View File

@@ -13,6 +13,7 @@
data-test-policy-create-link={{data-test-policy-create-link}}
data-test-policy-edit-toggle={{data-test-policy-edit-toggle}}
data-test-secret-backend-configure={{data-test-secret-backend-configure}}
...attributes
>
{{yield}}
<Icon @glyph={{glyph}} />

View File

@@ -0,0 +1,32 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in lib/core/addon/components/list-view.js. To make changes, first edit that file and run "yarn gen-story-md list-view" to re-generate the content.-->
## ListView
`ListView` components are used in conjuction with `ListItem` for rendering a list.
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| items | <code>Array</code> | <code></code> | An array of items to render as a list |
| [itemNoun] | <code>String</code> | <code></code> | A noun to use in the empty state of message and title. |
| [message] | <code>String</code> | <code></code> | The message to display within the banner. |
**Example**
```js
<ListView @items={{model}} @itemNoun="role" @paginationRouteName="scope.roles" as |list|>
{{#if list.empty}}
<list.empty @title="No roles here" />
{{else}}
<div>
{{list.item.id}}
</div>
{{/if}}
</ListView>
```
**See**
- [Uses of ListView](https://github.com/hashicorp/vault/search?l=Handlebars&q=ListView+OR+list-view)
- [ListView Source Code](https://github.com/hashicorp/vault/blob/master/ui/lib/core/addon/components/list-view.js)
---

View File

@@ -0,0 +1,58 @@
/* eslint-disable import/extensions */
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import { withKnobs, select } from '@storybook/addon-knobs';
import notes from './list-view.md';
import ArrayProxy from '@ember/array/proxy';
let filtered = ArrayProxy.create({ content: [] });
filtered.set('meta', {
lastPage: 1,
currentPage: 1,
total: 100,
});
let paginated = ArrayProxy.create({
content: [{ id: 'middle' }, { id: 'of' }, { id: 'the' }, { id: 'list' }],
});
paginated.set('meta', {
lastPage: 10,
currentPage: 4,
total: 100,
});
let options = {
list: [{ id: 'one' }, { id: 'two' }],
empty: [],
filtered,
paginated,
};
storiesOf('ListView/', module)
.addParameters({ options: { showPanel: true } })
.addDecorator(withKnobs())
.add(
`ListView`,
() => ({
template: hbs`
<h5 class="title is-5">{{title}}</h5>
<ListView @items={{items}} @itemNoun={{or noun "role"}} @paginationRouteName="vault" as |list|>
{{#if list.empty}}
<list.empty @title="No roles here" />
{{else if list.item}}
<div class="box is-marginless">
{{list.item.id}}
</div>
{{else}}
<div class="box">There aren't any items in this filter</div>
{{/if}}
</ListView>
`,
context: {
title: 'ListView',
items: select('items', options, options['list']),
},
}),
{ notes }
);

View File

@@ -98,7 +98,7 @@
</div>
{{#if cancelLinkParams}}
<div class="control">
{{#link-to params=cancelLinkParams class="button"}}
{{#link-to params=cancelLinkParams class="button" data-test-edit-form-cancel="true"}}
Cancel
{{/link-to}}
</div>

View File

@@ -12,12 +12,12 @@
<nav class="tabs">
<ul>
{{#link-to "credentials.index" @scope @role tagName="li"}}
{{#link-to "credentials.index" @scope @role}}
{{#link-to "credentials.index" @scope @role data-test-kmip-link-credentials="true"}}
Credentials
{{/link-to}}
{{/link-to}}
{{#link-to "role" @scope @role tagName="li"}}
{{#link-to "role" @scope @role}}
{{#link-to "role" @scope @role data-test-kmip-link-role-details="true"}}
Details
{{/link-to}}
{{/link-to}}

View File

@@ -17,12 +17,12 @@
<nav class="tabs">
<ul>
{{#link-to "scopes.index" tagName="li"}}
{{#link-to "scopes.index"}}
{{#link-to "scopes.index" data-test-kmip-link-scopes="true"}}
Scopes
{{/link-to}}
{{/link-to}}
{{#link-to "configuration" tagName="li"}}
{{#link-to "configuration"}}
{{#link-to "configuration" data-test-kmip-link-config="true"}}
Configuration
{{/link-to}}
{{/link-to}}

View File

@@ -11,6 +11,7 @@
{{/if}}
<ToolbarLink
@params={{array "configure"}}
data-test-kmip-link-configure
>
Configure
</ToolbarLink>

View File

@@ -33,6 +33,7 @@
<ToolbarLink
@type="add"
@params={{array "credentials.generate"}}
data-test-kmip-link-generate-credentials
>
Generate credentials
</ToolbarLink>

View File

@@ -12,6 +12,7 @@
<ToolbarActions>
<ToolbarLink
@params={{array "credentials.index" this.scope this.role}}
data-test-kmip-link-back-to-role
>
Back to role
</ToolbarLink>

View File

@@ -25,6 +25,7 @@
{{#if model.updatePath.canUpdate}}
<ToolbarLink
@params={{array "role.edit" this.scope this.role}}
data-test-kmip-link-edit-role
>
Edit role
</ToolbarLink>

View File

@@ -43,6 +43,7 @@
<ToolbarLink
@type="add"
@params={{array "scope.roles.create"}}
data-test-role-create
>
Create role
</ToolbarLink>

View File

@@ -2,19 +2,19 @@
<Toolbar>
{{#if model.meta.total}}
<ToolbarFilters>
<NavigateInput
@filterFocusDidChange={{action "setFilterFocus" }}
@filterDidChange={{action "setFilter"}}
@filter={{this.filter}}
@filterMatchesKey={{filterMatchesKey}}
@firstPartialMatch={{firstPartialMatch}}
@placeholder="Filter scopes by name"
@urls={{hash
create="vault.cluster.secrets.backend.kmip.scopes.create"
list="vault.cluster.secrets.backend.kmip.scopes.index"
show="vault.cluster.secrets.backend.kmip.scope.roles"
}}
/>
<NavigateInput
@filterFocusDidChange={{action "setFilterFocus" }}
@filterDidChange={{action "setFilter"}}
@filter={{this.filter}}
@filterMatchesKey={{filterMatchesKey}}
@firstPartialMatch={{firstPartialMatch}}
@placeholder="Filter scopes by name"
@urls={{hash
create="vault.cluster.secrets.backend.kmip.scopes.create"
list="vault.cluster.secrets.backend.kmip.scopes.index"
show="vault.cluster.secrets.backend.kmip.scope.roles"
}}
/>
{{#if filterFocused}}
{{#if filterMatchesKey}}
<p class="input-hint">
@@ -33,6 +33,7 @@
<ToolbarLink
@type="add"
@params={{array "scopes.create"}}
data-test-scope-create
>
Create scope
</ToolbarLink>

View File

@@ -1,5 +1,11 @@
#!/usr/bin/env node
/* eslint-disable */
// run this script via yarn in the ui directory:
// yarn gen-story-md some-component
//
// or if the story is for a component in an in-repo-addon or an engine:
// yarn gen-story-md some-component name-of-engine
const fs = require('fs');
const jsdoc2md = require('jsdoc-to-markdown');
var args = process.argv.slice(2);
@@ -8,7 +14,8 @@ const addonOrEngine = args[1];
const inputFile = addonOrEngine
? `lib/${addonOrEngine}/addon/components/${name}.js`
: `app/components/${name}.js`;
const outputFile = `stories/${name}.md`;
const outputFile = addonOrEngine ? `lib/${addonOrEngine}/stories/${name}.md` : `stories/${name}.md`;
const component = name
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))

View File

@@ -0,0 +1,233 @@
import { currentURL, currentRouteName } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { create } from 'ember-cli-page-object';
import consoleClass from 'vault/tests/pages/components/console/ui-panel';
import authPage from 'vault/tests/pages/auth';
import scopesPage from 'vault/tests/pages/secrets/backend/kmip/scopes';
import rolesPage from 'vault/tests/pages/secrets/backend/kmip/roles';
import credentialsPage from 'vault/tests/pages/secrets/backend/kmip/credentials';
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
const uiConsole = create(consoleClass);
const mount = async (shouldConfig = true) => {
let path = `kmip-${Date.now()}`;
let commands = shouldConfig
? [`write sys/mounts/${path} type=kmip`, `write ${path}/config -force`]
: [`write sys/mounts/${path} type=kmip`];
await uiConsole.runCommands(commands);
return path;
};
const createScope = async () => {
let path = await mount();
let scope = `scope-${Date.now()}`;
await uiConsole.runCommands([`write ${path}/scope/${scope} -force`]);
return { path, scope };
};
const createRole = async () => {
let { path, scope } = await createScope();
let role = `role-${Date.now()}`;
await uiConsole.runCommands([`write ${path}/scope/${scope}/role/${role} operation_all=true`]);
return { path, scope, role };
};
const generateCreds = async () => {
let { path, scope, role } = await createRole();
await uiConsole.runCommands([
`write ${path}/scope/${scope}/role/${role}/credential/generate format=pem
-field=serial_number`,
]);
let serial = uiConsole.lastLogOutput;
return { path, scope, role, serial };
};
module('Acceptance | Enterprise | KMIP secrets', function(hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function() {
return authPage.login();
});
test('it enables KMIP secrets engine', async function(assert) {
let path = `kmip-${Date.now()}`;
await mountSecrets.enable('kmip', path);
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/scopes`,
'mounts and redirects to the kmip scopes page'
);
assert.ok(scopesPage.isEmpty, 'renders empty state');
});
test('it can configure a KMIP secrets engine', async function(assert) {
let path = await mount(false);
await scopesPage.visit({ backend: path });
await scopesPage.configurationLink();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/configuration`,
'configuration navigates to the config page'
);
assert.ok(scopesPage.isEmpty, 'config page renders empty state');
await scopesPage.configureLink();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/configure`,
'configuration navigates to the configure page'
);
await scopesPage.submit();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/configuration`,
'redirects to configuration page after saving config'
);
assert.notOk(scopesPage.isEmpty, 'configuration page no longer renders empty state');
});
test('it can create a scope', async function(assert) {
let path = await mount(this);
await scopesPage.visit({ backend: path });
await scopesPage.createLink();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/scopes/create`,
'navigates to the kmip scope create page'
);
// create scope
await scopesPage.scopeName('foo');
await scopesPage.submit();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/scopes`,
'navigates to the kmip scopes page after create'
);
assert.equal(scopesPage.listItemLinks.length, 1, 'renders a single scope');
});
test('it can delete a scope from the list', async function(assert) {
let { path } = await createScope(this);
await scopesPage.visit({ backend: path });
// delete the scope
await scopesPage.listItemLinks.objectAt(0).menuToggle();
await scopesPage.delete();
await scopesPage.confirmDelete();
assert.equal(scopesPage.listItemLinks.length, 0, 'no scopes');
assert.ok(scopesPage.isEmpty, 'renders the empty state');
});
test('it can create a role', async function(assert) {
let { path, scope } = await createScope(this);
let role = `role-${Date.now()}`;
await rolesPage.visit({ backend: path, scope });
assert.ok(rolesPage.isEmpty, 'renders the empty role page');
await rolesPage.create();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/scopes/${scope}/roles/create`,
'links to the role create form'
);
await rolesPage.roleName(role);
await rolesPage.submit();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/scopes/${scope}/roles`,
'redirects to roles list'
);
assert.equal(rolesPage.listItemLinks.length, 1, 'renders a single role');
});
test('it can delete a role from the list', async function(assert) {
let { path, scope } = await createRole();
await rolesPage.visit({ backend: path, scope });
// delete the role
await rolesPage.listItemLinks.objectAt(0).menuToggle();
await rolesPage.delete();
await rolesPage.confirmDelete();
assert.equal(rolesPage.listItemLinks.length, 0, 'renders no roles');
assert.ok(rolesPage.isEmpty, 'renders empty');
});
test('it can delete a role from the detail page', async function(assert) {
let { path, scope, role } = await createRole(this);
await rolesPage.visitDetail({ backend: path, scope, role });
await rolesPage.detailEditLink();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/edit`,
'navigates to role edit'
);
await rolesPage.cancelLink();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}`,
'cancel navigates to role show'
);
await rolesPage
.detailDelete()
.delete()
.confirmDelete();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/scopes/${scope}/roles`,
'redirects to the roles list'
);
assert.ok(rolesPage.isEmpty, 'renders an empty roles page');
});
test('it can create a credential', async function(assert) {
let { path, scope, role } = await createRole();
await credentialsPage.visit({ backend: path, scope, role });
assert.ok(credentialsPage.isEmpty, 'renders empty creds page');
await credentialsPage.generateCredentialsLink();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/credentials/generate`,
'navigates to generate credentials'
);
await credentialsPage.submit();
assert.equal(
currentRouteName(),
'vault.cluster.secrets.backend.kmip.credentials.show',
'generate redirects to the show page'
);
await credentialsPage.backToRoleLink();
assert.equal(credentialsPage.listItemLinks.length, 1, 'renders a single credential');
});
test('it can revoke a credential from the list', async function(assert) {
let { path, scope, role } = await generateCreds();
await credentialsPage.visit({ backend: path, scope, role });
// revoke the credentials
await credentialsPage.listItemLinks.objectAt(0).menuToggle();
await credentialsPage.delete();
await credentialsPage.confirmDelete();
assert.equal(credentialsPage.listItemLinks.length, 0, 'renders no credentials');
assert.ok(credentialsPage.isEmpty, 'renders empty');
});
test('it can revoke from the credentials show page', async function(assert) {
let { path, scope, role, serial } = await generateCreds();
await credentialsPage.visitDetail({ backend: path, scope, role, serial });
await credentialsPage
.detailRevoke()
.delete()
.confirmDelete();
assert.equal(
currentURL(),
`/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/credentials`,
'redirects to the credentials list'
);
assert.ok(credentialsPage.isEmpty, 'renders an empty credentials page');
});
});

View File

@@ -0,0 +1,23 @@
import { text, isPresent, collection, clickable } from 'ember-cli-page-object';
export default {
isEmpty: isPresent('[data-test-component="empty-state"]'),
listItemLinks: collection('[data-test-list-item-link]', {
text: text(),
click: clickable(),
menuToggle: clickable('[data-test-popup-menu-trigger]'),
}),
listItems: collection('[data-test-list-item]', {
text: text(),
menuToggle: clickable('[data-test-popup-menu-trigger]'),
}),
menuItems: collection('.ember-basic-dropdown-content li', {
testContainer: '#ember-testing',
}),
delete: clickable('[data-test-confirm-action-trigger]', {
testContainer: '#ember-testing',
}),
confirmDelete: clickable('[data-test-confirm-button]', {
testContainer: '#ember-testing',
}),
};

View File

@@ -0,0 +1,15 @@
import { create, clickable, visitable } from 'ember-cli-page-object';
import ListView from 'vault/tests/pages/components/list-view';
export default create({
...ListView,
visit: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role/credentials'),
visitDetail: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role/credentials/:serial'),
create: clickable('[data-test-role-create]'),
credentialsLink: clickable('[data-test-kmip-link-credentials]'),
generateCredentialsLink: clickable('[data-test-kmip-link-generate-credentials]'),
roleDetailsLink: clickable('[data-test-kmip-link-role-details]'),
backToRoleLink: clickable('[data-test-kmip-link-back-to-role]'),
detailRevoke: clickable('[data-test-popup-menu-trigger]'),
submit: clickable('[data-test-edit-form-submit]'),
});

View File

@@ -0,0 +1,14 @@
import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
import ListView from 'vault/tests/pages/components/list-view';
export default create({
...ListView,
visit: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles'),
visitDetail: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role'),
create: clickable('[data-test-role-create]'),
roleName: fillable('[data-test-input="name"]'),
submit: clickable('[data-test-edit-form-submit]'),
detailDelete: clickable('[data-test-popup-menu-trigger]'),
detailEditLink: clickable('[data-test-kmip-link-edit-role]'),
cancelLink: clickable('[data-test-edit-form-cancel]'),
});

View File

@@ -0,0 +1,14 @@
import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
import ListView from 'vault/tests/pages/components/list-view';
export default create({
...ListView,
visit: visitable('/vault/secrets/:backend/kmip/scopes'),
visitCreate: visitable('/vault/secrets/:backend/kmip/scopes/create'),
createLink: clickable('[data-test-scope-create]'),
scopeName: fillable('[data-test-input="name"]'),
submit: clickable('[data-test-edit-form-submit]'),
configurationLink: clickable('[data-test-kmip-link-config]'),
configureLink: clickable('[data-test-kmip-link-configure]'),
scopesLink: clickable('[data-test-kmip-link-scopes]'),
});