mirror of https://github.com/status-im/consul.git
Merge pull request #4341 from hashicorp/feature/more-acceptance-tests
UI - More acceptance tests
This commit is contained in:
commit
865a20c03e
|
@ -13,37 +13,27 @@ You will need the following things properly installed on your computer.
|
|||
|
||||
## Installation
|
||||
|
||||
* `git clone <repository-url>` this repository
|
||||
* `cd ui`
|
||||
* `git clone https://github.com/hashicorp/consul.git` this repository
|
||||
* `cd ui-v2`
|
||||
* `yarn install`
|
||||
|
||||
## Running / Development
|
||||
|
||||
* `yarn run start`
|
||||
* `make start-api` or `yarn start:api` (this starts a Consul API double running
|
||||
on http://localhost:3000)
|
||||
* `make start` or `yarn start` to start the ember app that connects to the
|
||||
above API double
|
||||
* Visit your app at [http://localhost:4200](http://localhost:4200).
|
||||
* Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests).
|
||||
|
||||
|
||||
### Code Generators
|
||||
|
||||
Make use of the many generators for code, try `ember help generate` for more details
|
||||
|
||||
### Running Tests
|
||||
|
||||
* `ember test`
|
||||
* `ember test --server`
|
||||
You do not need to run `make start-api`/`yarn run start:api` to run the tests
|
||||
|
||||
### Building
|
||||
|
||||
* `ember build` (development)
|
||||
* `ember build --environment production` (production)
|
||||
|
||||
### Deploying
|
||||
|
||||
|
||||
## Further Reading / Useful Links
|
||||
|
||||
* [ember.js](https://emberjs.com/)
|
||||
* [ember-cli](https://ember-cli.com/)
|
||||
* Development Browser Extensions
|
||||
* [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
|
||||
* [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/)
|
||||
* `make test` or `yarn run test`
|
||||
* `make test-view` or `yarn run test:view` to view the tests running in Chrome
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
{{# if (and (not create) (not-eq item.ID 'anonymous')) }}
|
||||
{{#confirmation-dialog message='Are you sure you want to delete this ACL token?'}}
|
||||
{{#block-slot 'action' as |confirm|}}
|
||||
<button type="button" class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>
|
||||
<button type="button" data-test-delete class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'dialog' as |execute cancel message|}}
|
||||
<p>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{{#app-view class="acl edit" loading=isLoading}}
|
||||
{{#block-slot 'breadcrumbs'}}
|
||||
<ol>
|
||||
<li><a href={{href-to 'dc.acls'}}>All Tokens</a></li>
|
||||
<li><a data-test-back href={{href-to 'dc.acls'}}>All Tokens</a></li>
|
||||
</ol>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'header'}}
|
||||
|
@ -35,7 +35,7 @@
|
|||
<button type="button" {{ action "clone" item }}>Clone token</button>
|
||||
{{#confirmation-dialog message='Are you sure you want to use this ACL token?'}}
|
||||
{{#block-slot 'action' as |confirm|}}
|
||||
<button type="button" {{ action confirm 'use' item }}>Use token</button>
|
||||
<button data-test-use type="button" {{ action confirm 'use' item }}>Use token</button>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'dialog' as |execute cancel message|}}
|
||||
<p>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</h1>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'actions'}}
|
||||
<a href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
|
||||
<a data-test-create href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'toolbar'}}
|
||||
{{#if (gt items.length 0) }}
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
{{# if (and item.ID (not-eq item.ID 'anonymous')) }}
|
||||
{{#confirmation-dialog message='Are you sure you want to delete this Intention?'}}
|
||||
{{#block-slot 'action' as |confirm|}}
|
||||
<button type="button" class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>
|
||||
<button data-test-delete type="button" class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'dialog' as |execute cancel message|}}
|
||||
<p>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{{#app-view class="acl edit" loading=isLoading}}
|
||||
{{#block-slot 'breadcrumbs'}}
|
||||
<ol>
|
||||
<li><a href={{href-to 'dc.intentions'}}>All Intentions</a></li>
|
||||
<li><a data-test-back href={{href-to 'dc.intentions'}}>All Intentions</a></li>
|
||||
</ol>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'header'}}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</h1>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'actions'}}
|
||||
<a href="{{href-to 'dc.intentions.create'}}" class="type-create">Create</a>
|
||||
<a data-test-create href="{{href-to 'dc.intentions.create'}}" class="type-create">Create</a>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'toolbar'}}
|
||||
{{#if (gt items.length 0) }}
|
||||
|
@ -27,7 +27,7 @@
|
|||
{{/block-slot}}
|
||||
{{#block-slot 'row'}}
|
||||
<td class="source" data-test-intention="{{item.ID}}">
|
||||
<a href={{href-to 'dc.intentions.edit' item.ID}}>
|
||||
<a href={{href-to 'dc.intentions.edit' item.ID}} data-test-intention-source="{{item.SourceName}}">
|
||||
{{#if (eq item.SourceName '*') }}
|
||||
All Services (*)
|
||||
{{else}}
|
||||
|
@ -35,10 +35,10 @@
|
|||
{{/if}}
|
||||
</a>
|
||||
</td>
|
||||
<td class="intent-{{item.Action}}">
|
||||
<td class="intent-{{item.Action}}" data-test-intention-action="{{item.Action}}">
|
||||
<strong>{{item.Action}}</strong>
|
||||
</td>
|
||||
<td class="destination">
|
||||
<td class="destination" data-test-intention-destination="{{item.DestinationName}}">
|
||||
{{#if (eq item.DestinationName '*') }}
|
||||
All Services (*)
|
||||
{{else}}
|
||||
|
@ -58,7 +58,7 @@
|
|||
<a href={{href-to 'dc.intentions.edit' item.ID}}>Edit</a>
|
||||
</li>
|
||||
<li>
|
||||
<a onclick={{action confirm 'delete' item}}>Delete</a>
|
||||
<a data-test-delete onclick={{action confirm 'delete' item}}>Delete</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{/action-group}}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<button type="reset" {{ action "cancel" item parent}}>Cancel changes</button>
|
||||
{{#confirmation-dialog message='Are you sure you want to delete this key?'}}
|
||||
{{#block-slot 'action' as |confirm|}}
|
||||
<button type="button" class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>
|
||||
<button data-test-delete type="button" class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'dialog' as |execute cancel message|}}
|
||||
<p>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{{#app-view class="kv edit" loading=isLoading}}
|
||||
{{#block-slot 'breadcrumbs'}}
|
||||
<ol>
|
||||
<li><a href={{href-to 'dc.kv.index'}}>Key / Values</a></li>
|
||||
<li><a data-test-back href={{href-to 'dc.kv.index'}}>Key / Values</a></li>
|
||||
{{#if (not-eq parent.Key '/') }}
|
||||
{{#each (slice 0 -1 (split parent.Key '/')) as |breadcrumb index|}}
|
||||
<li><a href={{href-to 'dc.kv.folder' (join '/' (append (slice 0 (add index 1) (split parent.Key '/')) ''))}}>{{breadcrumb}}</a></li>
|
||||
|
|
|
@ -27,9 +27,9 @@
|
|||
{{/block-slot}}
|
||||
{{#block-slot 'actions'}}
|
||||
{{#if (not-eq parent.Key '/') }}
|
||||
<a href="{{href-to 'dc.kv.create' parent.Key}}" class="type-create">Create</a>
|
||||
<a data-test-create href="{{href-to 'dc.kv.create' parent.Key}}" class="type-create">Create</a>
|
||||
{{else}}
|
||||
<a href="{{href-to 'dc.kv.root-create'}}" class="type-create">Create</a>
|
||||
<a data-test-create href="{{href-to 'dc.kv.root-create'}}" class="type-create">Create</a>
|
||||
{{/if}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'content'}}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<td>
|
||||
{{#confirmation-dialog message='Are you sure you want to invalidate this session?'}}
|
||||
{{#block-slot 'action' as |confirm|}}
|
||||
<button type="button" class="type-delete" {{action confirm 'invalidateSession' item}}>Invalidate</button>
|
||||
<button data-test-delete type="button" class="type-delete" {{action confirm 'invalidateSession' item}}>Invalidate</button>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'dialog' as |execute cancel message|}}
|
||||
<p>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{{#app-view class="node show"}}
|
||||
{{#block-slot 'breadcrumbs'}}
|
||||
<ol>
|
||||
<li><a href={{href-to 'dc.nodes'}}>All Nodes</a></li>
|
||||
<li><a data-test-back href={{href-to 'dc.nodes'}}>All Nodes</a></li>
|
||||
</ol>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'header'}}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{{#app-view class="service show"}}
|
||||
{{#block-slot 'breadcrumbs'}}
|
||||
<ol>
|
||||
<li><a href={{href-to 'dc.services'}}>All Services</a></li>
|
||||
<li><a data-test-back href={{href-to 'dc.services'}}>All Services</a></li>
|
||||
</ol>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'header'}}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "consul-ui",
|
||||
"version": "2.2.0",
|
||||
"private": true,
|
||||
"description": "The web ui for Consul, by HashiCorp.",
|
||||
"description": "The web UI for Consul, by HashiCorp.",
|
||||
"directories": {
|
||||
"doc": "doc",
|
||||
"test": "tests"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / components /acl filter: Acl Filter
|
||||
Feature: components / acl filter: Acl Filter
|
||||
In order to find the acl token I'm looking for easier
|
||||
As a user
|
||||
I should be able to filter by type and freetext search tokens by name and token
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
@setupApplicationTest
|
||||
Feature: components / intention filter: Intention Filter
|
||||
In order to find the intention I'm looking for easier
|
||||
As a user
|
||||
I should be able to filter by 'policy' (allow/deny) and freetext search tokens by source and destination
|
||||
Scenario: Filtering [Model]
|
||||
Given 1 datacenter model with the value "dc-1"
|
||||
And 2 [Model] models
|
||||
When I visit the [Page] page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
Then the url should be [Url]
|
||||
|
||||
Then I see 2 [Model] models
|
||||
And I see allIsSelected on the filter
|
||||
|
||||
When I click allow on the filter
|
||||
Then I see allowIsSelected on the filter
|
||||
And I see 1 [Model] model
|
||||
And I see 1 [Model] model with the action "allow"
|
||||
|
||||
When I click deny on the filter
|
||||
Then I see denyIsSelected on the filter
|
||||
And I see 1 [Model] model
|
||||
And I see 1 [Model] model with the action "deny"
|
||||
|
||||
When I click all on the filter
|
||||
Then I see 2 [Model] models
|
||||
Then I see allIsSelected on the filter
|
||||
Then I fill in with yaml
|
||||
---
|
||||
s: alarm
|
||||
---
|
||||
And I see 1 [Model] model
|
||||
And I see 1 [Model] model with the source "alarm"
|
||||
Then I fill in with yaml
|
||||
---
|
||||
s: feed
|
||||
---
|
||||
And I see 1 [Model] model
|
||||
And I see 1 [Model] model with the destination "feed"
|
||||
Then I fill in with yaml
|
||||
---
|
||||
s: transmitter
|
||||
---
|
||||
And I see 2 [Model] models
|
||||
And I see 1 [Model] model with the source "transmitter"
|
||||
And I see 1 [Model] model with the destination "transmitter"
|
||||
|
||||
Where:
|
||||
---------------------------------------------
|
||||
| Model | Page | Url |
|
||||
| intention | intentions | /dc-1/intentions |
|
||||
---------------------------------------------
|
|
@ -1,5 +1,5 @@
|
|||
@setupApplicationTest
|
||||
Feature: Text input
|
||||
Feature: components / text-input: Text input
|
||||
Background:
|
||||
Given 1 datacenter model with the value "dc-1"
|
||||
Scenario:
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / acls / delete: ACL Delete
|
||||
Scenario: Delete ACL
|
||||
Given 1 datacenter model with the value "datacenter"
|
||||
And 1 acl model from yaml
|
||||
---
|
||||
Name: something
|
||||
ID: key
|
||||
---
|
||||
When I visit the acls page for yaml
|
||||
---
|
||||
dc: datacenter
|
||||
---
|
||||
And I click actions on the acls
|
||||
And I click delete on the acls
|
||||
And I click confirmDelete on the acls
|
||||
Then a PUT request is made to "/v1/acl/destroy/key?dc=datacenter"
|
|
@ -0,0 +1,40 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / acls / use: Using an ACL token
|
||||
Background:
|
||||
Given 1 datacenter model with the value "datacenter"
|
||||
And 1 acl model from yaml
|
||||
---
|
||||
ID: token
|
||||
---
|
||||
Scenario: Using an ACL token from the listing page
|
||||
When I visit the acls page for yaml
|
||||
---
|
||||
dc: datacenter
|
||||
---
|
||||
Then I have settings like yaml
|
||||
---
|
||||
token: ~
|
||||
---
|
||||
And I click actions on the acls
|
||||
And I click use on the acls
|
||||
And I click confirmUse on the acls
|
||||
Then I have settings like yaml
|
||||
---
|
||||
token: token
|
||||
---
|
||||
Scenario: Using an ACL token from the detail page
|
||||
When I visit the acl page for yaml
|
||||
---
|
||||
dc: datacenter
|
||||
acl: token
|
||||
---
|
||||
Then I have settings like yaml
|
||||
---
|
||||
token: ~
|
||||
---
|
||||
And I click use
|
||||
And I click confirmUse
|
||||
Then I have settings like yaml
|
||||
---
|
||||
token: token
|
||||
---
|
|
@ -1,16 +0,0 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / kvs / delete: KV Delete
|
||||
Scenario: Delete ACL
|
||||
Given 1 datacenter model with the value "datacenter"
|
||||
And 1 kv model from yaml
|
||||
---
|
||||
- key-name
|
||||
---
|
||||
When I visit the kvs page for yaml
|
||||
---
|
||||
dc: datacenter
|
||||
---
|
||||
And I click actions on the kvs
|
||||
And I click delete on the kvs
|
||||
And I click confirmDelete on the kvs
|
||||
Then a DELETE request is made to "/v1/kv/key-name?dc=datacenter"
|
|
@ -0,0 +1,26 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / nodes / sessions / invalidate: Invalidate Lock Sessions
|
||||
In order to invalidate a lock session
|
||||
As a user
|
||||
I should be able to invalidate a lock session by clicking a button and confirming
|
||||
Scenario: Given 2 lock sessions
|
||||
Given 1 datacenter model with the value "dc1"
|
||||
And 1 node model from yaml
|
||||
---
|
||||
- ID: node-0
|
||||
---
|
||||
And 2 session models from yaml
|
||||
---
|
||||
- ID: 7bbbd8bb-fff3-4292-b6e3-cfedd788546a
|
||||
- ID: 7ccd0bd7-a5e0-41ae-a33e-ed3793d803b2
|
||||
---
|
||||
When I visit the node page for yaml
|
||||
---
|
||||
dc: dc1
|
||||
node: node-0
|
||||
---
|
||||
And I click lockSessions on the tabs
|
||||
Then I see lockSessionsIsSelected on the tabs
|
||||
And I click delete on the sessions
|
||||
And I click confirmDelete on the sessions
|
||||
Then a PUT request is made to "/v1/session/destroy/7bbbd8bb-fff3-4292-b6e3-cfedd788546a?dc=dc1"
|
|
@ -1,5 +1,5 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / nodes / sessions /list: List Lock Sessions
|
||||
Feature: dc / nodes / sessions / list: List Lock Sessions
|
||||
In order to get information regarding lock sessions
|
||||
As a user
|
||||
I should be able to see a listing of lock sessions with necessary information under the lock sessions tab for a node
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
@setupApplicationTest
|
||||
Feature: deleting: Deleting from the listing and the detail page with confirmation
|
||||
Scenario: Deleting a [Model] from the [Model] listing page
|
||||
Given 1 datacenter model with the value "datacenter"
|
||||
And 1 [Model] model from json
|
||||
---
|
||||
[Data]
|
||||
---
|
||||
When I visit the [Model]s page for yaml
|
||||
---
|
||||
dc: datacenter
|
||||
---
|
||||
And I click actions on the [Model]s
|
||||
And I click delete on the [Model]s
|
||||
And I click confirmDelete on the [Model]s
|
||||
Then a [Method] request is made to "[URL]"
|
||||
When I visit the [Model] page for yaml
|
||||
---
|
||||
dc: datacenter
|
||||
[Slug]
|
||||
---
|
||||
And I click delete
|
||||
And I click confirmDelete
|
||||
Then a [Method] request is made to "[URL]"
|
||||
Where:
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
| Model | Method | URL | Data | Slug |
|
||||
| acl | PUT | /v1/acl/destroy/something?dc=datacenter | {"Name": "something", "ID": "something"} | acl: something |
|
||||
| kv | DELETE | /v1/kv/key-name?dc=datacenter | ["key-name"] | kv: key-name |
|
||||
| intention | DELETE | /v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter | {"SourceName": "name", "ID": "ee52203d-989f-4f7a-ab5a-2bef004164ca"} | intention: ee52203d-989f-4f7a-ab5a-2bef004164ca |
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ignore
|
||||
Scenario: Sort out the wide tables ^
|
||||
Then ok
|
|
@ -8,36 +8,74 @@ Feature: Page Navigation
|
|||
dc: dc-1
|
||||
---
|
||||
Then the url should be /dc-1/services
|
||||
Scenario: Clicking [Link] in the navigation takes me to [Url]
|
||||
Scenario: Clicking [Link] in the navigation takes me to [URL]
|
||||
When I visit the services page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
When I click [Link] on the navigation
|
||||
Then the url should be [Url]
|
||||
Then the url should be [URL]
|
||||
Where:
|
||||
--------------------------------------
|
||||
| Link | Url |
|
||||
| nodes | /dc-1/nodes |
|
||||
| kvs | /dc-1/kv |
|
||||
| acls | /dc-1/acls |
|
||||
| settings | /settings |
|
||||
--------------------------------------
|
||||
Scenario: Clicking a [Item] in the [Model] listing
|
||||
----------------------------------------
|
||||
| Link | URL |
|
||||
| nodes | /dc-1/nodes |
|
||||
| kvs | /dc-1/kv |
|
||||
| acls | /dc-1/acls |
|
||||
| intentions | /dc-1/intentions |
|
||||
| settings | /settings |
|
||||
----------------------------------------
|
||||
Scenario: Clicking a [Item] in the [Model] listing and back again
|
||||
When I visit the [Model] page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
When I click [Item] on the [Model]
|
||||
Then the url should be [Url]
|
||||
Then the url should be [URL]
|
||||
And I click "[data-test-back]"
|
||||
Then the url should be [Back]
|
||||
Where:
|
||||
--------------------------------------------------------
|
||||
| Item | Model | Url |
|
||||
| service | services | /dc-1/services/service-0 |
|
||||
| node | nodes | /dc-1/nodes/node-0 |
|
||||
| kv | kvs | /dc-1/kv/necessitatibus-0/edit |
|
||||
| acl | acls | /dc-1/acls/anonymous |
|
||||
--------------------------------------------------------
|
||||
--------------------------------------------------------------------------------------------------------
|
||||
| Item | Model | URL | Back |
|
||||
| service | services | /dc-1/services/service-0 | /dc-1/services |
|
||||
| node | nodes | /dc-1/nodes/node-0 | /dc-1/nodes |
|
||||
| kv | kvs | /dc-1/kv/necessitatibus-0/edit | /dc-1/kv |
|
||||
| acl | acls | /dc-1/acls/anonymous | /dc-1/acls |
|
||||
| intention | intentions | /dc-1/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca | /dc-1/intentions |
|
||||
--------------------------------------------------------------------------------------------------------
|
||||
Scenario: Clicking a [Item] in the [Model] listing and canceling
|
||||
When I visit the [Model] page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
When I click [Item] on the [Model]
|
||||
Then the url should be [URL]
|
||||
And I click "[type=reset]"
|
||||
Then the url should be [Back]
|
||||
Where:
|
||||
--------------------------------------------------------------------------------------------------------
|
||||
| Item | Model | URL | Back |
|
||||
| kv | kvs | /dc-1/kv/necessitatibus-0/edit | /dc-1/kv |
|
||||
| acl | acls | /dc-1/acls/anonymous | /dc-1/acls |
|
||||
| intention | intentions | /dc-1/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca | /dc-1/intentions |
|
||||
--------------------------------------------------------------------------------------------------------
|
||||
@ignore
|
||||
Scenario: Clicking a kv in the kvs listing, without depending on the salt ^
|
||||
Scenario: Clicking items in the listings, without depending on the salt ^
|
||||
Then ok
|
||||
Scenario: Clicking create in the [Model] listing
|
||||
When I visit the [Model] page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
When I click create
|
||||
Then the url should be [URL]
|
||||
And I click "[data-test-back]"
|
||||
Then the url should be [Back]
|
||||
Where:
|
||||
------------------------------------------------------------------------
|
||||
| Item | Model | URL | Back |
|
||||
| kv | kvs | /dc-1/kv/create | /dc-1/kv |
|
||||
| acl | acls | /dc-1/acls/create | /dc-1/acls |
|
||||
| intention | intentions | /dc-1/intentions/create | /dc-1/intentions |
|
||||
------------------------------------------------------------------------
|
||||
Scenario: Using I click on should change the currentPage ^
|
||||
Then ok
|
||||
|
|
|
@ -7,6 +7,10 @@ Feature: settings / update: Update Settings
|
|||
Given 1 datacenter model with the value "datacenter"
|
||||
When I visit the settings page
|
||||
Then the url should be /settings
|
||||
Then I have settings like yaml
|
||||
---
|
||||
token: ~
|
||||
---
|
||||
And I submit
|
||||
Then I have settings like yaml
|
||||
---
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import steps from '../../steps';
|
||||
import steps from '../steps';
|
||||
|
||||
// step definitions that are shared between features should be moved to the
|
||||
// tests/acceptance/steps/steps.js file
|
|
@ -0,0 +1,10 @@
|
|||
import steps from '../../../steps';
|
||||
|
||||
// step definitions that are shared between features should be moved to the
|
||||
// tests/acceptance/steps/steps.js file
|
||||
|
||||
export default function(assert) {
|
||||
return steps(assert).then('I should find a file', function() {
|
||||
assert.ok(true, this.step);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import steps from './steps';
|
||||
|
||||
// step definitions that are shared between features should be moved to the
|
||||
// tests/acceptance/steps/steps.js file
|
||||
|
||||
export default function(assert) {
|
||||
return steps(assert)
|
||||
.then('I should find a file', function() {
|
||||
assert.ok(true, this.step);
|
||||
});
|
||||
}
|
|
@ -13,11 +13,12 @@ Feature: submit blank
|
|||
And I submit
|
||||
Then the url should be /datacenter/[Slug]/create
|
||||
Where:
|
||||
------------------
|
||||
| Model | Slug |
|
||||
| kv | kv |
|
||||
| acl | acls |
|
||||
------------------
|
||||
--------------------------
|
||||
| Model | Slug |
|
||||
| kv | kv |
|
||||
| acl | acls |
|
||||
| intention | intentions |
|
||||
--------------------------
|
||||
@ignore
|
||||
Scenario: The button is disabled
|
||||
Then ok
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
export default function(clickable, is) {
|
||||
return function(obj) {
|
||||
return {
|
||||
...obj,
|
||||
...{
|
||||
cancel: clickable('[type=reset]'),
|
||||
cancelIsEnabled: is(':not(:disabled)', '[type=reset]'),
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export default function(clickable, is) {
|
||||
return function(obj) {
|
||||
return {
|
||||
...obj,
|
||||
...{
|
||||
create: clickable('[data-test-create]'),
|
||||
createIsEnabled: is(':not(:disabled)', '[data-test-create]'),
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export default function(clickable) {
|
||||
return function(obj) {
|
||||
return {
|
||||
...obj,
|
||||
...{
|
||||
delete: clickable('[data-test-delete]'),
|
||||
confirmDelete: clickable('button.type-delete'),
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export default function(clickable, is) {
|
||||
return function(obj) {
|
||||
return {
|
||||
...obj,
|
||||
...{
|
||||
submit: clickable('[type=submit]'),
|
||||
submitIsEnabled: is(':not(:disabled)', '[type=submit]'),
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,27 +1,48 @@
|
|||
import { create, clickable, is, attribute, collection, text } from 'ember-cli-page-object';
|
||||
import { visitable } from 'consul-ui/tests/lib/page-object/visitable';
|
||||
import createDeletable from 'consul-ui/tests/lib/page-object/createDeletable';
|
||||
import createSubmitable from 'consul-ui/tests/lib/page-object/createSubmitable';
|
||||
import createCreatable from 'consul-ui/tests/lib/page-object/createCreatable';
|
||||
import createCancelable from 'consul-ui/tests/lib/page-object/createCancelable';
|
||||
|
||||
import page from 'consul-ui/tests/pages/components/page';
|
||||
import radiogroup from 'consul-ui/tests/lib/page-object/radiogroup';
|
||||
|
||||
import index from 'consul-ui/tests/pages/index';
|
||||
import dcs from 'consul-ui/tests/pages/dc';
|
||||
import settings from 'consul-ui/tests/pages/settings';
|
||||
import catalogFilter from 'consul-ui/tests/pages/components/catalog-filter';
|
||||
import services from 'consul-ui/tests/pages/dc/services/index';
|
||||
import service from 'consul-ui/tests/pages/dc/services/show';
|
||||
import nodes from 'consul-ui/tests/pages/dc/nodes/index';
|
||||
import node from 'consul-ui/tests/pages/dc/nodes/show';
|
||||
import kvs from 'consul-ui/tests/pages/dc/kv/index';
|
||||
import kv from 'consul-ui/tests/pages/dc/kv/edit';
|
||||
import aclFilter from 'consul-ui/tests/pages/components/acl-filter';
|
||||
import acls from 'consul-ui/tests/pages/dc/acls/index';
|
||||
import acl from 'consul-ui/tests/pages/dc/acls/edit';
|
||||
import intentionFilter from 'consul-ui/tests/pages/components/intention-filter';
|
||||
import intentions from 'consul-ui/tests/pages/dc/intentions/index';
|
||||
import intention from 'consul-ui/tests/pages/dc/intentions/edit';
|
||||
|
||||
const deletable = createDeletable(clickable);
|
||||
const submitable = createSubmitable(clickable, is);
|
||||
const creatable = createCreatable(clickable, is);
|
||||
const cancelable = createCancelable(clickable, is);
|
||||
export default {
|
||||
index,
|
||||
dcs,
|
||||
settings,
|
||||
services,
|
||||
service,
|
||||
nodes,
|
||||
node,
|
||||
kvs,
|
||||
kv,
|
||||
acls,
|
||||
acl,
|
||||
intention,
|
||||
index: create(index(visitable, collection)),
|
||||
dcs: create(dcs(visitable, clickable, attribute, collection)),
|
||||
services: create(services(visitable, clickable, attribute, collection, page, catalogFilter)),
|
||||
service: create(service(visitable, attribute, collection, text, catalogFilter)),
|
||||
nodes: create(nodes(visitable, clickable, attribute, collection, catalogFilter)),
|
||||
node: create(node(visitable, deletable, clickable, attribute, collection, radiogroup)),
|
||||
kvs: create(kvs(visitable, deletable, creatable, clickable, attribute, collection)),
|
||||
kv: create(kv(visitable, submitable, deletable, cancelable, clickable)),
|
||||
acls: create(acls(visitable, deletable, creatable, clickable, attribute, collection, aclFilter)),
|
||||
acl: create(acl(visitable, submitable, deletable, cancelable, clickable)),
|
||||
intentions: create(
|
||||
intentions(visitable, deletable, creatable, clickable, attribute, collection, intentionFilter)
|
||||
),
|
||||
intention: create(intention(visitable, submitable, deletable, cancelable)),
|
||||
settings: create(settings(visitable, submitable)),
|
||||
};
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { triggerable } from 'ember-cli-page-object';
|
||||
import radiogroup from 'consul-ui/tests/lib/page-object/radiogroup';
|
||||
export default {
|
||||
...radiogroup('action', ['', 'allow', 'deny']),
|
||||
...{
|
||||
scope: '[data-test-intention-filter]',
|
||||
search: triggerable('keypress', '[name="s"]'),
|
||||
},
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import { clickable } from 'ember-cli-page-object';
|
||||
export default {
|
||||
navigation: ['services', 'nodes', 'kvs', 'acls', 'docs', 'settings'].reduce(
|
||||
navigation: ['services', 'nodes', 'kvs', 'acls', 'intentions', 'docs', 'settings'].reduce(
|
||||
function(prev, item, i, arr) {
|
||||
const key = item;
|
||||
return Object.assign({}, prev, {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { create, visitable, attribute, collection, clickable } from 'ember-cli-page-object';
|
||||
|
||||
export default create({
|
||||
visit: visitable('/:dc/'),
|
||||
dcs: collection('[data-test-datacenter-picker]'),
|
||||
showDatacenters: clickable('[data-test-datacenter-selected]'),
|
||||
selectedDc: attribute('data-test-datacenter-selected', '[data-test-datacenter-selected]'),
|
||||
selectedDatacenter: attribute('data-test-datacenter-selected', '[data-test-datacenter-selected]'),
|
||||
});
|
||||
export default function(visitable, clickable, attribute, collection) {
|
||||
return {
|
||||
visit: visitable('/:dc/'),
|
||||
dcs: collection('[data-test-datacenter-picker]'),
|
||||
showDatacenters: clickable('[data-test-datacenter-selected]'),
|
||||
selectedDc: attribute('data-test-datacenter-selected', '[data-test-datacenter-selected]'),
|
||||
selectedDatacenter: attribute(
|
||||
'data-test-datacenter-selected',
|
||||
'[data-test-datacenter-selected]'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { create, clickable, triggerable, is } from 'ember-cli-page-object';
|
||||
import { visitable } from 'consul-ui/tests/lib/page-object/visitable';
|
||||
|
||||
export default create({
|
||||
// custom visitable
|
||||
visit: visitable(['/:dc/acls/:acl', '/:dc/acls/create']),
|
||||
// fillIn: fillable('input, textarea, [contenteditable]'),
|
||||
name: triggerable('keypress', '[name="name"]'),
|
||||
submit: clickable('[type=submit]'),
|
||||
submitIsEnabled: is(':not(:disabled)', '[type=submit]'),
|
||||
});
|
||||
export default function(visitable, submitable, deletable, cancelable, clickable) {
|
||||
return submitable(
|
||||
cancelable(
|
||||
deletable({
|
||||
visit: visitable(['/:dc/acls/:acl', '/:dc/acls/create']),
|
||||
use: clickable('[data-test-use]'),
|
||||
confirmUse: clickable('button.type-delete'),
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { create, visitable, collection, attribute, clickable } from 'ember-cli-page-object';
|
||||
|
||||
import filter from 'consul-ui/tests/pages/components/acl-filter';
|
||||
export default create({
|
||||
visit: visitable('/:dc/acls'),
|
||||
acls: collection('[data-test-tabular-row]', {
|
||||
name: attribute('data-test-acl', '[data-test-acl]'),
|
||||
acl: clickable('a'),
|
||||
actions: clickable('label'),
|
||||
delete: clickable('[data-test-delete]'),
|
||||
confirmDelete: clickable('button.type-delete'),
|
||||
}),
|
||||
filter: filter,
|
||||
});
|
||||
export default function(visitable, deletable, creatable, clickable, attribute, collection, filter) {
|
||||
return creatable({
|
||||
visit: visitable('/:dc/acls'),
|
||||
acls: collection(
|
||||
'[data-test-tabular-row]',
|
||||
deletable({
|
||||
name: attribute('data-test-acl', '[data-test-acl]'),
|
||||
acl: clickable('a'),
|
||||
actions: clickable('label'),
|
||||
use: clickable('[data-test-use]'),
|
||||
confirmUse: clickable('button.type-delete'),
|
||||
})
|
||||
),
|
||||
filter: filter,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { create, clickable } from 'ember-cli-page-object';
|
||||
import { visitable } from 'consul-ui/tests/lib/page-object/visitable';
|
||||
|
||||
export default create({
|
||||
// custom visitable
|
||||
visit: visitable(['/:dc/intentions/:intention', '/:dc/intentions/create']),
|
||||
submit: clickable('[type=submit]'),
|
||||
});
|
||||
export default function(visitable, submitable, deletable, cancelable) {
|
||||
return submitable(
|
||||
cancelable(
|
||||
deletable({
|
||||
visit: visitable(['/:dc/intentions/:intention', '/:dc/intentions/create']),
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
export default function(visitable, deletable, creatable, clickable, attribute, collection, filter) {
|
||||
return creatable({
|
||||
visit: visitable('/:dc/intentions'),
|
||||
intentions: collection(
|
||||
'[data-test-tabular-row]',
|
||||
deletable({
|
||||
source: attribute('data-test-intention-source', '[data-test-intention-source]'),
|
||||
destination: attribute(
|
||||
'data-test-intention-destination',
|
||||
'[data-test-intention-destination]'
|
||||
),
|
||||
action: attribute('data-test-intention-action', '[data-test-intention-action]'),
|
||||
intention: clickable('a'),
|
||||
actions: clickable('label'),
|
||||
})
|
||||
),
|
||||
filter: filter,
|
||||
});
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
import { create, clickable, is } from 'ember-cli-page-object';
|
||||
import { visitable } from 'consul-ui/tests/lib/page-object/visitable';
|
||||
|
||||
export default create({
|
||||
// custom visitable
|
||||
visit: visitable(['/:dc/kv/:kv/edit', '/:dc/kv/create'], str => str),
|
||||
// fillIn: fillable('input, textarea, [contenteditable]'),
|
||||
// name: triggerable('keypress', '[name="additional"]'),
|
||||
submit: clickable('[type=submit]'),
|
||||
submitIsEnabled: is(':not(:disabled)', '[type=submit]'),
|
||||
});
|
||||
export default function(visitable, submitable, deletable, cancelable) {
|
||||
return submitable(
|
||||
cancelable(
|
||||
deletable({
|
||||
visit: visitable(['/:dc/kv/:kv/edit', '/:dc/kv/create'], str => str),
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { create, visitable, collection, attribute, clickable } from 'ember-cli-page-object';
|
||||
|
||||
export default create({
|
||||
visit: visitable('/:dc/kv'),
|
||||
kvs: collection('[data-test-tabular-row]', {
|
||||
name: attribute('data-test-kv', '[data-test-kv]'),
|
||||
kv: clickable('a'),
|
||||
actions: clickable('label'),
|
||||
delete: clickable('[data-test-delete]'),
|
||||
confirmDelete: clickable('button.type-delete'),
|
||||
}),
|
||||
});
|
||||
export default function(visitable, deletable, creatable, clickable, attribute, collection) {
|
||||
return creatable({
|
||||
visit: visitable('/:dc/kv'),
|
||||
kvs: collection(
|
||||
'[data-test-tabular-row]',
|
||||
deletable({
|
||||
name: attribute('data-test-kv', '[data-test-kv]'),
|
||||
kv: clickable('a'),
|
||||
actions: clickable('label'),
|
||||
})
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { create, visitable, collection, attribute, clickable } from 'ember-cli-page-object';
|
||||
import filter from 'consul-ui/tests/pages/components/catalog-filter';
|
||||
|
||||
export default create({
|
||||
visit: visitable('/:dc/nodes'),
|
||||
nodes: collection('[data-test-node]', {
|
||||
name: attribute('data-test-node'),
|
||||
node: clickable('header a'),
|
||||
}),
|
||||
filter: filter,
|
||||
});
|
||||
export default function(visitable, clickable, attribute, collection, filter) {
|
||||
return {
|
||||
visit: visitable('/:dc/nodes'),
|
||||
nodes: collection('[data-test-node]', {
|
||||
name: attribute('data-test-node'),
|
||||
node: clickable('header a'),
|
||||
}),
|
||||
filter: filter,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import { create, visitable, collection, attribute } from 'ember-cli-page-object';
|
||||
|
||||
import radiogroup from 'consul-ui/tests/lib/page-object/radiogroup';
|
||||
export default create({
|
||||
visit: visitable('/:dc/nodes/:node'),
|
||||
tabs: radiogroup('tab', ['health-checks', 'services', 'round-trip-time', 'lock-sessions']),
|
||||
healthchecks: collection('[data-test-node-healthcheck]', {
|
||||
name: attribute('data-test-node-healthcheck'),
|
||||
}),
|
||||
services: collection('#services [data-test-tabular-row]', {
|
||||
port: attribute('data-test-service-port', '.port'),
|
||||
}),
|
||||
sessions: collection('#lock-sessions [data-test-tabular-row]', {
|
||||
TTL: attribute('data-test-session-ttl', '[data-test-session-ttl]'),
|
||||
}),
|
||||
});
|
||||
export default function(visitable, deletable, clickable, attribute, collection, radiogroup) {
|
||||
return {
|
||||
visit: visitable('/:dc/nodes/:node'),
|
||||
tabs: radiogroup('tab', ['health-checks', 'services', 'round-trip-time', 'lock-sessions']),
|
||||
healthchecks: collection('[data-test-node-healthcheck]', {
|
||||
name: attribute('data-test-node-healthcheck'),
|
||||
}),
|
||||
services: collection('#services [data-test-tabular-row]', {
|
||||
port: attribute('data-test-service-port', '.port'),
|
||||
}),
|
||||
sessions: collection(
|
||||
'#lock-sessions [data-test-tabular-row]',
|
||||
deletable({
|
||||
TTL: attribute('data-test-session-ttl', '[data-test-session-ttl]'),
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
import { create, visitable, collection, attribute, clickable } from 'ember-cli-page-object';
|
||||
|
||||
import page from 'consul-ui/tests/pages/components/page';
|
||||
import filter from 'consul-ui/tests/pages/components/catalog-filter';
|
||||
|
||||
export default create({
|
||||
visit: visitable('/:dc/services'),
|
||||
services: collection('[data-test-service]', {
|
||||
name: attribute('data-test-service'),
|
||||
service: clickable('a'),
|
||||
}),
|
||||
dcs: collection('[data-test-datacenter-picker]'),
|
||||
navigation: page.navigation,
|
||||
|
||||
filter: filter,
|
||||
});
|
||||
export default function(visitable, clickable, attribute, collection, page, filter) {
|
||||
return {
|
||||
visit: visitable('/:dc/services'),
|
||||
services: collection('[data-test-service]', {
|
||||
name: attribute('data-test-service'),
|
||||
service: clickable('a'),
|
||||
}),
|
||||
dcs: collection('[data-test-datacenter-picker]'),
|
||||
navigation: page.navigation,
|
||||
filter: filter,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import { create, visitable, collection, attribute, text } from 'ember-cli-page-object';
|
||||
import filter from 'consul-ui/tests/pages/components/catalog-filter';
|
||||
|
||||
export default create({
|
||||
visit: visitable('/:dc/services/:service'),
|
||||
nodes: collection('[data-test-node]', {
|
||||
name: attribute('data-test-node'),
|
||||
}),
|
||||
healthy: collection('[data-test-healthy] [data-test-node]', {
|
||||
name: attribute('data-test-node'),
|
||||
address: text('header strong'),
|
||||
}),
|
||||
unhealthy: collection('[data-test-unhealthy] [data-test-node]', {
|
||||
name: attribute('data-test-node'),
|
||||
address: text('header strong'),
|
||||
}),
|
||||
filter: filter,
|
||||
});
|
||||
export default function(visitable, attribute, collection, text, filter) {
|
||||
return {
|
||||
visit: visitable('/:dc/services/:service'),
|
||||
nodes: collection('[data-test-node]', {
|
||||
name: attribute('data-test-node'),
|
||||
}),
|
||||
healthy: collection('[data-test-healthy] [data-test-node]', {
|
||||
name: attribute('data-test-node'),
|
||||
address: text('header strong'),
|
||||
}),
|
||||
unhealthy: collection('[data-test-unhealthy] [data-test-node]', {
|
||||
name: attribute('data-test-node'),
|
||||
address: text('header strong'),
|
||||
}),
|
||||
filter: filter,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { create, visitable, collection } from 'ember-cli-page-object';
|
||||
|
||||
export default create({
|
||||
visit: visitable('/'),
|
||||
dcs: collection('[data-test-datacenter-list]'),
|
||||
});
|
||||
export default function(visitable, collection) {
|
||||
return {
|
||||
visit: visitable('/'),
|
||||
dcs: collection('[data-test-datacenter-list]'),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { create, visitable, clickable } from 'ember-cli-page-object';
|
||||
|
||||
export default create({
|
||||
visit: visitable('/settings'),
|
||||
submit: clickable('[type=submit]'),
|
||||
});
|
||||
export default function(visitable, submitable) {
|
||||
return submitable({
|
||||
visit: visitable('/settings'),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import getDictionary from '@hashicorp/ember-cli-api-double/dictionary';
|
|||
import pages from 'consul-ui/tests/pages';
|
||||
import api from 'consul-ui/tests/helpers/api';
|
||||
|
||||
// const dont = `( don't| shouldn't| can't)?`;
|
||||
|
||||
const create = function(number, name, value) {
|
||||
// don't return a promise here as
|
||||
// I don't need it to wait
|
||||
|
@ -83,6 +85,15 @@ export default function(assert) {
|
|||
.when('I click "$selector"', function(selector) {
|
||||
return click(selector);
|
||||
})
|
||||
// TODO: Probably nicer to think of better vocab than having the 'without " rule'
|
||||
.when('I click (?!")$property(?!")', function(property) {
|
||||
try {
|
||||
return currentPage[property]();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw new Error(`The '${property}' property on the page object doesn't exist`);
|
||||
}
|
||||
})
|
||||
.when('I click $prop on the $component', function(prop, component) {
|
||||
// Collection
|
||||
var obj;
|
||||
|
@ -240,7 +251,9 @@ export default function(assert) {
|
|||
|
||||
assert.equal(len, num, `Expected ${num} ${model}s, saw ${len}`);
|
||||
})
|
||||
.then(['I see $num $model model with the $property "$value"'], function(
|
||||
// TODO: I${ dont } see
|
||||
.then([`I see $num $model model[s]? with the $property "$value"`], function(
|
||||
// negate,
|
||||
num,
|
||||
model,
|
||||
property,
|
||||
|
|
|
@ -82,8 +82,8 @@
|
|||
js-yaml "^3.10.0"
|
||||
|
||||
"@hashicorp/consul-api-double@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-1.2.0.tgz#2cd2a991818e13e7b97803af3d62ec6c9cb83b28"
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-1.3.0.tgz#fded48ca4db1e63c66e39b4433b2169b6add69ed"
|
||||
|
||||
"@hashicorp/ember-cli-api-double@^1.3.0":
|
||||
version "1.3.0"
|
||||
|
|
Loading…
Reference in New Issue